From d830ae1a47f543d87434317b85c5e74d83a09706 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Fri, 14 Jul 2023 11:56:19 +0200 Subject: [PATCH 01/88] add non matching conforming projection operators --- .../feec/multipatch/non_matching_operators.py | 658 ++++++++++++++++++ psydac/feec/multipatch/operators.py | 5 + 2 files changed, 663 insertions(+) create mode 100644 psydac/feec/multipatch/non_matching_operators.py diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py new file mode 100644 index 000000000..aa1186c1c --- /dev/null +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -0,0 +1,658 @@ +import os +import numpy as np +from scipy.sparse import eye as sparse_eye +from scipy.sparse import csr_matrix +from scipy.sparse.linalg import inv, norm + +from sympde.topology import Derham, Square +from sympde.topology import IdentityMapping +from sympde.topology import Boundary, Interface, Union + +from psydac.feec.multipatch.utilities import time_count +from psydac.linalg.utilities import array_to_psydac +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.fem.splines import SplineSpace + +from psydac.fem.basic import FemField +from psydac.feec.multipatch.plotting_utilities import plot_field + +from sympde.topology import IdentityMapping, PolarMapping +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain + +# to compare +from psydac.feec.multipatch.operators import ConformingProjection_V1 + + +def get_patch_index_from_face(domain, face): + """ Return the patch index of subdomain/boundary + + Parameters + ---------- + domain : + The Symbolic domain + + face : + A patch or a boundary of a patch + + Returns + ------- + i : + The index of a subdomain/boundary in the multipatch domain + """ + + if domain.mapping: + domain = domain.logical_domain + if face.mapping: + face = face.logical_domain + + domains = domain.interior.args + if isinstance(face, Interface): + raise NotImplementedError( + "This face is an interface, it has several indices -- I am a machine, I cannot choose. Help.") + elif isinstance(face, Boundary): + i = domains.index(face.domain) + else: + i = domains.index(face) + return i + + +class Local2GlobalIndexMap: + def __init__(self, ndim, n_patches, n_components): + # A[patch_index][component_index][i1,i2] + self._shapes = [None]*n_patches + self._ndofs = [None]*n_patches + self._ndim = ndim + self._n_patches = n_patches + self._n_components = n_components + + def set_patch_shapes(self, patch_index, *shapes): + assert len(shapes) == self._n_components + assert all(len(s) == self._ndim for s in shapes) + self._shapes[patch_index] = shapes + self._ndofs[patch_index] = sum(np.product(s) for s in shapes) + + def get_index(self, k, d, cartesian_index): + """ Return a global scalar index. + + Parameters + ---------- + k : int + The patch index. + + d : int + The component of a scalar field in the system of equations. + + cartesian_index: tuple[int] + Multi index [i1, i2, i3 ...] + + Returns + ------- + I : int + The global scalar index. + """ + sizes = [np.product(s) for s in self._shapes[k][:d]] + Ipc = np.ravel_multi_index( + cartesian_index, dims=self._shapes[k][d], order='C') + Ip = sum(sizes) + Ipc + I = sum(self._ndofs[:k]) + Ip + return I + + +def knots_to_insert(coarse_grid, fine_grid, tol=1e-14): + # assert len(coarse_grid)*2-2 == len(fine_grid)-1 + intersection = coarse_grid[( + np.abs(fine_grid[:, None] - coarse_grid) < tol).any(0)] + assert abs(intersection-coarse_grid).max() < tol + T = fine_grid[~(np.abs(coarse_grid[:, None] - fine_grid) < tol).any(0)] + return T + + +def construct_extension_operator_1D(domain, codomain): + """ + + compute the matrix of the extension operator on the interface space (1D space if global space is 2D) + + domain: 1d spline space on the interface (coarse grid) + codomain: 1d spline space on the interface (fine grid) + """ + #from psydac.core.interface import matrix_multi_stages + from psydac.core.bsplines import hrefinement_matrix + ops = [] + + assert domain.ncells <= codomain.ncells + + Ts = knots_to_insert(domain.breaks, codomain.breaks) + #P = matrix_multi_stages(Ts, domain.nbasis, domain.degree, domain.knots) + P = hrefinement_matrix(Ts, domain.degree, domain.knots) + if domain.basis == 'M': + assert codomain.basis == 'M' + P = np.diag( + 1/codomain._scaling_array) @ P @ np.diag(domain._scaling_array) + + return csr_matrix(P) # kronecker of 1 term... + + +def construct_V0_conforming_projection(V0h, domain_h, hom_bc=None, storage_fn=None): + dim_tot = V0h.nbasis + domain = V0h.symbolic_space.domain + ndim = 2 + n_components = 1 + n_patches = len(domain) + + l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) + for k in range(n_patches): + Vk = V0h.spaces[k] + # T is a TensorFemSpace and S is a 1D SplineSpace + shapes = [S.nbasis for S in Vk.spaces] + l2g.set_patch_shapes(k, shapes) + + Proj = sparse_eye(dim_tot, format="lil") + Proj_vertex = sparse_eye(dim_tot, format="lil") + + Interfaces = domain.interfaces + if isinstance(Interfaces, Interface): + Interfaces = (Interfaces, ) + + corner_indices = set() + stored_indices = [] + corners = get_corners(domain, False) + for (bd,co) in corners.items(): + + c = 0 + indices = set() + for patch in co: + c += 1 + multi_index_i = [None]*ndim + + nbasis0 = V0h.spaces[patch].spaces[co[patch][0]].nbasis-1 + nbasis1 = V0h.spaces[patch].spaces[co[patch][1]].nbasis-1 + + multi_index_i[0] = 0 if co[patch][0] == 0 else nbasis0 + multi_index_i[1] = 0 if co[patch][1] == 0 else nbasis1 + ig = l2g.get_index(patch, 0, multi_index_i) + indices.add(ig) + corner_indices.add(ig) + + stored_indices.append(indices) + for j in indices: + for i in indices: + Proj_vertex[j,i] = 1/c + + # First make all interfaces conforming + # We also touch the vertices here, but change them later again + for I in Interfaces: + + axis = I.axis + direction = I.ornt + + k_minus = get_patch_index_from_face(domain, I.minus) + k_plus = get_patch_index_from_face(domain, I.plus) + # logical directions normal to interface + minus_axis, plus_axis = I.minus.axis, I.plus.axis + # logical directions along the interface + + #d_minus, d_plus = 1-minus_axis, 1-plus_axis + I_minus_ncells = V0h.spaces[k_minus].ncells + I_plus_ncells = V0h.spaces[k_plus].ncells + + matching_interfaces = (I_minus_ncells == I_plus_ncells) + + if I_minus_ncells <= I_plus_ncells: + k_fine, k_coarse = k_plus, k_minus + fine_axis, coarse_axis = I.plus.axis, I.minus.axis + fine_ext, coarse_ext = I.plus.ext, I.minus.ext + + else: + k_fine, k_coarse = k_minus, k_plus + fine_axis, coarse_axis = I.minus.axis, I.plus.axis + fine_ext, coarse_ext = I.minus.ext, I.plus.ext + + d_fine = 1-fine_axis + d_coarse = 1-coarse_axis + + space_fine = V0h.spaces[k_fine] + space_coarse = V0h.spaces[k_coarse] + + + coarse_space_1d = space_coarse.spaces[d_coarse] + + fine_space_1d = space_fine.spaces[d_fine] + grid = np.linspace( + fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) + coarse_space_1d_k_plus = SplineSpace( + degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) + + if not matching_interfaces: + E_1D = construct_extension_operator_1D( + domain=coarse_space_1d_k_plus, codomain=fine_space_1d) + + product = (E_1D.T) @ E_1D + R_1D = inv(product.tocsc()) @ E_1D.T + ER_1D = E_1D @ R_1D + else: + ER_1D = R_1D = E_1D = sparse_eye( + fine_space_1d.nbasis, format="lil") + + # P_k_minus_k_minus + multi_index = [None]*ndim + multi_index[coarse_axis] = 0 if coarse_ext == - \ + 1 else space_coarse.spaces[coarse_axis].nbasis-1 + for i in range(coarse_space_1d.nbasis): + multi_index[d_coarse] = i + ig = l2g.get_index(k_coarse, 0, multi_index) + if not corner_indices.issuperset({ig}): + Proj[ig, ig] = 0.5 + + # P_k_plus_k_plus + multi_index_i = [None]*ndim + multi_index_j = [None]*ndim + multi_index_i[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine.spaces[fine_axis].nbasis-1 + multi_index_j[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine.spaces[fine_axis].nbasis-1 + + for i in range(fine_space_1d.nbasis): + multi_index_i[d_fine] = i + ig = l2g.get_index(k_fine, 0, multi_index_i) + for j in range(fine_space_1d.nbasis): + multi_index_j[d_fine] = j + jg = l2g.get_index(k_fine, 0, multi_index_j) + if not corner_indices.issuperset({ig}): + Proj[ig, jg] = 0.5*ER_1D[i, j] + + # P_k_plus_k_minus + multi_index_i = [None]*ndim + multi_index_j = [None]*ndim + multi_index_i[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine .spaces[fine_axis] .nbasis-1 + multi_index_j[coarse_axis] = 0 if coarse_ext == - \ + 1 else space_coarse.spaces[coarse_axis].nbasis-1 + + for i in range(fine_space_1d.nbasis): + multi_index_i[d_fine] = i + ig = l2g.get_index(k_fine, 0, multi_index_i) + for j in range(coarse_space_1d.nbasis): + multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 + jg = l2g.get_index(k_coarse, 0, multi_index_j) + if not corner_indices.issuperset({ig}): + Proj[ig, jg] = 0.5*E_1D[i, j]*direction + + # P_k_minus_k_plus + multi_index_i = [None]*ndim + multi_index_j = [None]*ndim + multi_index_i[coarse_axis] = 0 if coarse_ext == - \ + 1 else space_coarse.spaces[coarse_axis].nbasis-1 + multi_index_j[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine .spaces[fine_axis] .nbasis-1 + + for i in range(coarse_space_1d.nbasis): + multi_index_i[d_coarse] = i + ig = l2g.get_index(k_coarse, 0, multi_index_i) + for j in range(fine_space_1d.nbasis): + multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 + jg = l2g.get_index(k_fine, 0, multi_index_j) + if not corner_indices.issuperset({ig}): + Proj[ig, jg] = 0.5*R_1D[i, j]*direction + + + if hom_bc: + bd_co_indices = set() + for bn in domain.boundary: + k = get_patch_index_from_face(domain, bn) + space_k = V0h.spaces[k] + axis = bn.axis + d = 1-axis + ext = bn.ext + space_k_1d = space_k.spaces[d] # t + multi_index_i = [None]*ndim + multi_index_i[axis] = 0 if ext == - \ + 1 else space_k.spaces[axis].nbasis-1 + + for i in range(space_k_1d.nbasis): + multi_index_i[d] = i + ig = l2g.get_index(k, 0, multi_index_i) + bd_co_indices.add(ig) + Proj[ig, ig] = 0 + + # properly ensure vertex continuity + for ig in bd_co_indices: + for jg in bd_co_indices: + Proj_vertex[ig, jg] = 0 + + + return Proj @ Proj_vertex + + + +def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=None): + dim_tot = V1h.nbasis + domain = V1h.symbolic_space.domain + ndim = 2 + n_components = 2 + n_patches = len(domain) + + l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) + for k in range(n_patches): + Vk = V1h.spaces[k] + # T is a TensorFemSpace and S is a 1D SplineSpace + shapes = [[S.nbasis for S in T.spaces] for T in Vk.spaces] + l2g.set_patch_shapes(k, *shapes) + + Proj = sparse_eye(dim_tot, format="lil") + + Interfaces = domain.interfaces + if isinstance(Interfaces, Interface): + Interfaces = (Interfaces, ) + + for I in Interfaces: + axis = I.axis + direction = I.ornt + + k_minus = get_patch_index_from_face(domain, I.minus) + k_plus = get_patch_index_from_face(domain, I.plus) + # logical directions normal to interface + minus_axis, plus_axis = I.minus.axis, I.plus.axis + # logical directions along the interface + d_minus, d_plus = 1-minus_axis, 1-plus_axis + I_minus_ncells = V1h.spaces[k_minus].spaces[d_minus].ncells[d_minus] + I_plus_ncells = V1h.spaces[k_plus] .spaces[d_plus] .ncells[d_plus] + + matching_interfaces = (I_minus_ncells == I_plus_ncells) + + if I_minus_ncells <= I_plus_ncells: + k_fine, k_coarse = k_plus, k_minus + fine_axis, coarse_axis = I.plus.axis, I.minus.axis + fine_ext, coarse_ext = I.plus.ext, I.minus.ext + + else: + k_fine, k_coarse = k_minus, k_plus + fine_axis, coarse_axis = I.minus.axis, I.plus.axis + fine_ext, coarse_ext = I.minus.ext, I.plus.ext + + d_fine = 1-fine_axis + d_coarse = 1-coarse_axis + + space_fine = V1h.spaces[k_fine] + space_coarse = V1h.spaces[k_coarse] + + #print("coarse = \n", space_coarse.spaces[d_coarse]) + #print("coarse 2 = \n", space_coarse.spaces[d_coarse].spaces[d_coarse]) + # todo: merge with first test above + coarse_space_1d = space_coarse.spaces[d_coarse].spaces[d_coarse] + + #print("fine = \n", space_fine.spaces[d_fine]) + #print("fine 2 = \n", space_fine.spaces[d_fine].spaces[d_fine]) + + fine_space_1d = space_fine.spaces[d_fine].spaces[d_fine] + grid = np.linspace( + fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) + coarse_space_1d_k_plus = SplineSpace( + degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) + + if not matching_interfaces: + E_1D = construct_extension_operator_1D( + domain=coarse_space_1d_k_plus, codomain=fine_space_1d) + product = (E_1D.T) @ E_1D + R_1D = inv(product.tocsc()) @ E_1D.T + ER_1D = E_1D @ R_1D + else: + ER_1D = R_1D = E_1D = sparse_eye( + fine_space_1d.nbasis, format="lil") + + # P_k_minus_k_minus + multi_index = [None]*ndim + multi_index[coarse_axis] = 0 if coarse_ext == - \ + 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 + for i in range(coarse_space_1d.nbasis): + multi_index[d_coarse] = i + ig = l2g.get_index(k_coarse, d_coarse, multi_index) + Proj[ig, ig] = 0.5 + + # P_k_plus_k_plus + multi_index_i = [None]*ndim + multi_index_j = [None]*ndim + multi_index_i[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 + multi_index_j[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 + + for i in range(fine_space_1d.nbasis): + multi_index_i[d_fine] = i + ig = l2g.get_index(k_fine, d_fine, multi_index_i) + for j in range(fine_space_1d.nbasis): + multi_index_j[d_fine] = j + jg = l2g.get_index(k_fine, d_fine, multi_index_j) + Proj[ig, jg] = 0.5*ER_1D[i, j] + + # P_k_plus_k_minus + multi_index_i = [None]*ndim + multi_index_j = [None]*ndim + multi_index_i[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 + multi_index_j[coarse_axis] = 0 if coarse_ext == - \ + 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 + + for i in range(fine_space_1d.nbasis): + multi_index_i[d_fine] = i + ig = l2g.get_index(k_fine, d_fine, multi_index_i) + for j in range(coarse_space_1d.nbasis): + multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 + jg = l2g.get_index(k_coarse, d_coarse, multi_index_j) + Proj[ig, jg] = 0.5*E_1D[i, j]*direction + + # P_k_minus_k_plus + multi_index_i = [None]*ndim + multi_index_j = [None]*ndim + multi_index_i[coarse_axis] = 0 if coarse_ext == - \ + 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 + multi_index_j[fine_axis] = 0 if fine_ext == - \ + 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 + + for i in range(coarse_space_1d.nbasis): + multi_index_i[d_coarse] = i + ig = l2g.get_index(k_coarse, d_coarse, multi_index_i) + for j in range(fine_space_1d.nbasis): + multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 + jg = l2g.get_index(k_fine, d_fine, multi_index_j) + Proj[ig, jg] = 0.5*R_1D[i, j]*direction + + if hom_bc: + for bn in domain.boundary: + k = get_patch_index_from_face(domain, bn) + space_k = V1h.spaces[k] + axis = bn.axis + d = 1-axis + ext = bn.ext + space_k_1d = space_k.spaces[d].spaces[d] # t + multi_index_i = [None]*ndim + multi_index_i[axis] = 0 if ext == - \ + 1 else space_k.spaces[d].spaces[axis].nbasis-1 + + for i in range(space_k_1d.nbasis): + multi_index_i[d] = i + ig = l2g.get_index(k, d, multi_index_i) + Proj[ig, ig] = 0 + + return Proj + + +def get_corners(domain, boundary_only): + """ + Conforming projection from global broken V0 space to conforming global V0 space + Defined by averaging of interface dofs + + Parameters + ---------- + domain: + The discrete domain of the projector + + hom_bc : + Apply homogenous boundary conditions if True + + backend_language: + The backend used to accelerate the code + + storage_fn: + filename to store/load the operator sparse matrix + """ + # domain = V0h.symbolic_space.domain + cos = domain.corners + patches = domain.interior.args + + # corner_data[corner] = (patch_ind => coord) + corner_data = dict() + + # corner in domain corners + for co in cos: + # corner boundary in corner corner (?)direction + if boundary_only: + if not(domain.boundary.has(co.args[0].args[0]) or domain.boundary.has(co.args[0].args[1])): + continue + + corner_data[co] = dict() + + + for cb in co.corners: + p_ind = patches.index(cb.domain) + c_coord = cb.coordinates + corner_data[co][p_ind] = c_coord + + return corner_data + +if __name__ == '__main__': + + nc = 6 + deg = 4 + plot_dir = 'run_plots_nc={}_deg={}'.format(nc, deg) + + if plot_dir is not None and not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + ncells = [nc, nc] + degree = [deg, deg] + + print(' .. multi-patch domain...') + + #domain_name = 'square_6' + #domain_name = '2patch_nc_mapped' + domain_name = '2patch_nc' + + if domain_name == '2patch_nc_mapped': + + A = Square('A', bounds1=(0.5, 1), bounds2=(0, np.pi/2)) + B = Square('B', bounds1=(0.5, 1), bounds2=(np.pi/2, np.pi)) + M1 = PolarMapping('M1', 2, c1=0, c2=0, rmin=0., rmax=1.) + M2 = PolarMapping('M2', 2, c1=0, c2=0, rmin=0., rmax=1.) + A = M1(A) + B = M2(B) + + domain = create_domain([A, B], [[A.get_boundary(axis=1, ext=1), B.get_boundary(axis=1, ext=-1), 1]], name='domain') + + elif domain_name == '2patch_nc': + + A = Square('A', bounds1=(0, 0.5), bounds2=(0, 1)) + B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 1)) + M1 = IdentityMapping('M1', dim=2) + M2 = IdentityMapping('M2', dim=2) + A = M1(A) + B = M2(B) + + domain = create_domain([A, B], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1]], name='domain') + + else: + domain = build_multipatch_domain(domain_name=domain_name) + + n_patches = len(domain) + + def levelof(k): + # some random refinement level (1 or 2 here) + return 1+((2*k) % 3) % 2 + + if len(domain) == 1: + ncells_h = { + 'M1(A)': [nc, nc], + } + + elif len(domain) == 2: + ncells_h = { + 'M1(A)': [nc, nc], + 'M2(B)': [2*nc, 2*nc], + } + + else: + ncells_h = {} + for k, D in enumerate(domain.interior): + ncells_h[D.name] = [levelof(k)*nc, levelof(k)*nc] + + print('ncells_h = ', ncells_h) + backend_language = 'python' + + t_stamp = time_count() + print(' .. derham sequence...') + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + + t_stamp = time_count(t_stamp) + print(' .. discrete domain...') + + domain_h = discretize(domain, ncells=ncells_h) # Vh space + derham_h = discretize(derham, domain_h, degree=degree) + V0h = derham_h.V0 + V1h = derham_h.V1 + + cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + + + + #print("Error:") + #print( norm(cP1_m - conf_cP1_m) ) + np.set_printoptions(linewidth=100000, precision=2, + threshold=100000, suppress=True) + #print(cP0_m.toarray()) + + # apply cP1 on some discontinuous G + + # G_sol_log = [[lambda xi1, xi2, ii=i : ii+xi1+xi2**2 for d in [0,1]] for i in range(len(domain))] + # G_sol_log = [[lambda xi1, xi2, kk=k : levelof(kk)-1 for d in [0,1]] for k in range(len(domain))] + G_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0, 1]] + for k in range(len(domain))] + + P0, P1, P2 = derham_h.projectors() + + G1h = P1(G_sol_log) + G1h_coeffs = G1h.coeffs.toarray() + + plot_field(numpy_coeffs=G1h_coeffs, Vh=V1h, space_kind='hcurl', + plot_type='components', + domain=domain, title='G1h', cmap='viridis', + filename=plot_dir+'/G.png') + + G1h_conf_coeffs = cP1_m @ G1h_coeffs + + plot_field(numpy_coeffs=G1h_conf_coeffs, Vh=V1h, space_kind='hcurl', + plot_type='components', + domain=domain, title='PG', cmap='viridis', + filename=plot_dir+'/PG.png') + + + + #G0_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0]] + # for k in range(len(domain))] + G0_sol_log = [[lambda xi1, xi2, kk=k:kk for d in [0]] + for k in range(len(domain))] + + G0h = P0(G0_sol_log) + G0h_coeffs = G0h.coeffs.toarray() + + plot_field(numpy_coeffs=G0h_coeffs, Vh=V0h, space_kind='h1', + domain=domain, title='G0h', cmap='viridis', + filename=plot_dir+'/G0.png') + + G0h_conf_coeffs = cP0_m @ G0h_coeffs + + #G0h_conf_coeffs = G0h_conf_coeffs - G0h_coeffs + plot_field(numpy_coeffs=G0h_conf_coeffs, Vh=V0h, space_kind='h1', + domain=domain, title='PG0', cmap='viridis', + filename=plot_dir+'/PG0.png') + diff --git a/psydac/feec/multipatch/operators.py b/psydac/feec/multipatch/operators.py index f566dc417..1c7b8f0d7 100644 --- a/psydac/feec/multipatch/operators.py +++ b/psydac/feec/multipatch/operators.py @@ -188,6 +188,7 @@ def get_row_col_index(corner1, corner2, interface, axis, V1, V2): return row+col + #=============================================================================== def allocate_interface_matrix(corners, test_space, trial_space): """ Allocate the interface matrix for a vertex shared by two patches @@ -242,6 +243,10 @@ def allocate_interface_matrix(corners, test_space, trial_space): mat = StencilInterfaceMatrix(trial_space.vector_space, test_space.vector_space, s, s, axis, flip=flips[0], permutation=list(permutation)) return mat +#=============================================================================== +# The following operators are not compatible with the changes in the Stencil format +# and their datatype does not allow for non-matching interfaces, but they might be +# useful for future implementations #=============================================================================== class ConformingProjection_V0( FemLinearOperator): """ From a27a0620eef710188c4b810c7dbb9ec6cd4a8934 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Fri, 14 Jul 2023 11:56:59 +0200 Subject: [PATCH 02/88] add some plotting utilities --- psydac/feec/multipatch/plotting_utilities.py | 202 +++++++++++++------ 1 file changed, 142 insertions(+), 60 deletions(-) diff --git a/psydac/feec/multipatch/plotting_utilities.py b/psydac/feec/multipatch/plotting_utilities.py index b16b31c1b..3344f89a4 100644 --- a/psydac/feec/multipatch/plotting_utilities.py +++ b/psydac/feec/multipatch/plotting_utilities.py @@ -4,6 +4,7 @@ from sympy import lambdify import numpy as np +import matplotlib import matplotlib.pyplot as plt from matplotlib import cm, colors from mpl_toolkits import mplot3d @@ -11,10 +12,11 @@ from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField -from psydac.fem.vector import ProductFemSpace, VectorFemSpace from psydac.utilities.utils import refine_array_1d from psydac.feec.pull_push import push_2d_h1, push_2d_hcurl, push_2d_hdiv, push_2d_l2 +matplotlib.rcParams['font.size'] = 15 + #============================================================================== def is_vector_valued(u): # small utility function, only tested for FemFields in multi-patch spaces of the 2D grad-curl sequence @@ -57,18 +59,18 @@ def get_grid_vals(u, etas, mappings_list, space_kind='hcurl'): uk_field_0 = u[k] # computing the pushed-fwd values on the grid - if space_kind == 'h1': + if space_kind == 'h1' or space_kind == 'V0': assert not vector_valued # todo (MCP): add 2d_hcurl_vector push_field = lambda eta1, eta2: push_2d_h1(uk_field_0, eta1, eta2) - elif space_kind == 'hcurl': + elif space_kind == 'hcurl' or space_kind == 'V1': # todo (MCP): specify 2d_hcurl_scalar in push functions - push_field = lambda eta1, eta2: push_2d_hcurl(uk_field_0, uk_field_1, eta1, eta2, mappings_list[k]) - elif space_kind == 'hdiv': - push_field = lambda eta1, eta2: push_2d_hdiv(uk_field_0, uk_field_1, eta1, eta2, mappings_list[k]) + push_field = lambda eta1, eta2: push_2d_hcurl(uk_field_0, uk_field_1, eta1, eta2, mappings_list[k].get_callable_mapping()) + elif space_kind == 'hdiv' or space_kind == 'V2': + push_field = lambda eta1, eta2: push_2d_hdiv(uk_field_0, uk_field_1, eta1, eta2, mappings_list[k].get_callable_mapping()) elif space_kind == 'l2': assert not vector_valued - push_field = lambda eta1, eta2: push_2d_l2(uk_field_0, eta1, eta2, mappings_list[k]) + push_field = lambda eta1, eta2: push_2d_l2(uk_field_0, eta1, eta2, mappings_list[k].get_callable_mapping()) else: raise ValueError('unknown value for space_kind = {}'.format(space_kind)) @@ -81,9 +83,9 @@ def get_grid_vals(u, etas, mappings_list, space_kind='hcurl'): # always return a list, even for scalar-valued functions ? if not vector_valued: - return np.array(u_vals_components[0]) + return u_vals_components[0] else: - return [np.array(a) for a in u_vals_components] + return u_vals_components #------------------------------------------------------------------------------ def get_grid_quad_weights(etas, patch_logvols, mappings_list): #_obj): @@ -102,9 +104,11 @@ def get_grid_quad_weights(etas, patch_logvols, mappings_list): #_obj): N1 = eta_1.shape[1] log_weight = patch_logvols[k]/(N0*N1) + Fk = mappings_list[k].get_callable_mapping() for i, x1i in enumerate(eta_1[:, 0]): for j, x2j in enumerate(eta_2[0, :]): - quad_weights[k][i, j] = push_2d_l2(one_field, x1i, x2j, mapping=mappings_list[k]) * log_weight + det_Fk_ij = Fk.metric_det(x1i, x2j)**0.5 + quad_weights[k][i, j] = det_Fk_ij * log_weight return quad_weights @@ -169,12 +173,8 @@ def get_patch_knots_gridlines(Vh, N, mappings, plotted_patch=-1): F = [M.get_callable_mapping() for d,M in mappings.items()] if plotted_patch in range(len(mappings)): - space = Vh.spaces[plotted_patch] - if isinstance(space, (VectorFemSpace, ProductFemSpace)): - space = space.spaces[0] - - grid_x1 = space.breaks[0] - grid_x2 = space.breaks[1] + grid_x1 = Vh.spaces[plotted_patch].spaces[0].breaks[0] + grid_x2 = Vh.spaces[plotted_patch].spaces[0].breaks[1] x1 = refine_array_1d(grid_x1, N) x2 = refine_array_1d(grid_x2, N) @@ -192,13 +192,16 @@ def get_patch_knots_gridlines(Vh, N, mappings, plotted_patch=-1): return gridlines_x1, gridlines_x2 #------------------------------------------------------------------------------ -def plot_field(fem_field=None, stencil_coeffs=None, numpy_coeffs=None, Vh=None, domain=None, space_kind=None, title=None, filename='dummy_plot.png', subtitles=None, hide_plot=True): +def plot_field( + fem_field=None, stencil_coeffs=None, numpy_coeffs=None, Vh=None, domain=None, surface_plot=False, cb_min=None, cb_max=None, + plot_type='amplitude', cmap='hsv', space_kind=None, title=None, filename='dummy_plot.png', subtitles=None, N_vis=20, vf_skip=2, hide_plot=True): """ plot a discrete field (given as a FemField or by its coeffs in numpy or stencil format) on the given domain :param Vh: Fem space needed if v is given by its coeffs :param space_kind: type of the push-forward defining the physical Fem Space :param subtitles: in case one would like to have several subplots # todo: then v should be given as a list of fields... + :param N_vis: nb of visualization points per patch (per dimension) """ if not space_kind in ['h1', 'hcurl', 'l2']: raise ValueError('invalid value for space_kind = {}'.format(space_kind)) @@ -212,30 +215,89 @@ def plot_field(fem_field=None, stencil_coeffs=None, numpy_coeffs=None, Vh=None, mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) mappings_list = list(mappings.values()) - etas, xx, yy = get_plotting_grid(mappings, N=20) + etas, xx, yy = get_plotting_grid(mappings, N=N_vis) grid_vals = lambda v: get_grid_vals(v, etas, mappings_list, space_kind=space_kind) vh_vals = grid_vals(vh) - if is_vector_valued(vh): - # then vh_vals[d] contains the values of the d-component of vh (as a patch-indexed list) - vh_abs_vals = [np.sqrt(abs(v[0])**2 + abs(v[1])**2) for v in zip(vh_vals[0],vh_vals[1])] + if plot_type == 'vector_field' and not is_vector_valued(vh): + print("WARNING [plot_field]: vector_field plot is not possible with a scalar field, plotting the amplitude instead") + plot_type = 'amplitude' + + if plot_type == 'vector_field': + if is_vector_valued(vh): + my_small_streamplot( + title=title, + vals_x=vh_vals[0], + vals_y=vh_vals[1], + skip=vf_skip, + xx=xx, + yy=yy, + amp_factor=2, + save_fig=filename, + hide_plot=hide_plot, + dpi = 200, + ) + else: - # then vh_vals just contains the values of vh (as a patch-indexed list) - vh_abs_vals = np.abs(vh_vals) - - my_small_plot( - title=title, - vals=[vh_abs_vals], - titles=subtitles, - xx=xx, - yy=yy, - surface_plot=False, - save_fig=filename, - save_vals = True, - hide_plot=hide_plot, - cmap='hsv', - dpi = 400, - ) + # computing plot_vals_list: may have several elements for several plots + if plot_type=='amplitude': + + if is_vector_valued(vh): + # then vh_vals[d] contains the values of the d-component of vh (as a patch-indexed list) + plot_vals = [np.sqrt(abs(v[0])**2 + abs(v[1])**2) for v in zip(vh_vals[0],vh_vals[1])] + else: + # then vh_vals just contains the values of vh (as a patch-indexed list) + plot_vals = np.abs(vh_vals) + plot_vals_list = [plot_vals] + + elif plot_type=='components': + if is_vector_valued(vh): + # then vh_vals[d] contains the values of the d-component of vh (as a patch-indexed list) + plot_vals_list = vh_vals + if subtitles is None: + subtitles = ['x-component', 'y-component'] + else: + # then vh_vals just contains the values of vh (as a patch-indexed list) + plot_vals_list = [vh_vals] + else: + raise ValueError(plot_type) + + my_small_plot( + title=title, + vals=plot_vals_list, + titles=subtitles, + xx=xx, + yy=yy, + surface_plot=surface_plot, + cb_min=cb_min, + cb_max=cb_max, + save_fig=filename, + save_vals = False, + hide_plot=hide_plot, + cmap=cmap, + dpi = 300, + ) + + # if is_vector_valued(vh): + # # then vh_vals[d] contains the values of the d-component of vh (as a patch-indexed list) + # vh_abs_vals = [np.sqrt(abs(v[0])**2 + abs(v[1])**2) for v in zip(vh_vals[0],vh_vals[1])] + # else: + # # then vh_vals just contains the values of vh (as a patch-indexed list) + # vh_abs_vals = np.abs(vh_vals) + + # my_small_plot( + # title=title, + # vals=[vh_abs_vals], + # titles=subtitles, + # xx=xx, + # yy=yy, + # surface_plot=False, + # save_fig=filename, + # save_vals=False, + # hide_plot=hide_plot, + # cmap='hsv', + # dpi = 400, + # ) #------------------------------------------------------------------------------ def my_small_plot( @@ -245,6 +307,8 @@ def my_small_plot( gridlines_x2=None, surface_plot=False, cmap='viridis', + cb_min=None, + cb_max=None, save_fig=None, save_vals = False, hide_plot=False, @@ -257,46 +321,49 @@ def my_small_plot( assert xx and yy n_plots = len(vals) if n_plots > 1: - assert n_plots == len(titles) + if titles is None or n_plots != len(titles): + titles = n_plots*[title] else: if titles: print('Warning [my_small_plot]: will discard argument titles for a single plot') + titles = [title] n_patches = len(xx) assert n_patches == len(yy) if save_vals: + # saving as vals.npz np.savez('vals', xx=xx, yy=yy, vals=vals) fig = plt.figure(figsize=(2.6+4.8*n_plots, 4.8)) fig.suptitle(title, fontsize=14) for i in range(n_plots): - vmin = np.min(vals[i]) - vmax = np.max(vals[i]) + if cb_min is None: + vmin = np.min(vals[i]) + else: + vmin = cb_min + if cb_max is None: + vmax = np.max(vals[i]) + else: + vmax = cb_max cnorm = colors.Normalize(vmin=vmin, vmax=vmax) assert n_patches == len(vals[i]) + ax = fig.add_subplot(1, n_plots, i+1) for k in range(n_patches): - ax.contourf(xx[k], yy[k], vals[i][k], 50, norm=cnorm, cmap=cmap) #, extend='both') + ax.contourf(xx[k], yy[k], vals[i][k], 50, norm=cnorm, cmap=cmap, zorder=-10) #, extend='both') + ax.set_rasterization_zorder(0) cbar = fig.colorbar(cm.ScalarMappable(norm=cnorm, cmap=cmap), ax=ax, pad=0.05) - - if gridlines_x1 is not None and gridlines_x2 is not None: - if isinstance(gridlines_x1[0], (list,tuple)): - for x1,x2 in zip(gridlines_x1,gridlines_x2): - if x1 is None or x2 is None:continue - kwargs = {'lw': 0.5} - ax.plot(*x1, color='k', **kwargs) - ax.plot(*x2, color='k', **kwargs) - else: - ax.plot(*gridlines_x1, color='k') - ax.plot(*gridlines_x2, color='k') - + if gridlines_x1 is not None: + ax.plot(*gridlines_x1, color='k') + ax.plot(*gridlines_x2, color='k') if show_xylabel: ax.set_xlabel( r'$x$', rotation='horizontal' ) ax.set_ylabel( r'$y$', rotation='horizontal' ) if n_plots > 1: ax.set_title ( titles[i] ) + ax.set_aspect('equal') if save_fig: print('saving contour plot in file '+save_fig) @@ -310,8 +377,14 @@ def my_small_plot( fig.suptitle(title+' -- surface', fontsize=14) for i in range(n_plots): - vmin = np.min(vals[i]) - vmax = np.max(vals[i]) + if cb_min is None: + vmin = np.min(vals[i]) + else: + vmin = cb_min + if cb_max is None: + vmax = np.max(vals[i]) + else: + vmax = cb_max cnorm = colors.Normalize(vmin=vmin, vmax=vmax) assert n_patches == len(vals[i]) ax = fig.add_subplot(1, n_plots, i+1, projection='3d') @@ -331,7 +404,8 @@ def my_small_plot( save_fig_surf = save_fig[:-4]+'_surf'+ext print('saving surface plot in file '+save_fig_surf) plt.savefig(save_fig_surf, bbox_inches='tight', dpi=dpi) - else: + + if not hide_plot: plt.show() #------------------------------------------------------------------------------ @@ -341,6 +415,7 @@ def my_small_streamplot( amp_factor=1, save_fig=None, hide_plot=False, + show_xylabel=True, dpi='figure', ): """ @@ -349,7 +424,10 @@ def my_small_streamplot( n_patches = len(xx) assert n_patches == len(yy) - fig = plt.figure(figsize=(2.6+4.8, 4.8)) + # fig = plt.figure(figsize=(2.6+4.8, 4.8)) + + fig, ax = plt.subplots(1,1, figsize=(2.6+4.8, 4.8)) + fig.suptitle(title, fontsize=14) delta = 0.25 @@ -359,14 +437,18 @@ def my_small_streamplot( #print('max_val = {}'.format(max_val)) vf_amp = amp_factor/max_val for k in range(n_patches): - plt.quiver(xx[k][::skip, ::skip], yy[k][::skip, ::skip], vals_x[k][::skip, ::skip], vals_y[k][::skip, ::skip], + ax.quiver(xx[k][::skip, ::skip], yy[k][::skip, ::skip], vals_x[k][::skip, ::skip], vals_y[k][::skip, ::skip], scale=1/(vf_amp*0.05), width=0.002) # width=) units='width', pivot='mid', + if show_xylabel: + ax.set_xlabel( r'$x$', rotation='horizontal' ) + ax.set_ylabel( r'$y$', rotation='horizontal' ) + + ax.set_aspect('equal') + if save_fig: print('saving vector field (stream) plot in file '+save_fig) plt.savefig(save_fig, bbox_inches='tight', dpi=dpi) if not hide_plot: plt.show() - - From bbad5695a4c3dc881da06848625d01f69d88aa17 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Fri, 14 Jul 2023 14:22:33 +0200 Subject: [PATCH 03/88] make multipatch examples run --- .../examples/h1_source_pbms_conga_2d.py | 15 +++++++-------- .../examples/hcurl_eigen_pbms_conga_2d.py | 15 +++++++-------- .../examples/hcurl_source_pbms_conga_2d.py | 16 ++++++++-------- .../examples/mixed_source_pbms_conga_2d.py | 17 +++++++++-------- psydac/feec/multipatch/tests/__init__.py | 0 .../tests/test_feec_poisson_multipatch_2d.py | 5 ++++- 6 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 psydac/feec/multipatch/tests/__init__.py diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index cb03e4ff1..92b2451ba 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -25,6 +25,7 @@ from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField @@ -92,7 +93,7 @@ def solve_h1_source_pbm( print('building the symbolic and discrete deRham sequences...') derham = Derham(domain, ["H1", "Hcurl", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, backend=PSYDAC_BACKENDS[backend_language]) + derham_h = discretize(derham, domain_h, degree=degree) # multi-patch (broken) spaces V0h = derham_h.V0 @@ -128,10 +129,8 @@ def solve_h1_source_pbm( print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0 = derham_h.conforming_projection(space='V0', hom_bc=True, backend_language=backend_language) - cP0_m = cP0.to_sparse_matrix() - # cP1 = derham_h.conforming_projection(space='V1', hom_bc=True, backend_language=backend_language) - # cP1_m = cP1.to_sparse_matrix() + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + # cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) if not os.path.exists(plot_dir): os.makedirs(plot_dir) @@ -141,7 +140,7 @@ def lift_u_bc(u_bc): print('lifting the boundary condition in V0h... [warning: Not Tested Yet!]') # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs u_bc = lambdify(domain.coordinates, u_bc) - u_bc_log = [pull_2d_h1(u_bc, m) for m in mappings_list] + u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) for m in mappings_list] # it's a bit weird to apply P1 on the list of (pulled back) logical fields -- why not just apply it on u_bc ? uh_bc = P0(u_bc_log) ubc_c = uh_bc.coeffs.toarray() @@ -176,7 +175,7 @@ def lift_u_bc(u_bc): if source_proj == 'P_geom': print('projecting the source with commuting projection P0...') f = lambdify(domain.coordinates, f_scal) - f_log = [pull_2d_h1(f, m) for m in mappings_list] + f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] f_h = P0(f_log) f_c = f_h.coeffs.toarray() b_c = dH0_m.dot(f_c) @@ -186,7 +185,7 @@ def lift_u_bc(u_bc): v = element_of(V0h.symbolic_space, name='v') expr = f_scal * v l = LinearForm(v, integral(domain, expr)) - lh = discretize(l, domain_h, V0h, backend=PSYDAC_BACKENDS[backend_language]) + lh = discretize(l, domain_h, V0h) b = lh.assemble() b_c = b.toarray() if plot_source: diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index 759efce0e..ce719ea66 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -18,6 +18,7 @@ from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language='python', mu=1, nu=1, gamma_h=10, sigma=None, nb_eigs=4, nb_eigs_plot=4, @@ -71,7 +72,7 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('building symbolic and discrete derham sequences...') derham = Derham(domain, ["H1", "Hcurl", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, backend=PSYDAC_BACKENDS[backend_language]) + derham_h = discretize(derham, domain_h, degree=degree) V0h = derham_h.V0 V1h = derham_h.V1 @@ -102,10 +103,8 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0 = derham_h.conforming_projection(space='V0', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) - cP1 = derham_h.conforming_projection(space='V1', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) - cP0_m = cP0.to_sparse_matrix() - cP1_m = cP1.to_sparse_matrix() + cP0_m = construct_V0_conforming_projection(V0h, domain_h, True) + cP1_m = construct_V1_conforming_projection(V1h, domain_h, True) print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators @@ -214,10 +213,10 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): nc = 8 deg = 4 - domain_name = 'pretzel_f' - # domain_name = 'curved_L_shape' + #domain_name = 'pretzel_f' + domain_name = 'curved_L_shape' nc = 10 - deg = 2 + deg = 3 m_load_dir = 'matrices_{}_nc={}_deg={}/'.format(domain_name, nc, deg) run_dir = 'eigenpbm_{}_nc={}_deg={}/'.format(domain_name, nc, deg) diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index 8809898fe..55dd506e6 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -29,6 +29,8 @@ from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection + def solve_hcurl_source_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', eta=-10., mu=1., nu=1., gamma_h=10., @@ -107,7 +109,7 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building discrete derham sequence...') - derham_h = discretize(derham, domain_h, degree=degree, backend=PSYDAC_BACKENDS[backend_language]) + derham_h = discretize(derham, domain_h, degree=degree) t_stamp = time_count(t_stamp) print('building commuting projection operators...') @@ -162,10 +164,8 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building the conforming Projection operators and matrices...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0 = derham_h.conforming_projection(space='V0', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) - cP1 = derham_h.conforming_projection(space='V1', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) - cP0_m = cP0.to_sparse_matrix() - cP1_m = cP1.to_sparse_matrix() + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) t_stamp = time_count(t_stamp) print('building the broken differential operators and matrices...') @@ -183,7 +183,7 @@ def lift_u_bc(u_bc): # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs u_bc_x = lambdify(domain.coordinates, u_bc[0]) u_bc_y = lambdify(domain.coordinates, u_bc[1]) - u_bc_log = [pull_2d_hcurl([u_bc_x, u_bc_y], m) for m in mappings_list] + u_bc_log = [pull_2d_hcurl([u_bc_x, u_bc_y], m.get_callable_mapping()) for m in mappings_list] # it's a bit weird to apply P1 on the list of (pulled back) logical fields -- why not just apply it on u_bc ? uh_bc = P1(u_bc_log) ubc_c = uh_bc.coeffs.toarray() @@ -240,7 +240,7 @@ def lift_u_bc(u_bc): print('projecting the source with commuting projection...') f_x = lambdify(domain.coordinates, f_vect[0]) f_y = lambdify(domain.coordinates, f_vect[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m) for m in mappings_list] + f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] f_h = P1(f_log) f_c = f_h.coeffs.toarray() b_c = dH1_m.dot(f_c) @@ -251,7 +251,7 @@ def lift_u_bc(u_bc): v = element_of(V1h.symbolic_space, name='v') expr = dot(f_vect,v) l = LinearForm(v, integral(domain, expr)) - lh = discretize(l, domain_h, V1h, backend=PSYDAC_BACKENDS[backend_language]) + lh = discretize(l, domain_h, V1h) b = lh.assemble() b_c = b.toarray() if plot_source: diff --git a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py index 5fba55dc3..81212af59 100644 --- a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py @@ -28,6 +28,8 @@ from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import get_eigenvalues from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection + def solve_magnetostatic_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2_wcurl_J', source_type='dipole_J', bc_type='metallic', @@ -120,7 +122,7 @@ def solve_magnetostatic_pbm( print('building symbolic and discrete derham sequences...') derham = Derham(domain, ["H1", "Hcurl", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, backend=PSYDAC_BACKENDS[backend_language]) + derham_h = discretize(derham, domain_h, degree=degree) V0h = derham_h.V0 V1h = derham_h.V1 @@ -137,18 +139,18 @@ def solve_magnetostatic_pbm( # these physical projection operators should probably be in the interface... def P0_phys(f_phys): f = lambdify(domain.coordinates, f_phys) - f_log = [pull_2d_h1(f, m) for m in mappings_list] + f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] return P0(f_log) def P1_phys(f_phys): f_x = lambdify(domain.coordinates, f_phys[0]) f_y = lambdify(domain.coordinates, f_phys[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m) for m in mappings_list] + f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] return P1(f_log) def P2_phys(f_phys): f = lambdify(domain.coordinates, f_phys) - f_log = [pull_2d_l2(f, m) for m in mappings_list] + f_log = [pull_2d_l2(f, m.get_callable_mapping()) for m in mappings_list] return P2(f_log) I0_m = IdLinearOperator(V0h).to_sparse_matrix() @@ -175,10 +177,9 @@ def P2_phys(f_phys): print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0 = derham_h.conforming_projection(space='V0', hom_bc=hom_bc, backend_language=backend_language, load_dir=m_load_dir) - cP1 = derham_h.conforming_projection(space='V1', hom_bc=hom_bc, backend_language=backend_language, load_dir=m_load_dir) - cP0_m = cP0.to_sparse_matrix() - cP1_m = cP1.to_sparse_matrix() + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators diff --git a/psydac/feec/multipatch/tests/__init__.py b/psydac/feec/multipatch/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py index 8eea88ec0..7fd6eb040 100644 --- a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py @@ -16,7 +16,7 @@ def test_poisson_pretzel_f(): backend_language='pyccel-gcc', plot_source=False, plot_dir='./plots/h1_tests_source_february/'+run_dir) - + print(l2_error) assert abs(l2_error-8.054935880021907e-05)<1e-10 #============================================================================== @@ -30,3 +30,6 @@ def teardown_module(): def teardown_function(): from sympy.core import cache cache.clear_cache() + +if __name__ == '__main__': + test_poisson_pretzel_f() From ec10dfd7ccafe81216ee6939c1271da49ea1bd61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Mon, 17 Jul 2023 11:59:37 +0200 Subject: [PATCH 04/88] Use SymPDE development version from GitHub --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 383c28347..06c45e638 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ 'pyevtk', # Our packages from PyPi - 'sympde == 0.17.2', + 'sympde @ git+https://github.com/pyccel/sympde#master', 'pyccel >= 1.8.1', 'gelato == 0.12', From a166d02004f02803f378277562bbfe24e9056ad4 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 17 Jul 2023 16:45:28 +0200 Subject: [PATCH 05/88] allow for non-matching grids in postprocessing --- psydac/api/postprocessing.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/psydac/api/postprocessing.py b/psydac/api/postprocessing.py index c20d566c0..f6e9fdecf 100644 --- a/psydac/api/postprocessing.py +++ b/psydac/api/postprocessing.py @@ -956,9 +956,10 @@ def _reconstruct_spaces(self): ncells_dict = {interior_name: interior_names_to_ncells[interior_name] for interior_name in subdomain_names} # No need for a a dict until PR about non-conforming meshes is merged # Check for conformity - ncells = list(ncells_dict.values())[0] - assert all(ncells_patch == ncells for ncells_patch in ncells_dict.values()) - + ncells = ncells_dict#list(ncells_dict.values())[0] + #try non conforming + #assert all(ncells_patch == ncells for ncells_patch in ncells_dict.values()) + subdomain = domain.get_subdomain(subdomain_names) space_name_0 = list(space_dict.keys())[0] periodic = space_dict[space_name_0][2].get('periodic', None) From 87bbef6cce9853c70b692fe0406737e381be0be3 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 17 Jul 2023 17:23:50 +0200 Subject: [PATCH 06/88] add utils --- psydac/feec/multipatch/utilities.py | 34 ++++ psydac/feec/multipatch/utils_conga_2d.py | 213 +++++++++++++++++++++++ 2 files changed, 247 insertions(+) diff --git a/psydac/feec/multipatch/utilities.py b/psydac/feec/multipatch/utilities.py index ac37be3a5..16b891fdc 100644 --- a/psydac/feec/multipatch/utilities.py +++ b/psydac/feec/multipatch/utilities.py @@ -66,6 +66,11 @@ def get_fem_name(method=None, k=None, DG_full=False, conf_proj=None, domain_name fn += '_inhom' return fn +def FEM_sol_fn(source_type=None, source_proj=None): + """ Get the filename for FEM solution coeffs in numpy array format """ + fn = 'sol_'+source_name(source_type, source_proj)+'.npy' + return fn + def get_load_dir(method=None, DG_full=False, domain_name=None,nc=None,deg=None,data='matrices'): """ get load directory name based on the fem name""" assert data in ['matrices','solutions','rhs'] @@ -73,3 +78,32 @@ def get_load_dir(method=None, DG_full=False, domain_name=None,nc=None,deg=None,d assert data == 'rhs' fem_name = get_fem_name(domain_name=domain_name,method=method, nc=nc,deg=deg, DG_full=DG_full) return './saved_'+data+'/'+fem_name+'/' + +def get_run_dir(domain_name, nc, deg, source_type=None, conf_proj=None): + rdir = domain_name + if source_type: + rdir += '_'+source_type + if conf_proj: + rdir += '_P='+conf_proj + rdir += '_nc={}_deg={}'.format(nc, deg) + return rdir + +def get_plot_dir(case_dir, run_dir): + return './plots/'+case_dir+'/'+run_dir + +def get_mat_dir(domain_name, nc, deg, quad_param=None): + mat_dir = './saved_matrices/matrices_{}_nc={}_deg={}'.format(domain_name, nc, deg) + if quad_param is not None: + mat_dir += '_qp={}'.format(quad_param) + return mat_dir + +def get_sol_dir(case_dir, domain_name, nc, deg): + return './saved_solutions/'+case_dir+'/solutions_{}_nc={}_deg={}'.format(domain_name, nc, deg) + +def diag_fn(source_type=None, source_proj=None): + """ Get the diagnostics filename""" + if source_type is not None: + fn = 'diag_'+source_name(source_type, source_proj)+'.txt' + else: + fn = 'diag.txt' + return fn \ No newline at end of file diff --git a/psydac/feec/multipatch/utils_conga_2d.py b/psydac/feec/multipatch/utils_conga_2d.py index e69de29bb..1c3039519 100644 --- a/psydac/feec/multipatch/utils_conga_2d.py +++ b/psydac/feec/multipatch/utils_conga_2d.py @@ -0,0 +1,213 @@ +import os +import datetime + +import numpy as np + +from sympy import lambdify +from sympde.topology import Derham + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_l2 + +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.plotting_utilities import get_plotting_grid, get_grid_quad_weights, get_grid_vals + + +# commuting projections on the physical domain (should probably be in the interface) +def P0_phys(f_phys, P0, domain, mappings_list): + f = lambdify(domain.coordinates, f_phys) + f_log = [pull_2d_h1(f, m) for m in mappings_list] + return P0(f_log) + +def P1_phys(f_phys, P1, domain, mappings_list): + f_x = lambdify(domain.coordinates, f_phys[0]) + f_y = lambdify(domain.coordinates, f_phys[1]) + f_log = [pull_2d_hcurl([f_x, f_y], m) for m in mappings_list] + return P1(f_log) + +def P2_phys(f_phys, P2, domain, mappings_list): + f = lambdify(domain.coordinates, f_phys) + f_log = [pull_2d_l2(f, m) for m in mappings_list] + return P2(f_log) + +def get_kind(space='V*'): + # temp helper + if space == 'V0': + kind='h1' + elif space == 'V1': + kind='hcurl' + elif space == 'V2': + kind='l2' + else: + raise ValueError(space) + return kind + + +#=============================================================================== +class DiagGrid(): + """ + Class storing: + - a diagnostic cell-centered grid + - writing / quadrature utilities + - a ref solution + to compare solutions from different FEM spaces on same domain + """ + def __init__(self, mappings=None, N_diag=None): + + mappings_list = list(mappings.values()) + etas, xx, yy, patch_logvols = get_plotting_grid(mappings, N=N_diag, centered_nodes=True, return_patch_logvols=True) + quad_weights = get_grid_quad_weights(etas, patch_logvols, mappings_list) + + self.etas = etas + self.xx = xx + self.yy = yy + self.patch_logvols = patch_logvols + self.quad_weights = quad_weights + self.mappings_list = mappings_list + + self.sol_ref = {} # Fem fields + self.sol_vals = {} # values on diag grid + self.sol_ref_vals = {} # values on diag grid + + def grid_vals_h1(self, v): + return get_grid_vals(v, self.etas, self.mappings_list, space_kind='h1') + + def grid_vals_hcurl(self, v): + return get_grid_vals(v, self.etas, self.mappings_list, space_kind='hcurl') + + def create_ref_fem_spaces(self, domain=None, ref_nc=None, ref_deg=None): + print('[DiagGrid] Discretizing the ref FEM space...') + degree = [ref_deg, ref_deg] + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + ref_nc = {patch.name: [ref_nc, ref_nc] for patch in domain.interior} + + domain_h = discretize(domain, ncells=ref_nc) + derham_h = discretize(derham, domain_h, degree=degree) #, backend=PSYDAC_BACKENDS[backend_language]) + self.V0h = derham_h.V0 + self.V1h = derham_h.V1 + + def import_ref_sol_from_coeffs(self, sol_ref_filename=None, space='V*'): + print('[DiagGrid] loading coeffs of ref_sol from {}...'.format(sol_ref_filename)) + if space == 'V0': + Vh = self.V0h + elif space == 'V1': + Vh = self.V1h + else: + raise ValueError(space) + try: + coeffs = np.load(sol_ref_filename) + except OSError: + print("-- WARNING: file not found, setting sol_ref = 0") + coeffs = np.zeros(Vh.nbasis) + if space in self.sol_ref: + print('WARNING !! sol_ref[{}] exists -- will be overwritten !! '.format(space)) + print('use refined labels if several solutions are needed in the same space') + self.sol_ref[space] = FemField(Vh, coeffs=array_to_psydac(coeffs, Vh.vector_space)) + + def write_sol_values(self, v, space='V*'): + """ + v: FEM field + """ + if space in self.sol_vals: + print('WARNING !! sol_vals[{}] exists -- will be overwritten !! '.format(space)) + print('use refined labels if several solutions are needed in the same space') + self.sol_vals[space] = get_grid_vals(v, self.etas, self.mappings_list, space_kind=get_kind(space)) + + def write_sol_ref_values(self, v=None, space='V*'): + """ + if no FemField v is provided, then use the self.sol_ref (must have been imported) + """ + if space in self.sol_vals: + print('WARNING !! sol_ref_vals[{}] exists -- will be overwritten !! '.format(space)) + print('use refined labels if several solutions are needed in the same space') + if v is None: + # then sol_ref must have been imported + v = self.sol_ref[space] + self.sol_ref_vals[space] = get_grid_vals(v, self.etas, self.mappings_list, space_kind=get_kind(space)) + + def compute_l2_error(self, space='V*'): + if space in ['V0', 'V2']: + u = self.sol_ref_vals[space] + uh = self.sol_vals[space] + abs_u = [np.abs(p) for p in u] + abs_uh = [np.abs(p) for p in uh] + errors = [np.abs(p-q) for p, q in zip(u, uh)] + elif space == 'V1': + u_x, u_y = self.sol_ref_vals[space] + uh_x, uh_y = self.sol_vals[space] + abs_u = [np.sqrt( (u1)**2 + (u2)**2 ) for u1, u2 in zip(u_x, u_y)] + abs_uh = [np.sqrt( (u1)**2 + (u2)**2 ) for u1, u2 in zip(uh_x, uh_y)] + errors = [np.sqrt( (u1-v1)**2 + (u2-v2)**2 ) for u1, v1, u2, v2 in zip(u_x, uh_x, u_y, uh_y)] + else: + raise ValueError(space) + + l2_norm_uh = (np.sum([J_F * v**2 for v, J_F in zip(abs_uh, self.quad_weights)]))**0.5 + l2_norm_u = (np.sum([J_F * v**2 for v, J_F in zip(abs_u, self.quad_weights)]))**0.5 + l2_error = (np.sum([J_F * v**2 for v, J_F in zip(errors, self.quad_weights)]))**0.5 + + return l2_norm_uh, l2_norm_u, l2_error + + def get_diags_for(self, v, space='V*', print_diags=True): + self.write_sol_values(v, space) + sol_norm, sol_ref_norm, l2_error = self.compute_l2_error(space) + rel_l2_error = l2_error/(max(sol_norm, sol_ref_norm)) + diags = { + 'sol_norm': sol_norm, + 'sol_ref_norm': sol_ref_norm, + 'rel_l2_error': rel_l2_error, + } + if print_diags: + print(' .. l2 norms (computed via quadratures on diag_grid): ') + print(diags) + + return diags + + +def get_Vh_diags_for(v=None, v_ref=None, M_m=None, print_diags=True, msg='error between ?? and ?? in Vh'): + """ + v, v_ref: FemField + M_m: mass matrix in scipy format + """ + uh_c = v.coeffs.toarray() + uh_ref_c = v_ref.coeffs.toarray() + err_c = uh_c - uh_ref_c + l2_error = np.dot(err_c, M_m.dot(err_c))**0.5 + sol_norm = np.dot(uh_c, M_m.dot(uh_c))**0.5 + sol_ref_norm = np.dot(uh_ref_c, M_m.dot(uh_ref_c))**0.5 + rel_l2_error = l2_error/(max(sol_norm, sol_ref_norm)) + diags = { + 'sol_norm': sol_norm, + 'sol_ref_norm': sol_ref_norm, + 'rel_l2_error': rel_l2_error, + } + if print_diags: + print(' .. l2 norms ({}): '.format(msg)) + print(diags) + + return diags + + +def write_diags_to_file(diags, script_filename, diag_filename, params={}): + print(' -- writing diags to file {} --'.format(diag_filename)) + if not os.path.exists(diag_filename): + open(diag_filename, 'w') + + with open(diag_filename, 'a') as a_writer: + a_writer.write('\n') + a_writer.write(' ---------- ---------- ---------- ---------- ---------- ---------- \n') + a_writer.write(' run script: \n {}\n'.format(script_filename)) + a_writer.write(' executed on: \n {}\n\n'.format(datetime.datetime.now())) + a_writer.write(' params: \n') + for key, value in params.items(): + a_writer.write(' {}: {} \n'.format(key, value)) + a_writer.write('\n') + a_writer.write(' diags: \n') + for key, value in diags.items(): + a_writer.write(' {}: {} \n'.format(key, value)) + a_writer.write(' ---------- ---------- ---------- ---------- ---------- ---------- \n') + a_writer.write('\n') \ No newline at end of file From 1a41cd0c5bd289423544f47af55123d420234266 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 18 Jul 2023 09:06:22 +0200 Subject: [PATCH 07/88] adds non-matching domain utilities --- ...on_matching_multipatch_domain_utilities.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py diff --git a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py new file mode 100644 index 000000000..548690def --- /dev/null +++ b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py @@ -0,0 +1,99 @@ +from mpi4py import MPI +import numpy as np +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping +from sympde.topology import Boundary, Interface, Union + +from scipy.sparse import eye as sparse_eye +from scipy.sparse import csr_matrix +from scipy.sparse.linalg import inv +from scipy.sparse import coo_matrix, bmat +from scipy.sparse.linalg import inv as sp_inv + +from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.fem.splines import SplineSpace + +from psydac.feec.multipatch.multipatch_domain_utilities import create_domain +def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): + + """ + Create a 2D multipatch square domain with the prescribed number of patch in each direction. + + Parameters + ---------- + ncells: + + |2| + _____ + |4|2| + + [[2, None], + [4, 2]] + + [[2, 2, 0, 0], + [2, 4, 0, 0], + [4, 8, 4, 2], + [4, 4, 2, 2]] + number of patch in each direction + + Returns + ------- + domain : + The symbolic multipatch domain + """ + ax, bx = interval_x + ay, by = interval_y + nb_patchx, nb_patchy = np.shape(ncells) + + list_Omega = [[Square('OmegaLog_'+str(i)+'_'+str(j), + bounds1 = (ax + i/nb_patchx * (bx-ax),ax + (i+1)/nb_patchx * (bx-ax)), + bounds2 = (ay + j/nb_patchy * (by-ay),ay + (j+1)/nb_patchy * (by-ay))) for j in range(nb_patchy)] for i in range(nb_patchx)] + + + if mapping == 'identity': + list_mapping = [[IdentityMapping('M_'+str(i)+'_'+str(j),2) for j in range(nb_patchy)] for i in range(nb_patchx)] + + elif mapping == 'polar': + list_mapping = [[PolarMapping('M_'+str(i)+'_'+str(j),2, c1= 0., c2= 0., rmin = 0., rmax=1.) for j in range(nb_patchy)] for i in range(nb_patchx)] + + list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range(nb_patchy)] for i in range(nb_patchx)] + flat_list = [] + for i in range(nb_patchx): + for j in range(nb_patchy): + if ncells[i, j] != None: + flat_list.append(list_domain[i][j]) + + domains = flat_list + interfaces = [] + + #interfaces in y + for j in range(nb_patchy): + interfaces.extend([[list_domain[i][j].get_boundary(axis=0, ext=+1), list_domain[i+1][j].get_boundary(axis=0, ext=-1), 1] for i in range(nb_patchx-1) if ncells[i][j] != None and ncells[i+1][j] != None]) + + #interfaces in x + for i in range(nb_patchx): + interfaces.extend([[list_domain[i][j].get_boundary(axis=1, ext=+1), list_domain[i][j+1].get_boundary(axis=1, ext=-1), 1] for j in range(nb_patchy-1) if ncells[i][j] != None and ncells[i][j+1] != None]) + + domain = create_domain(domains, interfaces, name='domain') + + return domain + +def get_L_shape_ncells(patches, n0): + ncells = np.zeros((patches, patches), dtype = object) + + pm = int(patches/2) + assert patches/2 == pm + + for i in range(pm): + for j in range(pm): + ncells[i,j] = None + + for i in range(pm, patches): + for j in range(patches): + exp = 1+patches - (abs(i-pm)+abs(j-pm)) + ncells[i,j] = n0**exp + ncells[j,i] = n0**exp + + return ncells \ No newline at end of file From e84552a3241cd50db498d8b4c6d679e565280473 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 18 Jul 2023 14:43:37 +0200 Subject: [PATCH 08/88] adds curl-curl test case comparisson --- psydac/api/fem.py | 8 +- .../examples_nc/hcurl_eigen_pbms_nc.py | 408 ++++++++++++++++++ .../examples_nc/hcurl_eigen_testcase.py | 267 ++++++++++++ 3 files changed, 680 insertions(+), 3 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py create mode 100644 psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py diff --git a/psydac/api/fem.py b/psydac/api/fem.py index 281ff2107..2b28c01ee 100644 --- a/psydac/api/fem.py +++ b/psydac/api/fem.py @@ -726,7 +726,9 @@ def allocate_matrices(self, backend=None): elif use_prolongation: Ps = [knot_insertion_projection_operator(trs, trs.get_refined_space(ncells)) for trs in trial_fem_space.spaces] P = BlockLinearOperator(trial_fem_space.vector_space, trial_fem_space.get_refined_space(ncells).vector_space) - for ni,Pi in enumerate(Ps):P[ni,ni] = Pi + for ni,Pi in enumerate(Ps): + P[ni,ni] = Pi + mat = ComposedLinearOperator(trial_space, test_space, mat, P) self._matrix[i,j] = mat @@ -789,9 +791,9 @@ def allocate_matrices(self, backend=None): if is_conformal: matrix[k1, k2] = global_mats[k1, k2] elif use_restriction: - matrix.operators[-1][k1, k2] = global_mats[k1, k2] + matrix.multiplicants[-1][k1, k2] = global_mats[k1, k2] elif use_prolongation: - matrix.operators[0][k1, k2] = global_mats[k1, k2] + matrix.multiplicants[0][k1, k2] = global_mats[k1, k2] else: # case of scalar equation if is_broken: # multi-patch diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py new file mode 100644 index 000000000..d2fd42dfa --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -0,0 +1,408 @@ +import os +from mpi4py import MPI + +import numpy as np +import matplotlib.pyplot as plt +from collections import OrderedDict +from sympde.topology import Derham + +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file + +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping +from psydac.fem.vector import ProductFemSpace + +from scipy.sparse.linalg import spilu, lgmres +from scipy.sparse.linalg import LinearOperator, eigsh, minres +from scipy.sparse import csr_matrix +from scipy.linalg import norm + +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField + +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain +from psydac.feec.multipatch.non_matching_operators import construct_V1_conforming_projection + +from psydac.api.postprocessing import OutputManager, PostProcessManager + +#from said +from scipy.sparse.linalg import spsolve, inv + +from sympde.calculus import grad, dot, curl, cross +from sympde.calculus import minus, plus +from sympde.topology import VectorFunctionSpace +from sympde.topology import elements_of +from sympde.topology import NormalVector +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping +from sympde.expr.expr import LinearForm, BilinearForm +from sympde.expr.expr import integral +from sympde.expr.expr import Norm +from sympde.expr.equation import find, EssentialBC + +from psydac.api.tests.build_domain import build_pretzel +from psydac.fem.basic import FemField +from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL +from psydac.feec.pull_push import pull_2d_hcurl + +def hcurl_solve_eigen_pbm_multipatch_nc(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, + plot_dir=None, hide_plots=True, m_load_dir="",): + + diags = {} + + if sigma is None: + raise ValueError('please specify a value for sigma') + + print('---------------------------------------------------------------------------------------------------------') + print('Starting hcurl_solve_eigen_pbm function with: ') + print(' ncells = {}'.format(ncells)) + print(' degree = {}'.format(degree)) + print(' domain_name = {}'.format(domain_name)) + print(' backend_language = {}'.format(backend_language)) + print('---------------------------------------------------------------------------------------------------------') + t_stamp = time_count() + print('building symbolic and discrete domain...') + + int_x, int_y = domain + + if domain_name == 'refined_square' or domain_name =='square_L_shape': + domain = create_square_domain(ncells, int_x, int_y, mapping='identity') + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + elif domain_name == 'curved_L_shape': + domain = create_square_domain(ncells, int_x, int_y, mapping='polar') + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + elif domain_name == 'pretzel_f': + domain = build_multipatch_domain(domain_name=domain_name) + ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + + else: + ValueError("Domain not defined.") + + # domain = build_multipatch_domain(domain_name = 'curved_L_shape') + # + # ncells = np.array([4,8,4]) + # ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + + + t_stamp = time_count(t_stamp) + print(' .. discrete domain...') + domain_h = discretize(domain, ncells=ncells_h) # Vh space + + print('building symbolic and discrete derham sequences...') + t_stamp = time_count() + print(' .. derham sequence...') + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + + t_stamp = time_count(t_stamp) + print(' .. discrete derham sequence...') + derham_h = discretize(derham, domain_h, degree=degree) + + + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + print('dim(V0h) = {}'.format(V0h.nbasis)) + print('dim(V1h) = {}'.format(V1h.nbasis)) + print('dim(V2h) = {}'.format(V2h.nbasis)) + diags['ndofs_V0'] = V0h.nbasis + diags['ndofs_V1'] = V1h.nbasis + diags['ndofs_V2'] = V2h.nbasis + + + t_stamp = time_count(t_stamp) + print('building the discrete operators:') + #print('commuting projection operators...') + #nquads = [4*(d + 1) for d in degree] + #P0, P1, P2 = derham_h.projectors(nquads=nquads) + + I1 = IdLinearOperator(V1h) + I1_m = I1.to_sparse_matrix() + + t_stamp = time_count(t_stamp) + print('Hodge operators...') + # multi-patch (broken) linear operators / matrices + #H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) + H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) + H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) + + #H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + #dH0_m = H0.get_dual_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + dH2_m = H2.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print('conforming projection operators...') + # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) + cP0_m = None + cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + + t_stamp = time_count(t_stamp) + print('broken differential operators...') + bD0, bD1 = derham_h.broken_derivatives_as_operators + #bD0_m = bD0.to_sparse_matrix() + bD1_m = bD1.to_sparse_matrix() + + t_stamp = time_count(t_stamp) + print('converting some matrices to csr format...') + + H1_m = H1_m.tocsr() + dH1_m = dH1_m.tocsr() + H2_m = H2_m.tocsr() + cP1_m = cP1_m.tocsr() + bD1_m = bD1_m.tocsr() + + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + print('computing the full operator matrix...') + A_m = np.zeros_like(H1_m) + + # Conga (projection-based) stiffness matrices + if mu != 0: + # curl curl: + t_stamp = time_count(t_stamp) + print('mu = {}'.format(mu)) + print('curl-curl stiffness matrix...') + + pre_CC_m = bD1_m.transpose() @ dH2_m @ bD1_m + CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix + A_m += mu * CC_m + + # jump stabilization in V1h: + if gamma_h != 0 or generalized_pbm: + t_stamp = time_count(t_stamp) + print('jump stabilization matrix...') + jump_stab_m = I1_m - cP1_m + JS_m = jump_stab_m.transpose() @ dH1_m @ jump_stab_m + + if generalized_pbm: + print('adding jump stabilization to RHS of generalized eigenproblem...') + B_m = cP1_m.transpose() @ dH1_m @ cP1_m + JS_m + else: + B_m = dH1_m + + + t_stamp = time_count(t_stamp) + print('solving matrix eigenproblem...') + all_eigenvalues, all_eigenvectors_transp = get_eigenvalues(nb_eigs_solve, sigma, A_m, B_m) + #Eigenvalue processing + t_stamp = time_count(t_stamp) + print('sorting out eigenvalues...') + zero_eigenvalues = [] + if skip_eigs_threshold is not None: + eigenvalues = [] + eigenvectors = [] + for val, vect in zip(all_eigenvalues, all_eigenvectors_transp.T): + if abs(val) < skip_eigs_threshold: + zero_eigenvalues.append(val) + # we skip the eigenvector + else: + eigenvalues.append(val) + eigenvectors.append(vect) + else: + eigenvalues = all_eigenvalues + eigenvectors = all_eigenvectors_transp.T + + for k, val in enumerate(eigenvalues): + diags['eigenvalue_{}'.format(k)] = val #eigenvalues[k] + + for k, val in enumerate(zero_eigenvalues): + diags['skipped eigenvalue_{}'.format(k)] = val + + t_stamp = time_count(t_stamp) + print('plotting the eigenmodes...') + + # OM = OutputManager('spaces.yml', 'fields.h5') + # OM.add_spaces(V1h=V1h) + + nb_eigs = len(eigenvalues) + for i in range(min(nb_eigs_plot, nb_eigs)): + OM = OutputManager(plot_dir+'/spaces.yml', plot_dir+'/fields.h5') + OM.add_spaces(V1h=V1h) + print('looking at emode i = {}... '.format(i)) + lambda_i = eigenvalues[i] + emode_i = np.real(eigenvectors[i]) + norm_emode_i = np.dot(emode_i,H1_m.dot(emode_i)) + eh_c = emode_i/norm_emode_i + stencil_coeffs = array_to_psydac(cP1_m @ eh_c, V1h.vector_space) + vh = FemField(V1h, coeffs=stencil_coeffs) + OM.set_static() + #OM.add_snapshot(t=i , ts=0) + OM.export_fields(vh = vh) + + #print('norm of computed eigenmode: ', norm_emode_i) + # plot the broken eigenmode: + OM.export_space_info() + OM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces.yml', fields_file=plot_dir+'/fields.h5' ) + PM.export_to_vtk(plot_dir+"/eigen_{}".format(i),grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) + PM.close() + + t_stamp = time_count(t_stamp) + + ### Saids Code + + V = VectorFunctionSpace('V', domain, kind='hcurl') + + u, v, F = elements_of(V, names='u, v, F') + nn = NormalVector('nn') + + I = domain.interfaces + boundary = domain.boundary + + kappa = 10 + k = 1 + + jump = lambda w:plus(w)-minus(w) + avr = lambda w:0.5*plus(w) + 0.5*minus(w) + + expr1_I = cross(nn, jump(v))*curl(avr(u))\ + +k*cross(nn, jump(u))*curl(avr(v))\ + +kappa*cross(nn, jump(u))*cross(nn, jump(v)) + + expr1 = curl(u)*curl(v) + expr1_b = -cross(nn, v) * curl(u) -k*cross(nn, u)*curl(v) + kappa*cross(nn, u)*cross(nn, v) + ## curl curl u = - omega**2 u + + expr2 = dot(u,v) + #expr2_I = kappa*cross(nn, jump(u))*cross(nn, jump(v)) + #expr2_b = -k*cross(nn, u)*curl(v) + kappa * cross(nn, u) * cross(nn, v) + + # Bilinear form a: V x V --> R + a = BilinearForm((u,v), integral(domain, expr1) + integral(I, expr1_I) + integral(boundary, expr1_b)) + + # Linear form l: V --> R + b = BilinearForm((u,v), integral(domain, expr2))# + integral(I, expr2_I) + integral(boundary, expr2_b)) + + #+++++++++++++++++++++++++++++++ + # 2. Discretization + #+++++++++++++++++++++++++++++++ + + domain_h = discretize(domain, ncells=ncells_h) + Vh = discretize(V, domain_h, degree=degree) + + ah = discretize(a, domain_h, [Vh, Vh]) + Ah_m = ah.assemble().tosparse() + + bh = discretize(b, domain_h, [Vh, Vh]) + Bh_m = bh.assemble().tosparse() + + all_eigenvalues_2, all_eigenvectors_transp_2 = get_eigenvalues(nb_eigs_solve, sigma, Ah_m, Bh_m) + + #Eigenvalue processing + t_stamp = time_count(t_stamp) + print('sorting out eigenvalues...') + zero_eigenvalues2 = [] + if skip_eigs_threshold is not None: + eigenvalues2 = [] + eigenvectors2 = [] + for val, vect in zip(all_eigenvalues_2, all_eigenvectors_transp_2.T): + if abs(val) < skip_eigs_threshold: + zero_eigenvalues2.append(val) + # we skip the eigenvector + else: + eigenvalues2.append(val) + eigenvectors2.append(vect) + else: + eigenvalues2 = all_eigenvalues_2 + eigenvectors2 = all_eigenvectors_transp_2.T + diags['DG'] = True + for k, val in enumerate(eigenvalues2): + diags['eigenvalue2_{}'.format(k)] = val #eigenvalues[k] + + for k, val in enumerate(zero_eigenvalues2): + diags['skipped eigenvalue2_{}'.format(k)] = val + + t_stamp = time_count(t_stamp) + print('plotting the eigenmodes...') + + # OM = OutputManager('spaces.yml', 'fields.h5') + # OM.add_spaces(V1h=V1h) + + nb_eigs = len(eigenvalues2) + for i in range(min(nb_eigs_plot, nb_eigs)): + OM = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM.add_spaces(V1h=Vh) + print('looking at emode i = {}... '.format(i)) + lambda_i = eigenvalues2[i] + emode_i = np.real(eigenvectors2[i]) + norm_emode_i = np.dot(emode_i,Bh_m.dot(emode_i)) + eh_c = emode_i/norm_emode_i + stencil_coeffs = array_to_psydac(eh_c, Vh.vector_space) + vh = FemField(Vh, coeffs=stencil_coeffs) + OM.set_static() + #OM.add_snapshot(t=i , ts=0) + OM.export_fields(vh = vh) + + #print('norm of computed eigenmode: ', norm_emode_i) + # plot the broken eigenmode: + OM.export_space_info() + OM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) + PM.export_to_vtk(plot_dir+"/eigen2_{}".format(i),grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) + PM.close() + + t_stamp = time_count(t_stamp) + + return diags, eigenvalues, eigenvalues2 + + +def get_eigenvalues(nb_eigs, sigma, A_m, M_m): + print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') + print('computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format(nb_eigs, sigma) ) + mode = 'normal' + which = 'LM' + # from eigsh docstring: + # ncv = number of Lanczos vectors generated ncv must be greater than k and smaller than n; + # it is recommended that ncv > 2*k. Default: min(n, max(2*k + 1, 20)) + ncv = 4*nb_eigs + print('A_m.shape = ', A_m.shape) + try_lgmres = True + max_shape_splu = 24000 # OK for nc=20, deg=6 on pretzel_f + if A_m.shape[0] < max_shape_splu: + print('(via sparse LU decomposition)') + OPinv = None + tol_eigsh = 0 + else: + + OP_m = A_m - sigma*M_m + tol_eigsh = 1e-7 + if try_lgmres: + print('(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') + OP_spilu = spilu(OP_m, fill_factor=15, drop_tol=5e-5) + preconditioner = LinearOperator(OP_m.shape, lambda x: OP_spilu.solve(x) ) + tol = tol_eigsh + OPinv = LinearOperator( + matvec=lambda v: lgmres(OP_m, v, x0=None, tol=tol, atol=tol, M=preconditioner, + callback=lambda x: print('cg -- residual = ', norm(OP_m.dot(x)-v)) + )[0], + shape=M_m.shape, + dtype=M_m.dtype + ) + + else: + # from https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.eigsh.html: + # the user can supply the matrix or operator OPinv, which gives x = OPinv @ b = [A - sigma * M]^-1 @ b. + # > here, minres: MINimum RESidual iteration to solve Ax=b + # suggested in https://github.com/scipy/scipy/issues/4170 + print('(with minres iterative solver for A_m - sigma*M1_m)') + OPinv = LinearOperator(matvec=lambda v: minres(OP_m, v, tol=1e-10)[0], shape=M_m.shape, dtype=M_m.dtype) + + eigenvalues, eigenvectors = eigsh(A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) + + print("done: eigenvalues found: " + repr(eigenvalues)) + return eigenvalues, eigenvectors \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py new file mode 100644 index 000000000..9ecc16c1d --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py @@ -0,0 +1,267 @@ +import os +import numpy as np + +#from psydac.feec.multipatch.examples_nc.multipatch_non_conf_examples import hcurl_solve_eigen_pbm_multipatch_nc +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_multipatch_nc + + +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file + +from psydac.api.postprocessing import OutputManager, PostProcessManager + +t_stamp_full = time_count() + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# +# test-case and numerical parameters: + +operator = 'curl-curl' +degree = [3,3] # shared across all patches + + + +#pretzel_f (18 patches) +#domain_name = 'pretzel_f' +#ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) +#ncells = np.array([4 for _ in range(18)]) + +#domain onlyneeded for square like domains +domain=[[0, np.pi],[0, np.pi]] # interval in x- and y-direction + +# refined square domain +# domain_name = 'refined_square' +# the shape of ncells gives the shape of the domain, +# while the entries describe the isometric number of cells in each patch +# 2x2 = 4 patches +# ncells = np.array([[8, 4], +# [4, 4]]) +# 3x3= 9 patches +#ncells = np.array([[4, 2, 4], +# [2, 4, 2], +# [4, 2, 4]]) + +# L-shaped domain +#domain_name = 'square_L_shape' +#domain=[[-1, 1],[-1, 1]] # interval in x- and y-direction + +# The None indicates the patches to leave out +# 2x2 = 4 patches +#ncells = np.array([[None, 2], +# [2, 2]]) +# 4x4 = 16 patches +#ncells = np.array([[None, None, 4, 2], +# [None, None, 8, 4], +# [4, 8, 8, 4], +# [2, 4, 4, 2]]) +# 8x8 = 64 patches +#ncells = np.array([[None, None, None, None, 2, 2, 2,1 2], +# [None, None, None, None, 2, 2, 2, 2], +# [None, None, None, None, 2, 2, 2, 2], +# [None, None, None, None, 4, 4, 2, 2], +# [2, 2, 2, 4, 8, 4, 2, 2], +# [2, 2, 2, 4, 4, 4, 2, 2], +# [2, 2, 2, 2, 2, 2, 2, 2], +# [2, 2, 2, 2, 2, 2, 2, 2]]) + +# Curved L-shape domain +domain_name = 'curved_L_shape' +domain=[[1, 3],[0, np.pi/4]] # interval in x- and y-direction + + +ncells = np.array([[None, 10], + [10, 5]]) + + + +# ncells = np.array([[None, None, 2, 2], +# [None, None, 4, 2], +# [ 2, 4, 8, 4], +# [ 2, 2, 4, 4]]) + +# ncells = np.array([[None, None, None, 2, 2, 2], +# [None, None, None, 4, 4, 2], +# [None, None, None, 8, 4, 2], +# [2, 4, 8, 8, 4, 2], +# [2, 4, 4, 4, 4, 2], +# [2, 2, 2, 2, 2, 2]]) + +# ncells = np.array([[None, None, None, None, 2, 2, 2, 2], +# [None, None, None, None, 4, 4, 4, 2], +# [None, None, None, None, 8, 8, 4, 2], +# [None, None, None, None, 16, 8, 4, 2], +# [2, 4, 8, 16, 16, 8, 4, 2], +# [2, 4, 8, 8, 8, 8, 4, 2], +# [2, 4, 4, 4, 4, 4, 4, 2], +# [2, 2, 2, 2, 2, 2, 2, 2]]) + +# all kinds of different square refinements and constructions are possible, eg +# doubly connected domains +#ncells = np.array([[4, 2, 2, 4], +# [2, None, None, 2], +# [2, None, None, 2], +# [4, 2, 2, 4]]) + +gamma_h = 0 +generalized_pbm = True # solves generalized eigenvalue problem with: B(v,w) = + <(I-P)v,(I-P)w> in rhs + +if operator == 'curl-curl': + nu=0 + mu=1 +else: + raise ValueError(operator) + +case_dir = 'talk_eigenpbm_'+operator +ref_case_dir = case_dir + +cb_min_sol = None +cb_max_sol = None + + +if domain_name == 'refined_square': + assert domain == [[0, np.pi],[0, np.pi]] + ref_sigmas = [ + 1, 1, + 2, + 4, 4, + 5, 5, + 8, + 9, 9, + ] + sigma = 5 + nb_eigs_solve = 10 + nb_eigs_plot = 10 + skip_eigs_threshold = 1e-7 + +elif domain_name == 'square_L_shape': + assert domain == [[-1, 1],[-1, 1]] + ref_sigmas = [ + 1.47562182408, + 3.53403136678, + 9.86960440109, + 9.86960440109, + 11.3894793979, + ] + sigma = 6 + nb_eigs_solve = 5 + nb_eigs_plot = 5 + skip_eigs_threshold = 1e-7 + +elif domain_name == 'curved_L_shape': + # ref eigenvalues from Monique Dauge benchmark page + assert domain==[[1, 3],[0, np.pi/4]] + ref_sigmas = [ + 0.181857115231E+01, + 0.349057623279E+01, + 0.100656015004E+02, + 0.101118862307E+02, + 0.124355372484E+02, + ] + sigma = 7 + nb_eigs_solve = 7 + nb_eigs_plot = 7 + skip_eigs_threshold = 1e-7 + +elif domain_name in ['pretzel_f']: + if operator == 'curl-curl': + # ref sigmas computed with nc=20 and deg=6 and gamma = 0 (and generalized ev-pbm) + ref_sigmas = [ + 0.1795339843, + 0.1992261261, + 0.6992717244, + 0.8709410438, + 1.1945106937, + 1.2546992683, + ] + + sigma = .8 + nb_eigs_solve = 10 + nb_eigs_plot = 5 + skip_eigs_threshold = 1e-7 + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +common_diag_filename = './'+case_dir+'_diags.txt' + + +params = { + 'domain_name': domain_name, + 'domain': domain, + 'operator': operator, + 'mu': mu, + 'nu': nu, + 'ncells': ncells, + 'degree': degree, + 'gamma_h': gamma_h, + 'generalized_pbm': generalized_pbm, + 'nb_eigs_solve': nb_eigs_solve, + 'skip_eigs_threshold': skip_eigs_threshold +} + +print(params) + +# backend_language = 'numba' +backend_language='pyccel-gcc' + +dims = ncells.shape +sz = ncells[ncells != None].sum() +print(dims) +run_dir = domain_name+str(dims)+'patches_'+'size_{}'.format(sz) #get_run_dir(domain_name, nc, deg) +plot_dir = get_plot_dir(case_dir, run_dir) +diag_filename = plot_dir+'/'+diag_fn() +common_diag_filename = './'+case_dir+'_diags.txt' + +# to save and load matrices +#m_load_dir = get_mat_dir(domain_name, nc, deg) +m_load_dir = None + +print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') +print(' Calling hcurl_solve_eigen_pbm() with params = {}'.format(params)) +print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# calling eigenpbm solver for: +# +# find lambda in R and u in H0(curl), such that +# A u = lambda * u on \Omega +# with +# +# A u := mu * curl curl u - nu * grad div u +# +# note: +# - we look for nb_eigs_solve eigenvalues close to sigma (skip zero eigenvalues if skip_zero_eigs==True) +# - we plot nb_eigs_plot eigenvectors + +diags, eigenvalues, eigenvalues2 = hcurl_solve_eigen_pbm_multipatch_nc( + ncells=ncells, degree=degree, + gamma_h=gamma_h, + generalized_pbm=generalized_pbm, + nu=nu, + mu=mu, + sigma=sigma, + ref_sigmas=ref_sigmas, + skip_eigs_threshold=skip_eigs_threshold, + nb_eigs_solve=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + domain_name=domain_name, domain=domain, + backend_language=backend_language, + plot_dir=plot_dir, + hide_plots=True, + m_load_dir=m_load_dir, +) + +if ref_sigmas is not None: + errors = [] + n_errs = min(len(ref_sigmas), len(eigenvalues)) + for k in range(n_errs): + diags['error_{}'.format(k)] = abs(eigenvalues[k]-ref_sigmas[k]) + diags['error2_{}'.format(k)] = abs(eigenvalues2[k]-ref_sigmas[k]) +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +write_diags_to_file(diags, script_filename=__file__, diag_filename=diag_filename, params=params) +write_diags_to_file(diags, script_filename=__file__, diag_filename=common_diag_filename, params=params) + +#PM = PostProcessManager(geometry_file=, ) +time_count(t_stamp_full, msg='full program') \ No newline at end of file From 548881a361e69416940132d2408e690c3b84480e Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 24 Jul 2023 15:11:26 +0200 Subject: [PATCH 09/88] small changes --- .../examples/hcurl_eigen_pbms_conga_2d.py | 53 +++++++++++++++---- .../feec/multipatch/non_matching_operators.py | 16 +++--- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index ce719ea66..3f708bea2 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -22,7 +22,7 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language='python', mu=1, nu=1, gamma_h=10, sigma=None, nb_eigs=4, nb_eigs_plot=4, - plot_dir=None, hide_plots=True, m_load_dir="",): + plot_dir=None, hide_plots=True, m_load_dir="",skip_eigs_threshold = 1e-7,): """ solver for the eigenvalue problem: find lambda in R and u in H0(curl), such that @@ -134,15 +134,41 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('nu = {}'.format(nu)) A_m = mu * CC_m - nu * GD_m + gamma_h * JP_m - eigenvalues, eigenvectors = get_eigenvalues(nb_eigs, sigma, A_m, dH1_m) + if False: #gneralized problen + print('adding jump stabilization to RHS of generalized eigenproblem...') + B_m = cP1_m.transpose() @ dH1_m @ cP1_m + JS_m + else: + B_m = dH1_m + + print('solving matrix eigenproblem...') + all_eigenvalues, all_eigenvectors_transp = get_eigenvalues(nb_eigs, sigma, A_m, B_m) + #Eigenvalue processing + + zero_eigenvalues = [] + if skip_eigs_threshold is not None: + eigenvalues = [] + eigenvectors = [] + for val, vect in zip(all_eigenvalues, all_eigenvectors_transp.T): + if abs(val) < skip_eigs_threshold: + zero_eigenvalues.append(val) + # we skip the eigenvector + else: + eigenvalues.append(val) + eigenvectors.append(vect) + else: + eigenvalues = all_eigenvalues + eigenvectors = all_eigenvectors_transp.T + + # plot first eigenvalues - for i in range(min(nb_eigs_plot, nb_eigs)): + for i in range(min(nb_eigs_plot, len(eigenvalues))): - print('looking at emode i = {}... '.format(i)) lambda_i = eigenvalues[i] - emode_i = np.real(eigenvectors[:,i]) + print('looking at emode i = {}: {}... '.format(i, lambda_i)) + + emode_i = np.real(eigenvectors[i]) norm_emode_i = np.dot(emode_i,dH1_m.dot(emode_i)) print('norm of computed eigenmode: ', norm_emode_i) eh_c = emode_i/norm_emode_i # numpy coeffs of the normalized eigenmode @@ -202,8 +228,8 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): t_stamp_full = time_count() - quick_run = True - # quick_run = False + # quick_run = True + quick_run = False if quick_run: domain_name = 'curved_L_shape' @@ -218,18 +244,27 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): nc = 10 deg = 3 + sigma = 7 + nb_eigs_solve = 7 + nb_eigs_plot = 7 + skip_eigs_threshold = 1e-7 + m_load_dir = 'matrices_{}_nc={}_deg={}/'.format(domain_name, nc, deg) run_dir = 'eigenpbm_{}_nc={}_deg={}/'.format(domain_name, nc, deg) hcurl_solve_eigen_pbm( nc=nc, deg=deg, nu=0, mu=1, #1, - sigma=1, domain_name=domain_name, backend_language='pyccel-gcc', plot_dir='./plots/tests_source_february/'+run_dir, hide_plots=True, - m_load_dir=m_load_dir, + m_load_dir=m_load_dir, + gamma_h=0, + sigma=sigma, + nb_eigs=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + skip_eigs_threshold=skip_eigs_threshold, ) time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index aa1186c1c..71fb7ca87 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -535,8 +535,8 @@ def get_corners(domain, boundary_only): print(' .. multi-patch domain...') #domain_name = 'square_6' - #domain_name = '2patch_nc_mapped' - domain_name = '2patch_nc' + domain_name = '2patch_nc_mapped' + #domain_name = '2patch_nc' if domain_name == '2patch_nc_mapped': @@ -615,9 +615,10 @@ def levelof(k): # G_sol_log = [[lambda xi1, xi2, ii=i : ii+xi1+xi2**2 for d in [0,1]] for i in range(len(domain))] # G_sol_log = [[lambda xi1, xi2, kk=k : levelof(kk)-1 for d in [0,1]] for k in range(len(domain))] - G_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0, 1]] + #G_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0, 1]] + # for k in range(len(domain))] + G_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0, 1]] for k in range(len(domain))] - P0, P1, P2 = derham_h.projectors() G1h = P1(G_sol_log) @@ -639,9 +640,10 @@ def levelof(k): #G0_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0]] # for k in range(len(domain))] - G0_sol_log = [[lambda xi1, xi2, kk=k:kk for d in [0]] - for k in range(len(domain))] - + #G0_sol_log = [[lambda xi1, xi2, kk=k:kk for d in [0]] + # for k in range(len(domain))] + G0_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0]] + for k in range(len(domain))] G0h = P0(G0_sol_log) G0h_coeffs = G0h.coeffs.toarray() From b3d1451cb19a93ad1ffc4ec345a27238e9bdc119 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Thu, 27 Jul 2023 17:31:52 +0200 Subject: [PATCH 10/88] add time-domain Maxwell --- .../multipatch/examples/ppc_test_cases.py | 340 ++++- .../examples_nc/hcurl_eigen_pbms_dg.py | 228 ++++ .../td_maxwell_conga_2d_nc_absorbing.py | 1154 +++++++++++++++++ psydac/feec/multipatch/utils_conga_2d.py | 6 +- 4 files changed, 1677 insertions(+), 51 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py create mode 100644 psydac/feec/multipatch/examples_nc/td_maxwell_conga_2d_nc_absorbing.py diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index d535c7798..70295d573 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -5,7 +5,7 @@ import os import numpy as np -from sympy import pi, cos, sin, Tuple, exp +from sympy import pi, cos, sin, Tuple, exp, atan, atan2 from sympde.topology import Derham @@ -16,18 +16,145 @@ from psydac.feec.multipatch.plotting_utilities import get_plotting_grid, my_small_plot, my_small_streamplot from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.utilities import sol_ref_fn, error_fn, get_method_name, get_fem_name, get_load_dir - comm = MPI.COMM_WORLD # todo [MCP, 12/02/2022]: add an 'equation' argument to be able to return 'exact solution' +def get_phi_pulse(x_0, y_0, domain=None): + x,y = domain.coordinates + ds2_0 = (0.02)**2 + sigma_0 = (x-x_0)**2 + (y-y_0)**2 + phi_0 = exp(-sigma_0**2/(2*ds2_0)) + + return phi_0 + +def get_div_free_pulse(x_0, y_0, domain=None): + x,y = domain.coordinates + ds2_0 = (0.02)**2 + sigma_0 = (x-x_0)**2 + (y-y_0)**2 + phi_0 = exp(-sigma_0**2/(2*ds2_0)) + dx_sig_0 = 2*(x-x_0) + dy_sig_0 = 2*(y-y_0) + dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 + dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 + f_x = dy_phi_0 + f_y = - dx_phi_0 + f_vect = Tuple(f_x, f_y) + + return f_vect + +def get_curl_free_pulse(x_0, y_0, domain=None, pp=False): + # return -grad phi_0 + x,y = domain.coordinates + if pp: + # psi=phi + ds2_0 = (0.02)**2 + else: + ds2_0 = (0.1)**2 + sigma_0 = (x-x_0)**2 + (y-y_0)**2 + phi_0 = exp(-sigma_0**2/(2*ds2_0)) + dx_sig_0 = 2*(x-x_0) + dy_sig_0 = 2*(y-y_0) + dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 + dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 + f_x = -dx_phi_0 + f_y = -dy_phi_0 + f_vect = Tuple(f_x, f_y) + + return f_vect + +def get_Delta_phi_pulse(x_0, y_0, domain=None, pp=False): + # return -Delta phi_0, with same phi_0 as in get_curl_free_pulse() + x,y = domain.coordinates + if pp: + # psi=phi + ds2_0 = (0.02)**2 + else: + ds2_0 = (0.1)**2 + sigma_0 = (x-x_0)**2 + (y-y_0)**2 + phi_0 = exp(-sigma_0**2/(2*ds2_0)) + dx_sig_0 = 2*(x-x_0) + dy_sig_0 = 2*(y-y_0) + dxx_sig_0 = 2 + dyy_sig_0 = 2 + dxx_phi_0 = ((dx_sig_0 * sigma_0 / ds2_0)**2 - ((dx_sig_0)**2 + dxx_sig_0 * sigma_0)/ds2_0 ) * phi_0 + dyy_phi_0 = ((dy_sig_0 * sigma_0 / ds2_0)**2 - ((dy_sig_0)**2 + dyy_sig_0 * sigma_0)/ds2_0 ) * phi_0 + f = - dxx_phi_0 - dyy_phi_0 + + return f + +def get_Gaussian_beam(x_0, y_0, domain=None): + # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v + x,y = domain.coordinates + x = x - x_0 + y = y - y_0 + + k = 2*pi + sigma = 0.7 + + xy = x**2 + y**2 + ef = exp( - xy/(2*sigma**2) ) + + E = cos(k * y) * ef + B = y/(sigma**2) * E - sin(k * y) * ef + + return Tuple(E, 0), B + +def get_Gaussian_beam2(x_0, y_0, domain=None): + # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v + x,y = domain.coordinates + + + x0 = x_0 + y0 = y_0 + theta = pi/2 + w0 = 1 + + t = [(x-x0)*cos(theta) - (y - y0) * sin(theta), (x-x0)*sin(theta) + (y-y0) * cos(theta)] + + ## Gaussian beam + '''Beam inciding from the left, centered and normal to wall: + x: axial normalized distance to the beam's focus + y: radial normalized distance to the center axis of the beam + ''' + EW0 = 1.0 # amplitude at the waist + k0 = 2 * pi # free-space wavenumber + + x_ray = pi * w0 ** 2 # Rayleigh range + + w = w0 * ( 1 + t[0]**2/x_ray**2 )**0.5 #width + curv = t[0] / ( t[0]**2 + x_ray**2 ) #curvature + + gouy_psi = -0.5 * atan2(t[0] / x_ray, 1.) # corresponds to atan(x / x_ray), which is the Gouy phase + + EW_mod = EW0 * (w0 / w)**0.5 * exp(-(t[1] ** 2) / (w ** 2)) # Amplitude + phase = k0 * t[0] + 0.5 * k0 * curv * t[1] ** 2 + gouy_psi # Phase + + EW_r = EW_mod * cos(phase) # Real part + EW_i = EW_mod * sin(phase) # Imaginary part + + B = 0#t[1]/(w**2) * EW_r + + return Tuple(0,EW_r), B + + def get_source_and_sol_for_magnetostatic_pbm( source_type=None, domain=None, domain_name=None, refsol_params=None ): + """ + provide source, and exact solutions when available, for: + + Find u=B in H(curl) such that + + div B = 0 + curl B = j + + written as a mixed problem, see solve_magnetostatic_pbm() + """ + u_ex = None # exact solution x,y = domain.coordinates if source_type == 'dipole_J': # we compute two possible source terms: @@ -62,23 +189,171 @@ def get_source_and_sol_for_magnetostatic_pbm( else: raise ValueError(source_type) - # ref solution in V1h: - uh_ref = get_sol_ref_V1h(source_type, domain, domain_name, refsol_params) + return f_scal, f_vect, j_scal, u_ex - return f_scal, f_vect, j_scal, uh_ref + +def get_source_and_solution_hcurl( + source_type=None, eta=0, mu=0, nu=0, + domain=None, domain_name=None): + """ + provide source, and exact solutions when available, for: + + Find u in H(curl) such that + + A u = f on \Omega + n x u = n x u_bc on \partial \Omega + + with + + A u := eta * u + mu * curl curl u - nu * grad div u + + see solve_hcurl_source_pbm() + """ + # exact solutions (if available) + u_ex = None + curl_u_ex = None + div_u_ex = None + + # bc solution: describe the bc on boundary. Inside domain, values should not matter. Homogeneous bc will be used if None + u_bc = None + + # source terms + f_vect = None + + # auxiliary term (for more diagnostics) + grad_phi = None + phi = None + + x,y = domain.coordinates + + if source_type == 'manu_maxwell_inhom': + # used for Maxwell equation with manufactured solution + f_vect = Tuple(eta*sin(pi*y) - pi**2*sin(pi*y)*cos(pi*x) + pi**2*sin(pi*y), + eta*sin(pi*x)*cos(pi*y) + pi**2*sin(pi*x)*cos(pi*y)) + if nu == 0: + u_ex = Tuple(sin(pi*y), sin(pi*x)*cos(pi*y)) + curl_u_ex = pi*(cos(pi*x)*cos(pi*y) - cos(pi*y)) + div_u_ex = -pi*sin(pi*x)*sin(pi*y) + else: + raise NotImplementedError + u_bc = u_ex + + elif source_type == 'elliptic_J': + # no manufactured solution for Maxwell pbm + x0 = 1.5 + y0 = 1.5 + s = (x-x0) - (y-y0) + t = (x-x0) + (y-y0) + a = (1/1.9)**2 + b = (1/1.2)**2 + sigma2 = 0.0121 + tau = a*s**2 + b*t**2 - 1 + phi = exp(-tau**2/(2*sigma2)) + dx_tau = 2*( a*s + b*t) + dy_tau = 2*(-a*s + b*t) + + f_x = dy_tau * phi + f_y = - dx_tau * phi + f_vect = Tuple(f_x, f_y) + + else: + raise ValueError(source_type) -def get_source_and_solution(source_type=None, eta=0, mu=0, nu=0, + # u_ex = Tuple(0, 1) # DEBUG + return f_vect, u_bc, u_ex, curl_u_ex, div_u_ex #, phi, grad_phi + +def get_source_and_solution_h1(source_type=None, eta=0, mu=0, + domain=None, domain_name=None): + """ + provide source, and exact solutions when available, for: + + Find u in H^1, such that + + A u = f on \Omega + u = u_bc on \partial \Omega + + with + + A u := eta * u - mu * div grad u + + see solve_h1_source_pbm() + """ + + # exact solutions (if available) + u_ex = None + + # bc solution: describe the bc on boundary. Inside domain, values should not matter. Homogeneous bc will be used if None + u_bc = None + + # source terms + f_scal = None + + # auxiliary term (for more diagnostics) + grad_phi = None + phi = None + + x,y = domain.coordinates + + if source_type in ['manu_poisson_elliptic']: + x0 = 1.5 + y0 = 1.5 + s = (x-x0) - (y-y0) + t = (x-x0) + (y-y0) + a = (1/1.9)**2 + b = (1/1.2)**2 + sigma2 = 0.0121 + tau = a*s**2 + b*t**2 - 1 + phi = exp(-tau**2/(2*sigma2)) + dx_tau = 2*( a*s + b*t) + dy_tau = 2*(-a*s + b*t) + dxx_tau = 2*(a + b) + dyy_tau = 2*(a + b) + + dx_phi = (-tau*dx_tau/sigma2)*phi + dy_phi = (-tau*dy_tau/sigma2)*phi + grad_phi = Tuple(dx_phi, dy_phi) + + f_scal = -( (tau*dx_tau/sigma2)**2 - (tau*dxx_tau + dx_tau**2)/sigma2 + +(tau*dy_tau/sigma2)**2 - (tau*dyy_tau + dy_tau**2)/sigma2 )*phi + + # exact solution of -p'' = f with hom. bc's on pretzel domain + if mu == 1 and eta == 0: + u_ex = phi + else: + print('WARNING (54375385643): exact solution not available in this case!') + + if not domain_name in ['pretzel', 'pretzel_f']: + # we may have non-hom bc's + u_bc = u_ex + + elif source_type == 'manu_poisson_2': + f_scal = -4 + if mu == 1 and eta == 0: + u_ex = x**2+y**2 + else: + raise NotImplementedError + u_bc = u_ex + + elif source_type == 'manu_poisson_sincos': + u_ex = sin(pi*x)*cos(pi*y) + f_scal = (eta + 2*mu*pi**2) * u_ex + u_bc = u_ex + + else: + raise ValueError(source_type) + + return f_scal, u_bc, u_ex + + + +def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, domain=None, domain_name=None, refsol_params=None): """ - compute source and reference solution (exact, or reference values) when possible, depending on the source_type + OBSOLETE: kept for some test-cases """ - # ref solution (values on diag grid) - ph_ref = None - uh_ref = None - # exact solutions (if available) u_ex = None p_ex = None @@ -119,7 +394,7 @@ def get_source_and_solution(source_type=None, eta=0, mu=0, nu=0, elif source_type == 'manutor_poisson': # todo: remove if not used ? - # same as manu_poisson, with arbitrary value for tor + # same as manu_poisson_ellip, with arbitrary value for tor x0 = 1.5 y0 = 1.5 s = (x-x0) - (y-y0) @@ -174,6 +449,9 @@ def get_source_and_solution(source_type=None, eta=0, mu=0, nu=0, # exact solution of -p'' = f with hom. bc's on pretzel domain p_ex = phi + if source_type == 'manu_poisson' and mu == 1 and eta == 0: + u_ex = phi + if not domain_name in ['pretzel', 'pretzel_f']: print("WARNING (87656547) -- I'm not sure we have an exact solution -- check the bc's on the domain "+domain_name) # raise NotImplementedError(domain_name) @@ -325,40 +603,6 @@ def get_source_and_solution(source_type=None, eta=0, mu=0, nu=0, else: raise ValueError(source_type) - if u_ex is None: - uh_ref = get_sol_ref_V1h(source_type, domain, domain_name, refsol_params) - - return f_scal, f_vect, u_bc, ph_ref, uh_ref, p_ex, u_ex, phi, grad_phi - + return f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi -def get_sol_ref_V1h( source_type=None, domain=None, domain_name=None, refsol_params=None ): - """ - get a reference solution as a V1h FemField - """ - uh_ref = None - if refsol_params is not None: - N_diag, method_ref, source_proj_ref = refsol_params - u_ref_filename = ( get_load_dir(method=method_ref, domain_name=domain_name,nc=None,deg=None,data='solutions') - + sol_ref_fn(source_type, N_diag, source_proj=source_proj_ref) ) - print("no exact solution for this test-case, looking for ref solution values in file {}...".format(u_ref_filename)) - if os.path.isfile(u_ref_filename): - print("-- file found") - with open(u_ref_filename, 'rb') as file: - ncells_degree = np.load(file) - ncells = [int(i) for i in ncells_degree['ncells_degree'][0]] - degree = [int(i) for i in ncells_degree['ncells_degree'][1]] - - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - domain_h = discretize(domain, ncells=ncells, comm=comm) - V1h = discretize(derham.V1, domain_h, degree=degree, basis='M') - uh_ref = FemField(V1h) - for i,Vi in enumerate(V1h.spaces): - for j,Vij in enumerate(Vi.spaces): - filename = u_ref_filename+'_%d_%d'%(i,j) - uij = Vij.import_fields(filename, 'phi') - uh_ref.fields[i].fields[j].coeffs._data = uij[0].coeffs._data - - else: - print("-- no file, skipping it") - return uh_ref diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py new file mode 100644 index 000000000..525c357e0 --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py @@ -0,0 +1,228 @@ +import os +from mpi4py import MPI +from collections import OrderedDict + +import numpy as np +import matplotlib.pyplot +from scipy.sparse.linalg import spsolve, inv +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from sympde.calculus import grad, dot, curl, cross +from sympde.calculus import minus, plus +from sympde.topology import VectorFunctionSpace +from sympde.topology import elements_of +from sympde.topology import NormalVector +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping +from sympde.expr.expr import LinearForm, BilinearForm +from sympde.expr.expr import integral +from sympde.expr.expr import Norm +from sympde.expr.equation import find, EssentialBC +from scipy.sparse.linalg import LinearOperator, eigsh, minres +from psydac.linalg.utilities import array_to_psydac + +from psydac.api.tests.build_domain import build_pretzel +from psydac.fem.basic import FemField +from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain +from psydac.api.postprocessing import OutputManager, PostProcessManager + +def hcurl_solve_eigen_pbm_multipatch_dg(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, + plot_dir=None, hide_plots=True, m_load_dir="",): + + diags = {} + + if sigma is None: + raise ValueError('please specify a value for sigma') + + print('---------------------------------------------------------------------------------------------------------') + print('Starting hcurl_solve_eigen_pbm function with: ') + print(' ncells = {}'.format(ncells)) + print(' degree = {}'.format(degree)) + print(' domain_name = {}'.format(domain_name)) + print(' backend_language = {}'.format(backend_language)) + print('---------------------------------------------------------------------------------------------------------') + t_stamp = time_count() + print('building symbolic and discrete domain...') + + int_x, int_y = domain + + if domain_name == 'refined_square' or domain_name =='square_L_shape': + domain = create_square_domain(ncells, int_x, int_y, mapping='identity') + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + elif domain_name == 'curved_L_shape': + domain = create_square_domain(ncells, int_x, int_y, mapping='polar') + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + elif domain_name == 'pretzel_f': + domain = build_multipatch_domain(domain_name=domain_name) + ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + + else: + ValueError("Domain not defined.") + + # domain = build_multipatch_domain(domain_name = 'curved_L_shape') + # + # ncells = np.array([4,8,4]) + # ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + + + t_stamp = time_count(t_stamp) + print(' .. discrete domain...') + + V = VectorFunctionSpace('V', domain, kind='hcurl') + + u, v, F = elements_of(V, names='u, v, F') + nn = NormalVector('nn') + + I = domain.interfaces + boundary = domain.boundary + + kappa = 10 + k = 1 + + jump = lambda w:plus(w)-minus(w) + avr = lambda w:0.5*plus(w) + 0.5*minus(w) + + expr1_I = cross(nn, jump(v))*curl(avr(u))\ + +k*cross(nn, jump(u))*curl(avr(v))\ + +kappa*cross(nn, jump(u))*cross(nn, jump(v)) + + expr1 = curl(u)*curl(v) + expr1_b = -cross(nn, v) * curl(u) -k*cross(nn, u)*curl(v) + kappa*cross(nn, u)*cross(nn, v) + ## curl curl u = - omega**2 u + + expr2 = dot(u,v) + #expr2_I = kappa*cross(nn, jump(u))*cross(nn, jump(v)) + #expr2_b = -k*cross(nn, u)*curl(v) + kappa * cross(nn, u) * cross(nn, v) + + # Bilinear form a: V x V --> R + a = BilinearForm((u,v), integral(domain, expr1) + integral(I, expr1_I) + integral(boundary, expr1_b)) + + # Linear form l: V --> R + b = BilinearForm((u,v), integral(domain, expr2))# + integral(I, expr2_I) + integral(boundary, expr2_b)) + + #+++++++++++++++++++++++++++++++ + # 2. Discretization + #+++++++++++++++++++++++++++++++ + + domain_h = discretize(domain, ncells=ncells_h) + Vh = discretize(V, domain_h, degree=degree) + + ah = discretize(a, domain_h, [Vh, Vh]) + Ah_m = ah.assemble().tosparse() + + bh = discretize(b, domain_h, [Vh, Vh]) + Bh_m = bh.assemble().tosparse() + + all_eigenvalues_2, all_eigenvectors_transp_2 = get_eigenvalues(nb_eigs_solve, sigma, Ah_m, Bh_m) + + #Eigenvalue processing + t_stamp = time_count(t_stamp) + print('sorting out eigenvalues...') + zero_eigenvalues = [] + if skip_eigs_threshold is not None: + eigenvalues = [] + eigenvectors2 = [] + for val, vect in zip(all_eigenvalues_2, all_eigenvectors_transp_2.T): + if abs(val) < skip_eigs_threshold: + zero_eigenvalues.append(val) + # we skip the eigenvector + else: + eigenvalues.append(val) + eigenvectors2.append(vect) + else: + eigenvalues = all_eigenvalues_2 + eigenvectors2 = all_eigenvectors_transp_2.T + diags['DG'] = True + for k, val in enumerate(eigenvalues): + diags['eigenvalue2_{}'.format(k)] = val #eigenvalues[k] + + for k, val in enumerate(zero_eigenvalues): + diags['skipped eigenvalue2_{}'.format(k)] = val + + t_stamp = time_count(t_stamp) + print('plotting the eigenmodes...') + + # OM = OutputManager('spaces.yml', 'fields.h5') + # OM.add_spaces(V1h=V1h) + + nb_eigs = len(eigenvalues) + for i in range(min(nb_eigs_plot, nb_eigs)): + OM = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM.add_spaces(V1h=Vh) + print('looking at emode i = {}... '.format(i)) + lambda_i = eigenvalues[i] + emode_i = np.real(eigenvectors2[i]) + norm_emode_i = np.dot(emode_i,Bh_m.dot(emode_i)) + eh_c = emode_i/norm_emode_i + stencil_coeffs = array_to_psydac(eh_c, Vh.vector_space) + vh = FemField(Vh, coeffs=stencil_coeffs) + OM.set_static() + #OM.add_snapshot(t=i , ts=0) + OM.export_fields(vh = vh) + + #print('norm of computed eigenmode: ', norm_emode_i) + # plot the broken eigenmode: + OM.export_space_info() + OM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) + PM.export_to_vtk(plot_dir+"/eigen2_{}".format(i),grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) + PM.close() + + t_stamp = time_count(t_stamp) + + return diags, eigenvalues + + +def get_eigenvalues(nb_eigs, sigma, A_m, M_m): + print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') + print('computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format(nb_eigs, sigma) ) + mode = 'normal' + which = 'LM' + # from eigsh docstring: + # ncv = number of Lanczos vectors generated ncv must be greater than k and smaller than n; + # it is recommended that ncv > 2*k. Default: min(n, max(2*k + 1, 20)) + ncv = 4*nb_eigs + print('A_m.shape = ', A_m.shape) + try_lgmres = True + max_shape_splu = 24000 # OK for nc=20, deg=6 on pretzel_f + if A_m.shape[0] < max_shape_splu: + print('(via sparse LU decomposition)') + OPinv = None + tol_eigsh = 0 + else: + + OP_m = A_m - sigma*M_m + tol_eigsh = 1e-7 + if try_lgmres: + print('(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') + OP_spilu = spilu(OP_m, fill_factor=15, drop_tol=5e-5) + preconditioner = LinearOperator(OP_m.shape, lambda x: OP_spilu.solve(x) ) + tol = tol_eigsh + OPinv = LinearOperator( + matvec=lambda v: lgmres(OP_m, v, x0=None, tol=tol, atol=tol, M=preconditioner, + callback=lambda x: print('cg -- residual = ', norm(OP_m.dot(x)-v)) + )[0], + shape=M_m.shape, + dtype=M_m.dtype + ) + + else: + # from https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.eigsh.html: + # the user can supply the matrix or operator OPinv, which gives x = OPinv @ b = [A - sigma * M]^-1 @ b. + # > here, minres: MINimum RESidual iteration to solve Ax=b + # suggested in https://github.com/scipy/scipy/issues/4170 + print('(with minres iterative solver for A_m - sigma*M1_m)') + OPinv = LinearOperator(matvec=lambda v: minres(OP_m, v, tol=1e-10)[0], shape=M_m.shape, dtype=M_m.dtype) + + eigenvalues, eigenvectors = eigsh(A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) + + print("done: eigenvalues found: " + repr(eigenvalues)) + return eigenvalues, eigenvectors \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/td_maxwell_conga_2d_nc_absorbing.py b/psydac/feec/multipatch/examples_nc/td_maxwell_conga_2d_nc_absorbing.py new file mode 100644 index 000000000..fb662b54c --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/td_maxwell_conga_2d_nc_absorbing.py @@ -0,0 +1,1154 @@ +from pytest import param +from mpi4py import MPI + +import os +import numpy as np +import scipy as sp +from collections import OrderedDict +import matplotlib.pyplot as plt + +from sympy import lambdify, Matrix + +from scipy.sparse.linalg import spsolve +from scipy import special + +from sympde.calculus import dot +from sympde.topology import element_of +from sympde.expr.expr import LinearForm +from sympde.expr.expr import integral, Norm +from sympde.topology import Derham + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv +from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain + +from psydac.api.postprocessing import OutputManager, PostProcessManager + +def solve_td_maxwell_pbm(*, + nc = 4, + deg = 4, + final_time = 20, + cfl_max = 0.8, + dt_max = None, + domain_name = 'pretzel_f', + backend = None, + source_type = 'zero', + source_omega = None, + source_proj = 'P_geom', + conf_proj = 'BSP', + gamma_h = 10., + project_sol = False, + filter_source = True, + quad_param = 1, + E0_type = 'zero', + E0_proj = 'P_L2', + hide_plots = True, + plot_dir = None, + plot_time_ranges = None, + plot_source = False, + plot_divE = False, + diag_dt = None, +# diag_dtau = None, + cb_min_sol = None, + cb_max_sol = None, + m_load_dir = "", + th_sol_filename = "", + source_is_harmonic=False, + domain_lims=None +): + """ + solver for the TD Maxwell problem: find E(t) in H(curl), B in L2, such that + + dt E - curl B = -J on \Omega + dt B + curl E = 0 on \Omega + n x E = n x E_bc on \partial \Omega + + with Ampere discretized weakly and Faraday discretized strongly, in a broken-FEEC approach on a 2D multipatch domain \Omega, + + V0h --grad-> V1h -—curl-> V2h + (Eh) (Bh) + + Parameters + ---------- + nc : int + Number of cells (same along each direction) in every patch. + + deg : int + Polynomial degree (same along each direction) in every patch, for the + spline space V0 in H1. + + final_time : float + Final simulation time. Given that the speed of light is set to c=1, + this can be easily chosen based on the wave transit time in the domain. + + cfl_max : float + Maximum Courant parameter in the simulation domain, used to determine + the time step size. + + dt_max : float + Maximum time step size, which has to be met together with cfl_max. This + additional constraint is useful to resolve a time-dependent source. + + domain_name : str + Name of the multipatch geometry used in the simulation, to be chosen + among those available in the function `build_multipatch_domain`. + + backend : str + Name of the backend used for acceleration of the computational kernels, + to be chosen among the available keys of the PSYDAC_BACKENDS dict. + + source_type : str {'zero' | 'pulse' | 'cf_pulse' | 'Il_pulse'} + Name that identifies the space-time profile of the current source, to be + chosen among those available in the function get_source_and_solution(). + Available options: + - 'zero' : no current source + - 'pulse' : div-free current source, time-harmonic + - 'cf_pulse': curl-free current source, time-harmonic + - 'Il_pulse': Issautier-like pulse, with both a div-free and a + curl-free component, not time-harmonic. + + source_omega : float + Pulsation of the time-harmonic component (if any) of a time-dependent + current source. + + source_proj : str {'P_geom' | 'P_L2'} + Name of the approximation operator for the current source: 'P_geom' is + a geometric projector (based on inter/histopolation) which yields the + primal degrees of freedom; 'P_L2' is an L2 projector which yields the + dual degrees of freedom. Change of basis from primal to dual (and vice + versa) is obtained through multiplication with the proper Hodge matrix. + + conf_proj : str {'BSP' | 'GSP'} + Kind of conforming projection operator. Choose 'BSP' for an operator + based on the spline coefficients, which has maximum data locality. + Choose 'GSP' for an operator based on the geometric degrees of freedom, + which requires a change of basis (from B-spline to geometric, and then + vice versa) on the patch interfaces. + + gamma_h : float + Jump penalization parameter. + + project_sol : bool + Whether the solution fields should be projected onto the corresponding + conforming spaces before plotting them. + + filter_source : bool + If True, the current source will be filtered with the conforming + projector operator (or its dual, depending on which basis is used). + + quad_param : int + Multiplicative factor for the number of quadrature points; set + `quad_param` > 1 if you suspect that the quadrature is not accurate. + + E0_type : str {'zero', 'th_sol', 'pulse'} + Initial conditions for the electric field. Choose 'zero' for E0=0, + 'th_sol' for a field obtained from the time-harmonic Maxwell solver + (must provide a time-harmonic current source and set `source_omega`), + and 'pulse' for a non-zero field localized in a small region. + + E0_proj : str {'P_geom' | 'P_L2'} + Name of the approximation operator for the initial electric field E0 + (see source_proj for details). Only relevant if E0 is not zero. + + hide_plots : bool + If True, no windows are opened to show the figures interactively. + + plot_dir : str + Path to the directory where the figures will be saved. + + plot_time_ranges : list + List of lists, of the form `[[start, end], dtp]`, where `[start, end]` + is a time interval and `dtp` is the time between two successive plots. + + plot_source : bool + If True, plot the discrete field that approximates the current source. + + plot_divE : bool + If True, compute and plot the (weak) divergence of the electric field. + + diag_dt : float + Time elapsed between two successive calculations of scalar diagnostic + quantities. + + cb_min_sol : float + Minimum value to be used in colorbars when visualizing the solution. + + cb_max_sol : float + Maximum value to be used in colorbars when visualizing the solution. + + m_load_dir : str + Path to directory for matrix storage. + + th_sol_filename : str + Path to file with time-harmonic solution (to be used in conjuction with + `source_is_harmonic = True` and `E0_type = 'th_sol'`). + + """ + diags = {} + + #ncells = [nc, nc] + degree = [deg, deg] + + if source_omega is not None: + period_time = 2*np.pi / source_omega + Nt_pp = period_time // dt_max + + if plot_time_ranges is None: + plot_time_ranges = [ + [[0, final_time], final_time] + ] + + if diag_dt is None: + diag_dt = 0.1 + + # if backend is None: + # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: + # backend = 'numba' + # else: + # backend = 'python' + # print('[note: using '+backend_language+ ' backends in discretize functions]') + if m_load_dir is not None: + if not os.path.exists(m_load_dir): + os.makedirs(m_load_dir) + + print('---------------------------------------------------------------------------------------------------------') + print('Starting solve_td_maxwell_pbm function with: ') + print(' ncells = {}'.format(nc)) + print(' degree = {}'.format(degree)) + print(' domain_name = {}'.format(domain_name)) + print(' E0_type = {}'.format(E0_type)) + print(' E0_proj = {}'.format(E0_proj)) + print(' source_type = {}'.format(source_type)) + print(' source_proj = {}'.format(source_proj)) + print(' backend = {}'.format(backend)) + # TODO: print other parameters + print('---------------------------------------------------------------------------------------------------------') + + debug = False + + print() + print(' -- building discrete spaces and operators --') + + t_stamp = time_count() + print(' .. multi-patch domain...') + if domain_name == 'refined_square' or domain_name =='square_L_shape': + int_x, int_y = domain_lims + domain = create_square_domain(nc, int_x, int_y, mapping='identity') + ncells_h = {patch.name: [nc[int(patch.name[2])][int(patch.name[4])], nc[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + else: + domain = build_multipatch_domain(domain_name=domain_name) + ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + + + # for diagnosttics + diag_grid = DiagGrid(mappings=mappings, N_diag=100) + + t_stamp = time_count(t_stamp) + print(' .. derham sequence...') + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + + t_stamp = time_count(t_stamp) + print(' .. discrete domain...') + domain_h = discretize(domain, ncells=ncells_h) + + t_stamp = time_count(t_stamp) + print(' .. discrete derham sequence...') + + derham_h = discretize(derham, domain_h, degree=degree) + + t_stamp = time_count(t_stamp) + print(' .. commuting projection operators...') + nquads = [4*(d + 1) for d in degree] + P0, P1, P2 = derham_h.projectors(nquads=nquads) + + t_stamp = time_count(t_stamp) + print(' .. multi-patch spaces...') + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + print('dim(V0h) = {}'.format(V0h.nbasis)) + print('dim(V1h) = {}'.format(V1h.nbasis)) + print('dim(V2h) = {}'.format(V2h.nbasis)) + diags['ndofs_V0'] = V0h.nbasis + diags['ndofs_V1'] = V1h.nbasis + diags['ndofs_V2'] = V2h.nbasis + + t_stamp = time_count(t_stamp) + print(' .. Id operator and matrix...') + I1 = IdLinearOperator(V1h) + I1_m = I1.to_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. Hodge operators...') + # multi-patch (broken) linear operators / matrices + # other option: define as Hodge Operators: + H0 = HodgeOperator(V0h, domain_h, backend_language=backend, load_dir=m_load_dir, load_space_index=0) + H1 = HodgeOperator(V1h, domain_h, backend_language=backend, load_dir=m_load_dir, load_space_index=1) + H2 = HodgeOperator(V2h, domain_h, backend_language=backend, load_dir=m_load_dir, load_space_index=2) + + t_stamp = time_count(t_stamp) + print(' .. Hodge matrix H0_m = M0_m ...') + dH0_m = H0.to_sparse_matrix() + t_stamp = time_count(t_stamp) + print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') + H0_m = H0.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. Hodge matrix H1_m = M1_m ...') + dH1_m = H1.to_sparse_matrix() + t_stamp = time_count(t_stamp) + print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') + H1_m = H1.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. Hodge matrix dH2_m = M2_m ...') + dH2_m = H2.to_sparse_matrix() + H2_m = H2.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. conforming Projection operators...') + + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=False) + cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=False) + + + if conf_proj == 'GSP': + print(' [* GSP-conga: using Geometric Spline conf Projections ]') + K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) + cP0_m = K0_inv @ cP0_m @ K0 + K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) + cP1_m = K1_inv @ cP1_m @ K1 + elif conf_proj == 'BSP': + print(' [* BSP-conga: using B-Spline conf Projections ]') + else: + raise ValueError(conf_proj) + + t_stamp = time_count(t_stamp) + print(' .. broken differential operators...') + # broken (patch-wise) differential operators + bD0, bD1 = derham_h.broken_derivatives_as_operators + bD0_m = bD0.to_sparse_matrix() + bD1_m = bD1.to_sparse_matrix() + + if plot_dir is not None and not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + + # Conga (projection-based) matrices + t_stamp = time_count(t_stamp) + dH1_m = dH1_m.tocsr() + H2_m = H2_m.tocsr() + cP1_m = cP1_m.tocsr() + bD1_m = bD1_m.tocsr() + + print(' .. matrix of the primal curl (in primal bases)...') + C_m = bD1_m @ cP1_m + print(' .. matrix of the dual curl (also in primal bases)...') + + from sympde.calculus import grad, dot, curl, cross + from sympde.topology import NormalVector + from sympde.expr.expr import BilinearForm + from sympde.topology import elements_of + + u, v = elements_of(derham.V1, names='u, v') + nn = NormalVector('nn') + boundary = domain.boundary + expr_b = cross(nn, u)*cross(nn, v) + + a = BilinearForm((u,v), integral(boundary, expr_b)) + ah = discretize(a, domain_h, [V1h, V1h], backend=PSYDAC_BACKENDS[backend],) + A_eps = ah.assemble().tosparse() + + + dC_m = dH1_m @ C_m.transpose() @ H2_m + + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Compute stable time step size based on max CFL and max dt + dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=cfl_max, dt_max=dt_max) + #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + #Absorbing dC_m + CH2 = C_m.transpose() @ H2_m + H1A = H1_m + dt * A_eps + dC_m = sp.sparse.linalg.spsolve(H1A, CH2) + + dCH1_m = sp.sparse.linalg.spsolve(H1A, H1_m) + + print(' .. matrix of the dual div (still in primal bases)...') + div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m + + # jump stabilization (may not be needed) + t_stamp = time_count(t_stamp) + print(' .. jump stabilization matrix...') + jump_penal_m = I1_m - cP1_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m + + # t_stamp = time_count(t_stamp) + # print(' .. full operator matrix...') + # print('STABILIZATION: gamma_h = {}'.format(gamma_h)) + # pre_A_m = cP1_m.transpose() @ ( eta * H1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) + # A_m = pre_A_m @ cP1_m + gamma_h * JP_m + + + + print(" Reduce time step to match the simulation final time:") + Nt = int(np.ceil(final_time/dt)) + dt = final_time / Nt + print(f" . Time step size : dt = {dt}") + print(f" . Nb of time steps: Nt = {Nt}") + + # ... + def is_plotting_time(nt, *, dt=dt, Nt=Nt, plot_time_ranges=plot_time_ranges): + if nt in [0, Nt]: + return True + for [start, end], dt_plots in plot_time_ranges: + ds = max(dt_plots // dt, 1) # number of time steps between two successive plots + if (start <= nt * dt <= end) and (nt % ds == 0): + return True + return False + # ... + + # Number of time step between two successive calculations of the scalar diagnostics + diag_nt = max(int(diag_dt // dt), 1) + + print(' ------ ------ ------ ------ ------ ------ ------ ------ ') + print(' ------ ------ ------ ------ ------ ------ ------ ------ ') + print(' total nb of time steps: Nt = {}, final time: T = {:5.4f}'.format(Nt, final_time)) + print(' ------ ------ ------ ------ ------ ------ ------ ------ ') + print(' plotting times: the solution will be plotted for...') + for nt in range(Nt+1): + if is_plotting_time(nt): + print(' * nt = {}, t = {:5.4f}'.format(nt, dt*nt)) + print(' ------ ------ ------ ------ ------ ------ ------ ------ ') + print(' ------ ------ ------ ------ ------ ------ ------ ------ ') + + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # source + + t_stamp = time_count(t_stamp) + print() + print(' -- getting source --') + f0_c = None + f0_harmonic_c = None + if source_type == 'zero': + + f0 = None + f0_harmonic = None + + elif source_type == 'pulse': + + f0 = get_div_free_pulse(x_0=1.0, y_0=1.0, domain=domain) + + elif source_type == 'cf_pulse': + + f0 = get_curl_free_pulse(x_0=1.0, y_0=1.0, domain=domain) + + elif source_type == 'Il_pulse': #Issautier-like pulse + # source will be + # J = curl A + cos(om*t) * grad phi + # so that + # dt rho = - div J = - cos(om*t) Delta phi + # for instance, with rho(t=0) = 0 this gives + # rho = - sin(om*t)/om * Delta phi + # and Gauss' law reads + # div E = rho = - sin(om*t)/om * Delta phi + f0 = get_div_free_pulse(x_0=1.0, y_0=1.0, domain=domain) # this is curl A + f0_harmonic = get_curl_free_pulse(x_0=1.0, y_0=1.0, domain=domain) # this is grad phi + assert not source_is_harmonic + + rho0 = get_Delta_phi_pulse(x_0=1.0, y_0=1.0, domain=domain) # this is Delta phi + tilde_rho0_c = derham_h.get_dual_dofs(space='V0', f=rho0, backend_language=backend, return_format='numpy_array') + tilde_rho0_c = cP0_m.transpose() @ tilde_rho0_c + rho0_c = dH0_m.dot(tilde_rho0_c) + else: + + f0, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( + source_type=source_type, domain=domain, domain_name=domain_name, + ) + assert u_bc is None # only homogeneous BC's for now + + # f0_c = np.zeros(V1h.nbasis) + + def source_enveloppe(tau): + return 1 + + if source_omega is not None: + f0_harmonic = f0 + f0 = None + if E0_type == 'th_sol': + # use source enveloppe for smooth transition from 0 to 1 + def source_enveloppe(tau): + return (special.erf((tau/25)-2)-special.erf(-2))/2 + + t_stamp = time_count(t_stamp) + tilde_f0_c = f0_c = None + tilde_f0_harmonic_c = f0_harmonic_c = None + if source_proj == 'P_geom': + print(' .. projecting the source with commuting projection...') + if f0 is not None: + f0_h = P1_phys(f0, P1, domain, mappings_list) + f0_c = f0_h.coeffs.toarray() + tilde_f0_c = H1_m.dot(f0_c) + if f0_harmonic is not None: + f0_harmonic_h = P1_phys(f0_harmonic, P1, domain, mappings_list) + f0_harmonic_c = f0_harmonic_h.coeffs.toarray() + tilde_f0_harmonic_c = H1_m.dot(f0_harmonic_c) + + elif source_proj == 'P_L2': + # helper: save/load coefs + if f0 is not None: + if source_type == 'Il_pulse': + source_name = 'Il_pulse_f0' + else: + source_name = source_type + sdd_filename = m_load_dir+'/'+source_name+'_dual_dofs_qp{}.npy'.format(quad_param) + if os.path.exists(sdd_filename): + print(' .. loading source dual dofs from file {}'.format(sdd_filename)) + tilde_f0_c = np.load(sdd_filename) + else: + print(' .. projecting the source f0 with L2 projection...') + tilde_f0_c = derham_h.get_dual_dofs(space='V1', f=f0, backend_language=backend, return_format='numpy_array') + print(' .. saving source dual dofs to file {}'.format(sdd_filename)) + np.save(sdd_filename, tilde_f0_c) + if f0_harmonic is not None: + if source_type == 'Il_pulse': + source_name = 'Il_pulse_f0_harmonic' + else: + source_name = source_type + sdd_filename = m_load_dir+'/'+source_name+'_dual_dofs_qp{}.npy'.format(quad_param) + if os.path.exists(sdd_filename): + print(' .. loading source dual dofs from file {}'.format(sdd_filename)) + tilde_f0_harmonic_c = np.load(sdd_filename) + else: + print(' .. projecting the source f0_harmonic with L2 projection...') + tilde_f0_harmonic_c = derham_h.get_dual_dofs(space='V1', f=f0_harmonic, backend_language=backend, return_format='numpy_array') + print(' .. saving source dual dofs to file {}'.format(sdd_filename)) + np.save(sdd_filename, tilde_f0_harmonic_c) + + else: + raise ValueError(source_proj) + + t_stamp = time_count(t_stamp) + if filter_source: + print(' .. filtering the source...') + if tilde_f0_c is not None: + tilde_f0_c = cP1_m.transpose() @ tilde_f0_c + if tilde_f0_harmonic_c is not None: + tilde_f0_harmonic_c = cP1_m.transpose() @ tilde_f0_harmonic_c + + if tilde_f0_c is not None: + f0_c = dH1_m.dot(tilde_f0_c) + + if debug: + title = 'f0 part of source' + params_str = 'omega={}_gamma_h={}_Pf={}'.format(source_omega, gamma_h, source_proj) + plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_f0.pdf', + plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_f0_vf.pdf', + plot_type='vector_field', cb_min=None, cb_max=None, hide_plot=hide_plots) + divf0_c = div_m @ f0_c + title = 'div f0' + plot_field(numpy_coeffs=divf0_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_divf0.pdf', + plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + + + if tilde_f0_harmonic_c is not None: + f0_harmonic_c = dH1_m.dot(tilde_f0_harmonic_c) + + if debug: + title = 'f0_harmonic part of source' + params_str = 'omega={}_gamma_h={}_Pf={}'.format(source_omega, gamma_h, source_proj) + plot_field(numpy_coeffs=f0_harmonic_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_f0_harmonic.pdf', + plot_type='components', cb_min=None, cb_max=None, hide_plot=hide_plots) + plot_field(numpy_coeffs=f0_harmonic_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_f0_harmonic_vf.pdf', + plot_type='vector_field', cb_min=None, cb_max=None, hide_plot=hide_plots) + divf0_c = div_m @ f0_harmonic_c + title = 'div f0_harmonic' + plot_field(numpy_coeffs=divf0_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_divf0_harmonic.pdf', + plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + + # else: + # raise NotImplementedError + + if f0_c is None: + f0_c = np.zeros(V1h.nbasis) + + + # if plot_source and plot_dir: + # plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, title='f0_h with P = '+source_proj, filename=plot_dir+'/f0h_'+source_proj+'.png', hide_plot=hide_plots) + # plot_field(numpy_coeffs=f0_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, title='f0_h with P = '+source_proj, filename=plot_dir+'/f0h_'+source_proj+'_vf.png', hide_plot=hide_plots) + + t_stamp = time_count(t_stamp) + + def plot_J_source_nPlusHalf(f_c, nt): + print(' .. plotting the source...') + title = r'source $J^{n+1/2}_h$ (amplitude)'+' for $\omega = {}$, $n = {}$'.format(source_omega, nt) + params_str = 'omega={}_gamma_h={}_Pf={}'.format(source_omega, gamma_h, source_proj) + plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_Jh_nt={}.pdf'.format(nt), + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + title = r'source $J^{n+1/2}_h$'+' for $\omega = {}$, $n = {}$'.format(source_omega, nt) + plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title, + filename=plot_dir+'/'+params_str+'_Jh_vf_nt={}.pdf'.format(nt), + plot_type='vector_field', vf_skip=1, hide_plot=hide_plots) + + def plot_E_field(E_c, nt, project_sol=False, plot_divE=False): + + # only E for now + if plot_dir: + + plot_omega_normalized_sol = (source_omega is not None) + # project the homogeneous solution on the conforming problem space + if project_sol: + # t_stamp = time_count(t_stamp) + print(' .. projecting the homogeneous solution on the conforming problem space...') + Ep_c = cP1_m.dot(E_c) + else: + Ep_c = E_c + print(' .. NOT projecting the homogeneous solution on the conforming problem space') + if plot_omega_normalized_sol: + print(' .. plotting the E/omega field...') + u_c = (1/source_omega)*Ep_c + title = r'$u_h = E_h/\omega$ (amplitude) for $\omega = {:5.4f}$, $t = {:5.4f}$'.format(source_omega, dt*nt) + params_str = 'omega={:5.4f}_gamma_h={}_Pf={}_Nt_pp={}'.format(source_omega, gamma_h, source_proj, Nt_pp) + else: + print(' .. plotting the E field...') + if E0_type == 'pulse': + title = r'$t = {:5.4f}$'.format(dt*nt) + else: + title = r'$E_h$ (amplitude) at $t = {:5.4f}$'.format(dt*nt) + u_c = Ep_c + params_str = f'gamma_h={gamma_h}_dt={dt}' + + plot_field(numpy_coeffs=u_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_Eh_nt={}.pdf'.format(nt), + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + + if plot_divE: + params_str = f'gamma_h={gamma_h}_dt={dt}' + if source_type == 'Il_pulse': + plot_type = 'components' + rho_c = rho0_c * np.sin(source_omega*dt*nt) / source_omega + rho_norm2 = np.dot(rho_c, H0_m.dot(rho_c)) + title = r'$\rho_h$ at $t = {:5.4f}, norm = {}$'.format(dt*nt, np.sqrt(rho_norm2)) + plot_field(numpy_coeffs=rho_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_rho_nt={}.pdf'.format(nt), + plot_type=plot_type, cb_min=None, cb_max=None, hide_plot=hide_plots) + else: + plot_type = 'amplitude' + + divE_c = div_m @ Ep_c + divE_norm2 = np.dot(divE_c, H0_m.dot(divE_c)) + if project_sol: + title = r'div $P^1_h E_h$ at $t = {:5.4f}, norm = {}$'.format(dt*nt, np.sqrt(divE_norm2)) + else: + title = r'div $E_h$ at $t = {:5.4f}, norm = {}$'.format(dt*nt, np.sqrt(divE_norm2)) + plot_field(numpy_coeffs=divE_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_divEh_nt={}.pdf'.format(nt), + plot_type=plot_type, cb_min=None, cb_max=None, hide_plot=hide_plots) + + else: + print(' -- WARNING: unknown plot_dir !!') + + def plot_B_field(B_c, nt): + + if plot_dir: + + print(' .. plotting B field...') + params_str = f'gamma_h={gamma_h}_dt={dt}' + + title = r'$B_h$ (amplitude) for $t = {:5.4f}$'.format(dt*nt) + plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_Bh_nt={}.pdf'.format(nt), + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + + else: + print(' -- WARNING: unknown plot_dir !!') + + def plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start, nt_end, + GaussErr_norm2_diag=None, GaussErrP_norm2_diag=None, + PE_norm2_diag=None, I_PE_norm2_diag=None, J_norm2_diag=None, skip_titles=True): + + nt_start = max(nt_start, 0) + nt_end = min(nt_end, Nt) + + td = time_diag[nt_start:nt_end+1] + t_label = r'$t$' + + # norm || E || + fig, ax = plt.subplots() + ax.plot(td, np.sqrt(E_norm2_diag[nt_start:nt_end+1]), '-', ms=7, mfc='None', mec='k') #, label='||E||', zorder=10) + if skip_titles: + title = '' + else: + title = r'$||E_h(t)||$ vs '+t_label + ax.set_xlabel(t_label, fontsize=16) + ax.set_title(title, fontsize=18) + fig.tight_layout() + diag_fn = plot_dir + f'/diag_E_norm_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start}, {dt*nt_end}].pdf' + print(f"saving plot for '{title}' in figure '{diag_fn}") + fig.savefig(diag_fn) + + # energy + fig, ax = plt.subplots() + E_energ = .5*E_norm2_diag[nt_start:nt_end+1] + B_energ = .5*B_norm2_diag[nt_start:nt_end+1] + ax.plot(td, E_energ, '-', ms=7, mfc='None', c='k', label=r'$\frac{1}{2}||E||^2$') #, zorder=10) + ax.plot(td, B_energ, '-', ms=7, mfc='None', c='g', label=r'$\frac{1}{2}||B||^2$') #, zorder=10) + ax.plot(td, E_energ+B_energ, '-', ms=7, mfc='None', c='b', label=r'$\frac{1}{2}(||E||^2+||B||^2)$') #, zorder=10) + ax.legend(loc='best') + if skip_titles: + title = '' + else: + title = r'energy vs '+t_label + if E0_type == 'pulse': + ax.set_ylim([0, 5]) + ax.set_xlabel(t_label, fontsize=16) + ax.set_title(title, fontsize=18) + fig.tight_layout() + diag_fn = plot_dir + f'/diag_energy_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start},{dt*nt_end}].pdf' + print(f"saving plot for '{title}' in figure '{diag_fn}") + fig.savefig(diag_fn) + + # One curve per plot from now on. + # Collect information in a list where each item is of the form [tag, data, title] + time_diagnostics = [] + + if project_sol: + time_diagnostics += [['divPE', divE_norm2_diag, r'$||div_h P^1_h E_h(t)||$ vs '+t_label]] + else: + time_diagnostics += [['divE', divE_norm2_diag, r'$||div_h E_h(t)||$ vs '+t_label]] + + time_diagnostics += [ + ['I_PE' , I_PE_norm2_diag, r'$||(I-P^1)E_h(t)||$ vs '+t_label], + ['PE' , PE_norm2_diag, r'$||(I-P^1)E_h(t)||$ vs '+t_label], + ['GaussErr' , GaussErr_norm2_diag, r'$||(\rho_h - div_h E_h)(t)||$ vs '+t_label], + ['GaussErrP', GaussErrP_norm2_diag, r'$||(\rho_h - div_h E_h)(t)||$ vs '+t_label], + ['J_norm' , J_norm2_diag, r'$||J_h(t)||$ vs '+t_label], + ] + + for tag, data, title in time_diagnostics: + if data is None: + continue + fig, ax = plt.subplots() + ax.plot(td, np.sqrt(I_PE_norm2_diag[nt_start:nt_end+1]), '-', ms=7, mfc='None', mec='k') #, label='||E||', zorder=10) + diag_fn = plot_dir + f'/diag_{tag}_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start},{dt*nt_end}].pdf' + ax.set_xlabel(t_label, fontsize=16) + if not skip_titles: + ax.set_title(title, fontsize=18) + fig.tight_layout() + print(f"saving plot for '{title}' in figure '{diag_fn}") + fig.savefig(diag_fn) + + # diags arrays + E_norm2_diag = np.zeros(Nt+1) + B_norm2_diag = np.zeros(Nt+1) + divE_norm2_diag = np.zeros(Nt+1) + time_diag = np.zeros(Nt+1) + PE_norm2_diag = np.zeros(Nt+1) + I_PE_norm2_diag = np.zeros(Nt+1) + J_norm2_diag = np.zeros(Nt+1) + if source_type == 'Il_pulse': + GaussErr_norm2_diag = np.zeros(Nt+1) + GaussErrP_norm2_diag = np.zeros(Nt+1) + else: + GaussErr_norm2_diag = None + GaussErrP_norm2_diag = None + + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # initial solution + + print(' .. initial solution ..') + + # initial B sol + B_c = np.zeros(V2h.nbasis) + + # initial E sol + if E0_type == 'th_sol': + + if os.path.exists(th_sol_filename): + print(' .. loading time-harmonic solution from file {}'.format(th_sol_filename)) + E_c = source_omega * np.load(th_sol_filename) + assert len(E_c) == V1h.nbasis + else: + print(' .. Error: time-harmonic solution file given {}, but not found'.format(th_sol_filename)) + raise ValueError(th_sol_filename) + + elif E0_type == 'zero': + E_c = np.zeros(V1h.nbasis) + + elif E0_type == 'pulse': + + E0 = get_div_free_pulse(x_0=1.0, y_0=1.0, domain=domain) + + if E0_proj == 'P_geom': + print(' .. projecting E0 with commuting projection...') + E0_h = P1_phys(E0, P1, domain, mappings_list) + E_c = E0_h.coeffs.toarray() + + elif E0_proj == 'P_L2': + # helper: save/load coefs + E0dd_filename = m_load_dir+'/E0_pulse_dual_dofs_qp{}.npy'.format(quad_param) + if os.path.exists(E0dd_filename): + print(' .. loading E0 dual dofs from file {}'.format(E0dd_filename)) + tilde_E0_c = np.load(E0dd_filename) + else: + print(' .. projecting E0 with L2 projection...') + tilde_E0_c = derham_h.get_dual_dofs(space='V1', f=E0, backend_language=backend, return_format='numpy_array') + print(' .. saving E0 dual dofs to file {}'.format(E0dd_filename)) + np.save(E0dd_filename, tilde_E0_c) + E_c = dH1_m.dot(tilde_E0_c) + + elif E0_type == 'pulse_2': + #E0 = get_praxial_Gaussian_beam_E(x_0=3.14, y_0=3.14, domain=domain) + + #E0 = get_easy_Gaussian_beam_E_2(x_0=0.05, y_0=0.05, domain=domain) + #B0 = get_easy_Gaussian_beam_B_2(x_0=0.05, y_0=0.05, domain=domain) + + E0, B0 = get_Gaussian_beam(x_0=3.14, y_0=0.05, domain=domain) + #B0 = get_easy_Gaussian_beam_B(x_0=3.14, y_0=0.05, domain=domain) + + if E0_proj == 'P_geom': + print(' .. projecting E0 with commuting projection...') + + E0_h = P1_phys(E0, P1, domain, mappings_list) + E_c = E0_h.coeffs.toarray() + + #B_c = np.real( - 1j * C_m @ E_c) + #E_c = np.real(E_c) + B0_h = P2_phys(B0, P2, domain, mappings_list) + B_c = B0_h.coeffs.toarray() + + elif E0_proj == 'P_L2': + # helper: save/load coefs + E0dd_filename = m_load_dir+'/E0_pulse_dual_dofs_qp{}.npy'.format(quad_param) + if False:#os.path.exists(E0dd_filename): + print(' .. loading E0 dual dofs from file {}'.format(E0dd_filename)) + tilde_E0_c = np.load(E0dd_filename) + else: + print(' .. projecting E0 with L2 projection...') + + tilde_E0_c = derham_h.get_dual_dofs(space='V1', f=E0, backend_language=backend, return_format='numpy_array') + print(' .. saving E0 dual dofs to file {}'.format(E0dd_filename)) + #np.save(E0dd_filename, tilde_E0_c) + + + E_c = dH1_m.dot(tilde_E0_c) + dH2_m = H2.get_dual_sparse_matrix() + tilde_B0_c = derham_h.get_dual_dofs(space='V2', f=B0, backend_language=backend, return_format='numpy_array') + B_c = dH2_m.dot(tilde_B0_c) + + #B_c = np.real( - C_m @ E_c) + #E_c = np.real(E_c) + else: + raise ValueError(E0_type) + + + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # time loop + def compute_diags(E_c, B_c, J_c, nt): + time_diag[nt] = (nt)*dt + PE_c = cP1_m.dot(E_c) + I_PE_c = E_c-PE_c + E_norm2_diag[nt] = np.dot(E_c,H1_m.dot(E_c)) + PE_norm2_diag[nt] = np.dot(PE_c,H1_m.dot(PE_c)) + I_PE_norm2_diag[nt] = np.dot(I_PE_c,H1_m.dot(I_PE_c)) + J_norm2_diag[nt] = np.dot(J_c,H1_m.dot(J_c)) + B_norm2_diag[nt] = np.dot(B_c,H2_m.dot(B_c)) + divE_c = div_m @ E_c + divE_norm2_diag[nt] = np.dot(divE_c, H0_m.dot(divE_c)) + if source_type == 'Il_pulse': + rho_c = rho0_c * np.sin(source_omega*nt*dt)/omega + GaussErr = rho_c - divE_c + GaussErrP = rho_c - div_m @ PE_c + GaussErr_norm2_diag[nt] = np.dot(GaussErr, H0_m.dot(GaussErr)) + GaussErrP_norm2_diag[nt] = np.dot(GaussErrP, H0_m.dot(GaussErrP)) + + OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') + OM1.add_spaces(V1h=V1h) + OM1.export_space_info() + + OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM2.add_spaces(V2h=V2h) + OM2.export_space_info() + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=0 , ts=0) + OM1.export_fields(Eh=Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=0 , ts=0) + OM2.export_fields(Bh=Bh) + + + #PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) + #PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=[6]*2, snapshots='all', fields='vh' ) + + #OM1.close() + #PM.close() + + #plot_E_field(E_c, nt=0, project_sol=project_sol, plot_divE=plot_divE) + #plot_B_field(B_c, nt=0) + + f_c = np.copy(f0_c) + for nt in range(Nt): + print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) + + # 1/2 faraday: Bn -> Bn+1/2 + B_c[:] -= (dt/2) * C_m @ E_c + + # ampere: En -> En+1 + if f0_harmonic_c is not None: + f_harmonic_c = f0_harmonic_c * (np.sin(source_omega*(nt+1)*dt)-np.sin(source_omega*(nt)*dt))/(dt*source_omega) # * source_enveloppe(omega*(nt+1/2)*dt) + f_c[:] = f0_c + f_harmonic_c + + if nt == 0: + plot_J_source_nPlusHalf(f_c, nt=0) + compute_diags(E_c, B_c, f_c, nt=0) + + E_c[:] = dCH1_m @ E_c + dt * (dC_m @ B_c - f_c) + + #if abs(gamma_h) > 1e-10: + # E_c[:] -= dt * gamma_h * JP_m @ E_c + + # 1/2 faraday: Bn+1/2 -> Bn+1 + B_c[:] -= (dt/2) * C_m @ E_c + + # diags: + compute_diags(E_c, B_c, f_c, nt=nt+1) + + # PE_c = cP1_m.dot(E_c) + # I_PE_c = E_c-PE_c + # E_norm2_diag[nt+1] = np.dot(E_c,H1_m.dot(E_c)) + # PE_norm2_diag[nt+1] = np.dot(PE_c,H1_m.dot(PE_c)) + # I_PE_norm2_diag[nt+1] = np.dot(I_PE_c,H1_m.dot(I_PE_c)) + # B_norm2_diag[nt+1] = np.dot(B_c,H2_m.dot(B_c)) + # time_diag[nt+1] = (nt+1)*dt + + # diags: div + # if project_sol: + # Ep_c = PE_c # = cP1_m.dot(E_c) + # else: + # Ep_c = E_c + # divE_c = div_m @ Ep_c + # divE_norm2 = np.dot(divE_c, H0_m.dot(divE_c)) + # # print('in diag[{}]: divE_norm = {}'.format(nt+1, np.sqrt(divE_norm2))) + # divE_norm2_diag[nt+1] = divE_norm2 + + # if source_type == 'Il_pulse': + # rho_c = rho0_c * np.sin(omega*dt*(nt+1))/omega + # GaussErr = rho_c - div_m @ E_c + # GaussErrP = rho_c - div_m @ (cP1_m.dot(E_c)) + # GaussErr_norm2_diag[nt+1] = np.dot(GaussErr, H0_m.dot(GaussErr)) + # GaussErrP_norm2_diag[nt+1] = np.dot(GaussErrP, H0_m.dot(GaussErrP)) + + if debug: + divCB_c = div_m @ dC_m @ B_c + divCB_norm2 = np.dot(divCB_c, H0_m.dot(divCB_c)) + print('-- [{}]: dt*|| div CB || = {}'.format(nt+1, dt*np.sqrt(divCB_norm2))) + + divf_c = div_m @ f_c + divf_norm2 = np.dot(divf_c, H0_m.dot(divf_c)) + print('-- [{}]: dt*|| div f || = {}'.format(nt+1, dt*np.sqrt(divf_norm2))) + + divE_c = div_m @ E_c + divE_norm2 = np.dot(divE_c, H0_m.dot(divE_c)) + print('-- [{}]: || div E || = {}'.format(nt+1, np.sqrt(divE_norm2))) + + if is_plotting_time(nt+1): + print("Plot Stuff") + #plot_E_field(E_c, nt=nt+1, project_sol=True, plot_divE=False) + #plot_B_field(B_c, nt=nt+1) + #plot_J_source_nPlusHalf(f_c, nt=nt) + + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=nt*dt, ts=nt) + OM1.export_fields(Eh = Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=nt*dt, ts=nt) + OM2.export_fields(Bh=Bh) + + #if (nt+1) % diag_nt == 0: + #plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=(nt+1)-diag_nt, nt_end=(nt+1), + #PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, + #GaussErr_norm2_diag=GaussErr_norm2_diag, GaussErrP_norm2_diag=GaussErrP_norm2_diag) + + OM1.close() + + print("Do some PP") + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) + PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=[6]*2,snapshots='all', fields = 'Eh' ) + PM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) + PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=[6]*2,snapshots='all', fields = 'Bh' ) + PM.close() + + # plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=0, nt_end=Nt, + # PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, + # GaussErr_norm2_diag=GaussErr_norm2_diag, GaussErrP_norm2_diag=GaussErrP_norm2_diag) + + # Eh = FemField(V1h, coeffs=array_to_stencil(E_c, V1h.vector_space)) + # t_stamp = time_count(t_stamp) + + # if sol_filename: + # raise NotImplementedError + # print(' .. saving final solution coeffs to file {}'.format(sol_filename)) + # np.save(sol_filename, E_c) + + # time_count(t_stamp) + + # print() + # print(' -- plots and diagnostics --') + + # # diagnostics: errors + # err_diags = diag_grid.get_diags_for(v=uh, space='V1') + # for key, value in err_diags.items(): + # diags[key] = value + + # if u_ex is not None: + # check_diags = get_Vh_diags_for(v=uh, v_ref=uh_ref, M_m=H1_m, msg='error between Ph(u_ex) and u_h') + # diags['norm_Pu_ex'] = check_diags['sol_ref_norm'] + # diags['rel_l2_error_in_Vh'] = check_diags['rel_l2_error'] + + # if curl_u_ex is not None: + # print(' .. diag on curl_u:') + # curl_uh_c = bD1_m @ cP1_m @ uh_c + # title = r'curl $u_h$ (amplitude) for $\eta = $'+repr(eta) + # params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format(eta, mu, nu, gamma_h, source_proj) + # plot_field(numpy_coeffs=curl_uh_c, Vh=V2h, space_kind='l2', domain=domain, surface_plot=False, title=title, filename=plot_dir+'/'+params_str+'_curl_uh.png', + # plot_type='amplitude', cb_min=None, cb_max=None, hide_plot=hide_plots) + + # curl_uh = FemField(V2h, coeffs=array_to_stencil(curl_uh_c, V2h.vector_space)) + # curl_diags = diag_grid.get_diags_for(v=curl_uh, space='V2') + # diags['curl_error (to be checked)'] = curl_diags['rel_l2_error'] + + + # title = r'div_h $u_h$ (amplitude) for $\eta = $'+repr(eta) + # params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format(eta, mu, nu, gamma_h, source_proj) + # plot_field(numpy_coeffs=div_uh_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, filename=plot_dir+'/'+params_str+'_div_uh.png', + # plot_type='amplitude', cb_min=None, cb_max=None, hide_plot=hide_plots) + + # div_uh = FemField(V0h, coeffs=array_to_stencil(div_uh_c, V0h.vector_space)) + # div_diags = diag_grid.get_diags_for(v=div_uh, space='V0') + # diags['div_error (to be checked)'] = div_diags['rel_l2_error'] + + return diags + + +#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): +def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): + """ + Compute a stable time step size based on the maximum CFL parameter in the + domain. To this end we estimate the operator norm of + + `dC_m @ C_m: V1h -> V1h`, + + find the largest stable time step compatible with Strang splitting, and + rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the + scheme exactly at its stability limit, which is not safe because of the + unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. + + Optionally the user can provide a maximum time step size in order to + properly resolve some time scales of interest (e.g. a time-dependent + current source). + + Parameters + ---------- + C_m : scipy.sparse.spmatrix + Matrix of the Curl operator. + + dC_m : scipy.sparse.spmatrix + Matrix of the dual Curl operator. + + cfl_max : float + Maximum Courant parameter in the domain, intended as a stability + parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. + + dt_max : float, optional + If not None, restrict the computed dt by this value in order to + properly resolve time scales of interest. Must be > 0. + + Returns + ------- + dt : float + Largest stable dt which satisfies the provided constraints. + + """ + + print (" .. compute_stable_dt by estimating the operator norm of ") + print (" .. dC_m @ C_m: V1h -> V1h ") + print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) + + if not (0 < cfl_max < 1): + print(' ****** ****** ****** ****** ****** ****** ') + print(' WARNING !!! cfl = {} '.format(cfl)) + print(' ****** ****** ****** ****** ****** ****** ') + + def vect_norm_2 (vv): + return np.sqrt(np.dot(vv,vv)) + + t_stamp = time_count() + vv = np.random.random(C_m.shape[1]) + norm_vv = vect_norm_2(vv) + max_ncfl = 500 + ncfl = 0 + spectral_rho = 1 + conv = False + CC_m = dC_m @ C_m + + while not( conv or ncfl > max_ncfl ): + + vv[:] = (1./norm_vv)*vv + ncfl += 1 + vv[:] = CC_m.dot(vv) + + norm_vv = vect_norm_2(vv) + old_spectral_rho = spectral_rho + spectral_rho = vect_norm_2(vv) # approximation + conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 + print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) + t_stamp = time_count(t_stamp) + + norm_op = np.sqrt(spectral_rho) + c_dt_max = 2./norm_op + + light_c = 1 + dt = cfl_max * c_dt_max / light_c + + if dt_max is not None: + dt = min(dt, dt_max) + + print( " Time step dt computed for Maxwell solver:") + print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") + print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") + print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") + + return dt \ No newline at end of file diff --git a/psydac/feec/multipatch/utils_conga_2d.py b/psydac/feec/multipatch/utils_conga_2d.py index 1c3039519..8be53b4f8 100644 --- a/psydac/feec/multipatch/utils_conga_2d.py +++ b/psydac/feec/multipatch/utils_conga_2d.py @@ -21,18 +21,18 @@ # commuting projections on the physical domain (should probably be in the interface) def P0_phys(f_phys, P0, domain, mappings_list): f = lambdify(domain.coordinates, f_phys) - f_log = [pull_2d_h1(f, m) for m in mappings_list] + f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] return P0(f_log) def P1_phys(f_phys, P1, domain, mappings_list): f_x = lambdify(domain.coordinates, f_phys[0]) f_y = lambdify(domain.coordinates, f_phys[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m) for m in mappings_list] + f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] return P1(f_log) def P2_phys(f_phys, P2, domain, mappings_list): f = lambdify(domain.coordinates, f_phys) - f_log = [pull_2d_l2(f, m) for m in mappings_list] + f_log = [pull_2d_l2(f, m.get_callable_mapping()) for m in mappings_list] return P2(f_log) def get_kind(space='V*'): From a806b6d7b28414217abe887d07432d1bdeb07e67 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Fri, 28 Jul 2023 15:37:48 +0200 Subject: [PATCH 11/88] add time harmonic and time domain maxwell examples --- .../multipatch/examples/ppc_test_cases.py | 8 +- .../examples_nc/hcurl_eigen_pbms_dg.py | 5 +- .../examples_nc/hcurl_eigen_pbms_nc.py | 126 +------ .../examples_nc/hcurl_eigen_testcase.py | 38 +- .../examples_nc/hcurl_source_pbms_nc.py | 356 ++++++++++++++++++ .../examples_nc/hcurl_source_testcase.py | 177 +++++++++ ..._absorbing.py => timedomain_maxwell_nc.py} | 0 .../timedomain_maxwells_testcase.py | 250 ++++++++++++ 8 files changed, 821 insertions(+), 139 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py create mode 100644 psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py rename psydac/feec/multipatch/examples_nc/{td_maxwell_conga_2d_nc_absorbing.py => timedomain_maxwell_nc.py} (100%) create mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index 70295d573..ddfd6659d 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -90,15 +90,15 @@ def get_Gaussian_beam(x_0, y_0, domain=None): x = x - x_0 y = y - y_0 - k = 2*pi - sigma = 0.7 + k = pi + sigma = 0.5 xy = x**2 + y**2 ef = exp( - xy/(2*sigma**2) ) E = cos(k * y) * ef - B = y/(sigma**2) * E - sin(k * y) * ef - + B = -y/(sigma**2) * E + return Tuple(E, 0), B def get_Gaussian_beam2(x_0, y_0, domain=None): diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py index 525c357e0..c2e986a5c 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py @@ -148,7 +148,10 @@ def hcurl_solve_eigen_pbm_multipatch_dg(ncells=[[2,2], [2,2]], degree=[3,3], dom t_stamp = time_count(t_stamp) print('plotting the eigenmodes...') - + + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + # OM = OutputManager('spaces.yml', 'fields.h5') # OM.add_spaces(V1h=V1h) diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py index d2fd42dfa..7efa56b15 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -32,25 +32,6 @@ from psydac.api.postprocessing import OutputManager, PostProcessManager -#from said -from scipy.sparse.linalg import spsolve, inv - -from sympde.calculus import grad, dot, curl, cross -from sympde.calculus import minus, plus -from sympde.topology import VectorFunctionSpace -from sympde.topology import elements_of -from sympde.topology import NormalVector -from sympde.topology import Square -from sympde.topology import IdentityMapping, PolarMapping -from sympde.expr.expr import LinearForm, BilinearForm -from sympde.expr.expr import integral -from sympde.expr.expr import Norm -from sympde.expr.equation import find, EssentialBC - -from psydac.api.tests.build_domain import build_pretzel -from psydac.fem.basic import FemField -from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL -from psydac.feec.pull_push import pull_2d_hcurl def hcurl_solve_eigen_pbm_multipatch_nc(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, @@ -253,112 +234,7 @@ def hcurl_solve_eigen_pbm_multipatch_nc(ncells=[[2,2], [2,2]], degree=[3,3], dom t_stamp = time_count(t_stamp) - ### Saids Code - - V = VectorFunctionSpace('V', domain, kind='hcurl') - - u, v, F = elements_of(V, names='u, v, F') - nn = NormalVector('nn') - - I = domain.interfaces - boundary = domain.boundary - - kappa = 10 - k = 1 - - jump = lambda w:plus(w)-minus(w) - avr = lambda w:0.5*plus(w) + 0.5*minus(w) - - expr1_I = cross(nn, jump(v))*curl(avr(u))\ - +k*cross(nn, jump(u))*curl(avr(v))\ - +kappa*cross(nn, jump(u))*cross(nn, jump(v)) - - expr1 = curl(u)*curl(v) - expr1_b = -cross(nn, v) * curl(u) -k*cross(nn, u)*curl(v) + kappa*cross(nn, u)*cross(nn, v) - ## curl curl u = - omega**2 u - - expr2 = dot(u,v) - #expr2_I = kappa*cross(nn, jump(u))*cross(nn, jump(v)) - #expr2_b = -k*cross(nn, u)*curl(v) + kappa * cross(nn, u) * cross(nn, v) - - # Bilinear form a: V x V --> R - a = BilinearForm((u,v), integral(domain, expr1) + integral(I, expr1_I) + integral(boundary, expr1_b)) - - # Linear form l: V --> R - b = BilinearForm((u,v), integral(domain, expr2))# + integral(I, expr2_I) + integral(boundary, expr2_b)) - - #+++++++++++++++++++++++++++++++ - # 2. Discretization - #+++++++++++++++++++++++++++++++ - - domain_h = discretize(domain, ncells=ncells_h) - Vh = discretize(V, domain_h, degree=degree) - - ah = discretize(a, domain_h, [Vh, Vh]) - Ah_m = ah.assemble().tosparse() - - bh = discretize(b, domain_h, [Vh, Vh]) - Bh_m = bh.assemble().tosparse() - - all_eigenvalues_2, all_eigenvectors_transp_2 = get_eigenvalues(nb_eigs_solve, sigma, Ah_m, Bh_m) - - #Eigenvalue processing - t_stamp = time_count(t_stamp) - print('sorting out eigenvalues...') - zero_eigenvalues2 = [] - if skip_eigs_threshold is not None: - eigenvalues2 = [] - eigenvectors2 = [] - for val, vect in zip(all_eigenvalues_2, all_eigenvectors_transp_2.T): - if abs(val) < skip_eigs_threshold: - zero_eigenvalues2.append(val) - # we skip the eigenvector - else: - eigenvalues2.append(val) - eigenvectors2.append(vect) - else: - eigenvalues2 = all_eigenvalues_2 - eigenvectors2 = all_eigenvectors_transp_2.T - diags['DG'] = True - for k, val in enumerate(eigenvalues2): - diags['eigenvalue2_{}'.format(k)] = val #eigenvalues[k] - - for k, val in enumerate(zero_eigenvalues2): - diags['skipped eigenvalue2_{}'.format(k)] = val - - t_stamp = time_count(t_stamp) - print('plotting the eigenmodes...') - - # OM = OutputManager('spaces.yml', 'fields.h5') - # OM.add_spaces(V1h=V1h) - - nb_eigs = len(eigenvalues2) - for i in range(min(nb_eigs_plot, nb_eigs)): - OM = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') - OM.add_spaces(V1h=Vh) - print('looking at emode i = {}... '.format(i)) - lambda_i = eigenvalues2[i] - emode_i = np.real(eigenvectors2[i]) - norm_emode_i = np.dot(emode_i,Bh_m.dot(emode_i)) - eh_c = emode_i/norm_emode_i - stencil_coeffs = array_to_psydac(eh_c, Vh.vector_space) - vh = FemField(Vh, coeffs=stencil_coeffs) - OM.set_static() - #OM.add_snapshot(t=i , ts=0) - OM.export_fields(vh = vh) - - #print('norm of computed eigenmode: ', norm_emode_i) - # plot the broken eigenmode: - OM.export_space_info() - OM.close() - - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/eigen2_{}".format(i),grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) - PM.close() - - t_stamp = time_count(t_stamp) - - return diags, eigenvalues, eigenvalues2 + return diags, eigenvalues def get_eigenvalues(nb_eigs, sigma, A_m, M_m): diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py index 9ecc16c1d..e2ea765c9 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py @@ -1,9 +1,9 @@ import os import numpy as np -#from psydac.feec.multipatch.examples_nc.multipatch_non_conf_examples import hcurl_solve_eigen_pbm_multipatch_nc -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_multipatch_nc +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_multipatch_nc +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_multipatch_dg from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file @@ -15,6 +15,8 @@ # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # # test-case and numerical parameters: +# method = 'feec' +method = 'dg' operator = 'curl-curl' degree = [3,3] # shared across all patches @@ -69,8 +71,8 @@ domain=[[1, 3],[0, np.pi/4]] # interval in x- and y-direction -ncells = np.array([[None, 10], - [10, 5]]) +ncells = np.array([[None, 5], + [5, 10]]) @@ -111,7 +113,7 @@ else: raise ValueError(operator) -case_dir = 'talk_eigenpbm_'+operator +case_dir = 'eigenpbm_'+operator+'_'+method ref_case_dir = case_dir cb_min_sol = None @@ -232,8 +234,26 @@ # note: # - we look for nb_eigs_solve eigenvalues close to sigma (skip zero eigenvalues if skip_zero_eigs==True) # - we plot nb_eigs_plot eigenvectors - -diags, eigenvalues, eigenvalues2 = hcurl_solve_eigen_pbm_multipatch_nc( +if method == 'feec': + diags, eigenvalues = hcurl_solve_eigen_pbm_multipatch_nc( + ncells=ncells, degree=degree, + gamma_h=gamma_h, + generalized_pbm=generalized_pbm, + nu=nu, + mu=mu, + sigma=sigma, + ref_sigmas=ref_sigmas, + skip_eigs_threshold=skip_eigs_threshold, + nb_eigs_solve=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + domain_name=domain_name, domain=domain, + backend_language=backend_language, + plot_dir=plot_dir, + hide_plots=True, + m_load_dir=m_load_dir, + ) +elif method == 'dg': + diags, eigenvalues = hcurl_solve_eigen_pbm_multipatch_dg( ncells=ncells, degree=degree, gamma_h=gamma_h, generalized_pbm=generalized_pbm, @@ -249,14 +269,14 @@ plot_dir=plot_dir, hide_plots=True, m_load_dir=m_load_dir, -) + ) + if ref_sigmas is not None: errors = [] n_errs = min(len(ref_sigmas), len(eigenvalues)) for k in range(n_errs): diags['error_{}'.format(k)] = abs(eigenvalues[k]-ref_sigmas[k]) - diags['error2_{}'.format(k)] = abs(eigenvalues2[k]-ref_sigmas[k]) # # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py new file mode 100644 index 000000000..bbb35ce0f --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py @@ -0,0 +1,356 @@ +# coding: utf-8 + +from mpi4py import MPI + +import os +import numpy as np +from collections import OrderedDict + +from sympy import lambdify, Matrix + +from scipy.sparse.linalg import spsolve + +from sympde.calculus import dot +from sympde.topology import element_of +from sympde.expr.expr import LinearForm +from sympde.expr.expr import integral, Norm +from sympde.topology import Derham + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField + +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.api.postprocessing import OutputManager, PostProcessManager + +def solve_hcurl_source_pbm_nc( + nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', + eta=-10., mu=1., nu=1., gamma_h=10., + project_sol=False, + plot_source=False, plot_dir=None, hide_plots=True, skip_plot_titles=False, + cb_min_sol=None, cb_max_sol=None, + m_load_dir="", sol_filename="", sol_ref_filename="", + ref_nc=None, ref_deg=None, +): + """ + solver for the problem: find u in H(curl), such that + + A u = f on \Omega + n x u = n x u_bc on \partial \Omega + + where the operator + + A u := eta * u + mu * curl curl u - nu * grad div u + + is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + + V0h --grad-> V1h -—curl-> V2h + + Examples: + + - time-harmonic maxwell equation with + eta = -omega**2 + mu = 1 + nu = 0 + + - Hodge-Laplacian operator L = A with + eta = 0 + mu = 1 + nu = 1 + + :param nc: nb of cells per dimension, in each patch + :param deg: coordinate degree in each patch + :param gamma_h: jump penalization parameter + :param source_proj: approximation operator (in V1h) for the source, possible values are + - 'P_geom': primal commuting projection based on geometric dofs + - 'P_L2': L2 projection on the broken space + - 'tilde_Pi': dual commuting projection, an L2 projection filtered by the adjoint conforming projection) + :param source_type: must be implemented in get_source_and_solution() + :param m_load_dir: directory for matrix storage + """ + diags = {} + + ncells = nc + degree = [deg,deg] + + # if backend_language is None: + # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: + # backend_language='numba' + # else: + # backend_language='python' + # print('[note: using '+backend_language+ ' backends in discretize functions]') + if m_load_dir is not None: + if not os.path.exists(m_load_dir): + os.makedirs(m_load_dir) + + print('---------------------------------------------------------------------------------------------------------') + print('Starting solve_hcurl_source_pbm function with: ') + print(' ncells = {}'.format(ncells)) + print(' degree = {}'.format(degree)) + print(' domain_name = {}'.format(domain_name)) + print(' source_proj = {}'.format(source_proj)) + print(' backend_language = {}'.format(backend_language)) + print('---------------------------------------------------------------------------------------------------------') + + print() + print(' -- building discrete spaces and operators --') + + t_stamp = time_count() + print(' .. multi-patch domain...') + domain = build_multipatch_domain(domain_name=domain_name) + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + + #corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0, 0 ] + #ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) + #ncells = np.array([4 for _ in range(18)]) + + # for diagnosttics + diag_grid = DiagGrid(mappings=mappings, N_diag=100) + + t_stamp = time_count(t_stamp) + print(' .. derham sequence...') + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + + t_stamp = time_count(t_stamp) + print(' .. discrete domain...') + domain_h = discretize(domain, ncells=ncells_h) + + t_stamp = time_count(t_stamp) + print(' .. discrete derham sequence...') + derham_h = discretize(derham, domain_h, degree=degree) + + t_stamp = time_count(t_stamp) + print(' .. commuting projection operators...') + nquads = [4*(d + 1) for d in degree] + P0, P1, P2 = derham_h.projectors(nquads=nquads) + + t_stamp = time_count(t_stamp) + print(' .. multi-patch spaces...') + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + print('dim(V0h) = {}'.format(V0h.nbasis)) + print('dim(V1h) = {}'.format(V1h.nbasis)) + print('dim(V2h) = {}'.format(V2h.nbasis)) + diags['ndofs_V0'] = V0h.nbasis + diags['ndofs_V1'] = V1h.nbasis + diags['ndofs_V2'] = V2h.nbasis + + t_stamp = time_count(t_stamp) + print(' .. Id operator and matrix...') + I1 = IdLinearOperator(V1h) + I1_m = I1.to_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. Hodge operators...') + # multi-patch (broken) linear operators / matrices + # other option: define as Hodge Operators: + H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) + H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) + H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) + + t_stamp = time_count(t_stamp) + print(' .. Hodge matrix H0_m = M0_m ...') + H0_m = H0.to_sparse_matrix() + t_stamp = time_count(t_stamp) + print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') + dH0_m = H0.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. Hodge matrix H1_m = M1_m ...') + H1_m = H1.to_sparse_matrix() + t_stamp = time_count(t_stamp) + print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') + dH1_m = H1.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. Hodge matrix dH2_m = M2_m ...') + H2_m = H2.to_sparse_matrix() + dH2_m = H2.get_dual_Hodge_sparse_matrix() + + t_stamp = time_count(t_stamp) + print(' .. conforming Projection operators...') + # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) + #cP0 = derham_h.conforming_projection(space='V0', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) + #cP1 = derham_h.conforming_projection(space='V1', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) + #cP0_m = cP0.to_sparse_matrix() + #cP1_m = cP1.to_sparse_matrix() + + # Try the NC one + cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + + t_stamp = time_count(t_stamp) + print(' .. broken differential operators...') + # broken (patch-wise) differential operators + bD0, bD1 = derham_h.broken_derivatives_as_operators + bD0_m = bD0.to_sparse_matrix() + bD1_m = bD1.to_sparse_matrix() + + if plot_dir is not None and not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + def lift_u_bc(u_bc): + if u_bc is not None: + print('lifting the boundary condition in V1h...') + # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs + uh_bc = P1_phys(u_bc, P1, domain, mappings_list) + ubc_c = uh_bc.coeffs.toarray() + # removing internal dofs (otherwise ubc_c may already be a very good approximation of uh_c ...) + ubc_c = ubc_c - cP1_m.dot(ubc_c) + else: + ubc_c = None + return ubc_c + + # Conga (projection-based) stiffness matrices + # curl curl: + t_stamp = time_count(t_stamp) + print(' .. curl-curl stiffness matrix...') + print(bD1_m.shape, H2_m.shape ) + pre_CC_m = bD1_m.transpose() @ dH2_m @ bD1_m + # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix + + # grad div: + t_stamp = time_count(t_stamp) + print(' .. grad-div stiffness matrix...') + pre_GD_m = - dH1_m @ bD0_m @ cP0_m @ H0_m @ cP0_m.transpose() @ bD0_m.transpose() @ dH1_m + # GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix + + # jump stabilization: + t_stamp = time_count(t_stamp) + print(' .. jump stabilization matrix...') + jump_penal_m = I1_m - cP1_m + JP_m = jump_penal_m.transpose() * dH1_m * jump_penal_m + + t_stamp = time_count(t_stamp) + print(' .. full operator matrix...') + print('eta = {}'.format(eta)) + print('mu = {}'.format(mu)) + print('nu = {}'.format(nu)) + print('STABILIZATION: gamma_h = {}'.format(gamma_h)) + pre_A_m = cP1_m.transpose() @ ( eta * dH1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) + A_m = pre_A_m @ cP1_m + gamma_h * JP_m + + t_stamp = time_count(t_stamp) + print() + print(' -- getting source --') + f_vect, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( + source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, + ) + # compute approximate source f_h + t_stamp = time_count(t_stamp) + tilde_f_c = f_c = None + if source_proj == 'P_geom': + # f_h = P1-geometric (commuting) projection of f_vect + print(' .. projecting the source with primal (geometric) commuting projection...') + f_h = P1_phys(f_vect, P1, domain, mappings_list) + f_c = f_h.coeffs.toarray() + tilde_f_c = dH1_m.dot(f_c) + + elif source_proj in ['P_L2', 'tilde_Pi']: + # f_h = L2 projection of f_vect, with filtering if tilde_Pi + print(' .. projecting the source with '+source_proj+' projection...') + tilde_f_c = derham_h.get_dual_dofs(space='V1', f=f_vect, backend_language=backend_language, return_format='numpy_array') + if source_proj == 'tilde_Pi': + print(' .. filtering the discrete source with P0.T ...') + tilde_f_c = cP1_m.transpose() @ tilde_f_c + else: + raise ValueError(source_proj) + + + + if plot_source: + if True: + title = '' + title_vf = '' + else: + title = 'f_h with P = '+source_proj + title_vf = 'f_h with P = '+source_proj + if f_c is None: + f_c = H1_m.dot(tilde_f_c) + plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, + title=title, filename=plot_dir+'/fh_'+source_proj+'.pdf', hide_plot=hide_plots) + plot_field(numpy_coeffs=f_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, + title=title_vf, filename=plot_dir+'/fh_'+source_proj+'_vf.pdf', hide_plot=hide_plots) + + ubc_c = lift_u_bc(u_bc) + if ubc_c is not None: + # modified source for the homogeneous pbm + t_stamp = time_count(t_stamp) + print(' .. modifying the source with lifted bc solution...') + tilde_f_c = tilde_f_c - pre_A_m.dot(ubc_c) + + # direct solve with scipy spsolve + t_stamp = time_count(t_stamp) + print() + print(' -- solving source problem with scipy.spsolve...') + uh_c = spsolve(A_m, tilde_f_c) + + # project the homogeneous solution on the conforming problem space + if project_sol: + t_stamp = time_count(t_stamp) + print(' .. projecting the homogeneous solution on the conforming problem space...') + uh_c = cP1_m.dot(uh_c) + else: + print(' .. NOT projecting the homogeneous solution on the conforming problem space') + + if ubc_c is not None: + # adding the lifted boundary condition + t_stamp = time_count(t_stamp) + print(' .. adding the lifted boundary condition...') + uh_c += ubc_c + + uh = FemField(V1h, coeffs=array_to_psydac(uh_c, V1h.vector_space)) + f_c = H1_m.dot(tilde_f_c) + jh = FemField(V1h, coeffs=array_to_psydac(f_c, V1h.vector_space)) + + t_stamp = time_count(t_stamp) + + print() + print(' -- plots and diagnostics --') + if plot_dir: + print(' .. plotting the FEM solution...') + if skip_plot_titles: + title = '' + title_vf = '' + else: + title = r'solution $u_h$ (amplitude) for $\eta = $'+repr(eta) + title_vf = r'solution $u_h$ for $\eta = $'+repr(eta) + params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format(eta, mu, nu, gamma_h, source_proj) + plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir+'/'+params_str+'_uh.pdf', + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title_vf, + filename=plot_dir+'/'+params_str+'_uh_vf.pdf', + plot_type='vector_field', hide_plot=hide_plots) + + OM = OutputManager(plot_dir+'/spaces.yml', plot_dir+'/fields.h5') + OM.add_spaces(V1h=V1h) + OM.set_static() + OM.export_fields(vh = uh) + OM.export_fields(jh = jh) + OM.export_space_info() + OM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces.yml', fields_file=plot_dir+'/fields.h5' ) + PM.export_to_vtk(plot_dir+"/sol",grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) + PM.export_to_vtk(plot_dir+"/source",grid=None, npts_per_cell=[6]*2,snapshots='all', fields='jh' ) + + PM.close() + + time_count(t_stamp) + + + return diags \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py new file mode 100644 index 000000000..fe283a1b7 --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py @@ -0,0 +1,177 @@ +import os +import numpy as np +from psydac.feec.multipatch.examples_nc.hcurl_source_pbms_nc import solve_hcurl_source_pbm_nc + + +from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file + +t_stamp_full = time_count() + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# +# main test-cases used for the ppc paper: + +#test_case = 'maxwell_hom_eta=50' # used in paper +test_case = 'maxwell_hom_eta=170' # used in paper +# test_case = 'maxwell_inhom' # used in paper + +compute_ref_sol = False # (not needed for inhomogeneous test-case, as exact solution is known) + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + + +# numerical parameters: +domain_name = 'pretzel_f' +#domain_name = 'curved_L_shape' + +source_proj = 'tilde_Pi' +# other values are: + +#source_proj = 'P_L2' # L2 projection in broken space +# source_proj = 'P_geom' # geometric projection (primal commuting proj) + +#nc_s = [np.array([16 for _ in range(18)])] + +#corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0 ] +nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16, 8, 8])] + +#refine handles only +#nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 2, 2, 4, 16, 16, 16, 2, 2])] + +#refine source +#nc_s = [np.array([32, 8, 8, 32, 32, 32, 32, 8, 8, 8, 8, 8, 8, 32, 8, 8, 8, 8])] + +deg_s = [3] + +if test_case == 'maxwell_hom_eta=50': + homogeneous = True + source_type = 'elliptic_J' + omega = np.sqrt(50) # source time pulsation + + cb_min_sol = 0 + cb_max_sol = 1 + + # ref solution (no exact solution) + ref_nc = 10 + ref_deg = 6 + +elif test_case == 'maxwell_hom_eta=170': + homogeneous = True + source_type = 'elliptic_J' + omega = np.sqrt(170) # source time pulsation + + cb_min_sol = 0 + cb_max_sol = 1 + + # ref solution (no exact solution) + ref_nc = 10 + ref_deg = 6 + + +elif test_case == 'maxwell_inhom': + + homogeneous = False # + source_type = 'manu_maxwell_inhom' + omega = np.pi + + cb_min_sol = 0 + cb_max_sol = 1 + + # dummy ref solution (there is an exact solution) + ref_nc = 2 + ref_deg = 2 + +else: + raise ValueError(test_case) + +case_dir = test_case +ref_case_dir = case_dir + +roundoff = 1e4 +eta = int(-omega**2 * roundoff)/roundoff + +project_sol = True # True # (use conf proj of solution for visualization) +gamma_h = 10 + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +common_diag_filename = './'+case_dir+'_diags.txt' + +for nc in nc_s: + for deg in deg_s: + + params = { + 'domain_name': domain_name, + 'nc': nc, + 'deg': deg, + 'homogeneous': homogeneous, + 'source_type': source_type, + 'source_proj': source_proj, + 'project_sol': project_sol, + 'omega': omega, + 'gamma_h': gamma_h, + 'ref_nc': ref_nc, + 'ref_deg': ref_deg, + } + # backend_language = 'numba' + backend_language='pyccel-gcc' + + run_dir = get_run_dir(domain_name, nc, deg, source_type=source_type) + plot_dir = get_plot_dir(case_dir, run_dir) + diag_filename = plot_dir+'/'+diag_fn(source_type=source_type, source_proj=source_proj) + + # to save and load matrices + m_load_dir = get_mat_dir(domain_name, nc, deg) + # to save the FEM sol + + # to load the ref FEM sol + sol_ref_dir = get_sol_dir(ref_case_dir, domain_name, ref_nc, ref_deg) + sol_ref_filename = sol_ref_dir+'/'+FEM_sol_fn(source_type=source_type, source_proj=source_proj) + + print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') + print(' Calling solve_hcurl_source_pbm() with params = {}'.format(params)) + print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') + + # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + # calling solver for: + # + # find u in H(curl), s.t. + # A u = f on \Omega + # n x u = n x u_bc on \partial \Omega + # with + # A u := eta * u + mu * curl curl u - nu * grad div u + + diags = solve_hcurl_source_pbm_nc( + nc=nc, deg=deg, + eta=eta, + nu=0, + mu=1, + domain_name=domain_name, + source_type=source_type, + source_proj=source_proj, + backend_language=backend_language, + plot_source=False, + project_sol=project_sol, + gamma_h=gamma_h, + plot_dir=plot_dir, + hide_plots=True, + skip_plot_titles=False, + cb_min_sol=cb_min_sol, + cb_max_sol=cb_max_sol, + m_load_dir=m_load_dir, + sol_filename=None, + sol_ref_filename=sol_ref_filename, + ref_nc=ref_nc, + ref_deg=ref_deg, + ) + + # + # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + + write_diags_to_file(diags, script_filename=__file__, diag_filename=diag_filename, params=params) + write_diags_to_file(diags, script_filename=__file__, diag_filename=common_diag_filename, params=params) + +time_count(t_stamp_full, msg='full program') \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/td_maxwell_conga_2d_nc_absorbing.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py similarity index 100% rename from psydac/feec/multipatch/examples_nc/td_maxwell_conga_2d_nc_absorbing.py rename to psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py new file mode 100644 index 000000000..493b922b0 --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py @@ -0,0 +1,250 @@ +import numpy as np +from psydac.feec.multipatch.examples_nc.td_maxwell_conga_2d_nc_absorbing import solve_td_maxwell_pbm +from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file + +t_stamp_full = time_count() + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# +# main test-cases and parameters used for the ppc paper: + +test_case = 'E0_pulse_no_source' # used in paper +#test_case = 'Issautier_like_source' # used in paper +#test_case = 'transient_to_harmonic' # actually, not used in paper + +# J_proj_case = 'P_geom' +J_proj_case = 'P_L2' +#J_proj_case = 'tilde Pi_1' + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +# Parameters to be changed in the batch run +deg = 4 + +# Common simulation parameters +#domain_name = 'square_6' +#ncells = [4,4,4,4,4,4] +#domain_name = 'pretzel_f' + +#non-conf domains +domain=[[0, 2*np.pi],[0, 3*np.pi]] # interval in x- and y-direction +domain_name = 'refined_square' +#use isotropic meshes (probably with a square domain) +# 4x8= 64 patches +#care for the transpose +ncells = np.array([[8, 8], + [8, 8]]) +#ncells = np.array([[8,8,16,8], +# [8,8,16,8], +# [8,8,16,8], +# [8,8,16,8]]) +# ncells = np.array([[8,8,8,8], +# [8,8,8,8], +# [8,8,8,8], +# [8,8,8,8]]) +# ncells = np.array([[8,8,16,8,8,8], +# [8,8,16,8,8,8], +# [8,8,16,8,8,8], +# [8,8,16,8,8,8]]) + +# ncells = np.array([[4, 4, 4], +# [4, 8, 4], +# [8, 16, 8], +# [4, 8, 4], +# [4, 4, 4]]) +# ncells = np.array([[4, 4, 4, 4], +# [4, 8, 8, 4], +# [8, 16, 16, 8], +# [4, 8, 8, 4], +# [4, 4, 4, 4]]).transpose() +# ncells = np.array([[4, 4, 4, 4], +# [4, 4, 4, 4], +# [4, 8, 8, 4], +# [8, 16, 16, 8], +# [8, 16, 16, 8], +# [4, 8, 8, 4], +# [4, 4, 4, 4], +# [4, 4, 4, 4]]) + + + + +cfl_max = 0.8 +E0_proj = 'P_geom' # 'P_geom' # projection used for initial E0 (B0 = 0 in all cases) +backend = 'pyccel-gcc' +project_sol = True # whether cP1 E_h is plotted instead of E_h +quad_param = 4 # multiplicative parameter for quadrature order in (bi)linear forms discretization +gamma_h = 0 # jump dissipation parameter (not used in paper) +conf_proj = 'GSP' # 'BSP' # type of conforming projection operators (averaging B-spline or Geometric-splines coefficients) +hide_plots = True +plot_divE = True +diag_dt = None # time interval between scalar diagnostics (if None, compute every time step) + +# Parameters that depend on test case +if test_case == 'E0_pulse_no_source': + + E0_type = 'pulse_2' # non-zero initial conditions + source_type = 'zero' # no current source + source_omega = None + final_time = 8 # wave transit time in domain is > 4 + dt_max = None + plot_source = False + + plot_a_lot = True + if plot_a_lot: + plot_time_ranges = [[[0, final_time], 0.1]] + else: + plot_time_ranges = [ + [[0, 2], 0.1], + [[final_time - 1, final_time], 0.1], + ] + + cb_min_sol = 0 + cb_max_sol = 5 + +# TODO: check +elif test_case == 'Issautier_like_source': + + E0_type = 'zero' # zero initial conditions + source_type = 'Il_pulse' + source_omega = None + final_time = 20 + plot_source = True + dt_max = None + if deg_s == [3] and final_time == 20: + + plot_time_ranges = [ + [[ 1.9, 2], 0.1], + [[ 4.9, 5], 0.1], + [[ 9.9, 10], 0.1], + [[19.9, 20], 0.1], + ] + + # plot_time_ranges = [ + # ] + # if nc_s == [8]: + # Nt_pp = 10 + + cb_min_sol = 0 # None + cb_max_sol = 0.3 # None + +# TODO: check +elif test_case == 'transient_to_harmonic': + + E0_type = 'th_sol' + source_type = 'elliptic_J' + source_omega = np.sqrt(50) # source time pulsation + plot_source = True + + source_period = 2 * np.pi / source_omega + nb_t_periods = 100 + Nt_pp = 20 + + dt_max = source_period / Nt_pp + final_time = nb_t_periods * source_period + + plot_time_ranges = [ + [[(nb_t_periods-2) * source_period, final_time], dt_max] + ] + + cb_min_sol = 0 + cb_max_sol = 1 + +else: + raise ValueError(test_case) + + +# projection used for the source J +if J_proj_case == 'P_geom': + source_proj = 'P_geom' + filter_source = False + +elif J_proj_case == 'P_L2': + source_proj = 'P_L2' + filter_source = False + +elif J_proj_case == 'tilde Pi_1': + source_proj = 'P_L2' + filter_source = True + +else: + raise ValueError(J_proj_case) + +case_dir = 'talk_wave_td_maxwell_' + test_case + '_J_proj=' + J_proj_case + '_qp{}'.format(quad_param) +if filter_source: + case_dir += '_Jfilter' +else: + case_dir += '_Jnofilter' +if not project_sol: + case_dir += '_E_noproj' + +if source_omega is not None: + case_dir += f'_omega={source_omega}' + +case_dir += f'_tend={final_time}' + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +common_diag_filename = './'+case_dir+'_diags.txt' + + +run_dir = get_run_dir(domain_name, sum(ncells), deg, source_type=source_type, conf_proj=conf_proj) +plot_dir = get_plot_dir(case_dir, run_dir) +diag_filename = plot_dir+'/'+diag_fn(source_type=source_type, source_proj=source_proj) + +# to save and load matrices +m_load_dir = get_mat_dir(domain_name, sum(ncells), deg, quad_param=quad_param) + +if E0_type == 'th_sol': + # initial E0 will be loaded from time-harmonic FEM solution + th_case_dir = 'maxwell_hom_eta=50' + th_sol_dir = get_sol_dir(th_case_dir, domain_name, sum(ncells), deg) + th_sol_filename = th_sol_dir+'/'+FEM_sol_fn(source_type=source_type, source_proj=source_proj) +else: + # no initial solution to load + th_sol_filename = '' + +params = { + 'nc' : ncells, + 'deg' : deg, + 'final_time' : final_time, + 'cfl_max' : cfl_max, + 'dt_max' : dt_max, + 'domain_name' : domain_name, + 'backend' : backend, + 'source_type' : source_type, + 'source_omega' : source_omega, + 'source_proj' : source_proj, + 'conf_proj' : conf_proj, + 'gamma_h' : gamma_h, + 'project_sol' : project_sol, + 'filter_source' : filter_source, + 'quad_param' : quad_param, + 'E0_type' : E0_type, + 'E0_proj' : E0_proj, + 'hide_plots' : hide_plots, + 'plot_dir' : plot_dir, + 'plot_time_ranges': plot_time_ranges, + 'plot_source' : plot_source, + 'plot_divE' : plot_divE, + 'diag_dt' : diag_dt, + 'cb_min_sol' : cb_min_sol, + 'cb_max_sol' : cb_max_sol, + 'm_load_dir' : m_load_dir, + 'th_sol_filename' : th_sol_filename, + 'domain_lims' : domain +} + +print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') +print(' Calling solve_td_maxwell_pbm() with params = {}'.format(params)) +print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') + +diags = solve_td_maxwell_pbm(**params) + +write_diags_to_file(diags, script_filename=__file__, diag_filename=diag_filename, params=params) +write_diags_to_file(diags, script_filename=__file__, diag_filename=common_diag_filename, params=params) + +time_count(t_stamp_full, msg='full program') From 26578f6b12d16351a281f27e55f53514815360ea Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Wed, 2 Aug 2023 14:43:26 +0200 Subject: [PATCH 12/88] add new tests and last example --- .../examples/h1_source_pbms_conga_2d.py | 4 +- .../examples/hcurl_source_pbms_conga_2d.py | 5 +- .../examples_nc/h1_source_pbms_nc.py | 278 ++++++++++++++++++ .../examples_nc/hcurl_eigen_pbms_dg.py | 2 +- .../examples_nc/hcurl_eigen_pbms_nc.py | 4 +- .../examples_nc/hcurl_eigen_testcase.py | 8 +- .../examples_nc/hcurl_source_pbms_nc.py | 23 +- .../examples_nc/hcurl_source_testcase.py | 1 - .../tests/test_feec_maxwell_multipatch_2d.py | 162 +++++++++- .../tests/test_feec_poisson_multipatch_2d.py | 30 +- 10 files changed, 493 insertions(+), 24 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index 92b2451ba..bbe83f525 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -23,7 +23,7 @@ from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE from psydac.feec.multipatch.utilities import time_count from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection @@ -165,7 +165,7 @@ def lift_u_bc(u_bc): # (not all the returned functions are useful here) N_diag = 200 method = 'conga' - f_scal, f_vect, u_bc, ph_ref, uh_ref, p_ex, u_ex, phi, grad_phi = get_source_and_solution( + f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, refsol_params=[N_diag, method, source_proj], ) diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index 55dd506e6..94f4992ba 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -24,7 +24,7 @@ from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE from psydac.feec.multipatch.utilities import time_count from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField @@ -227,9 +227,8 @@ def lift_u_bc(u_bc): print('getting the source and ref solution...') N_diag = 200 method = 'conga' - f_scal, f_vect, u_bc, ph_ref, uh_ref, p_ex, u_ex, phi, grad_phi = get_source_and_solution( + f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - refsol_params=[N_diag, method, source_proj], ) # compute approximate source f_h diff --git a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py new file mode 100644 index 000000000..9e81eb69d --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py @@ -0,0 +1,278 @@ +# coding: utf-8 + +from mpi4py import MPI + +import os +import numpy as np +from collections import OrderedDict + +from sympy import lambdify +from scipy.sparse.linalg import spsolve + +from sympde.expr.expr import LinearForm +from sympde.expr.expr import integral, Norm +from sympde.topology import Derham +from sympde.topology import element_of + + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.multipatch.api import discretize +from psydac.feec.pull_push import pull_2d_h1 + +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.api.postprocessing import OutputManager, PostProcessManager + +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField + +def solve_h1_source_pbm_nc( + nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2', source_type='manu_poisson', + eta=-10., mu=1., gamma_h=10., + plot_source=False, plot_dir=None, hide_plots=True +): + """ + solver for the problem: find u in H^1, such that + + A u = f on \Omega + u = u_bc on \partial \Omega + + where the operator + + A u := eta * u - mu * div grad u + + is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + + V0h --grad-> V1h -—curl-> V2h + + Examples: + + - Helmholtz equation with + eta = -omega**2 + mu = 1 + + - Poisson equation with Laplace operator L = A, + eta = 0 + mu = 1 + + :param nc: nb of cells per dimension, in each patch + :param deg: coordinate degree in each patch + :param gamma_h: jump penalization parameter + :param source_proj: approximation operator for the source, possible values are 'P_geom' or 'P_L2' + :param source_type: must be implemented in get_source_and_solution() + """ + + ncells = nc + degree = [deg,deg] + + # if backend_language is None: + # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: + # backend_language='numba' + # else: + # backend_language='python' + # print('[note: using '+backend_language+ ' backends in discretize functions]') + + print('---------------------------------------------------------------------------------------------------------') + print('Starting solve_h1_source_pbm function with: ') + print(' ncells = {}'.format(ncells)) + print(' degree = {}'.format(degree)) + print(' domain_name = {}'.format(domain_name)) + print(' source_proj = {}'.format(source_proj)) + print(' backend_language = {}'.format(backend_language)) + print('---------------------------------------------------------------------------------------------------------') + + print('building the multipatch domain...') + domain = build_multipatch_domain(domain_name=domain_name) + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + domain_h = discretize(domain, ncells=ncells_h) + + print('building the symbolic and discrete deRham sequences...') + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree) + + # multi-patch (broken) spaces + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + print('dim(V0h) = {}'.format(V0h.nbasis)) + print('dim(V1h) = {}'.format(V1h.nbasis)) + print('dim(V2h) = {}'.format(V2h.nbasis)) + + print('broken differential operators...') + # broken (patch-wise) differential operators + bD0, bD1 = derham_h.broken_derivatives_as_operators + bD0_m = bD0.to_sparse_matrix() + # bD1_m = bD1.to_sparse_matrix() + + print('building the discrete operators:') + print('commuting projection operators...') + nquads = [4*(d + 1) for d in degree] + P0, P1, P2 = derham_h.projectors(nquads=nquads) + + I0 = IdLinearOperator(V0h) + I0_m = I0.to_sparse_matrix() + + print('Hodge operators...') + # multi-patch (broken) linear operators / matrices + H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language) + H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) + + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = mass matrix of V0 + H0_m = H0.to_sparse_matrix() # = inverse mass matrix of V0 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = mass matrix of V1 + # H1_m = H1.to_sparse_matrix() # = inverse mass matrix of V1 + + print('conforming projection operators...') + # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) + cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + # cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + def lift_u_bc(u_bc): + if u_bc is not None: + print('lifting the boundary condition in V0h... [warning: Not Tested Yet!]') + # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs + u_bc = lambdify(domain.coordinates, u_bc) + u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) for m in mappings_list] + # it's a bit weird to apply P1 on the list of (pulled back) logical fields -- why not just apply it on u_bc ? + uh_bc = P0(u_bc_log) + ubc_c = uh_bc.coeffs.toarray() + # removing internal dofs (otherwise ubc_c may already be a very good approximation of uh_c ...) + ubc_c = ubc_c - cP0_m.dot(ubc_c) + else: + ubc_c = None + return ubc_c + + # Conga (projection-based) stiffness matrices: + # div grad: + pre_DG_m = - bD0_m.transpose() @ dH1_m @ bD0_m + + # jump penalization: + jump_penal_m = I0_m - cP0_m + JP0_m = jump_penal_m.transpose() * dH0_m * jump_penal_m + + pre_A_m = cP0_m.transpose() @ ( eta * dH0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) + A_m = pre_A_m @ cP0_m + gamma_h * JP0_m + + print('getting the source and ref solution...') + # (not all the returned functions are useful here) + N_diag = 200 + method = 'conga' + f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( + source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, + refsol_params=[N_diag, method, source_proj], + ) + + # compute approximate source f_h + b_c = f_c = None + if source_proj == 'P_geom': + print('projecting the source with commuting projection P0...') + f = lambdify(domain.coordinates, f_scal) + f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] + f_h = P0(f_log) + f_c = f_h.coeffs.toarray() + b_c = dH0_m.dot(f_c) + + elif source_proj == 'P_L2': + print('projecting the source with L2 projection...') + v = element_of(V0h.symbolic_space, name='v') + expr = f_scal * v + l = LinearForm(v, integral(domain, expr)) + lh = discretize(l, domain_h, V0h) + b = lh.assemble() + b_c = b.toarray() + if plot_source: + f_c = H0_m.dot(b_c) + else: + raise ValueError(source_proj) + + if plot_source: + plot_field(numpy_coeffs=f_c, Vh=V0h, space_kind='h1', domain=domain, title='f_h with P = '+source_proj, filename=plot_dir+'/fh_'+source_proj+'.png', hide_plot=hide_plots) + + ubc_c = lift_u_bc(u_bc) + + if ubc_c is not None: + # modified source for the homogeneous pbm + print('modifying the source with lifted bc solution...') + b_c = b_c - pre_A_m.dot(ubc_c) + + # direct solve with scipy spsolve + print('solving source problem with scipy.spsolve...') + uh_c = spsolve(A_m, b_c) + + # project the homogeneous solution on the conforming problem space + print('projecting the homogeneous solution on the conforming problem space...') + uh_c = cP0_m.dot(uh_c) + + if ubc_c is not None: + # adding the lifted boundary condition + print('adding the lifted boundary condition...') + uh_c += ubc_c + + print('getting and plotting the FEM solution from numpy coefs array...') + title = r'solution $\phi_h$ (amplitude)' + params_str = 'eta={}_mu={}_gamma_h={}'.format(eta, mu, gamma_h) + plot_field(numpy_coeffs=uh_c, Vh=V0h, space_kind='h1', domain=domain, title=title, filename=plot_dir+params_str+'_phi_h.png', hide_plot=hide_plots) + + + if u_ex: + u = element_of(V0h.symbolic_space, name='u') + l2norm = Norm(u - u_ex, domain, kind='l2') + l2norm_h = discretize(l2norm, domain_h, V0h) + uh_c = array_to_psydac(uh_c, V0h.vector_space) + l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) + return l2_error + +if __name__ == '__main__': + + t_stamp_full = time_count() + + quick_run = True + # quick_run = False + + omega = np.sqrt(170) # source + roundoff = 1e4 + eta = int(-omega**2 * roundoff)/roundoff + # print(eta) + # source_type = 'elliptic_J' + source_type = 'manu_poisson' + + # if quick_run: + # domain_name = 'curved_L_shape' + # nc = 4 + # deg = 2 + # else: + # nc = 8 + # deg = 4 + + domain_name = 'pretzel_f' + # domain_name = 'curved_L_shape' + nc = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) + deg = 2 + + # nc = 2 + # deg = 2 + + run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) + solve_h1_source_pbm_nc( + nc=nc, deg=deg, + eta=eta, + mu=1, #1, + domain_name=domain_name, + source_type=source_type, + backend_language='pyccel-gcc', + plot_source=True, + plot_dir='./plots/h1_tests_source_february/'+run_dir, + hide_plots=True, + ) + + time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py index c2e986a5c..682ff3226 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py @@ -30,7 +30,7 @@ from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_multipatch_dg(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, +def hcurl_solve_eigen_pbm_dg(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, plot_dir=None, hide_plots=True, m_load_dir="",): diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py index 7efa56b15..a99d601a2 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -33,9 +33,9 @@ from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_multipatch_nc(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, +def hcurl_solve_eigen_pbm_nc(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, - plot_dir=None, hide_plots=True, m_load_dir="",): + plot_dir=None, hide_plots=True, m_load_dir=None,): diags = {} diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py index e2ea765c9..853d33816 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py @@ -2,8 +2,8 @@ import numpy as np -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_multipatch_nc -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_multipatch_dg +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_nc +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_dg from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file @@ -235,7 +235,7 @@ # - we look for nb_eigs_solve eigenvalues close to sigma (skip zero eigenvalues if skip_zero_eigs==True) # - we plot nb_eigs_plot eigenvectors if method == 'feec': - diags, eigenvalues = hcurl_solve_eigen_pbm_multipatch_nc( + diags, eigenvalues = hcurl_solve_eigen_pbm_nc( ncells=ncells, degree=degree, gamma_h=gamma_h, generalized_pbm=generalized_pbm, @@ -253,7 +253,7 @@ m_load_dir=m_load_dir, ) elif method == 'dg': - diags, eigenvalues = hcurl_solve_eigen_pbm_multipatch_dg( + diags, eigenvalues = hcurl_solve_eigen_pbm_dg( ncells=ncells, degree=degree, gamma_h=gamma_h, generalized_pbm=generalized_pbm, diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py index bbb35ce0f..7f6b314cf 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py @@ -29,6 +29,7 @@ from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -39,8 +40,8 @@ def solve_hcurl_source_pbm_nc( project_sol=False, plot_source=False, plot_dir=None, hide_plots=True, skip_plot_titles=False, cb_min_sol=None, cb_max_sol=None, - m_load_dir="", sol_filename="", sol_ref_filename="", - ref_nc=None, ref_deg=None, + m_load_dir=None, sol_filename="", sol_ref_filename="", + ref_nc=None, ref_deg=None, test=False ): """ solver for the problem: find u in H(curl), such that @@ -246,9 +247,14 @@ def lift_u_bc(u_bc): t_stamp = time_count(t_stamp) print() print(' -- getting source --') - f_vect, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( + if source_type == 'manu_maxwell': + f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - ) + ) + else: + f_vect, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( + source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, + ) # compute approximate source f_h t_stamp = time_count(t_stamp) tilde_f_c = f_c = None @@ -352,5 +358,14 @@ def lift_u_bc(u_bc): time_count(t_stamp) + if test: + u = element_of(V1h.symbolic_space, name='u') + l2norm = Norm(Matrix([u[0] - u_ex[0],u[1] - u_ex[1]]), domain, kind='l2') + l2norm_h = discretize(l2norm, domain_h, V1h) + uh_c = array_to_psydac(uh_c, V1h.vector_space) + l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) + print(l2_error) + return l2_error + return diags \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py index fe283a1b7..23ef31fb9 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py @@ -16,7 +16,6 @@ test_case = 'maxwell_hom_eta=170' # used in paper # test_case = 'maxwell_inhom' # used in paper -compute_ref_sol = False # (not needed for inhomogeneous test-case, as exact solution is known) # # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- diff --git a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py index 51746c218..4d88e519f 100644 --- a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py @@ -3,6 +3,11 @@ import numpy as np from psydac.feec.multipatch.examples.hcurl_source_pbms_conga_2d import solve_hcurl_source_pbm +from psydac.feec.multipatch.examples_nc.hcurl_source_pbms_nc import solve_hcurl_source_pbm_nc + +from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import hcurl_solve_eigen_pbm +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_nc +from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_dg def test_time_harmonic_maxwell_pretzel_f(): nc,deg = 10,2 @@ -24,6 +29,161 @@ def test_time_harmonic_maxwell_pretzel_f(): assert abs(l2_error - 0.06246693595198972)<1e-10 +def test_time_harmonic_maxwell_pretzel_f_nc(): + deg = 2 + nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10, 20, 20, 20, 10, 10]) + + source_type = 'manu_maxwell' + domain_name = 'pretzel_f' + source_proj = 'tilde_Pi' + + omega = np.sqrt(170) # source + roundoff = 1e4 + eta = int(-omega**2 * roundoff)/roundoff + + l2_error = solve_hcurl_source_pbm_nc( + nc=nc, deg=deg, + eta=eta, + nu=0, + mu=1, + domain_name=domain_name, + source_type=source_type, + source_proj=source_proj, + plot_dir='./plots/th_maxell_nc', + backend_language='pyccel-gcc', + test=True) + + assert abs(l2_error - 0.04753982587323614)<1e-10 + +def test_maxwell_eigen_curved_L_shape(): + domain_name = 'curved_L_shape' + + nc = 10 + deg = 2 + + ref_sigmas = [ + 0.181857115231E+01, + 0.349057623279E+01, + 0.100656015004E+02, + 0.101118862307E+02, + 0.124355372484E+02, + ] + sigma = 7 + nb_eigs_solve = 7 + nb_eigs_plot = 7 + skip_eigs_threshold = 1e-7 + + eigenvalues, eigenvectors = hcurl_solve_eigen_pbm( + nc=nc, deg=deg, + gamma_h=0, + nu=0, + mu=1, + sigma=sigma, + skip_eigs_threshold=skip_eigs_threshold, + nb_eigs=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + domain_name=domain_name, + backend_language='pyccel-gcc', + plot_dir='./plots/eigen_maxell', + ) + + error = 0 + n_errs = min(len(ref_sigmas), len(eigenvalues)) + for k in range(n_errs): + error += (eigenvalues[k]-ref_sigmas[k])**2 + error = np.sqrt(error) + + assert abs(error - 0.023413963252245817)<1e-10 + +def test_maxwell_eigen_curved_L_shape_nc(): + domain_name = 'curved_L_shape' + domain=[[1, 3],[0, np.pi/4]] + + ncells = np.array([[None, 10], + [10, 20]]) + + degree = [2, 2] + + ref_sigmas = [ + 0.181857115231E+01, + 0.349057623279E+01, + 0.100656015004E+02, + 0.101118862307E+02, + 0.124355372484E+02, + ] + sigma = 7 + nb_eigs_solve = 7 + nb_eigs_plot = 7 + skip_eigs_threshold = 1e-7 + + diags, eigenvalues = hcurl_solve_eigen_pbm_nc( + ncells=ncells, degree=degree, + gamma_h=0, + generalized_pbm=True, + nu=0, + mu=1, + sigma=sigma, + ref_sigmas=ref_sigmas, + skip_eigs_threshold=skip_eigs_threshold, + nb_eigs_solve=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + domain_name=domain_name, domain=domain, + backend_language='pyccel-gcc', + plot_dir='./plots/eigen_maxell_nc', + ) + + error = 0 + n_errs = min(len(ref_sigmas), len(eigenvalues)) + for k in range(n_errs): + error += (eigenvalues[k]-ref_sigmas[k])**2 + error = np.sqrt(error) + + assert abs(error - 0.004289103786542442)<1e-10 + +def test_maxwell_eigen_curved_L_shape_dg(): + domain_name = 'curved_L_shape' + domain=[[1, 3],[0, np.pi/4]] + + ncells = np.array([[None, 10], + [10, 20]]) + + degree = [2, 2] + + ref_sigmas = [ + 0.181857115231E+01, + 0.349057623279E+01, + 0.100656015004E+02, + 0.101118862307E+02, + 0.124355372484E+02, + ] + sigma = 7 + nb_eigs_solve = 7 + nb_eigs_plot = 7 + skip_eigs_threshold = 1e-7 + + diags, eigenvalues = hcurl_solve_eigen_pbm_dg( + ncells=ncells, degree=degree, + gamma_h=0, + generalized_pbm=True, + nu=0, + mu=1, + sigma=sigma, + ref_sigmas=ref_sigmas, + skip_eigs_threshold=skip_eigs_threshold, + nb_eigs_solve=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + domain_name=domain_name, domain=domain, + backend_language='pyccel-gcc', + plot_dir='./plots/eigen_maxell_dg', + ) + + error = 0 + n_errs = min(len(ref_sigmas), len(eigenvalues)) + for k in range(n_errs): + error += (eigenvalues[k]-ref_sigmas[k])**2 + error = np.sqrt(error) + + assert abs(error - 0.004208158031148591)<1e-10 #============================================================================== # CLEAN UP SYMPY NAMESPACE @@ -35,4 +195,4 @@ def teardown_module(): def teardown_function(): from sympy.core import cache - cache.clear_cache() + cache.clear_cache() \ No newline at end of file diff --git a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py index 7fd6eb040..c18cfe0a2 100644 --- a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py @@ -1,4 +1,7 @@ +import numpy as np + from psydac.feec.multipatch.examples.h1_source_pbms_conga_2d import solve_h1_source_pbm +from psydac.feec.multipatch.examples_nc.h1_source_pbms_nc import solve_h1_source_pbm_nc def test_poisson_pretzel_f(): @@ -16,9 +19,27 @@ def test_poisson_pretzel_f(): backend_language='pyccel-gcc', plot_source=False, plot_dir='./plots/h1_tests_source_february/'+run_dir) - print(l2_error) - assert abs(l2_error-8.054935880021907e-05)<1e-10 + assert abs(l2_error-0.11860734907095004)<1e-10 + +def test_poisson_pretzel_f_nc(): + + source_type = 'manu_poisson_2' + domain_name = 'pretzel_f' + nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10, 20, 20, 20, 10, 10]) + deg = 2 + run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) + l2_error = solve_h1_source_pbm_nc( + nc=nc, deg=deg, + eta=0, + mu=1, + domain_name=domain_name, + source_type=source_type, + backend_language='pyccel-gcc', + plot_source=False, + plot_dir='./plots/h1_tests_source_february/'+run_dir) + + assert abs(l2_error-0.04324704991715671)<1e-10 #============================================================================== # CLEAN UP SYMPY NAMESPACE #============================================================================== @@ -29,7 +50,4 @@ def teardown_module(): def teardown_function(): from sympy.core import cache - cache.clear_cache() - -if __name__ == '__main__': - test_poisson_pretzel_f() + cache.clear_cache() \ No newline at end of file From 7fbaecf0e53dba1c0d4f06b3abd5de3058475eff Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Wed, 20 Sep 2023 11:48:46 +0200 Subject: [PATCH 13/88] make Codacy happy --- psydac/feec/multipatch/examples/ppc_test_cases.py | 14 ++++++++------ psydac/feec/multipatch/examples_nc/__init__.py | 0 .../multipatch/examples_nc/hcurl_eigen_pbms_dg.py | 4 ++-- .../multipatch/examples_nc/hcurl_eigen_pbms_nc.py | 4 ++-- .../multipatch/examples_nc/hcurl_eigen_testcase.py | 10 +++++++--- .../examples_nc/timedomain_maxwell_nc.py | 6 +++--- psydac/feec/multipatch/utils_conga_2d.py | 2 +- 7 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/__init__.py diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index ddfd6659d..339d1f015 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -102,7 +102,12 @@ def get_Gaussian_beam(x_0, y_0, domain=None): return Tuple(E, 0), B def get_Gaussian_beam2(x_0, y_0, domain=None): - # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v + """ + Gaussian beam + Beam inciding from the left, centered and normal to wall: + x: axial normalized distance to the beam's focus + y: radial normalized distance to the center axis of the beam + """ x,y = domain.coordinates @@ -113,11 +118,8 @@ def get_Gaussian_beam2(x_0, y_0, domain=None): t = [(x-x0)*cos(theta) - (y - y0) * sin(theta), (x-x0)*sin(theta) + (y-y0) * cos(theta)] - ## Gaussian beam - '''Beam inciding from the left, centered and normal to wall: - x: axial normalized distance to the beam's focus - y: radial normalized distance to the center axis of the beam - ''' + + EW0 = 1.0 # amplitude at the waist k0 = 2 * pi # free-space wavenumber diff --git a/psydac/feec/multipatch/examples_nc/__init__.py b/psydac/feec/multipatch/examples_nc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py index 682ff3226..0fba6297d 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py @@ -30,8 +30,8 @@ from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_dg(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, - generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, +def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), domain=([0, np.pi],[0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, plot_dir=None, hide_plots=True, m_load_dir="",): diags = {} diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py index a99d601a2..77d7b1db9 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -33,8 +33,8 @@ from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_nc(ncells=[[2,2], [2,2]], degree=[3,3], domain=[[0, np.pi],[0, np.pi]], domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, - generalized_pbm=False, sigma=None, ref_sigmas=[], nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, +def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), domain=([0, np.pi],[0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, plot_dir=None, hide_plots=True, m_load_dir=None,): diags = {} diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py index 853d33816..6e4defabf 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py @@ -116,9 +116,13 @@ case_dir = 'eigenpbm_'+operator+'_'+method ref_case_dir = case_dir -cb_min_sol = None -cb_max_sol = None - +ref_sigmas = None +sigma = None +nb_eigs_solve = None +nb_eigs_plot = None +skip_eigs_threshold = None +diags = None +eigenvalues = None if domain_name == 'refined_square': assert domain == [[0, np.pi],[0, np.pi]] diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py index fb662b54c..0fd1c0e11 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py @@ -484,9 +484,6 @@ def is_plotting_time(nt, *, dt=dt, Nt=Nt, plot_time_ranges=plot_time_ranges): # f0_c = np.zeros(V1h.nbasis) - def source_enveloppe(tau): - return 1 - if source_omega is not None: f0_harmonic = f0 f0 = None @@ -494,6 +491,9 @@ def source_enveloppe(tau): # use source enveloppe for smooth transition from 0 to 1 def source_enveloppe(tau): return (special.erf((tau/25)-2)-special.erf(-2))/2 + else: + def source_enveloppe(tau): + return 1 t_stamp = time_count(t_stamp) tilde_f0_c = f0_c = None diff --git a/psydac/feec/multipatch/utils_conga_2d.py b/psydac/feec/multipatch/utils_conga_2d.py index 8be53b4f8..23046ed32 100644 --- a/psydac/feec/multipatch/utils_conga_2d.py +++ b/psydac/feec/multipatch/utils_conga_2d.py @@ -192,7 +192,7 @@ def get_Vh_diags_for(v=None, v_ref=None, M_m=None, print_diags=True, msg='error return diags -def write_diags_to_file(diags, script_filename, diag_filename, params={}): +def write_diags_to_file(diags, script_filename, diag_filename, params=None): print(' -- writing diags to file {} --'.format(diag_filename)) if not os.path.exists(diag_filename): open(diag_filename, 'w') From b52041f793759f40e0d6534abc65d969f07bb016 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 27 Nov 2023 16:24:44 +0100 Subject: [PATCH 14/88] add pml example --- .../examples_nc/timedomain_maxwell_pml.py | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py new file mode 100644 index 000000000..4d0f730b5 --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py @@ -0,0 +1,352 @@ +from pytest import param +from mpi4py import MPI + +import os +import numpy as np +import scipy as sp +from collections import OrderedDict +import matplotlib.pyplot as plt + +from sympy import lambdify, Matrix + +from scipy.sparse.linalg import spsolve +from scipy import special + +from sympde.calculus import dot +from sympde.topology import element_of +from sympde.expr.expr import LinearForm +from sympde.expr.expr import integral, Norm +from sympde.topology import Derham + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv +from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection, construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain + +from sympde.calculus import grad, dot, curl, cross +from sympde.topology import NormalVector +from sympde.expr.expr import BilinearForm +from sympde.topology import elements_of +from sympde import Tuple + +from psydac.api.postprocessing import OutputManager, PostProcessManager +from sympy.functions.special.error_functions import erf + +def run_sim(): + ## Minimal example for a PML implementation of the Time-Domain Maxwells equation + ncells = [16, 16, 16, 16] + degree = [3,3] + plot_dir = "plots/PML/further" + final_time = 3 + + domain = build_multipatch_domain(domain_name='square_4') + ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + domain_h = discretize(domain, ncells=ncells_h) + derham_h = discretize(derham, domain_h, degree=degree) + + nquads = [4*(d + 1) for d in degree] + P0, P1, P2 = derham_h.projectors(nquads=nquads) + + + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + + I1 = IdLinearOperator(V1h) + I1_m = I1.to_sparse_matrix() + + backend = 'pyccel-gcc' + + H0 = HodgeOperator(V0h, domain_h) + H1 = HodgeOperator(V1h, domain_h) + H2 = HodgeOperator(V2h, domain_h) + + dH0_m = H0.to_sparse_matrix() + H0_m = H0.get_dual_Hodge_sparse_matrix() + dH1_m = H1.to_sparse_matrix() + H1_m = H1.get_dual_Hodge_sparse_matrix() + dH2_m = H2.to_sparse_matrix() + H2_m = H2.get_dual_Hodge_sparse_matrix() + cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + + ## PML + u, v = elements_of(derham.V1, names='u, v') + x,y = domain.coordinates + + u1 = dot(Tuple(1,0),u) + u2 = dot(Tuple(0,1),u) + v1 = dot(Tuple(1,0),v) + v2 = dot(Tuple(0,1),v) + + def heaviside(x_direction, xmin, xmax, delta, sign, domain): + x,y = domain.coordinates + + if sign == -1: + d = xmax - delta + else: + d = xmin + delta + + if x_direction == True: + return 1/2*(erf(-sign*(x-d) *1000)+1) + else: + return 1/2*(erf(-sign*(y-d) *1000)+1) + + def parabola(x_direction, xmin, xmax, delta, sign, domain): + x,y = domain.coordinates + + if sign == -1: + d = xmax - delta + else: + d = xmin + delta + + if x_direction == True: + return ((x - d)/delta)**2 + else: + return ((y - d)/delta)**2 + + def sigma_fun(x, xmin, xmax, delta, sign, sigma_m, domain): + return sigma_m * heaviside(x, xmin, xmax, delta, sign, domain) * parabola(x, xmin, xmax, delta, sign, domain) + + def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): + return sigma_fun(x, xmin, xmax, delta, 1, sigma_m, domain) + sigma_fun(x, xmin, xmax, delta, -1, sigma_m, domain) + + delta = np.pi/8 + xmin = 0 + xmax = np.pi + ymin = 0 + ymax = np.pi + sigma_0 = 20 + + sigma_x = sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) + sigma_y = sigma_fun_sym(False, ymin, ymax, delta, sigma_0, domain) + + mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) + massh = discretize(mass, domain_h, [V1h, V1h]) + M = massh.assemble().tosparse() + + u, v = elements_of(derham.V2, names='u, v') + mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) + massh = discretize(mass, domain_h, [V2h, V2h]) + M2 = massh.assemble().tosparse() + #### + + ### Silvermueller ABC + # u, v = elements_of(derham.V1, names='u, v') + # nn = NormalVector('nn') + # boundary = domain.boundary + # expr_b = cross(nn, u)*cross(nn, v) + + # a = BilinearForm((u,v), integral(boundary, expr_b)) + # ah = discretize(a, domain_h, [V1h, V1h], backend=PSYDAC_BACKENDS[backend],) + # A_eps = ah.assemble().tosparse() + ### + + + # conf_proj = GSP + K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) + cP0_m = K0_inv @ cP0_m @ K0 + K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) + cP1_m = K1_inv @ cP1_m @ K1 + + bD0, bD1 = derham_h.broken_derivatives_as_operators + bD0_m = bD0.to_sparse_matrix() + bD1_m = bD1.to_sparse_matrix() + + + dH1_m = dH1_m.tocsr() + H2_m = H2_m.tocsr() + cP1_m = cP1_m.tocsr() + bD1_m = bD1_m.tocsr() + + C_m = bD1_m @ cP1_m + dC_m = dH1_m @ C_m.transpose() @ H2_m + + + div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m + + jump_penal_m = I1_m - cP1_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m + + f0_c = np.zeros(V1h.nbasis) + + + E0, B0 = get_Gaussian_beam(x_0=3.14/2 , y_0=1, domain=domain) + E0_h = P1_phys(E0, P1, domain, mappings_list) + E_c = E0_h.coeffs.toarray() + + B0_h = P2_phys(B0, P2, domain, mappings_list) + B_c = B0_h.coeffs.toarray() + + E_c = dC_m @ B_c + B_c[:] = 0 + + OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') + OM1.add_spaces(V1h=V1h) + OM1.export_space_info() + + OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM2.add_spaces(V2h=V2h) + OM2.export_space_info() + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=0 , ts=0) + OM1.export_fields(Eh=Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=0 , ts=0) + OM2.export_fields(Bh=Bh) + + dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=0.8, dt_max=None) + Nt = int(np.ceil(final_time/dt)) + dt = final_time / Nt + Epml = sp.sparse.linalg.spsolve(H1_m, M) + Bpml = sp.sparse.linalg.spsolve(H2_m, M2) + #H1A = H1_m + dt * A_eps + #A_eps = sp.sparse.linalg.spsolve(H1A, H1_m) + + f_c = np.copy(f0_c) + for nt in range(Nt): + print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) + + # 1/2 faraday: Bn -> Bn+1/2 + B_c[:] -= dt/2*Bpml*B_c + (dt/2) * C_m @ E_c + + E_c[:] += -dt*Epml @ E_c + dt * (dC_m @ B_c - f_c) + #E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) + + B_c[:] -= dt/2*Bpml*B_c + (dt/2) * C_m @ E_c + + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=nt*dt, ts=nt) + OM1.export_fields(Eh = Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=nt*dt, ts=nt) + OM2.export_fields(Bh=Bh) + + OM1.close() + + print("Do some PP") + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) + PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Eh' ) + PM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) + PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Bh' ) + PM.close() + + +#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): +def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): + """ + Compute a stable time step size based on the maximum CFL parameter in the + domain. To this end we estimate the operator norm of + + `dC_m @ C_m: V1h -> V1h`, + + find the largest stable time step compatible with Strang splitting, and + rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the + scheme exactly at its stability limit, which is not safe because of the + unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. + + Optionally the user can provide a maximum time step size in order to + properly resolve some time scales of interest (e.g. a time-dependent + current source). + + Parameters + ---------- + C_m : scipy.sparse.spmatrix + Matrix of the Curl operator. + + dC_m : scipy.sparse.spmatrix + Matrix of the dual Curl operator. + + cfl_max : float + Maximum Courant parameter in the domain, intended as a stability + parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. + + dt_max : float, optional + If not None, restrict the computed dt by this value in order to + properly resolve time scales of interest. Must be > 0. + + Returns + ------- + dt : float + Largest stable dt which satisfies the provided constraints. + + """ + + print (" .. compute_stable_dt by estimating the operator norm of ") + print (" .. dC_m @ C_m: V1h -> V1h ") + print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) + + if not (0 < cfl_max < 1): + print(' ****** ****** ****** ****** ****** ****** ') + print(' WARNING !!! cfl = {} '.format(cfl)) + print(' ****** ****** ****** ****** ****** ****** ') + + def vect_norm_2 (vv): + return np.sqrt(np.dot(vv,vv)) + + t_stamp = time_count() + vv = np.random.random(C_m.shape[1]) + norm_vv = vect_norm_2(vv) + max_ncfl = 500 + ncfl = 0 + spectral_rho = 1 + conv = False + CC_m = dC_m @ C_m + + while not( conv or ncfl > max_ncfl ): + + vv[:] = (1./norm_vv)*vv + ncfl += 1 + vv[:] = CC_m.dot(vv) + + norm_vv = vect_norm_2(vv) + old_spectral_rho = spectral_rho + spectral_rho = vect_norm_2(vv) # approximation + conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 + print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) + t_stamp = time_count(t_stamp) + + norm_op = np.sqrt(spectral_rho) + c_dt_max = 2./norm_op + + light_c = 1 + dt = cfl_max * c_dt_max / light_c + + if dt_max is not None: + dt = min(dt, dt_max) + + print( " Time step dt computed for Maxwell solver:") + print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") + print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") + print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") + + return dt + + +if __name__ == '__main__': + run_sim() \ No newline at end of file From 7f5be3227fc37a93e2aa6f750b29db51b2a65499 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 27 Nov 2023 16:46:45 +0100 Subject: [PATCH 15/88] update non_matching operators and add proposal of tests --- .../multipatch/multipatch_domain_utilities.py | 148 ++ .../feec/multipatch/non_matching_operators.py | 1618 ++++++++++++++--- .../test_feec_conf_projectors_cart_2d.py | 316 ++++ psydac/feec/multipatch/utils_conga_2d.py | 28 + 4 files changed, 1813 insertions(+), 297 deletions(-) create mode 100644 psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index d26027b1e..e19b74a33 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -123,6 +123,31 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): interfaces = [ [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1] ] + elif domain_name == 'square_4': + # C D + # A B + + A = Square('A', bounds1=(0, np.pi/2), bounds2=(0, np.pi/2)) + B = Square('B', bounds1=(np.pi/2, np.pi), bounds2=(0, np.pi/2)) + C = Square('C', bounds1=(0, np.pi/2), bounds2=(np.pi/2, np.pi)) + D = Square('D', bounds1=(np.pi/2, np.pi), bounds2=(np.pi/2, np.pi)) + M1 = IdentityMapping('M1', dim=2) + M2 = IdentityMapping('M2', dim=2) + M3 = IdentityMapping('M3', dim=2) + M4 = IdentityMapping('M4', dim=2) + A = M1(A) + B = M2(B) + C = M3(C) + D = M4(D) + + patches = [A,B,C,D] + + interfaces = [ + [A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], + [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], + [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], + [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1], + ] elif domain_name == 'square_6': @@ -604,6 +629,129 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): return domain +def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np.pi, y_min=0, y_max=np.pi, perio=[True,True], ncells=[4,4], comm=None, F_name='Identity'): + """ + Create a 2D multipatch rectangle domain with the prescribed number of patch in each direction. + (copied from Valentin's code) + + Parameters + ---------- + nb_patch_x: + number of patch in x direction + + nb_patch_y: + number of patch in y direction + + x_min: + x cordinate for the left boundary of the domain + + x_max: + x cordinate for the right boundary of the domain + + y_min: + y cordinate for the bottom boundary of the domain + + y_max: + y cordinate for the top boundary of the domain + + perio: list of + periodicity of the domain in each direction + + F_name: + name of a (global) mapping to apply to all the patches + + Returns + ------- + domain : + The symbolic multipatch domain + """ + + x_diff=x_max-x_min + y_diff=y_max-y_min + + list_Omega = [[Square('OmegaLog_'+str(i)+'_'+str(j), + bounds1 = (x_min+i/nb_patch_x*x_diff,x_min+(i+1)/nb_patch_x*x_diff), + bounds2 = (y_min+j/nb_patch_y*y_diff,y_min+(j+1)/nb_patch_y*y_diff)) for j in range(nb_patch_y)] for i in range(nb_patch_x)] + + if F_name == 'Identity': + F = lambda name: IdentityMapping(name, 2) + elif F_name == 'Collela': + F = lambda name: CollelaMapping2D(name, eps=0.5) + else: + raise NotImplementedError(F_name) + + list_mapping = [[F('M_'+str(i)+'_'+str(j)) for j in range(nb_patch_y)] for i in range(nb_patch_x)] + + list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range(nb_patch_y)] for i in range(nb_patch_x)] + + patches = [] + + for i in range(nb_patch_x): + patches.extend(list_domain[i]) + + # domain = union([domain_1, domain_2, domain_3, domain_4, domain_5, domain_6], name = 'domain') + + # patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6] + + # domain = union(flat_list, name='domain') + + interfaces = [] + #interfaces in x + list_right_bnd = [] + list_left_bnd = [] + list_top_bnd = [] + list_bottom_bnd1 = [] + list_bottom_bnd2 = [] + for j in range(nb_patch_y): + interfaces.extend([[list_domain[i][j].get_boundary(axis=0, ext=+1), list_domain[i+1][j].get_boundary(axis=0, ext=-1), 1] for i in range(nb_patch_x-1)]) + #periodic boundaries + if perio[0]: + interfaces.append([list_domain[nb_patch_x-1][j].get_boundary(axis=0, ext=+1), list_domain[0][j].get_boundary(axis=0, ext=-1), 1]) + else: + list_right_bnd.append(list_domain[nb_patch_x-1][j].get_boundary(axis=0, ext=+1)) + list_left_bnd.append(list_domain[0][j].get_boundary(axis=0, ext=-1)) + + + #interfaces in y + for i in range(nb_patch_x): + interfaces.extend([[list_domain[i][j].get_boundary(axis=1, ext=+1), list_domain[i][j+1].get_boundary(axis=1, ext=-1), 1] for j in range(nb_patch_y-1)]) + #periodic boundariesnb_patch_y-1 + if perio[1]: + interfaces.append([list_domain[i][nb_patch_y-1].get_boundary(axis=1, ext=+1), list_domain[i][0].get_boundary(axis=1, ext=-1), 1]) + else: + list_top_bnd.append(list_domain[i][nb_patch_y-1].get_boundary(axis=1, ext=+1)) + if i0: + bottom_bnd2 = union_bnd(list_bottom_bnd2) + else : + bottom_bnd2 = None + if nb_patch_x>1 and nb_patch_y>1: + # domain = set_interfaces(domain, interfaces) + domain_h = discretize(domain, ncells=ncells, comm=comm) + else: + domain_h = discretize(domain, ncells=ncells, periodic=perio, comm=comm) + + return domain, domain_h, [right_bnd, left_bnd, top_bnd, bottom_bnd1, bottom_bnd2] + + + def get_ref_eigenvalues(domain_name, operator): # return ref_eigenvalues for the given operator and domain # and 'sigma' value, around which discrete eigenvalues will be searched by eigenvalue solver such as eigsh diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index 71fb7ca87..be10f036f 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -7,6 +7,7 @@ from sympde.topology import Derham, Square from sympde.topology import IdentityMapping from sympde.topology import Boundary, Interface, Union +from scipy.sparse.linalg import norm as sp_norm from psydac.feec.multipatch.utilities import time_count from psydac.linalg.utilities import array_to_psydac @@ -20,8 +21,9 @@ from sympde.topology import IdentityMapping, PolarMapping from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain -# to compare -from psydac.feec.multipatch.operators import ConformingProjection_V1 +from psydac.utilities.quadratures import gauss_legendre +from psydac.core.bsplines import breakpoints, quadrature_grid, basis_ders_on_quad_grid, find_spans, elements_spans +from copy import deepcopy def get_patch_index_from_face(domain, face): @@ -132,55 +134,521 @@ def construct_extension_operator_1D(domain, codomain): return csr_matrix(P) # kronecker of 1 term... +# Legacy code +# def construct_V0_conforming_projection(V0h, hom_bc=None): +# dim_tot = V0h.nbasis +# domain = V0h.symbolic_space.domain +# ndim = 2 +# n_components = 1 +# n_patches = len(domain) + +# l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) +# for k in range(n_patches): +# Vk = V0h.spaces[k] +# # T is a TensorFemSpace and S is a 1D SplineSpace +# shapes = [S.nbasis for S in Vk.spaces] +# l2g.set_patch_shapes(k, shapes) + +# Proj = sparse_eye(dim_tot, format="lil") +# Proj_vertex = sparse_eye(dim_tot, format="lil") + +# Interfaces = domain.interfaces +# if isinstance(Interfaces, Interface): +# Interfaces = (Interfaces, ) + +# corner_indices = set() +# stored_indices = [] +# corners = get_corners(domain, False) +# for (bd,co) in corners.items(): + +# c = 0 +# indices = set() +# for patch in co: +# c += 1 +# multi_index_i = [None]*ndim + +# nbasis0 = V0h.spaces[patch].spaces[co[patch][0]].nbasis-1 +# nbasis1 = V0h.spaces[patch].spaces[co[patch][1]].nbasis-1 + +# multi_index_i[0] = 0 if co[patch][0] == 0 else nbasis0 +# multi_index_i[1] = 0 if co[patch][1] == 0 else nbasis1 +# ig = l2g.get_index(patch, 0, multi_index_i) +# indices.add(ig) + + +# corner_indices.add(ig) + +# stored_indices.append(indices) +# for j in indices: +# for i in indices: +# Proj_vertex[j,i] = 1/c + +# # First make all interfaces conforming +# # We also touch the vertices here, but change them later again +# for I in Interfaces: + +# axis = I.axis +# direction = I.ornt + +# k_minus = get_patch_index_from_face(domain, I.minus) +# k_plus = get_patch_index_from_face(domain, I.plus) +# # logical directions normal to interface +# minus_axis, plus_axis = I.minus.axis, I.plus.axis +# # logical directions along the interface + +# #d_minus, d_plus = 1-minus_axis, 1-plus_axis +# I_minus_ncells = V0h.spaces[k_minus].ncells +# I_plus_ncells = V0h.spaces[k_plus].ncells + +# matching_interfaces = (I_minus_ncells == I_plus_ncells) + +# if I_minus_ncells <= I_plus_ncells: +# k_fine, k_coarse = k_plus, k_minus +# fine_axis, coarse_axis = I.plus.axis, I.minus.axis +# fine_ext, coarse_ext = I.plus.ext, I.minus.ext + +# else: +# k_fine, k_coarse = k_minus, k_plus +# fine_axis, coarse_axis = I.minus.axis, I.plus.axis +# fine_ext, coarse_ext = I.minus.ext, I.plus.ext + +# d_fine = 1-fine_axis +# d_coarse = 1-coarse_axis + +# space_fine = V0h.spaces[k_fine] +# space_coarse = V0h.spaces[k_coarse] + + +# coarse_space_1d = space_coarse.spaces[d_coarse] + +# fine_space_1d = space_fine.spaces[d_fine] +# grid = np.linspace( +# fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) +# coarse_space_1d_k_plus = SplineSpace( +# degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) + +# if not matching_interfaces: +# E_1D = construct_extension_operator_1D( +# domain=coarse_space_1d_k_plus, codomain=fine_space_1d) + +# product = (E_1D.T) @ E_1D +# R_1D = inv(product.tocsc()) @ E_1D.T +# ER_1D = E_1D @ R_1D +# else: +# ER_1D = R_1D = E_1D = sparse_eye( +# fine_space_1d.nbasis, format="lil") + +# # P_k_minus_k_minus +# multi_index = [None]*ndim +# multi_index[coarse_axis] = 0 if coarse_ext == - \ +# 1 else space_coarse.spaces[coarse_axis].nbasis-1 +# for i in range(coarse_space_1d.nbasis): +# multi_index[d_coarse] = i +# ig = l2g.get_index(k_coarse, 0, multi_index) +# if not corner_indices.issuperset({ig}): +# Proj[ig, ig] = 0.5 + +# # P_k_plus_k_plus +# multi_index_i = [None]*ndim +# multi_index_j = [None]*ndim +# multi_index_i[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine.spaces[fine_axis].nbasis-1 +# multi_index_j[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine.spaces[fine_axis].nbasis-1 + +# for i in range(fine_space_1d.nbasis): +# multi_index_i[d_fine] = i +# ig = l2g.get_index(k_fine, 0, multi_index_i) +# for j in range(fine_space_1d.nbasis): +# multi_index_j[d_fine] = j +# jg = l2g.get_index(k_fine, 0, multi_index_j) +# if not corner_indices.issuperset({ig}): +# Proj[ig, jg] = 0.5*ER_1D[i, j] + +# # P_k_plus_k_minus +# multi_index_i = [None]*ndim +# multi_index_j = [None]*ndim +# multi_index_i[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine .spaces[fine_axis] .nbasis-1 +# multi_index_j[coarse_axis] = 0 if coarse_ext == - \ +# 1 else space_coarse.spaces[coarse_axis].nbasis-1 + +# for i in range(fine_space_1d.nbasis): +# multi_index_i[d_fine] = i +# ig = l2g.get_index(k_fine, 0, multi_index_i) +# for j in range(coarse_space_1d.nbasis): +# multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 +# jg = l2g.get_index(k_coarse, 0, multi_index_j) +# if not corner_indices.issuperset({ig}): +# Proj[ig, jg] = 0.5*E_1D[i, j]*direction + +# # P_k_minus_k_plus +# multi_index_i = [None]*ndim +# multi_index_j = [None]*ndim +# multi_index_i[coarse_axis] = 0 if coarse_ext == - \ +# 1 else space_coarse.spaces[coarse_axis].nbasis-1 +# multi_index_j[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine .spaces[fine_axis] .nbasis-1 + +# for i in range(coarse_space_1d.nbasis): +# multi_index_i[d_coarse] = i +# ig = l2g.get_index(k_coarse, 0, multi_index_i) +# for j in range(fine_space_1d.nbasis): +# multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 +# jg = l2g.get_index(k_fine, 0, multi_index_j) +# if not corner_indices.issuperset({ig}): +# Proj[ig, jg] = 0.5*R_1D[i, j]*direction + + +# if hom_bc: +# bd_co_indices = set() +# for bn in domain.boundary: +# k = get_patch_index_from_face(domain, bn) +# space_k = V0h.spaces[k] +# axis = bn.axis +# d = 1-axis +# ext = bn.ext +# space_k_1d = space_k.spaces[d] # t +# multi_index_i = [None]*ndim +# multi_index_i[axis] = 0 if ext == - \ +# 1 else space_k.spaces[axis].nbasis-1 + +# for i in range(space_k_1d.nbasis): +# multi_index_i[d] = i +# ig = l2g.get_index(k, 0, multi_index_i) +# bd_co_indices.add(ig) +# Proj[ig, ig] = 0 + +# # properly ensure vertex continuity +# for ig in bd_co_indices: +# for jg in bd_co_indices: +# Proj_vertex[ig, jg] = 0 + + +# return Proj @ Proj_vertex + +# def construct_V1_conforming_projection(V1h, hom_bc=None): +# dim_tot = V1h.nbasis +# domain = V1h.symbolic_space.domain +# ndim = 2 +# n_components = 2 +# n_patches = len(domain) + +# l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) +# for k in range(n_patches): +# Vk = V1h.spaces[k] +# # T is a TensorFemSpace and S is a 1D SplineSpace +# shapes = [[S.nbasis for S in T.spaces] for T in Vk.spaces] +# l2g.set_patch_shapes(k, *shapes) + +# Proj = sparse_eye(dim_tot, format="lil") + +# Interfaces = domain.interfaces +# if isinstance(Interfaces, Interface): +# Interfaces = (Interfaces, ) + +# for I in Interfaces: +# axis = I.axis +# direction = I.ornt + +# k_minus = get_patch_index_from_face(domain, I.minus) +# k_plus = get_patch_index_from_face(domain, I.plus) +# # logical directions normal to interface +# minus_axis, plus_axis = I.minus.axis, I.plus.axis +# # logical directions along the interface +# d_minus, d_plus = 1-minus_axis, 1-plus_axis +# I_minus_ncells = V1h.spaces[k_minus].spaces[d_minus].ncells[d_minus] +# I_plus_ncells = V1h.spaces[k_plus] .spaces[d_plus] .ncells[d_plus] + +# matching_interfaces = (I_minus_ncells == I_plus_ncells) + +# if I_minus_ncells <= I_plus_ncells: +# k_fine, k_coarse = k_plus, k_minus +# fine_axis, coarse_axis = I.plus.axis, I.minus.axis +# fine_ext, coarse_ext = I.plus.ext, I.minus.ext + +# else: +# k_fine, k_coarse = k_minus, k_plus +# fine_axis, coarse_axis = I.minus.axis, I.plus.axis +# fine_ext, coarse_ext = I.minus.ext, I.plus.ext + +# d_fine = 1-fine_axis +# d_coarse = 1-coarse_axis + +# space_fine = V1h.spaces[k_fine] +# space_coarse = V1h.spaces[k_coarse] + +# #print("coarse = \n", space_coarse.spaces[d_coarse]) +# #print("coarse 2 = \n", space_coarse.spaces[d_coarse].spaces[d_coarse]) +# # todo: merge with first test above +# coarse_space_1d = space_coarse.spaces[d_coarse].spaces[d_coarse] + +# #print("fine = \n", space_fine.spaces[d_fine]) +# #print("fine 2 = \n", space_fine.spaces[d_fine].spaces[d_fine]) + +# fine_space_1d = space_fine.spaces[d_fine].spaces[d_fine] +# grid = np.linspace( +# fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) +# coarse_space_1d_k_plus = SplineSpace( +# degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) + +# if not matching_interfaces: +# E_1D = construct_extension_operator_1D( +# domain=coarse_space_1d_k_plus, codomain=fine_space_1d) +# product = (E_1D.T) @ E_1D +# R_1D = inv(product.tocsc()) @ E_1D.T +# ER_1D = E_1D @ R_1D +# else: +# ER_1D = R_1D = E_1D = sparse_eye( +# fine_space_1d.nbasis, format="lil") + +# # P_k_minus_k_minus +# multi_index = [None]*ndim +# multi_index[coarse_axis] = 0 if coarse_ext == - \ +# 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 +# for i in range(coarse_space_1d.nbasis): +# multi_index[d_coarse] = i +# ig = l2g.get_index(k_coarse, d_coarse, multi_index) +# Proj[ig, ig] = 0.5 + +# # P_k_plus_k_plus +# multi_index_i = [None]*ndim +# multi_index_j = [None]*ndim +# multi_index_i[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 +# multi_index_j[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 + +# for i in range(fine_space_1d.nbasis): +# multi_index_i[d_fine] = i +# ig = l2g.get_index(k_fine, d_fine, multi_index_i) +# for j in range(fine_space_1d.nbasis): +# multi_index_j[d_fine] = j +# jg = l2g.get_index(k_fine, d_fine, multi_index_j) +# Proj[ig, jg] = 0.5*ER_1D[i, j] + +# # P_k_plus_k_minus +# multi_index_i = [None]*ndim +# multi_index_j = [None]*ndim +# multi_index_i[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 +# multi_index_j[coarse_axis] = 0 if coarse_ext == - \ +# 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 + +# for i in range(fine_space_1d.nbasis): +# multi_index_i[d_fine] = i +# ig = l2g.get_index(k_fine, d_fine, multi_index_i) +# for j in range(coarse_space_1d.nbasis): +# multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 +# jg = l2g.get_index(k_coarse, d_coarse, multi_index_j) +# Proj[ig, jg] = 0.5*E_1D[i, j]*direction + +# # P_k_minus_k_plus +# multi_index_i = [None]*ndim +# multi_index_j = [None]*ndim +# multi_index_i[coarse_axis] = 0 if coarse_ext == - \ +# 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 +# multi_index_j[fine_axis] = 0 if fine_ext == - \ +# 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 + +# for i in range(coarse_space_1d.nbasis): +# multi_index_i[d_coarse] = i +# ig = l2g.get_index(k_coarse, d_coarse, multi_index_i) +# for j in range(fine_space_1d.nbasis): +# multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 +# jg = l2g.get_index(k_fine, d_fine, multi_index_j) +# Proj[ig, jg] = 0.5*R_1D[i, j]*direction + +# if hom_bc: +# for bn in domain.boundary: +# k = get_patch_index_from_face(domain, bn) +# space_k = V1h.spaces[k] +# axis = bn.axis +# d = 1-axis +# ext = bn.ext +# space_k_1d = space_k.spaces[d].spaces[d] # t +# multi_index_i = [None]*ndim +# multi_index_i[axis] = 0 if ext == - \ +# 1 else space_k.spaces[d].spaces[axis].nbasis-1 + +# for i in range(space_k_1d.nbasis): +# multi_index_i[d] = i +# ig = l2g.get_index(k, d, multi_index_i) +# Proj[ig, ig] = 0 + +# return Proj + + +def get_corners(domain, boundary_only): + """ + Given the domain, extract the vertices on their respective domains with local coordinates. + + Parameters + ---------- + domain: + The discrete domain of the projector + + boundary_only : + Only return vertices that lie on a boundary + + """ + cos = domain.corners + patches = domain.interior.args + bd = domain.boundary + + # corner_data[corner] = (patch_ind => local coordinates) + corner_data = dict() + + if boundary_only: + for co in cos: + + corner_data[co] = dict() + c = 0 + for cb in co.corners: + axis = set() + #check if corner boundary is part of the domain boundary + for cbbd in cb.args: + if bd.has(cbbd): + axis.add(cbbd.axis) + c += 1 + + p_ind = patches.index(cb.domain) + c_coord = cb.coordinates + corner_data[co][p_ind] = (c_coord, axis) + + if c == 0: corner_data.pop(co) + + else: + for co in cos: + corner_data[co] = dict() + + for cb in co.corners: + p_ind = patches.index(cb.domain) + c_coord = cb.coordinates + corner_data[co][p_ind] = c_coord + + return corner_data + + +def construct_scalar_conforming_projection(Vh, reg_orders=[0,0], p_moments=[-1,-1], nquads=None, hom_bc=[False, False]): + #construct conforming projection for a 2-dimensional scalar space -def construct_V0_conforming_projection(V0h, domain_h, hom_bc=None, storage_fn=None): - dim_tot = V0h.nbasis - domain = V0h.symbolic_space.domain + dim_tot = Vh.nbasis + + + # fully discontinuous space + if reg_orders[0] < 0 and reg_orders[1] < 0: + return sparse_eye(dim_tot, format="lil") + + + # moment corrections perpendicular to interfaces + # a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, cc_0_ax + cor_x = get_scalar_moment_correction(Vh.spaces[0], 0, reg_orders[0], p_moments[0], nquads, hom_bc[0]) + cor_y = get_scalar_moment_correction(Vh.spaces[0], 1, reg_orders[1], p_moments[1], nquads, hom_bc[1]) + corrections = [cor_x, cor_y] + domain = Vh.symbolic_space.domain ndim = 2 n_components = 1 n_patches = len(domain) l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) for k in range(n_patches): - Vk = V0h.spaces[k] + Vk = Vh.spaces[k] # T is a TensorFemSpace and S is a 1D SplineSpace shapes = [S.nbasis for S in Vk.spaces] l2g.set_patch_shapes(k, shapes) - Proj = sparse_eye(dim_tot, format="lil") + + # vertex correction matrix Proj_vertex = sparse_eye(dim_tot, format="lil") + # edge correction matrix + Proj_edge = sparse_eye(dim_tot, format="lil") + Interfaces = domain.interfaces if isinstance(Interfaces, Interface): Interfaces = (Interfaces, ) corner_indices = set() - stored_indices = [] corners = get_corners(domain, False) + + + #loop over all vertices for (bd,co) in corners.items(): - c = 0 - indices = set() - for patch in co: - c += 1 - multi_index_i = [None]*ndim + # len(co) is the number of adjacent patches at a vertex + corr = len(co) + for patch1 in co: + + #local vertex coordinates in patch1 + coords1 = co[patch1] + nbasis01 = Vh.spaces[patch1].spaces[coords1[0]].nbasis-1 + nbasis11 = Vh.spaces[patch1].spaces[coords1[1]].nbasis-1 - nbasis0 = V0h.spaces[patch].spaces[co[patch][0]].nbasis-1 - nbasis1 = V0h.spaces[patch].spaces[co[patch][1]].nbasis-1 + #patch local index + multi_index_i = [None]*ndim + multi_index_i[0] = 0 if coords1[0] == 0 else nbasis01 + multi_index_i[1] = 0 if coords1[1] == 0 else nbasis11 - multi_index_i[0] = 0 if co[patch][0] == 0 else nbasis0 - multi_index_i[1] = 0 if co[patch][1] == 0 else nbasis1 - ig = l2g.get_index(patch, 0, multi_index_i) - indices.add(ig) + #global index + ig = l2g.get_index(patch1, 0, multi_index_i) corner_indices.add(ig) - - stored_indices.append(indices) - for j in indices: - for i in indices: - Proj_vertex[j,i] = 1/c - # First make all interfaces conforming - # We also touch the vertices here, but change them later again + for patch2 in co: + + # local vertex coordinates in patch2 + coords2 = co[patch2] + nbasis02 = Vh.spaces[patch2].spaces[coords2[0]].nbasis-1 + nbasis12 = Vh.spaces[patch2].spaces[coords2[1]].nbasis-1 + + #patch local index + multi_index_j = [None]*ndim + multi_index_j[0] = 0 if coords2[0] == 0 else nbasis02 + multi_index_j[1] = 0 if coords2[1] == 0 else nbasis12 + + #global index + jg = l2g.get_index(patch2, 0, multi_index_j) + + #conformity constraint + Proj_vertex[jg,ig] = 1/corr + + if patch1 == patch2: continue + + if (p_moments[0] == -1 and p_moments[1] == -1): continue + + #moment corrections from patch1 to patch2 + axis = 0 + d = 1 + multi_index_p = [None]*ndim + for pd in range(0, max(1, p_moments[d]+1)): + p_indd = pd+0+1 + multi_index_p[d] = p_indd if coords2[d] == 0 else Vh.spaces[patch2].spaces[coords2[d]].nbasis-1-p_indd + + for p in range(0, max(1,p_moments[axis]+1)): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[axis] = p_ind if coords2[axis] == 0 else Vh.spaces[patch2].spaces[coords2[axis]].nbasis-1-p_ind + pg = l2g.get_index(patch2, 0, multi_index_p) + Proj_vertex[pg, ig] += - 1/corr * corrections[axis][5][p] * corrections[d][5][pd] + + if (p_moments[0] == -1 and p_moments[1]) == -1: continue + + #moment corrections from patch1 to patch1 + axis = 0 + d = 1 + multi_index_p = [None]*ndim + for pd in range(0, max(1, p_moments[d]+1)): + p_indd = pd+0+1 + multi_index_p[d] = p_indd if coords1[d] == 0 else Vh.spaces[patch1].spaces[coords1[d]].nbasis-1-p_indd + for p in range(0, max(1, p_moments[axis]+1)): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[axis] = p_ind if coords1[axis] == 0 else Vh.spaces[patch1].spaces[coords1[axis]].nbasis-1-p_ind + pg = l2g.get_index(patch1, 0, multi_index_p) + Proj_vertex[pg,ig] += (1-1/corr) * corrections[axis][5][p] * corrections[d][5][pd] + + + # loop over all interfaces for I in Interfaces: axis = I.axis @@ -188,16 +656,13 @@ def construct_V0_conforming_projection(V0h, domain_h, hom_bc=None, storage_fn=No k_minus = get_patch_index_from_face(domain, I.minus) k_plus = get_patch_index_from_face(domain, I.plus) - # logical directions normal to interface - minus_axis, plus_axis = I.minus.axis, I.plus.axis - # logical directions along the interface - #d_minus, d_plus = 1-minus_axis, 1-plus_axis - I_minus_ncells = V0h.spaces[k_minus].ncells - I_plus_ncells = V0h.spaces[k_plus].ncells + I_minus_ncells = Vh.spaces[k_minus].ncells + I_plus_ncells = Vh.spaces[k_plus].ncells matching_interfaces = (I_minus_ncells == I_plus_ncells) + # logical directions normal to interface if I_minus_ncells <= I_plus_ncells: k_fine, k_coarse = k_plus, k_minus fine_axis, coarse_axis = I.plus.axis, I.minus.axis @@ -208,133 +673,274 @@ def construct_V0_conforming_projection(V0h, domain_h, hom_bc=None, storage_fn=No fine_axis, coarse_axis = I.minus.axis, I.plus.axis fine_ext, coarse_ext = I.minus.ext, I.plus.ext + # logical directions along the interface d_fine = 1-fine_axis d_coarse = 1-coarse_axis - space_fine = V0h.spaces[k_fine] - space_coarse = V0h.spaces[k_coarse] + space_fine = Vh.spaces[k_fine] + space_coarse = Vh.spaces[k_coarse] coarse_space_1d = space_coarse.spaces[d_coarse] - fine_space_1d = space_fine.spaces[d_fine] - grid = np.linspace( - fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) - coarse_space_1d_k_plus = SplineSpace( - degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) - - if not matching_interfaces: - E_1D = construct_extension_operator_1D( - domain=coarse_space_1d_k_plus, codomain=fine_space_1d) - - product = (E_1D.T) @ E_1D - R_1D = inv(product.tocsc()) @ E_1D.T - ER_1D = E_1D @ R_1D - else: - ER_1D = R_1D = E_1D = sparse_eye( - fine_space_1d.nbasis, format="lil") + + E_1D, R_1D, ER_1D = get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, 'B') # P_k_minus_k_minus multi_index = [None]*ndim - multi_index[coarse_axis] = 0 if coarse_ext == - \ - 1 else space_coarse.spaces[coarse_axis].nbasis-1 + multi_index_m = [None]*ndim + multi_index[coarse_axis] = 0 if coarse_ext == - 1 else space_coarse.spaces[coarse_axis].nbasis-1 + + for i in range(coarse_space_1d.nbasis): multi_index[d_coarse] = i + multi_index_m[d_coarse] = i ig = l2g.get_index(k_coarse, 0, multi_index) + if not corner_indices.issuperset({ig}): - Proj[ig, ig] = 0.5 + Proj_edge[ig, ig] = corrections[coarse_axis][0][0] + + for p in range(0, p_moments[coarse_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_m[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[coarse_axis].nbasis-1-p_ind + mg = l2g.get_index(k_coarse, 0, multi_index_m) + Proj_edge[mg, ig] += corrections[coarse_axis][0][p_ind] + # P_k_plus_k_plus multi_index_i = [None]*ndim multi_index_j = [None]*ndim - multi_index_i[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine.spaces[fine_axis].nbasis-1 - multi_index_j[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine.spaces[fine_axis].nbasis-1 + multi_index_p = [None]*ndim + + multi_index_i[fine_axis] = 0 if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1 + multi_index_j[fine_axis] = 0 if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1 for i in range(fine_space_1d.nbasis): multi_index_i[d_fine] = i ig = l2g.get_index(k_fine, 0, multi_index_i) + + multi_index_p[d_fine] = i + for j in range(fine_space_1d.nbasis): multi_index_j[d_fine] = j jg = l2g.get_index(k_fine, 0, multi_index_j) + if not corner_indices.issuperset({ig}): - Proj[ig, jg] = 0.5*ER_1D[i, j] + Proj_edge[ig, jg] = corrections[fine_axis][0][0] * ER_1D[i,j] + + for p in range(0, p_moments[fine_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1-p_ind + pg = l2g.get_index(k_fine, 0, multi_index_p) + + Proj_edge[pg, jg] += corrections[fine_axis][0][p_ind] * ER_1D[i, j] # P_k_plus_k_minus multi_index_i = [None]*ndim multi_index_j = [None]*ndim - multi_index_i[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine .spaces[fine_axis] .nbasis-1 - multi_index_j[coarse_axis] = 0 if coarse_ext == - \ - 1 else space_coarse.spaces[coarse_axis].nbasis-1 + multi_index_p = [None]*ndim + + multi_index_i[fine_axis] = 0 if fine_ext == -1 else space_fine .spaces[fine_axis] .nbasis-1 + multi_index_j[coarse_axis] = 0 if coarse_ext == -1 else space_coarse.spaces[coarse_axis].nbasis-1 for i in range(fine_space_1d.nbasis): multi_index_i[d_fine] = i + multi_index_p[d_fine] = i ig = l2g.get_index(k_fine, 0, multi_index_i) + for j in range(coarse_space_1d.nbasis): multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 jg = l2g.get_index(k_coarse, 0, multi_index_j) + if not corner_indices.issuperset({ig}): - Proj[ig, jg] = 0.5*E_1D[i, j]*direction + Proj_edge[ig, jg] = corrections[coarse_axis][1][0] *E_1D[i,j]*direction + + for p in range(0, p_moments[fine_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1-p_ind + pg = l2g.get_index(k_fine, 0, multi_index_p) + + Proj_edge[pg, jg] += corrections[fine_axis][1][p_ind] *E_1D[i, j]*direction # P_k_minus_k_plus multi_index_i = [None]*ndim multi_index_j = [None]*ndim - multi_index_i[coarse_axis] = 0 if coarse_ext == - \ - 1 else space_coarse.spaces[coarse_axis].nbasis-1 - multi_index_j[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine .spaces[fine_axis] .nbasis-1 + multi_index_p = [None]*ndim + + multi_index_i[coarse_axis] = 0 if coarse_ext == -1 else space_coarse.spaces[coarse_axis].nbasis-1 + multi_index_j[fine_axis] = 0 if fine_ext == -1 else space_fine .spaces[fine_axis] .nbasis-1 for i in range(coarse_space_1d.nbasis): multi_index_i[d_coarse] = i + multi_index_p[d_coarse] = i ig = l2g.get_index(k_coarse, 0, multi_index_i) + for j in range(fine_space_1d.nbasis): multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 jg = l2g.get_index(k_fine, 0, multi_index_j) + if not corner_indices.issuperset({ig}): - Proj[ig, jg] = 0.5*R_1D[i, j]*direction - - - if hom_bc: - bd_co_indices = set() - for bn in domain.boundary: - k = get_patch_index_from_face(domain, bn) - space_k = V0h.spaces[k] - axis = bn.axis - d = 1-axis - ext = bn.ext - space_k_1d = space_k.spaces[d] # t + Proj_edge[ig, jg] = corrections[fine_axis][1][0] *R_1D[i,j]*direction + + for p in range(0, p_moments[coarse_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[coarse_axis].nbasis-1-p_ind + pg = l2g.get_index(k_coarse, 0, multi_index_p) + + Proj_edge[pg, jg] += corrections[coarse_axis][1][p_ind] *R_1D[i, j]*direction + + # boundary conditions + + # interface correction + bd_co_indices = set() + for bn in domain.boundary: + k = get_patch_index_from_face(domain, bn) + space_k = Vh.spaces[k] + axis = bn.axis + if not hom_bc[axis]: + continue + + d = 1-axis + ext = bn.ext + space_k_1d = space_k.spaces[d] # t + multi_index_i = [None]*ndim + multi_index_i[axis] = 0 if ext == - \ + 1 else space_k.spaces[axis].nbasis-1 + + multi_index_p = [None]*ndim + multi_index_p[axis] = 0 if ext == - \ + 1 else space_k.spaces[axis].nbasis-1 + + for i in range(0, space_k_1d.nbasis): + multi_index_i[d] = i + ig = l2g.get_index(k, 0, multi_index_i) + bd_co_indices.add(ig) + Proj_edge[ig, ig] = 0 + + multi_index_p[d] = i + + # interface correction + if (i != 0 and i != space_k_1d.nbasis-1): + for p in range(0, p_moments[axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[axis] = p_ind if ext == - 1 else space_k.spaces[axis].nbasis-1-p_ind + pg = l2g.get_index(k, 0, multi_index_p) + #a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd + Proj_edge[pg, ig] = corrections[axis][4][p] #* corrections[d][4][p] + + + # vertex corrections + corners = get_corners(domain, True) + for (bd,co) in corners.items(): + + # len(co) is the number of adjacent patches at a vertex + corr = len(co) + for patch1 in co: + c = 0 + if hom_bc[0]: + if 0 in co[patch1][1]: c += 1 + if hom_bc[1]: + if 1 in co[patch1][1]: c+=1 + if c == 0: break + + #local vertex coordinates in patch1 + coords1 = co[patch1][0] + nbasis01 = Vh.spaces[patch1].spaces[coords1[0]].nbasis-1 + nbasis11 = Vh.spaces[patch1].spaces[coords1[1]].nbasis-1 + + #patch local index multi_index_i = [None]*ndim - multi_index_i[axis] = 0 if ext == - \ - 1 else space_k.spaces[axis].nbasis-1 - - for i in range(space_k_1d.nbasis): - multi_index_i[d] = i - ig = l2g.get_index(k, 0, multi_index_i) - bd_co_indices.add(ig) - Proj[ig, ig] = 0 - - # properly ensure vertex continuity - for ig in bd_co_indices: - for jg in bd_co_indices: - Proj_vertex[ig, jg] = 0 - + multi_index_i[0] = 0 if coords1[0] == 0 else nbasis01 + multi_index_i[1] = 0 if coords1[1] == 0 else nbasis11 + + #global index + ig = l2g.get_index(patch1, 0, multi_index_i) + corner_indices.add(ig) + + for patch2 in co: + + # local vertex coordinates in patch2 + coords2 = co[patch2][0] + nbasis02 = Vh.spaces[patch2].spaces[coords2[0]].nbasis-1 + nbasis12 = Vh.spaces[patch2].spaces[coords2[1]].nbasis-1 + + #patch local index + multi_index_j = [None]*ndim + multi_index_j[0] = 0 if coords2[0] == 0 else nbasis02 + multi_index_j[1] = 0 if coords2[1] == 0 else nbasis12 + + #global index + jg = l2g.get_index(patch2, 0, multi_index_j) + + #conformity constraint + Proj_vertex[jg,ig] = 0 + + if patch1 == patch2: continue + + if (p_moments[0] == -1 and p_moments[1] == -1): continue + + #moment corrections from patch1 to patch2 + axis = 0 + d = 1 + multi_index_p = [None]*ndim + for pd in range(0, max(1, p_moments[d]+1)): + p_indd = pd+0+1 + multi_index_p[d] = p_indd if coords2[d] == 0 else Vh.spaces[patch2].spaces[coords2[d]].nbasis-1-p_indd + + for p in range(0, max(1,p_moments[axis]+1)): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[axis] = p_ind if coords2[axis] == 0 else Vh.spaces[patch2].spaces[coords2[axis]].nbasis-1-p_ind + pg = l2g.get_index(patch2, 0, multi_index_p) + Proj_vertex[pg, ig] = 0 + + if (p_moments[0] == -1 and p_moments[1]) == -1: continue + + #moment corrections from patch1 to patch1 + axis = 0 + d = 1 + multi_index_p = [None]*ndim + for pd in range(0, max(1, p_moments[d]+1)): + p_indd = pd+0+1 + multi_index_p[d] = p_indd if coords1[d] == 0 else Vh.spaces[patch1].spaces[coords1[d]].nbasis-1-p_indd + for p in range(0, max(1, p_moments[axis]+1)): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[axis] = p_ind if coords1[axis] == 0 else Vh.spaces[patch1].spaces[coords1[axis]].nbasis-1-p_ind + pg = l2g.get_index(patch1, 0, multi_index_p) + Proj_vertex[pg,ig] = corrections[axis][5][p] * corrections[d][5][pd] + + return Proj_edge @ Proj_vertex + +def construct_vector_conforming_projection(Vh, reg_orders= [0,0], p_moments=[-1,-1], nquads=None, hom_bc=[False, False]): + dim_tot = Vh.nbasis + + # fully discontinuous space + if reg_orders[0] < 0 and reg_orders[1] < 0: + return sparse_eye(dim_tot, format="lil") - return Proj @ Proj_vertex + #moment corrections + corrections_0 = get_vector_moment_correction(Vh.spaces[0], 0, 0, reg=reg_orders[0], p_moments=p_moments[0], nquads=nquads, hom_bc=hom_bc[0]) + corrections_1 = get_vector_moment_correction(Vh.spaces[0], 0, 1, reg=reg_orders[1], p_moments=p_moments[1], nquads=nquads, hom_bc=hom_bc[1]) + corrections_00 = get_vector_moment_correction(Vh.spaces[0], 1, 0, reg=reg_orders[0], p_moments=p_moments[0], nquads=nquads, hom_bc=hom_bc[0]) + corrections_11 = get_vector_moment_correction(Vh.spaces[0], 1, 1, reg=reg_orders[1], p_moments=p_moments[1], nquads=nquads, hom_bc=hom_bc[1]) + corrections = [[corrections_0, corrections_1], [corrections_00, corrections_11]] -def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=None): - dim_tot = V1h.nbasis - domain = V1h.symbolic_space.domain + domain = Vh.symbolic_space.domain ndim = 2 n_components = 2 n_patches = len(domain) l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) for k in range(n_patches): - Vk = V1h.spaces[k] + Vk = Vh.spaces[k] # T is a TensorFemSpace and S is a 1D SplineSpace shapes = [[S.nbasis for S in T.spaces] for T in Vk.spaces] l2g.set_patch_shapes(k, *shapes) @@ -355,8 +961,8 @@ def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=No minus_axis, plus_axis = I.minus.axis, I.plus.axis # logical directions along the interface d_minus, d_plus = 1-minus_axis, 1-plus_axis - I_minus_ncells = V1h.spaces[k_minus].spaces[d_minus].ncells[d_minus] - I_plus_ncells = V1h.spaces[k_plus] .spaces[d_plus] .ncells[d_plus] + I_minus_ncells = Vh.spaces[k_minus].spaces[d_minus].ncells[d_minus] + I_plus_ncells = Vh.spaces[k_plus] .spaces[d_plus] .ncells[d_plus] matching_interfaces = (I_minus_ncells == I_plus_ncells) @@ -373,45 +979,38 @@ def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=No d_fine = 1-fine_axis d_coarse = 1-coarse_axis - space_fine = V1h.spaces[k_fine] - space_coarse = V1h.spaces[k_coarse] + space_fine = Vh.spaces[k_fine] + space_coarse = Vh.spaces[k_coarse] - #print("coarse = \n", space_coarse.spaces[d_coarse]) - #print("coarse 2 = \n", space_coarse.spaces[d_coarse].spaces[d_coarse]) - # todo: merge with first test above coarse_space_1d = space_coarse.spaces[d_coarse].spaces[d_coarse] - - #print("fine = \n", space_fine.spaces[d_fine]) - #print("fine 2 = \n", space_fine.spaces[d_fine].spaces[d_fine]) - fine_space_1d = space_fine.spaces[d_fine].spaces[d_fine] - grid = np.linspace( - fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) - coarse_space_1d_k_plus = SplineSpace( - degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) - - if not matching_interfaces: - E_1D = construct_extension_operator_1D( - domain=coarse_space_1d_k_plus, codomain=fine_space_1d) - product = (E_1D.T) @ E_1D - R_1D = inv(product.tocsc()) @ E_1D.T - ER_1D = E_1D @ R_1D - else: - ER_1D = R_1D = E_1D = sparse_eye( - fine_space_1d.nbasis, format="lil") + + E_1D, R_1D, ER_1D = get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, 'M') # P_k_minus_k_minus multi_index = [None]*ndim + multi_index_m = [None]*ndim multi_index[coarse_axis] = 0 if coarse_ext == - \ 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 + for i in range(coarse_space_1d.nbasis): multi_index[d_coarse] = i + multi_index_m[d_coarse] = i ig = l2g.get_index(k_coarse, d_coarse, multi_index) - Proj[ig, ig] = 0.5 + Proj[ig, ig] = corrections[d_coarse][coarse_axis][0][0] + + for p in range(0, p_moments[coarse_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_m[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1-p_ind + mg = l2g.get_index(k_coarse, d_coarse, multi_index_m) + + Proj[mg, ig] = corrections[d_coarse][coarse_axis][0][p_ind] # P_k_plus_k_plus multi_index_i = [None]*ndim multi_index_j = [None]*ndim + multi_index_p = [None]*ndim multi_index_i[fine_axis] = 0 if fine_ext == - \ 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 multi_index_j[fine_axis] = 0 if fine_ext == - \ @@ -419,15 +1018,26 @@ def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=No for i in range(fine_space_1d.nbasis): multi_index_i[d_fine] = i + multi_index_p[d_fine] = i ig = l2g.get_index(k_fine, d_fine, multi_index_i) + for j in range(fine_space_1d.nbasis): multi_index_j[d_fine] = j jg = l2g.get_index(k_fine, d_fine, multi_index_j) - Proj[ig, jg] = 0.5*ER_1D[i, j] + Proj[ig, jg] = corrections[d_fine][fine_axis][0][0] * ER_1D[i, j] + + for p in range(0, p_moments[fine_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1-p_ind + pg = l2g.get_index(k_fine, d_fine, multi_index_p) + + Proj[pg, jg] = corrections[d_fine][fine_axis][0][p_ind] * ER_1D[i, j] # P_k_plus_k_minus multi_index_i = [None]*ndim multi_index_j = [None]*ndim + multi_index_p = [None]*ndim multi_index_i[fine_axis] = 0 if fine_ext == - \ 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 multi_index_j[coarse_axis] = 0 if coarse_ext == - \ @@ -435,15 +1045,26 @@ def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=No for i in range(fine_space_1d.nbasis): multi_index_i[d_fine] = i + multi_index_p[d_fine] = i ig = l2g.get_index(k_fine, d_fine, multi_index_i) + for j in range(coarse_space_1d.nbasis): multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 jg = l2g.get_index(k_coarse, d_coarse, multi_index_j) - Proj[ig, jg] = 0.5*E_1D[i, j]*direction + Proj[ig, jg] = corrections[d_fine][fine_axis][1][0] *E_1D[i, j]*direction + + for p in range(0, p_moments[fine_axis]+1): + + p_ind = p+0+1 # 0 = regularity + multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1-p_ind + pg = l2g.get_index(k_fine, d_fine, multi_index_p) + + Proj[pg, jg] = corrections[d_fine][fine_axis][1][p_ind] *E_1D[i, j]*direction # P_k_minus_k_plus multi_index_i = [None]*ndim multi_index_j = [None]*ndim + multi_index_p = [None]*ndim multi_index_i[coarse_axis] = 0 if coarse_ext == - \ 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 multi_index_j[fine_axis] = 0 if fine_ext == - \ @@ -451,210 +1072,613 @@ def construct_V1_conforming_projection(V1h, domain_h, hom_bc=None, storage_fn=No for i in range(coarse_space_1d.nbasis): multi_index_i[d_coarse] = i + multi_index_p[d_coarse] = i ig = l2g.get_index(k_coarse, d_coarse, multi_index_i) for j in range(fine_space_1d.nbasis): multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 jg = l2g.get_index(k_fine, d_fine, multi_index_j) - Proj[ig, jg] = 0.5*R_1D[i, j]*direction - - if hom_bc: - for bn in domain.boundary: - k = get_patch_index_from_face(domain, bn) - space_k = V1h.spaces[k] - axis = bn.axis - d = 1-axis - ext = bn.ext - space_k_1d = space_k.spaces[d].spaces[d] # t - multi_index_i = [None]*ndim - multi_index_i[axis] = 0 if ext == - \ - 1 else space_k.spaces[d].spaces[axis].nbasis-1 + Proj[ig, jg] = corrections[d_coarse][coarse_axis][1][0] *R_1D[i, j]*direction + + for p in range(0, p_moments[coarse_axis]+1): - for i in range(space_k_1d.nbasis): - multi_index_i[d] = i - ig = l2g.get_index(k, d, multi_index_i) - Proj[ig, ig] = 0 + p_ind = p+0+1 # 0 = regularity + multi_index_p[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1-p_ind + pg = l2g.get_index(k_coarse, d_coarse, multi_index_p) - return Proj + Proj[pg, jg] = corrections[d_coarse][coarse_axis][1][p_ind] *R_1D[i, j]*direction + #if hom_bc: + for bn in domain.boundary: + k = get_patch_index_from_face(domain, bn) + space_k = Vh.spaces[k] + axis = bn.axis + d = 1-axis + ext = bn.ext -def get_corners(domain, boundary_only): - """ - Conforming projection from global broken V0 space to conforming global V0 space - Defined by averaging of interface dofs + if not hom_bc[axis]: + continue - Parameters - ---------- - domain: - The discrete domain of the projector + space_k_1d = space_k.spaces[d].spaces[d] # t + multi_index_i = [None]*ndim + multi_index_i[axis] = 0 if ext == - \ + 1 else space_k.spaces[d].spaces[axis].nbasis-1 + multi_index_p = [None]*ndim - hom_bc : - Apply homogenous boundary conditions if True + for i in range(space_k_1d.nbasis): + multi_index_i[d] = i + multi_index_p[d] = i + ig = l2g.get_index(k, d, multi_index_i) + Proj[ig, ig] = 0 - backend_language: - The backend used to accelerate the code + for p in range(0, p_moments[axis]+1): - storage_fn: - filename to store/load the operator sparse matrix - """ - # domain = V0h.symbolic_space.domain - cos = domain.corners - patches = domain.interior.args + p_ind = p+0+1 # 0 = regularity + multi_index_p[axis] = p_ind if ext == - 1 else space_k.spaces[d].spaces[axis].nbasis-1-p_ind + pg = l2g.get_index(k, d, multi_index_p) + #a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd - # corner_data[corner] = (patch_ind => coord) - corner_data = dict() + Proj[pg, ig] = corrections[d][axis][4][p] - # corner in domain corners - for co in cos: - # corner boundary in corner corner (?)direction - if boundary_only: - if not(domain.boundary.has(co.args[0].args[0]) or domain.boundary.has(co.args[0].args[1])): - continue + return Proj - corner_data[co] = dict() +def get_scalar_moment_correction(patch_space, conf_axis, reg=0, p_moments=-1, nquads=None, hom_bc=False): + + proj_op = 0 + #patch_space = Vh.spaces[0] + local_shape = [patch_space.spaces[0].nbasis,patch_space.spaces[1].nbasis] + Nel = patch_space.ncells # number of elements + degree = patch_space.degree + breakpoints_xy = [breakpoints(patch_space.knots[axis],degree[axis]) for axis in range(2)] + + if nquads is None: + # default: Gauss-Legendre quadratures should be exact for polynomials of deg ≤ 2*degree + nquads = [ degree[axis]+1 for axis in range(2)] + + #Creating vector of weights for moments preserving + uw = [gauss_legendre( k-1 ) for k in nquads] + u = [u[::-1] for u,w in uw] + w = [w[::-1] for u,w in uw] + + grid = [np.array([deepcopy((0.5*(u[axis]+1)*(breakpoints_xy[axis][i+1]-breakpoints_xy[axis][i])+breakpoints_xy[axis][i])) + for i in range(Nel[axis])]) + for axis in range(2)] + _, basis, span, _ = patch_space.preprocess_regular_tensor_grid(grid,der=1) # todo: why not der=0 ? + + span = [deepcopy(span[k] + patch_space.vector_space.starts[k] - patch_space.vector_space.shifts[k] * patch_space.vector_space.pads[k]) for k in range(2)] + p_axis = degree[conf_axis] + enddom = breakpoints_xy[conf_axis][-1] + begdom = breakpoints_xy[conf_axis][0] + denom = enddom-begdom + + a_sm = np.zeros(p_moments+2+reg) # coefs of P B0 on same patch + a_nb = np.zeros(p_moments+2+reg) # coefs of P B0 on neighbor patch + b_sm = np.zeros(p_moments+3) # coefs of P B1 on same patch + b_nb = np.zeros(p_moments+3) # coefs of P B1 on neighbor patch + Correct_coef_bnd = np.zeros(p_moments+1) + Correct_coef_0 = np.zeros(p_moments+2+reg) + + if reg >= 0: + # projection coefs: + a_sm[0] = 1/2 + a_nb[0] = a_sm[0] + if reg == 1: + + if proj_op == 0: + # new slope is average of old ones + a_sm[1] = 0 + elif proj_op == 1: + # new slope is average of old ones after averaging of interface coef + a_sm[1] = 1/2 + elif proj_op == 2: + # new slope is average of reconstructed ones using local values and slopes + a_sm[1] = 1/(2*p_axis) + else: + # just to try something else + a_sm[1] = proj_op/2 + + a_nb[1] = 2*a_sm[0] - a_sm[1] + b_sm[0] = 0 + b_sm[1] = 1/2 + b_nb[0] = b_sm[0] + b_nb[1] = 2*b_sm[0] - b_sm[1] + + if p_moments >= 0: + # to preserve moments of degree p we need 1+p conforming basis functions in the patch (the "interior" ones) + # and for the given regularity constraint, there are local_shape[conf_axis]-2*(1+reg) such conforming functions + p_max = local_shape[conf_axis]-2*(1+reg) - 1 + if p_max < p_moments: + print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") + print( " ** WARNING -- WARNING -- WARNING ") + print(f" ** conf. projection imposing C{reg} smoothness on scalar space along axis {conf_axis}:") + print(f" ** there are not enough dofs in a patch to preserve moments of degree {p_moments} !") + print(f" ** Only able to preserve up to degree --> {p_max} <-- ") + print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") + p_moments = p_max + + # computing the contribution to every moment of the differents basis function + # for simplicity we assemble the full matrix with all basis functions (ok if patches not too large) + Mass_mat = np.zeros((p_moments+1,local_shape[conf_axis])) + for poldeg in range(p_moments+1): + for ie1 in range(Nel[conf_axis]): #loop on cells + for il1 in range(p_axis+1): #loops on basis function in each cell + val=0. + for q1 in range(nquads[conf_axis]): #loops on quadrature points + v0 = basis[conf_axis][ie1,il1,0,q1] + x = grid[conf_axis][ie1,q1] + val += w[conf_axis][q1]*v0*((enddom-x)/denom)**poldeg + locind=span[conf_axis][ie1]-p_axis+il1 + Mass_mat[poldeg,locind]+=val + Rhs_0 = Mass_mat[:,0] + + if reg == 0: + Mat_to_inv = Mass_mat[:,1:p_moments+2] + else: + Mat_to_inv = Mass_mat[:,2:p_moments+3] - for cb in co.corners: - p_ind = patches.index(cb.domain) - c_coord = cb.coordinates - corner_data[co][p_ind] = c_coord + Correct_coef_0 = np.linalg.solve(Mat_to_inv,Rhs_0) + cc_0_ax = Correct_coef_0 + + if reg == 1: + Rhs_1 = Mass_mat[:,1] + Correct_coef_1 = np.linalg.solve(Mat_to_inv,Rhs_1) + cc_1_ax = Correct_coef_1 + + if hom_bc: + # homogeneous bc is on the point value: no constraint on the derivatives + # so only the projection of B0 (to 0) has to be corrected + Mat_to_inv_bnd = Mass_mat[:,1:p_moments+2] + Correct_coef_bnd = np.linalg.solve(Mat_to_inv_bnd,Rhs_0) + + + for p in range(0,p_moments+1): + # correction for moment preserving : + # we use the first p_moments+1 conforming ("interior") functions to preserve the p+1 moments + # modified by the C0 or C1 enforcement + if reg == 0: + a_sm[p+1] = (1-a_sm[0]) * cc_0_ax[p] + # proj constraint: + a_nb[p+1] = -a_sm[p+1] - return corner_data - -if __name__ == '__main__': - - nc = 6 - deg = 4 - plot_dir = 'run_plots_nc={}_deg={}'.format(nc, deg) - - if plot_dir is not None and not os.path.exists(plot_dir): - os.makedirs(plot_dir) - - ncells = [nc, nc] - degree = [deg, deg] - - print(' .. multi-patch domain...') - - #domain_name = 'square_6' - domain_name = '2patch_nc_mapped' - #domain_name = '2patch_nc' - - if domain_name == '2patch_nc_mapped': - - A = Square('A', bounds1=(0.5, 1), bounds2=(0, np.pi/2)) - B = Square('B', bounds1=(0.5, 1), bounds2=(np.pi/2, np.pi)) - M1 = PolarMapping('M1', 2, c1=0, c2=0, rmin=0., rmax=1.) - M2 = PolarMapping('M2', 2, c1=0, c2=0, rmin=0., rmax=1.) - A = M1(A) - B = M2(B) - - domain = create_domain([A, B], [[A.get_boundary(axis=1, ext=1), B.get_boundary(axis=1, ext=-1), 1]], name='domain') - - elif domain_name == '2patch_nc': + else: + a_sm[p+2] = (1-a_sm[0]) * cc_0_ax[p] -a_sm[1] * cc_1_ax[p] + b_sm[p+2] = -b_sm[0] * cc_0_ax[p] + (1-b_sm[1]) * cc_1_ax[p] + + # proj constraint: + b_nb[p+2] = b_sm[p+2] + a_nb[p+2] = -(a_sm[p+2] + 2*b_sm[p+2]) + return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 + +def get_vector_moment_correction(patch_space, conf_comp, conf_axis, reg=[[0,0], [0,0]], p_moments=[[-1,-1], [-1,-1]], nquads=None, hom_bc=[[False, False],[False, False]]): + + proj_op = 0 + local_shape = [[patch_space.spaces[comp].spaces[axis].nbasis + for axis in range(2)] for comp in range(2)] + Nel = patch_space.ncells # number of elements + patch_space_x, patch_space_y = [patch_space.spaces[comp] for comp in range(2)] + degree = patch_space.degree + p_comp_axis = degree[conf_comp][conf_axis] + + breaks_comp_axis = [[breakpoints(patch_space.spaces[comp].knots[axis],degree[comp][axis]) + for axis in range(2)] for comp in range(2)] + if nquads is None: + # default: Gauss-Legendre quadratures should be exact for polynomials of deg ≤ 2*degree + nquads = [ degree[0][k]+1 for k in range(2)] + #Creating vector of weights for moments preserving + uw = [gauss_legendre( k-1 ) for k in nquads] + u = [u[::-1] for u,w in uw] + w = [w[::-1] for u,w in uw] + + grid = [np.array([deepcopy((0.5*(u[axis]+1)*(breaks_comp_axis[0][axis][i+1]-breaks_comp_axis[0][axis][i])+breaks_comp_axis[0][axis][i])) + for i in range(Nel[axis])]) + for axis in range(2)] + + _, basis_x, span_x, _ = patch_space_x.preprocess_regular_tensor_grid(grid,der=0) + _, basis_y, span_y, _ = patch_space_y.preprocess_regular_tensor_grid(grid,der=0) + span_x = [deepcopy(span_x[k] + patch_space_x.vector_space.starts[k] - patch_space_x.vector_space.shifts[k] * patch_space_x.vector_space.pads[k]) for k in range(2)] + span_y = [deepcopy(span_y[k] + patch_space_y.vector_space.starts[k] - patch_space_y.vector_space.shifts[k] * patch_space_y.vector_space.pads[k]) for k in range(2)] + basis = [basis_x, basis_y] + span = [span_x, span_y] + enddom = breaks_comp_axis[0][0][-1] + begdom = breaks_comp_axis[0][0][0] + denom = enddom-begdom + + # projection coefficients + + a_sm = np.zeros(p_moments+2+reg) # coefs of P B0 on same patch + a_nb = np.zeros(p_moments+2+reg) # coefs of P B0 on neighbor patch + b_sm = np.zeros(p_moments+3) # coefs of P B1 on same patch + b_nb = np.zeros(p_moments+3) # coefs of P B1 on neighbor patch + Correct_coef_bnd = np.zeros(p_moments+1) + Correct_coef_0 = np.zeros(p_moments+2+reg) + a_sm[0] = 1/2 + a_nb[0] = a_sm[0] + + if reg == 1: + b_sm = np.zeros(p_moments+3) # coefs of P B1 on same patch + b_nb = np.zeros(p_moments+3) # coefs of P B1 on neighbor patch + if proj_op == 0: + # new slope is average of old ones + a_sm[1] = 0 + elif proj_op == 1: + # new slope is average of old ones after averaging of interface coef + a_sm[1] = 1/2 + elif proj_op == 2: + # new slope is average of reconstructed ones using local values and slopes + a_sm[1] = 1/(2*p_comp_axis) + else: + # just to try something else + a_sm[1] = proj_op/2 + + a_nb[1] = 2*a_sm[0] - a_sm[1] + b_sm[0] = 0 + b_sm[1] = 1/2 + b_nb[0] = b_sm[0] + b_nb[1] = 2*b_sm[0] - b_sm[1] + + if p_moments >= 0: + # to preserve moments of degree p we need 1+p conforming basis functions in the patch (the "interior" ones) + # and for the given regularity constraint, there are local_shape[conf_comp][conf_axis]-2*(1+reg) such conforming functions + p_max = local_shape[conf_comp][conf_axis]-2*(1+reg) - 1 + if p_max < p_moments: + print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") + print( " ** WARNING -- WARNING -- WARNING ") + print(f" ** conf. projection imposing C{reg} smoothness on component {conf_comp} along axis {conf_axis}:") + print(f" ** there are not enough dofs in a patch to preserve moments of degree {p_moments} !") + print(f" ** Only able to preserve up to degree --> {p_max} <-- ") + print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") + p_moments = p_max + + # computing the contribution to every moment of the differents basis function + # for simplicity we assemble the full matrix with all basis functions (ok if patches not too large) + Mass_mat = np.zeros((p_moments+1,local_shape[conf_comp][conf_axis])) + for poldeg in range(p_moments+1): + for ie1 in range(Nel[conf_axis]): #loop on cells + # cell_size = breaks_comp_axis[conf_comp][conf_axis][ie1+1]-breakpoints_x_y[ie1] # todo: try without (probably not needed + for il1 in range(p_comp_axis+1): #loops on basis function in each cell + val=0. + for q1 in range(nquads[conf_axis]): #loops on quadrature points + v0 = basis[conf_comp][conf_axis][ie1,il1,0,q1] + xd = grid[conf_axis][ie1,q1] + val += w[conf_axis][q1]*v0*((enddom-xd)/denom)**poldeg + locind=span[conf_comp][conf_axis][ie1]-p_comp_axis+il1 + Mass_mat[poldeg,locind]+=val + Rhs_0 = Mass_mat[:,0] + + if reg == 0: + Mat_to_inv = Mass_mat[:,1:p_moments+2] + else: + Mat_to_inv = Mass_mat[:,2:p_moments+3] + Correct_coef_0 = np.linalg.solve(Mat_to_inv,Rhs_0) + cc_0_ax = Correct_coef_0 + + if reg == 1: + Rhs_1 = Mass_mat[:,1] + Correct_coef_1 = np.linalg.solve(Mat_to_inv,Rhs_1) + cc_1_ax = Correct_coef_1 + + if hom_bc: + # homogeneous bc is on the point value: no constraint on the derivatives + # so only the projection of B0 (to 0) has to be corrected + Mat_to_inv_bnd = Mass_mat[:,1:p_moments+2] + Correct_coef_bnd = np.linalg.solve(Mat_to_inv_bnd,Rhs_0) + + for p in range(0,p_moments+1): + # correction for moment preserving : + # we use the first p_moments+1 conforming ("interior") functions to preserve the p+1 moments + # modified by the C0 or C1 enforcement + if reg == 0: + a_sm[p+1] = (1-a_sm[0]) * cc_0_ax[p] + # proj constraint: + a_nb[p+1] = -a_sm[p+1] + + else: + a_sm[p+2] = (1-a_sm[0]) * cc_0_ax[p] -a_sm[1] * cc_1_ax[p] + b_sm[p+2] = -b_sm[0] * cc_0_ax[p] + (1-b_sm[1]) * cc_1_ax[p] + + # proj constraint: + b_nb[p+2] = b_sm[p+2] + a_nb[p+2] = -(a_sm[p+2] + 2*b_sm[p+2]) + + return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 + +def get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, spl_type): + grid = np.linspace(fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) + coarse_space_1d_k_plus = SplineSpace(degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) + + if not matching_interfaces: + E_1D = construct_extension_operator_1D( + domain=coarse_space_1d_k_plus, codomain=fine_space_1d) + + # Calculate the mass matrices + M_coarse = calculate_mass_matrix(coarse_space_1d, spl_type) + M_fine = calculate_mass_matrix(fine_space_1d, spl_type) + + if spl_type == 'B': + M_coarse[:, 0] *= 1e13 + M_coarse[:, -1] *= 1e13 + + M_coarse_inv = np.linalg.inv(M_coarse) + R_1D = M_coarse_inv @ E_1D.T @ M_fine + + if spl_type == 'B': + R_1D[0,0] = R_1D[-1,-1] = 1 - A = Square('A', bounds1=(0, 0.5), bounds2=(0, 1)) - B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 1)) - M1 = IdentityMapping('M1', dim=2) - M2 = IdentityMapping('M2', dim=2) - A = M1(A) - B = M2(B) + ER_1D = E_1D @ R_1D + - domain = create_domain([A, B], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1]], name='domain') + # id_err = np.linalg.norm(R_1D @ E_1D - sparse_eye( coarse_space_1d.nbasis, format="lil")) else: - domain = build_multipatch_domain(domain_name=domain_name) - - n_patches = len(domain) - - def levelof(k): - # some random refinement level (1 or 2 here) - return 1+((2*k) % 3) % 2 - - if len(domain) == 1: - ncells_h = { - 'M1(A)': [nc, nc], - } - - elif len(domain) == 2: - ncells_h = { - 'M1(A)': [nc, nc], - 'M2(B)': [2*nc, 2*nc], - } + ER_1D = R_1D = E_1D = sparse_eye( + fine_space_1d.nbasis, format="lil") + + return E_1D, R_1D, ER_1D + +def calculate_mass_matrix(space_1d, spl_type): + Nel = space_1d.ncells + deg = space_1d.degree + knots = space_1d.knots + + u, w = gauss_legendre(deg ) + # invert order + u = u[::-1] + w = w[::-1] + + nquad = len(w) + quad_x, quad_w = quadrature_grid(space_1d.breaks, u, w) + + coarse_basis = basis_ders_on_quad_grid(knots, deg, quad_x, 0, spl_type) + spans = elements_spans(knots, deg) + + Mass_mat = np.zeros((space_1d.nbasis,space_1d.nbasis)) + + for ie1 in range(Nel): #loop on cells + for il1 in range(deg+1): #loops on basis function in each cell + for il2 in range(deg+1): #loops on basis function in each cell + val=0. + + for q1 in range(nquad): #loops on quadrature points + v0 = coarse_basis[ie1,il1,0,q1] + w0 = coarse_basis[ie1,il2,0,q1] + val += quad_w[ie1, q1] * v0 * w0 + + locind1 = il1 + spans[ie1] - deg + locind2 = il2 + spans[ie1] - deg + Mass_mat[locind1,locind2] += val + + return Mass_mat + + +# if __name__ == '__main__': +# from psydac.feec.multipatch.conf_proj_martin import conf_proj_scalar_space, conf_proj_vector_space, conf_projectors_scipy + +# nc = 5 +# deg = 3 +# nonconforming = True +# plot_dir = 'run_plots_nc={}_deg={}'.format(nc, deg) + +# if plot_dir is not None and not os.path.exists(plot_dir): +# os.makedirs(plot_dir) + +# ncells = [nc, nc] +# degree = [deg, deg] +# reg_orders=[0,0] +# p_moments=[3,3] + +# nquads=None +# hom_bc=[False, False] +# print(' .. multi-patch domain...') + +# #domain_name = 'square_6' +# #domain_name = '2patch_nc_mapped' +# domain_name = '4patch_nc' +# #domain_name = "curved_L_shape" + +# if domain_name == '2patch_nc_mapped': + +# A = Square('A', bounds1=(0.5, 1), bounds2=(0, np.pi/2)) +# B = Square('B', bounds1=(0.5, 1), bounds2=(np.pi/2, np.pi)) +# M1 = PolarMapping('M1', 2, c1=0, c2=0, rmin=0., rmax=1.) +# M2 = PolarMapping('M2', 2, c1=0, c2=0, rmin=0., rmax=1.) +# A = M1(A) +# B = M2(B) + +# domain = create_domain([A, B], [[A.get_boundary(axis=1, ext=1), B.get_boundary(axis=1, ext=-1), 1]], name='domain') + +# elif domain_name == '2patch_nc': + +# A = Square('A', bounds1=(0, 0.5), bounds2=(0, 1)) +# B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 1)) +# M1 = IdentityMapping('M1', dim=2) +# M2 = IdentityMapping('M2', dim=2) +# A = M1(A) +# B = M2(B) + +# domain = create_domain([A, B], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1]], name='domain') +# elif domain_name == '4patch_nc': + +# A = Square('A', bounds1=(0, 0.5), bounds2=(0, 0.5)) +# B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 0.5)) +# C = Square('C', bounds1=(0, 0.5), bounds2=(0.5, 1)) +# D = Square('D', bounds1=(0.5, 1.), bounds2=(0.5, 1)) +# M1 = IdentityMapping('M1', dim=2) +# M2 = IdentityMapping('M2', dim=2) +# M3 = IdentityMapping('M3', dim=2) +# M4 = IdentityMapping('M4', dim=2) +# A = M1(A) +# B = M2(B) +# C = M3(C) +# D = M4(D) + +# domain = create_domain([A, B, C, D], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], +# [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], +# [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], +# [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1] ], name='domain') +# else: +# domain = build_multipatch_domain(domain_name=domain_name) + + +# n_patches = len(domain) + +# def levelof(k): +# # some random refinement level (1 or 2 here) +# return 1+((2*k) % 3) % 2 +# if nonconforming: +# if len(domain) == 1: +# ncells_h = { +# 'M1(A)': [nc, nc], +# } + +# elif len(domain) == 2: +# ncells_h = { +# 'M1(A)': [nc, nc], +# 'M2(B)': [2*nc, 2*nc], +# } + +# else: +# ncells_h = {} +# for k, D in enumerate(domain.interior): +# print(k, D.name) +# ncells_h[D.name] = [2**k *nc, 2**k * nc ] +# else: +# ncells_h = {} +# for k, D in enumerate(domain.interior): +# ncells_h[D.name] = [nc, nc] + +# print('ncells_h = ', ncells_h) +# backend_language = 'python' + +# t_stamp = time_count() +# print(' .. derham sequence...') +# derham = Derham(domain, ["H1", "Hcurl", "L2"]) + +# t_stamp = time_count(t_stamp) +# print(' .. discrete domain...') + +# domain_h = discretize(domain, ncells=ncells_h) # Vh space +# derham_h = discretize(derham, domain_h, degree=degree) +# V0h = derham_h.V0 +# V1h = derham_h.V1 + +# # test_extension_restriction(V1h, domain) + + +# #cP1_m_old = construct_V1_conforming_projection(V1h, True) +# # cP0_m_old = construct_V0_conforming_projection(V0h,hom_bc[0]) +# cP0_m = construct_scalar_conforming_projection(V0h, reg_orders, p_moments, nquads, hom_bc) +# cP1_m = construct_vector_conforming_projection(V1h, reg_orders, p_moments, nquads, hom_bc) + +# #print("Error:") +# #print( norm(cP1_m - conf_cP1_m) ) +# np.set_printoptions(linewidth=100000, precision=2, +# threshold=100000, suppress=True) +# #print(cP0_m.toarray()) + +# # apply cP1 on some discontinuous G - else: - ncells_h = {} - for k, D in enumerate(domain.interior): - ncells_h[D.name] = [levelof(k)*nc, levelof(k)*nc] +# # G_sol_log = [[lambda xi1, xi2, ii=i : ii+xi1+xi2**2 for d in [0,1]] for i in range(len(domain))] +# # G_sol_log = [[lambda xi1, xi2, kk=k : levelof(kk)-1 for d in [0,1]] for k in range(len(domain))] +# G_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0, 1]] +# for k in range(len(domain))] +# #G_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0, 1]] +# # for k in range(len(domain))] +# P0, P1, P2 = derham_h.projectors() - print('ncells_h = ', ncells_h) - backend_language = 'python' +# G1h = P1(G_sol_log) +# G1h_coeffs = G1h.coeffs.toarray() - t_stamp = time_count() - print(' .. derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) +# #G1h_coeffs = np.zeros(G1h_coeffs.size) +# #183, 182, 184 +# #G1h_coeffs[27] = 1 - t_stamp = time_count(t_stamp) - print(' .. discrete domain...') +# plot_field(numpy_coeffs=G1h_coeffs, Vh=V1h, space_kind='hcurl', +# plot_type='components', +# domain=domain, title='G1h', cmap='viridis', +# filename=plot_dir+'/G.png') - domain_h = discretize(domain, ncells=ncells_h) # Vh space - derham_h = discretize(derham, domain_h, degree=degree) - V0h = derham_h.V0 - V1h = derham_h.V1 + - cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) - +# G1h_conf_coeffs = cP1_m @ G1h_coeffs +# plot_field(numpy_coeffs=G1h_conf_coeffs, Vh=V1h, space_kind='hcurl', +# plot_type='components', +# domain=domain, title='PG', cmap='viridis', +# filename=plot_dir+'/PG.png') + + + +# #G0_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0]] +# # for k in range(len(domain))] +# G0_sol_log = [[lambda xi1, xi2, kk=k:kk for d in [0]] +# for k in range(len(domain))] +# #G0_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0]] +# # for k in range(len(domain))] +# G0h = P0(G0_sol_log) +# G0h_coeffs = G0h.coeffs.toarray() + +# #G0h_coeffs = np.zeros(G0h_coeffs.size) +# #183, 182, 184 +# #conforming +# # 30 - 24 +# # 28 - 23 +# #nc = 4, co: 59, co_ed:45, fi_ed:54 +# #G0h_coeffs[54] = 1 +# #G0h_coeffs[23] = 1 + +# plot_field(numpy_coeffs=G0h_coeffs, Vh=V0h, space_kind='h1', +# domain=domain, title='G0h', cmap='viridis', +# filename=plot_dir+'/G0.png') + +# G0h_conf_coeffs = (cP0_m@cP0_m-cP0_m) @ G0h_coeffs + +# plot_field(numpy_coeffs=G0h_conf_coeffs, Vh=V0h, space_kind='h1', +# domain=domain, title='PG0', cmap='viridis', +# filename=plot_dir+'/PG0.png') + +# plot_field(numpy_coeffs=cP0_m @ G0h_coeffs, Vh=V0h, space_kind='h1', +# domain=domain, title='PG00', cmap='viridis', +# filename=plot_dir+'/PG00.png') + +# if not nonconforming: +# cP0_martin = conf_proj_scalar_space(V0h, reg_orders, p_moments, nquads, hom_bc) + +# G0h_conf_coeffs_martin = cP0_martin @ G0h_coeffs +# #plot_field(numpy_coeffs=G0h_conf_coeffs_martin, Vh=V0h, space_kind='h1', +# # domain=domain, title='PG0_martin', cmap='viridis', +# # filename=plot_dir+'/PG0_martin.png') + +# import numpy as np +# import matplotlib.pyplot as plt +# reg = 0 +# reg_orders = [[reg-1, reg ], [reg, reg-1]] +# hom_bc_list = [[False, hom_bc[1]], [hom_bc[0], False]] +# deg_moments = [p_moments,p_moments] +# V1h = derham_h.V1 +# V1 = V1h.symbolic_space +# cP1_martin = conf_proj_vector_space(V1h, reg_orders=reg_orders, deg_moments=deg_moments, nquads=None, hom_bc_list=hom_bc_list) + +# #cP0_martin, cP1_martin, cP2_martin = conf_projectors_scipy(derham_h, single_space=None, reg=0, mom_pres=True, nquads=None, hom_bc=False) + +# G1h_conf_martin = cP1_martin @ G1h_coeffs - #print("Error:") - #print( norm(cP1_m - conf_cP1_m) ) - np.set_printoptions(linewidth=100000, precision=2, - threshold=100000, suppress=True) - #print(cP0_m.toarray()) - - # apply cP1 on some discontinuous G - - # G_sol_log = [[lambda xi1, xi2, ii=i : ii+xi1+xi2**2 for d in [0,1]] for i in range(len(domain))] - # G_sol_log = [[lambda xi1, xi2, kk=k : levelof(kk)-1 for d in [0,1]] for k in range(len(domain))] - #G_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0, 1]] - # for k in range(len(domain))] - G_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0, 1]] - for k in range(len(domain))] - P0, P1, P2 = derham_h.projectors() - - G1h = P1(G_sol_log) - G1h_coeffs = G1h.coeffs.toarray() - - plot_field(numpy_coeffs=G1h_coeffs, Vh=V1h, space_kind='hcurl', - plot_type='components', - domain=domain, title='G1h', cmap='viridis', - filename=plot_dir+'/G.png') - - G1h_conf_coeffs = cP1_m @ G1h_coeffs - - plot_field(numpy_coeffs=G1h_conf_coeffs, Vh=V1h, space_kind='hcurl', - plot_type='components', - domain=domain, title='PG', cmap='viridis', - filename=plot_dir+'/PG.png') - +# # plot_field(numpy_coeffs=G1h_conf_martin, Vh=V1h, space_kind='hcurl', +# # plot_type='components', +# # domain=domain, title='PG_martin', cmap='viridis', +# # filename=plot_dir+'/PG_martin.png') - #G0_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0]] - # for k in range(len(domain))] - #G0_sol_log = [[lambda xi1, xi2, kk=k:kk for d in [0]] - # for k in range(len(domain))] - G0_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0]] - for k in range(len(domain))] - G0h = P0(G0_sol_log) - G0h_coeffs = G0h.coeffs.toarray() +# plt.matshow((cP1_m - cP1_martin).toarray()) +# plt.colorbar() +# print(sp_norm(cP1_m - cP1_martin)) +# #plt.matshow((cP0_m).toarray()) - plot_field(numpy_coeffs=G0h_coeffs, Vh=V0h, space_kind='h1', - domain=domain, title='G0h', cmap='viridis', - filename=plot_dir+'/G0.png') +# #plt.matshow((cP0_martin).toarray()) +# #plt.show() - G0h_conf_coeffs = cP0_m @ G0h_coeffs +# #print( np.sum(cP0_m - cP0_martin)) +# # print( cP0_m - cP0_martin) - #G0h_conf_coeffs = G0h_conf_coeffs - G0h_coeffs - plot_field(numpy_coeffs=G0h_conf_coeffs, Vh=V0h, space_kind='h1', - domain=domain, title='PG0', cmap='viridis', - filename=plot_dir+'/PG0.png') +# print(sp_norm(cP0_m- cP0_m @ cP0_m)) +# print(sp_norm(cP1_m- cP1_m @ cP1_m)) diff --git a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py new file mode 100644 index 000000000..2ed5433a5 --- /dev/null +++ b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py @@ -0,0 +1,316 @@ +import numpy as np +import pytest + +from collections import OrderedDict +from sympde.topology import Derham, Square +from sympde.topology import IdentityMapping +from sympde.topology import Boundary, Interface, Union +from scipy.sparse.linalg import norm as sp_norm +from sympy import Tuple +from sympde.topology import Derham +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain + +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection + +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_rectangle, build_multipatch_domain +from psydac.feec.multipatch.utils_conga_2d import P_phys_l2, P_phys_hdiv, P_phys_hcurl, P_phys_h1 + + +def get_polynomial_function(degree, hom_bc_axes, domain): + x, y = domain.coordinates + if hom_bc_axes[0]: + assert degree[0] > 1 + g0_x = x * (x-np.pi) * (x-1.554)**(degree[0]-2) + else: + # if degree[0] > 1: + # g0_x = (x-0.543)**2 * (x-1.554)**(degree[0]-2) + # else: + g0_x = (x-0.25)#**degree[0] + + if hom_bc_axes[1]: + assert degree[1] > 1 + g0_y = y * (y-np.pi) * (y-0.324)**(degree[1]-2) + else: + # if degree[1] > 1: + # g0_y = (y-1.675)**2 * (y-0.324)**(degree[1]-2) + + # else: + g0_y = (y-0.75)#**degree[1] + + return g0_x * g0_y + +#============================================================================== +# @pytest.mark.parametrize('V1_type', ["Hcurl"]) +# @pytest.mark.parametrize('degree', [[2,2], [3,3]]) +# @pytest.mark.parametrize('nc', [2, 4]) +# @pytest.mark.parametrize('reg', [0]) +# @pytest.mark.parametrize('hom_bc', [[False, False], True]) +# @pytest.mark.parametrize('mom_pres', [False, True]) +# @pytest.mark.parametrize('domain_name', ["4patch_nc", "curved_L_shape"]) + +def test_conf_projectors_2d( + V1_type, + degree, + nc, + reg, + hom_bc, + mom_pres, + domain_name, + ): + + nquads=None + nonconforming=True + print(' .. multi-patch domain...') + + + if domain_name == '2patch_nc_mapped': + + A = Square('A', bounds1=(0.5, 1), bounds2=(0, np.pi/2)) + B = Square('B', bounds1=(0.5, 1), bounds2=(np.pi/2, np.pi)) + M1 = PolarMapping('M1', 2, c1=0, c2=0, rmin=0., rmax=1.) + M2 = PolarMapping('M2', 2, c1=0, c2=0, rmin=0., rmax=1.) + A = M1(A) + B = M2(B) + + domain = create_domain([A, B], [[A.get_boundary(axis=1, ext=1), B.get_boundary(axis=1, ext=-1), 1]], name='domain') + + elif domain_name == '2patch_nc': + + A = Square('A', bounds1=(0, 0.5), bounds2=(0, 1)) + B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 1)) + M1 = IdentityMapping('M1', dim=2) + M2 = IdentityMapping('M2', dim=2) + A = M1(A) + B = M2(B) + + domain = create_domain([A, B], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1]], name='domain') + + elif domain_name == '4patch_nc': + + A = Square('A', bounds1=(0, 0.5), bounds2=(0, 0.5)) + B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 0.5)) + C = Square('C', bounds1=(0, 0.5), bounds2=(0.5, 1)) + D = Square('D', bounds1=(0.5, 1.), bounds2=(0.5, 1)) + M1 = IdentityMapping('M1', dim=2) + M2 = IdentityMapping('M2', dim=2) + M3 = IdentityMapping('M3', dim=2) + M4 = IdentityMapping('M4', dim=2) + A = M1(A) + B = M2(B) + C = M3(C) + D = M4(D) + + domain = create_domain([A, B, C, D], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], + [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], + [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], + [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1] ], name='domain') + else: + domain = build_multipatch_domain(domain_name=domain_name) + + n_patches = len(domain) + + def levelof(k): + # some random refinement level (1 or 2 here) + return 1+((2*k) % 3) % 2 + + if nonconforming: + if len(domain) == 1: + ncells_h = { + 'M1(A)': [nc, nc], + } + + elif len(domain) == 2: + ncells_h = { + 'M1(A)': [nc, nc], + 'M2(B)': [2*nc, 2*nc], + } + elif len(domain) == 4: + ncells_h = { + 'M1(A)': [nc, nc], + 'M2(B)': [2*nc, 2*nc], + 'M3(C)': [2*nc, 2*nc], + 'M4(D)': [4*nc, 4*nc], + } + else: + ncells_h = {} + for k, D in enumerate(domain.interior): + print(k, D.name) + ncells_h[D.name] = [2**k *nc, 2**k * nc] + else: + ncells_h = {} + for k, D in enumerate(domain.interior): + ncells_h[D.name] = [nc, nc] + + print('ncells_h = ', ncells_h) + backend_language = 'python' + + print(' .. derham sequence...') + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + + print(ncells_h) + + domain_h = discretize(domain, ncells=ncells_h) # Vh space + derham_h = discretize(derham, domain_h, degree=degree) + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = [m.get_callable_mapping() for m in mappings.values()] + p_derham = Derham(domain, ["H1", V1_type, "L2"]) + + nquads = [(d + 1) for d in degree] + p_derham_h = discretize(p_derham, domain_h, degree=degree, nquads=nquads) + p_V0h = p_derham_h.V0 + p_V1h = p_derham_h.V1 + p_V2h = p_derham_h.V2 + + # full moment preservation only possible if enough interior functions in a patch (<=> enough cells) + full_mom_pres = mom_pres and (nc >= 3 + 2*reg[0]) and (nc >= 3 + 2*reg[1]) + # NOTE: if mom_pres but not full_mom_pres we could test reduced order moment preservation... + + # geometric projections (operators) + p_geomP0, p_geomP1, p_geomP2 = p_derham_h.projectors() + + # conforming projections (scipy matrices) + cP0 = construct_scalar_conforming_projection(V0h, reg, mom_pres, nquads, hom_bc) + cP1 = construct_vector_conforming_projection(V1h, reg, mom_pres, nquads, hom_bc) + cP2 = construct_scalar_conforming_projection(V2h, [reg[0]- 1, reg[1]-1], mom_pres, nquads, hom_bc) + + HOp0 = HodgeOperator(p_V0h, domain_h) + M0 = HOp0.get_dual_Hodge_sparse_matrix() # mass matrix + M0_inv = HOp0.to_sparse_matrix() # inverse mass matrix + + HOp1 = HodgeOperator(p_V1h, domain_h) + M1 = HOp1.get_dual_Hodge_sparse_matrix() # mass matrix + M1_inv = HOp1.to_sparse_matrix() # inverse mass matrix + + HOp2 = HodgeOperator(p_V2h, domain_h) + M2 = HOp2.get_dual_Hodge_sparse_matrix() # mass matrix + M2_inv = HOp2.to_sparse_matrix() # inverse mass matrix + + bD0, bD1 = p_derham_h.broken_derivatives_as_operators + + bD0 = bD0.to_sparse_matrix() # broken grad + bD1 = bD1.to_sparse_matrix() # broken curl or div + D0 = bD0 @ cP0 # Conga grad + D1 = bD1 @ cP1 # Conga curl or div + + np.allclose(sp_norm(cP0 - cP0@cP0), 0, 1e-12, 1e-12) # cP0 is a projection + print(sp_norm(cP0 - cP0@cP0)) + np.allclose(sp_norm(cP1 - cP1@cP1), 0, 1e-12, 1e-12) # cP1 is a projection + print(sp_norm(cP1 - cP1@cP1)) + np.allclose(sp_norm(cP2 - cP2@cP2), 0, 1e-12, 1e-12) # cP2 is a projection + print(sp_norm(cP2 - cP2@cP2)) + + np.allclose(sp_norm( D0 - cP1@D0), 0, 1e-12, 1e-12) # D0 maps in the conforming V1 space (where cP1 coincides with Id) + print(sp_norm( D0 - cP1@D0)) + np.allclose(sp_norm( D1 - cP2@D1), 0, 1e-12, 1e-12) # D1 maps in the conforming V2 space (where cP2 coincides with Id) + print(sp_norm( D1 - cP2@D1)) + + # comparing projections of polynomials which should be exact + + # tests on cP0: + g0 = get_polynomial_function(degree=degree, hom_bc_axes=[hom_bc,hom_bc], domain=domain) + g0h = P_phys_h1(g0, p_geomP0, domain, mappings_list) + g0_c = g0h.coeffs.toarray() + + tilde_g0_c = p_derham_h.get_dual_dofs(space='V0', f=g0, return_format='numpy_array') + g0_L2_c = M0_inv @ tilde_g0_c + + np.allclose(g0_c, g0_L2_c, 1e-12, 1e-12) # (P0_geom - P0_L2) polynomial = 0 + np.allclose(g0_c, cP0@g0_L2_c, 1e-12, 1e-12) # (P0_geom - confP0 @ P0_L2) polynomial= 0 + print(np.linalg.norm(g0_c- g0_L2_c)) + print(np.linalg.norm(g0_c- cP0@g0_L2_c)) + if full_mom_pres: + # testing that polynomial moments are preserved: + # the following projection should be exact for polynomials of proper degree (no bc) + # conf_P0* : L2 -> V0 defined by := for all phi in V0 + g0 = get_polynomial_function(degree=degree, hom_bc_axes=[False, False], domain=domain) + g0h = P_phys_h1(g0, p_geomP0, domain, mappings_list) + g0_c = g0h.coeffs.toarray() + + tilde_g0_c = p_derham_h.get_dual_dofs(space='V0', f=g0, return_format='numpy_array') + g0_star_c = M0_inv @ cP0.transpose() @ tilde_g0_c + np.allclose(g0_c, g0_star_c, 1e-12, 1e-12) # (P10_geom - P0_star) polynomial = 0 + print(np.linalg.norm(g0_c- g0_star_c)) + + # tests on cP1: + + G1 = Tuple( + get_polynomial_function(degree=[degree[0]-1,degree[1]], hom_bc_axes=[False,hom_bc], domain=domain), + get_polynomial_function(degree=[degree[0], degree[1]-1], hom_bc_axes=[hom_bc,False], domain=domain) + ) + + if V1_type == "Hcurl": + G1h = P_phys_hcurl(G1, p_geomP1, domain, mappings_list) + elif V1_type == "Hdiv": + G1h = P_phys_hdiv(G1, p_geomP1, domain, mappings_list) + G1_c = G1h.coeffs.toarray() + tilde_G1_c = p_derham_h.get_dual_dofs(space='V1', f=G1, return_format='numpy_array') + G1_L2_c = M1_inv @ tilde_G1_c + + np.allclose(G1_c, G1_L2_c, 1e-12, 1e-12) + print(np.linalg.norm(G1_c- G1_L2_c))# (P1_geom - P1_L2) polynomial = 0 + np.allclose(G1_c, cP1 @ G1_L2_c, 1e-12, 1e-12) # (P1_geom - confP1 @ P1_L2) polynomial= 0 + print(np.linalg.norm(G1_c- cP1 @ G1_L2_c)) + + + if full_mom_pres: + # as above + G1 = Tuple( + get_polynomial_function(degree=[degree[0]-1,degree[1]], hom_bc_axes=[False,False], domain=domain), + get_polynomial_function(degree=[degree[0], degree[1]-1], hom_bc_axes=[False,False], domain=domain) + ) + + G1h = P_phys_hcurl(G1, p_geomP1, domain, mappings_list) + G1_c = G1h.coeffs.toarray() + + tilde_G1_c = p_derham_h.get_dual_dofs(space='V1', f=G1, return_format='numpy_array') + G1_star_c = M1_inv @ cP1.transpose() @ tilde_G1_c + np.allclose(G1_c, G1_star_c, 1e-12, 1e-12) # (P1_geom - P1_star) polynomial = 0 + print(np.linalg.norm(G1_c- G1_star_c)) + + # tests on cP2 (non trivial for reg = 1): + g2 = get_polynomial_function(degree=[degree[0]-1,degree[1]-1], hom_bc_axes=[False,False], domain=domain) + g2h = P_phys_l2(g2, p_geomP2, domain, mappings_list) + g2_c = g2h.coeffs.toarray() + + tilde_g2_c = p_derham_h.get_dual_dofs(space='V2', f=g2, return_format='numpy_array') + g2_L2_c = M2_inv @ tilde_g2_c + + np.allclose(g2_c, g2_L2_c, 1e-12, 1e-12) # (P2_geom - P2_L2) polynomial = 0 + np.allclose(g2_c, cP2 @ g2_L2_c, 1e-12, 1e-12) # (P2_geom - confP2 @ P2_L2) polynomial = 0 + + if full_mom_pres: + # as above, here with same degree and bc as + # tilde_g2_c = p_derham_h.get_dual_dofs(space='V2', f=g2, return_format='numpy_array', nquads=nquads) + g2_star_c = M2_inv @ cP2.transpose() @ tilde_g2_c + np.allclose(g2_c, g2_star_c, 1e-12, 1e-12) # (P2_geom - P2_star) polynomial = 0 + +if __name__ == '__main__': + V1_type = "Hcurl" + nc = 7 + deg = 2 + + degree = [deg, deg] + reg=[0,0] + mom_pres=[5,5] + hom_bc = [False, False] + + # domain_name = 'square_6' + # domain_name = 'curved_L_shape' + # domain_name = '2patch_nc_mapped' + domain_name = '4patch_nc' + + test_conf_projectors_2d( + V1_type, + degree, + nc, + reg, + hom_bc, + mom_pres, + domain_name + ) \ No newline at end of file diff --git a/psydac/feec/multipatch/utils_conga_2d.py b/psydac/feec/multipatch/utils_conga_2d.py index 23046ed32..bc3fb83ad 100644 --- a/psydac/feec/multipatch/utils_conga_2d.py +++ b/psydac/feec/multipatch/utils_conga_2d.py @@ -35,6 +35,34 @@ def P2_phys(f_phys, P2, domain, mappings_list): f_log = [pull_2d_l2(f, m.get_callable_mapping()) for m in mappings_list] return P2(f_log) +# commuting projections on the physical domain (should probably be in the interface) +def P_phys_h1(f_phys, P0, domain, mappings_list): + f = lambdify(domain.coordinates, f_phys) + if len(mappings_list) == 1: + m = mappings_list[0] + f_log = pull_2d_h1(f, m) + else: + f_log = [pull_2d_h1(f, m) for m in mappings_list] + return P0(f_log) + +def P_phys_hcurl(f_phys, P1, domain, mappings_list): + f_x = lambdify(domain.coordinates, f_phys[0]) + f_y = lambdify(domain.coordinates, f_phys[1]) + f_log = [pull_2d_hcurl([f_x, f_y], m) for m in mappings_list] + return P1(f_log) + +def P_phys_hdiv(f_phys, P1, domain, mappings_list): + f_x = lambdify(domain.coordinates, f_phys[0]) + f_y = lambdify(domain.coordinates, f_phys[1]) + f_log = [pull_2d_hdiv([f_x, f_y], m) for m in mappings_list] + return P1(f_log) + +def P_phys_l2(f_phys, P2, domain, mappings_list): + f = lambdify(domain.coordinates, f_phys) + f_log = [pull_2d_l2(f, m) for m in mappings_list] + return P2(f_log) + + def get_kind(space='V*'): # temp helper if space == 'V0': From 16925707ecedc5c1628214c3b42bda6028d269c2 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 27 Nov 2023 18:26:23 +0100 Subject: [PATCH 16/88] adapt files to new projection --- .../examples/h1_source_pbms_conga_2d.py | 7 ++- .../examples/hcurl_eigen_pbms_conga_2d.py | 8 +-- .../examples/hcurl_source_pbms_conga_2d.py | 6 +- .../examples/mixed_source_pbms_conga_2d.py | 6 +- .../multipatch/examples/ppc_test_cases.py | 21 +++++++ .../examples_nc/h1_source_pbms_nc.py | 8 ++- .../examples_nc/hcurl_eigen_pbms_nc.py | 4 +- .../examples_nc/hcurl_source_pbms_nc.py | 6 +- .../examples_nc/timedomain_maxwell_nc.py | 15 +++-- .../examples_nc/timedomain_maxwell_pml.py | 2 +- .../timedomain_maxwells_testcase.py | 17 +++--- .../feec/multipatch/non_matching_operators.py | 1 - .../test_feec_conf_projectors_cart_2d.py | 60 +++++++++---------- .../tests/test_feec_maxwell_multipatch_2d.py | 2 +- .../tests/test_feec_poisson_multipatch_2d.py | 6 +- 15 files changed, 96 insertions(+), 73 deletions(-) diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index bbe83f525..2dcf304df 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -25,7 +25,7 @@ from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField @@ -129,8 +129,8 @@ def solve_h1_source_pbm( print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) - # cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) + # cP1_m = construct_vector_conforming_projection(V1h, domain_h, hom_bc=True) if not os.path.exists(plot_dir): os.makedirs(plot_dir) @@ -267,6 +267,7 @@ def lift_u_bc(u_bc): mu=1, #1, domain_name=domain_name, source_type=source_type, + source_proj = 'P_geom', backend_language='pyccel-gcc', plot_source=True, plot_dir='./plots/h1_tests_source_february/'+run_dir, diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index 3f708bea2..f1cfc3d71 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -18,9 +18,9 @@ from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection -def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language='python', mu=1, nu=1, gamma_h=10, +def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language='python', mu=1, nu=0, gamma_h=10, sigma=None, nb_eigs=4, nb_eigs_plot=4, plot_dir=None, hide_plots=True, m_load_dir="",skip_eigs_threshold = 1e-7,): """ @@ -103,8 +103,8 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, True) - cP1_m = construct_V1_conforming_projection(V1h, domain_h, True) + cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True, True]) + cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True, True]) print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index 94f4992ba..c2749fb05 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -29,7 +29,7 @@ from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection def solve_hcurl_source_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', @@ -164,8 +164,8 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building the conforming Projection operators and matrices...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) - cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) + cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True,True]) t_stamp = time_count(t_stamp) print('building the broken differential operators and matrices...') diff --git a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py index 81212af59..d686a517a 100644 --- a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py @@ -28,7 +28,7 @@ from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import get_eigenvalues from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection def solve_magnetostatic_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2_wcurl_J', @@ -177,8 +177,8 @@ def P2_phys(f_phys): print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) - cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) + cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True,True]) print('broken differential operators...') diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index 339d1f015..6f826124a 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -90,6 +90,27 @@ def get_Gaussian_beam(x_0, y_0, domain=None): x = x - x_0 y = y - y_0 + k = (np.pi, 0) + nk = np.sqrt(k[0]**2 + k[1]**2) + + v = (k[0]/nk, k[1]/nk) + + sigma = 0.25 + + xy = x**2 + y**2 + ef = exp( - xy/(2*sigma**2) ) + + E = cos(k[1] * x + k[0] * y) * ef + B = (-v[1]*x + v[0]*y)/(sigma**2) * E + + return Tuple(v[0]*E, v[1]*E), B + +def get_easy_Gaussian_beam(x_0, y_0, domain=None): + # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v + x,y = domain.coordinates + x = x - x_0 + y = y - y_0 + k = pi sigma = 0.5 diff --git a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py index 9e81eb69d..a06204c83 100644 --- a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py @@ -25,7 +25,7 @@ from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager from psydac.linalg.utilities import array_to_psydac @@ -131,8 +131,8 @@ def solve_h1_source_pbm_nc( print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) - # cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) + # cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True, True]) if not os.path.exists(plot_dir): os.makedirs(plot_dir) @@ -257,6 +257,8 @@ def lift_u_bc(u_bc): domain_name = 'pretzel_f' # domain_name = 'curved_L_shape' nc = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) + + deg = 2 # nc = 2 diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py index 77d7b1db9..d62515534 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -28,7 +28,7 @@ from psydac.fem.basic import FemField from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain -from psydac.feec.multipatch.non_matching_operators import construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -127,7 +127,7 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) cP0_m = None - cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) + cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True,True]) t_stamp = time_count(t_stamp) print('broken differential operators...') diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py index 7f6b314cf..30b1ca36c 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py @@ -31,7 +31,7 @@ from psydac.fem.basic import FemField from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager def solve_hcurl_source_pbm_nc( @@ -190,8 +190,8 @@ def solve_hcurl_source_pbm_nc( #cP1_m = cP1.to_sparse_matrix() # Try the NC one - cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=True) - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=True) + cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True, True]) + cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True, True]) t_stamp = time_count(t_stamp) print(' .. broken differential operators...') diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py index 0fd1c0e11..d9b5890fc 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py @@ -31,7 +31,7 @@ from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -322,10 +322,9 @@ def solve_td_maxwell_pbm(*, t_stamp = time_count(t_stamp) print(' .. conforming Projection operators...') - - cP0_m = construct_V0_conforming_projection(V0h, domain_h, hom_bc=False) - cP1_m = construct_V1_conforming_projection(V1h, domain_h, hom_bc=False) - + #(Vh, reg_orders=[0,0], p_moments=[-1,-1], nquads=None, hom_bc=[False, False]) + cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) if conf_proj == 'GSP': print(' [* GSP-conga: using Geometric Spline conf Projections ]') @@ -826,7 +825,7 @@ def plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_s #E0 = get_easy_Gaussian_beam_E_2(x_0=0.05, y_0=0.05, domain=domain) #B0 = get_easy_Gaussian_beam_B_2(x_0=0.05, y_0=0.05, domain=domain) - E0, B0 = get_Gaussian_beam(x_0=3.14, y_0=0.05, domain=domain) + E0, B0 = get_Gaussian_beam(y_0=3.14, x_0=3.14 , domain=domain) #B0 = get_easy_Gaussian_beam_B(x_0=3.14, y_0=0.05, domain=domain) if E0_proj == 'P_geom': @@ -1004,11 +1003,11 @@ def compute_diags(E_c, B_c, J_c, nt): print("Do some PP") PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) - PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=[6]*2,snapshots='all', fields = 'Eh' ) + PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=2,snapshots='all', fields = 'Eh' ) PM.close() PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=[6]*2,snapshots='all', fields = 'Bh' ) + PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=2,snapshots='all', fields = 'Bh' ) PM.close() # plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=0, nt_end=Nt, diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py index 4d0f730b5..7a645c668 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py @@ -31,7 +31,7 @@ from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection, construct_V0_conforming_projection, construct_V1_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from sympde.calculus import grad, dot, curl, cross diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py index 493b922b0..f47734832 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py @@ -1,5 +1,5 @@ import numpy as np -from psydac.feec.multipatch.examples_nc.td_maxwell_conga_2d_nc_absorbing import solve_td_maxwell_pbm +from psydac.feec.multipatch.examples_nc.timedomain_maxwell_nc import solve_td_maxwell_pbm from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file @@ -21,7 +21,7 @@ # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # Parameters to be changed in the batch run -deg = 4 +deg = 3 # Common simulation parameters #domain_name = 'square_6' @@ -29,13 +29,14 @@ #domain_name = 'pretzel_f' #non-conf domains -domain=[[0, 2*np.pi],[0, 3*np.pi]] # interval in x- and y-direction +domain=[[0, 2*np.pi],[0, 2*np.pi]] # interval in x- and y-direction domain_name = 'refined_square' #use isotropic meshes (probably with a square domain) # 4x8= 64 patches #care for the transpose -ncells = np.array([[8, 8], - [8, 8]]) +ncells = np.array([[16, 16], + [16, 16]]) + #ncells = np.array([[8,8,16,8], # [8,8,16,8], # [8,8,16,8], @@ -75,7 +76,7 @@ E0_proj = 'P_geom' # 'P_geom' # projection used for initial E0 (B0 = 0 in all cases) backend = 'pyccel-gcc' project_sol = True # whether cP1 E_h is plotted instead of E_h -quad_param = 4 # multiplicative parameter for quadrature order in (bi)linear forms discretization +quad_param = 4 # multiplicative parameter for quadrature order in (bi)linear forms discretizaion gamma_h = 0 # jump dissipation parameter (not used in paper) conf_proj = 'GSP' # 'BSP' # type of conforming projection operators (averaging B-spline or Geometric-splines coefficients) hide_plots = True @@ -88,7 +89,7 @@ E0_type = 'pulse_2' # non-zero initial conditions source_type = 'zero' # no current source source_omega = None - final_time = 8 # wave transit time in domain is > 4 + final_time = 9.02 # wave transit time in domain is > 4 dt_max = None plot_source = False @@ -172,7 +173,7 @@ else: raise ValueError(J_proj_case) -case_dir = 'talk_wave_td_maxwell_' + test_case + '_J_proj=' + J_proj_case + '_qp{}'.format(quad_param) +case_dir = 'nov14_' + test_case + '_J_proj=' + J_proj_case + '_qp{}'.format(quad_param) if filter_source: case_dir += '_Jfilter' else: diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index be10f036f..2632f7511 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -1451,7 +1451,6 @@ def calculate_mass_matrix(space_1d, spl_type): # if __name__ == '__main__': -# from psydac.feec.multipatch.conf_proj_martin import conf_proj_scalar_space, conf_proj_vector_space, conf_projectors_scipy # nc = 5 # deg = 3 diff --git a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py index 2ed5433a5..bae7682e8 100644 --- a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py @@ -42,13 +42,13 @@ def get_polynomial_function(degree, hom_bc_axes, domain): return g0_x * g0_y #============================================================================== -# @pytest.mark.parametrize('V1_type', ["Hcurl"]) -# @pytest.mark.parametrize('degree', [[2,2], [3,3]]) -# @pytest.mark.parametrize('nc', [2, 4]) -# @pytest.mark.parametrize('reg', [0]) -# @pytest.mark.parametrize('hom_bc', [[False, False], True]) -# @pytest.mark.parametrize('mom_pres', [False, True]) -# @pytest.mark.parametrize('domain_name', ["4patch_nc", "curved_L_shape"]) +@pytest.mark.parametrize('V1_type', ["Hcurl"]) +@pytest.mark.parametrize('degree', [[3,3]]) +@pytest.mark.parametrize('nc', [4]) +@pytest.mark.parametrize('reg', [[0,0]]) +@pytest.mark.parametrize('hom_bc', [[False, False]]) +@pytest.mark.parametrize('mom_pres', [[-1, -1]]) +@pytest.mark.parametrize('domain_name', ["4patch_nc"]) def test_conf_projectors_2d( V1_type, @@ -175,7 +175,7 @@ def levelof(k): p_geomP0, p_geomP1, p_geomP2 = p_derham_h.projectors() # conforming projections (scipy matrices) - cP0 = construct_scalar_conforming_projection(V0h, reg, mom_pres, nquads, hom_bc) + cP0 = construct_scalar_conforming_projection(V0h, reg, mom_pres, nquads, hom_bc) cP1 = construct_vector_conforming_projection(V1h, reg, mom_pres, nquads, hom_bc) cP2 = construct_scalar_conforming_projection(V2h, [reg[0]- 1, reg[1]-1], mom_pres, nquads, hom_bc) @@ -290,27 +290,27 @@ def levelof(k): g2_star_c = M2_inv @ cP2.transpose() @ tilde_g2_c np.allclose(g2_c, g2_star_c, 1e-12, 1e-12) # (P2_geom - P2_star) polynomial = 0 -if __name__ == '__main__': - V1_type = "Hcurl" - nc = 7 - deg = 2 +# if __name__ == '__main__': +# V1_type = "Hcurl" +# nc = 7 +# deg = 2 - degree = [deg, deg] - reg=[0,0] - mom_pres=[5,5] - hom_bc = [False, False] +# degree = [deg, deg] +# reg=[0,0] +# mom_pres=[5,5] +# hom_bc = [False, False] - # domain_name = 'square_6' - # domain_name = 'curved_L_shape' - # domain_name = '2patch_nc_mapped' - domain_name = '4patch_nc' - - test_conf_projectors_2d( - V1_type, - degree, - nc, - reg, - hom_bc, - mom_pres, - domain_name - ) \ No newline at end of file +# # domain_name = 'square_6' +# # domain_name = 'curved_L_shape' +# # domain_name = '2patch_nc_mapped' +# domain_name = '4patch_nc' + +# test_conf_projectors_2d( +# V1_type, +# degree, +# nc, +# reg, +# hom_bc, +# mom_pres, +# domain_name +# ) \ No newline at end of file diff --git a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py index 4d88e519f..5b9668c18 100644 --- a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py @@ -137,7 +137,7 @@ def test_maxwell_eigen_curved_L_shape_nc(): for k in range(n_errs): error += (eigenvalues[k]-ref_sigmas[k])**2 error = np.sqrt(error) - + assert abs(error - 0.004289103786542442)<1e-10 def test_maxwell_eigen_curved_L_shape_dg(): diff --git a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py index c18cfe0a2..59e19ca3b 100644 --- a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py @@ -20,7 +20,7 @@ def test_poisson_pretzel_f(): plot_source=False, plot_dir='./plots/h1_tests_source_february/'+run_dir) - assert abs(l2_error-0.11860734907095004)<1e-10 + assert abs(l2_error-0.1173467869129417)<1e-10 def test_poisson_pretzel_f_nc(): @@ -38,8 +38,8 @@ def test_poisson_pretzel_f_nc(): backend_language='pyccel-gcc', plot_source=False, plot_dir='./plots/h1_tests_source_february/'+run_dir) - - assert abs(l2_error-0.04324704991715671)<1e-10 + print(l2_error) + assert abs(l2_error-0.03821274975800339)<1e-10 #============================================================================== # CLEAN UP SYMPY NAMESPACE #============================================================================== From 544a23750512c111cb47cc6fb8c986dd02a27b35 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 28 Nov 2023 13:21:44 +0100 Subject: [PATCH 17/88] make codacy happy --- psydac/feec/multipatch/multipatch_domain_utilities.py | 2 +- psydac/feec/multipatch/non_matching_operators.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index e19b74a33..76d507fd9 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -629,7 +629,7 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): return domain -def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np.pi, y_min=0, y_max=np.pi, perio=[True,True], ncells=[4,4], comm=None, F_name='Identity'): +def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np.pi, y_min=0, y_max=np.pi, perio=(True,True), ncells=(4,4), comm=None, F_name='Identity'): """ Create a 2D multipatch rectangle domain with the prescribed number of patch in each direction. (copied from Valentin's code) diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index 2632f7511..b6ea734d4 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -530,7 +530,7 @@ def get_corners(domain, boundary_only): return corner_data -def construct_scalar_conforming_projection(Vh, reg_orders=[0,0], p_moments=[-1,-1], nquads=None, hom_bc=[False, False]): +def construct_scalar_conforming_projection(Vh, reg_orders=(0,0), p_moments=(-1,-1), nquads=None, hom_bc=(False, False)): #construct conforming projection for a 2-dimensional scalar space dim_tot = Vh.nbasis @@ -917,7 +917,7 @@ def construct_scalar_conforming_projection(Vh, reg_orders=[0,0], p_moments=[-1,- return Proj_edge @ Proj_vertex -def construct_vector_conforming_projection(Vh, reg_orders= [0,0], p_moments=[-1,-1], nquads=None, hom_bc=[False, False]): +def construct_vector_conforming_projection(Vh, reg_orders= (0,0), p_moments=(-1,-1), nquads=None, hom_bc=(False, False)): dim_tot = Vh.nbasis # fully discontinuous space @@ -1249,7 +1249,7 @@ def get_scalar_moment_correction(patch_space, conf_axis, reg=0, p_moments=-1, nq a_nb[p+2] = -(a_sm[p+2] + 2*b_sm[p+2]) return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 -def get_vector_moment_correction(patch_space, conf_comp, conf_axis, reg=[[0,0], [0,0]], p_moments=[[-1,-1], [-1,-1]], nquads=None, hom_bc=[[False, False],[False, False]]): +def get_vector_moment_correction(patch_space, conf_comp, conf_axis, reg=([0,0], [0,0]), p_moments=([-1,-1], [-1,-1]), nquads=None, hom_bc=([False, False],[False, False])): proj_op = 0 local_shape = [[patch_space.spaces[comp].spaces[axis].nbasis From 7b357bb6141259089a78f17f4a667528cba054ff Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 12 Dec 2023 13:51:55 +0100 Subject: [PATCH 18/88] add some pml experiments --- .../multipatch/examples/ppc_test_cases.py | 46 +- .../multipatch/examples_nc/interface_pml.py | 467 ++++++++++++++++++ .../examples_nc/timedomain_maxwell_min.py | 395 +++++++++++++++ .../examples_nc/timedomain_maxwell_pml.py | 13 +- .../feec/multipatch/non_matching_operators.py | 4 +- 5 files changed, 915 insertions(+), 10 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/interface_pml.py create mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index 6f826124a..5cbe53480 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -84,13 +84,55 @@ def get_Delta_phi_pulse(x_0, y_0, domain=None, pp=False): return f +def get_Gaussian_beam_old(x_0, y_0, domain=None): + # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v + x,y = domain.coordinates + x = x - x_0 + y = y - y_0 + + k = (10, 0) + nk = np.sqrt(k[0]**2 + k[1]**2) + + v = (k[0]/nk, k[1]/nk) + + sigma = 0.05 + + xy = x**2 + y**2 + ef = exp( - xy/(2*sigma**2) ) + + E = cos(k[1] * x + k[0] * y) * ef + B = (-v[1]*x + v[0]*y)/(sigma**2) * E + + return Tuple(v[0]*E, v[1]*E), B + +from sympy.functions.special.error_functions import erf def get_Gaussian_beam(x_0, y_0, domain=None): # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v x,y = domain.coordinates + x = x - x_0 y = y - y_0 - k = (np.pi, 0) + sigma = 0.1 + + xy = x**2 + y**2 + ef = 1/(sigma**2) * exp( - xy/(2*sigma**2) ) + + # E = curl exp + E = Tuple( y * ef, -x * ef) + + # B = curl E + B = (xy/(sigma**2) - 2) * ef + + return E, B + +def get_diag_Gaussian_beam(x_0, y_0, domain=None): + # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v + x,y = domain.coordinates + x = x - x_0 + y = y - y_0 + + k = (np.pi, np.pi) nk = np.sqrt(k[0]**2 + k[1]**2) v = (k[0]/nk, k[1]/nk) @@ -104,7 +146,7 @@ def get_Gaussian_beam(x_0, y_0, domain=None): B = (-v[1]*x + v[0]*y)/(sigma**2) * E return Tuple(v[0]*E, v[1]*E), B - + def get_easy_Gaussian_beam(x_0, y_0, domain=None): # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v x,y = domain.coordinates diff --git a/psydac/feec/multipatch/examples_nc/interface_pml.py b/psydac/feec/multipatch/examples_nc/interface_pml.py new file mode 100644 index 000000000..172db3cfb --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/interface_pml.py @@ -0,0 +1,467 @@ +from pytest import param +from mpi4py import MPI + +import os +import numpy as np +import scipy as sp +from collections import OrderedDict +import matplotlib.pyplot as plt + +from sympy import lambdify, Matrix + +from scipy.sparse.linalg import spsolve +from scipy import special + +from sympde.calculus import dot +from sympde.topology import element_of +from sympde.expr.expr import LinearForm +from sympde.expr.expr import integral, Norm +from sympde.topology import Derham + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv +from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam, get_diag_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain + +from sympde.calculus import grad, dot, curl, cross +from sympde.topology import NormalVector +from sympde.expr.expr import BilinearForm +from sympde.topology import elements_of +from sympde import Tuple +from sympde.topology import Square, Domain +from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping #TransposedPolarMapping + +from psydac.api.postprocessing import OutputManager, PostProcessManager +from sympy.functions.special.error_functions import erf + +from psydac.feec.multipatch.non_matching_operators import get_moment_pres_scalar_extension_restriction +from psydac.fem.splines import SplineSpace + +def run_sim(): + ## Minimal example for a PML implementation of the Time-Domain Maxwells equation + ncells = [8, 16] + degree = [3,3] + plot_dir = "plots/PML/interface_diffusion" + final_time = 5 + + OmegaLog1 = Square('OmegaLog1',bounds1=(0., 2*np.pi), bounds2=(0., np.pi)) + mapping_1 = IdentityMapping('M1',2) + domain_1 = mapping_1(OmegaLog1) + + OmegaLog2 = Square('OmegaLog2',bounds1=(0., 2*np.pi), bounds2=(np.pi, 2*np.pi)) + mapping_2 = IdentityMapping('M2',2) + domain_2 = mapping_2(OmegaLog2) + + patches = [domain_1, domain_2] + + interfaces = [ + [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1] + ] + domain = create_domain(patches, interfaces, name='domain') + + ncells_h = {patch.name: [2 * ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + domain_h = discretize(domain, ncells=ncells_h) + derham_h = discretize(derham, domain_h, degree=degree) + + nquads = [4*(d + 1) for d in degree] + P0, P1, P2 = derham_h.projectors(nquads=nquads) + + + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + + I1 = IdLinearOperator(V1h) + I1_m = I1.to_sparse_matrix() + + I2 = IdLinearOperator(V2h) + I2_m = I2.to_sparse_matrix() + + I0 = IdLinearOperator(V0h) + I0_m = I0.to_sparse_matrix() + + backend = 'pyccel-gcc' + + H0 = HodgeOperator(V0h, domain_h) + H1 = HodgeOperator(V1h, domain_h) + H2 = HodgeOperator(V2h, domain_h) + + dH0_m = H0.to_sparse_matrix() + H0_m = H0.get_dual_Hodge_sparse_matrix() + dH1_m = H1.to_sparse_matrix() + H1_m = H1.get_dual_Hodge_sparse_matrix() + dH2_m = H2.to_sparse_matrix() + H2_m = H2.get_dual_Hodge_sparse_matrix() + cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + + def patch_extension_restriction(coarse_space, fine_space): + # coarse patch, fine patch -> Matrix: fine -> coarse + E_xy = [] + R_xy = [] + for k in range(2): + cs_k = coarse_space.spaces[k] + knots = [ (k - cs_k.knots[0])/(cs_k.knots[-1] - cs_k.knots[0]) for k in cs_k.knots] + css_k = SplineSpace(cs_k.degree, knots = knots, basis=cs_k.basis) + + fs_k = fine_space.spaces[k] + knots = [ (k - fs_k.knots[0])/(fs_k.knots[-1] - fs_k.knots[0]) for k in fs_k.knots] + fss_k = SplineSpace(fs_k.degree, knots=knots, basis=fs_k.basis) + + matching = (css_k.ncells == fss_k.ncells) + E_k, R_k, ER_k = get_moment_pres_scalar_extension_restriction(matching, css_k, fss_k, css_k.basis) + E_xy.append(E_k) + R_xy.append(R_k) + + E = np.kron(E_xy[0].toarray(), E_xy[1].toarray()) + if matching: + R = np.kron(R_xy[0].toarray(), R_xy[1].toarray()) + else: + R = np.kron(R_xy[0], R_xy[1]) + + return E, R + + def global_matrices(V0h, V1h, V2h): + + E, R = patch_extension_restriction(V0h.spaces[0], V0h.spaces[1]) + n0 = V0h.spaces[0].nbasis + n1 = V0h.spaces[1].nbasis + R0_global = np.block([[np.eye(n0), np.zeros((n0, n1))], + [np.zeros((n1, n0)), E@R]]) + I0_global = np.eye(n0+n0) + + # first component + # E, R = patch_extension_restriction(V1h.spaces[0].spaces[0], V1h.spaces[1].spaces[0]) + n = V1h.spaces[0].nbasis + #10 = V1h.spaces[1].spaces[0].nbasis + R00_global = np.eye(n) #np.block([[np.eye(n00), np.zeros((n00, n10))], + #[np.zeros((n10, n00)), E@R]]) + + #second component + E, R = patch_extension_restriction(V1h.spaces[0].spaces[0], V1h.spaces[1].spaces[0]) + R0 = E@R + E, R = patch_extension_restriction(V1h.spaces[0].spaces[1], V1h.spaces[1].spaces[1]) + R1 = E@R + + n01 = V1h.spaces[1].spaces[0].nbasis + n11 = V1h.spaces[1].spaces[1].nbasis + + R11_global = np.block([[R0, np.zeros((n01, n11))], + [np.zeros((n11, n01)), R1]]) + + m11 = n11 + n01 + R1_global = np.block([[R00_global, np.zeros((n, m11))], + [np.zeros((m11, n)), R11_global]]) + I1_global = np.eye(n+n01+m11) + + E, R = patch_extension_restriction(V2h.spaces[0], V2h.spaces[1]) + n0 = V2h.spaces[0].nbasis + n1 = V2h.spaces[1].nbasis + R2_global = np.block([[np.eye(n0), np.zeros((n0, n1))], + [np.zeros((n1, n0)), E@R]]) + I2_global = np.eye(n0+n0) + + return R0_global, R1_global, R2_global + + R0_global, R1_global, R2_global = global_matrices(V0h, V1h, V2h) + + + ## boundary PML + u, v = elements_of(derham.V1, names='u, v') + x,y = domain.coordinates + + u1 = dot(Tuple(1,0),u) + u2 = dot(Tuple(0,1),u) + v1 = dot(Tuple(1,0),v) + v2 = dot(Tuple(0,1),v) + + def heaviside(x_direction, xmin, xmax, delta, sign, domain, fact): + x,y = domain.coordinates + + if sign == -1: + d = xmax - delta + else: + d = xmin + delta + + if x_direction == True: + return 1/2*(erf(-sign*(x-d) *fact)+1) + else: + return 1/2*(erf(-sign*(y-d) *fact)+1) + + def parabola(x_direction, xmin, xmax, delta, sign, domain): + x,y = domain.coordinates + + if sign == -1: + d = xmax - delta + else: + d = xmin + delta + + if x_direction == True: + return ((x - d)/delta)**2 + else: + return ((y - d)/delta)**2 + + def sigma_fun(x, xmin, xmax, delta, sign, sigma_m, domain): + return sigma_m * heaviside(x, xmin, xmax, delta, sign, domain, 1000) * parabola(x, xmin, xmax, delta, sign, domain) + + def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): + return sigma_fun(x, xmin, xmax, delta, 1, sigma_m, domain) + sigma_fun(x, xmin, xmax, delta, -1, sigma_m, domain) + + delta = np.pi/6 + xmin = 0 + xmax = 2*np.pi + ymin = 0 + ymax = 2*np.pi + sigma_0 = 20 + + sigma_x = sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) + sigma_y = sigma_fun_sym(False, ymin, ymax, delta, sigma_0, domain) + + mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) + massh = discretize(mass, domain_h, [V1h, V1h]) + M = massh.assemble().tosparse() + + u, v = elements_of(derham.V2, names='u, v') + mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) + massh = discretize(mass, domain_h, [V2h, V2h]) + M2 = massh.assemble().tosparse() + + # interface PML at y = pi + + u, v = elements_of(derham.V1, names='u, v') + x,y = domain.coordinates + + u1 = dot(Tuple(1,0),u) + u2 = dot(Tuple(0,1),u) + v1 = dot(Tuple(1,0),v) + v2 = dot(Tuple(0,1),v) + + delta = np.pi/6 + ycenter = np.pi + 3/2*delta + + sigma_0 = 0.5 + + sigma_x = 0#sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) + sigma_y = sigma_0 * heaviside(False, ycenter-delta, ycenter, delta, -1, domain, 10) * heaviside(False, ycenter, ycenter+delta, delta, 1, domain, 10) + + mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) + massh = discretize(mass, domain_h, [V1h, V1h]) + M_int = massh.assemble().tosparse() + + u, v = elements_of(derham.V2, names='u, v') + mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) + massh = discretize(mass, domain_h, [V2h, V2h]) + M2_int = massh.assemble().tosparse() + + # conf_proj = GSP + K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) + cP0_m = K0_inv @ cP0_m @ K0 + K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) + cP1_m = K1_inv @ cP1_m @ K1 + + bD0, bD1 = derham_h.broken_derivatives_as_operators + bD0_m = bD0.to_sparse_matrix() + bD1_m = bD1.to_sparse_matrix() + + + dH1_m = dH1_m.tocsr() + H2_m = H2_m.tocsr() + cP1_m = cP1_m.tocsr() + bD1_m = bD1_m.tocsr() + + C_m = bD1_m @ cP1_m + dC_m = dH1_m @ C_m.transpose() @ H2_m + + + div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m + + jump_penal_m = I1_m - cP1_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m + + f0_c = np.zeros(V1h.nbasis) + + + #E0, B0 = get_Gaussian_beam(x_0=3.14 , y_0=0.5*3.14, domain=domain) + E0, B0 = get_diag_Gaussian_beam(x_0=2/3 * np.pi + np.pi, y_0=np.pi/2, domain=domain) + E0_h = P1_phys(E0, P1, domain, mappings_list) + E_c = E0_h.coeffs.toarray() + + B0_h = P2_phys(B0, P2, domain, mappings_list) + B_c = B0_h.coeffs.toarray() + + E_c = dC_m @ B_c + B_c[:] = 0 + + OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') + OM1.add_spaces(V1h=V1h) + OM1.export_space_info() + + OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM2.add_spaces(V2h=V2h) + OM2.export_space_info() + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=0 , ts=0) + OM1.export_fields(Eh=Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=0 , ts=0) + OM2.export_fields(Bh=Bh) + + dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=0.8, dt_max=None) + Nt = int(np.ceil(final_time/dt)) + dt = final_time / Nt + Epml = sp.sparse.linalg.spsolve(H1_m, M) + Bpml = sp.sparse.linalg.spsolve(H2_m, M2) + + Epml_int = sp.sparse.linalg.spsolve(H1_m, (I1_m - R1_global).transpose()@M_int@(I1_m - R1_global)) + Bpml_int = sp.sparse.linalg.spsolve(H2_m, (I2_m - R2_global).transpose()@M2_int@(I2_m - R2_global)) + + #Epml_int = sp.sparse.linalg.spsolve(H1_m, (I1_m - R1_global).transpose()@C_m.transpose()@M2_int@C_m@(I1_m - R1_global)) + + + f_c = np.copy(f0_c) + for nt in range(Nt): + print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) + + # 1/2 faraday: Bn -> Bn+1/2 + B_c[:] -= dt/2*(Bpml @ B_c + Bpml_int@B_c) + (dt/2) * C_m @ E_c + + E_c[:] += -dt*(Epml @ E_c + Epml_int @ E_c) + dt * (dC_m @ B_c - f_c) + #E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) + + B_c[:] -= dt/2*(Bpml @ B_c + Bpml_int@B_c) + (dt/2) * C_m @ E_c + + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=nt*dt, ts=nt) + OM1.export_fields(Eh = Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=nt*dt, ts=nt) + OM2.export_fields(Bh=Bh) + + OM1.close() + + print("Do some PP") + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) + PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=6,snapshots='all', fields = 'Eh' ) + PM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) + PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=6,snapshots='all', fields = 'Bh' ) + PM.close() + + +#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): +def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): + """ + Compute a stable time step size based on the maximum CFL parameter in the + domain. To this end we estimate the operator norm of + + `dC_m @ C_m: V1h -> V1h`, + + find the largest stable time step compatible with Strang splitting, and + rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the + scheme exactly at its stability limit, which is not safe because of the + unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. + + Optionally the user can provide a maximum time step size in order to + properly resolve some time scales of interest (e.g. a time-dependent + current source). + + Parameters + ---------- + C_m : scipy.sparse.spmatrix + Matrix of the Curl operator. + + dC_m : scipy.sparse.spmatrix + Matrix of the dual Curl operator. + + cfl_max : float + Maximum Courant parameter in the domain, intended as a stability + parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. + + dt_max : float, optional + If not None, restrict the computed dt by this value in order to + properly resolve time scales of interest. Must be > 0. + + Returns + ------- + dt : float + Largest stable dt which satisfies the provided constraints. + + """ + + print (" .. compute_stable_dt by estimating the operator norm of ") + print (" .. dC_m @ C_m: V1h -> V1h ") + print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) + + if not (0 < cfl_max < 1): + print(' ****** ****** ****** ****** ****** ****** ') + print(' WARNING !!! cfl = {} '.format(cfl)) + print(' ****** ****** ****** ****** ****** ****** ') + + def vect_norm_2 (vv): + return np.sqrt(np.dot(vv,vv)) + + t_stamp = time_count() + vv = np.random.random(C_m.shape[1]) + norm_vv = vect_norm_2(vv) + max_ncfl = 500 + ncfl = 0 + spectral_rho = 1 + conv = False + CC_m = dC_m @ C_m + + while not( conv or ncfl > max_ncfl ): + + vv[:] = (1./norm_vv)*vv + ncfl += 1 + vv[:] = CC_m.dot(vv) + + norm_vv = vect_norm_2(vv) + old_spectral_rho = spectral_rho + spectral_rho = vect_norm_2(vv) # approximation + conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 + print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) + t_stamp = time_count(t_stamp) + + norm_op = np.sqrt(spectral_rho) + c_dt_max = 2./norm_op + + light_c = 1 + dt = cfl_max * c_dt_max / light_c + + if dt_max is not None: + dt = min(dt, dt_max) + + print( " Time step dt computed for Maxwell solver:") + print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") + print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") + print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") + + return dt + + +if __name__ == '__main__': + run_sim() \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py new file mode 100644 index 000000000..f429d804e --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py @@ -0,0 +1,395 @@ +from pytest import param +from mpi4py import MPI + +import os +import numpy as np +import scipy as sp +from collections import OrderedDict +import matplotlib.pyplot as plt + +from sympy import lambdify, Matrix + +from scipy.sparse.linalg import spsolve +from scipy import special + +from sympde.calculus import dot +from sympde.topology import element_of +from sympde.expr.expr import LinearForm +from sympde.expr.expr import integral, Norm +from sympde.topology import Derham + +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv +from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain + +from sympde.calculus import grad, dot, curl, cross +from sympde.topology import NormalVector +from sympde.expr.expr import BilinearForm +from sympde.topology import elements_of +from sympde import Tuple + +from psydac.api.postprocessing import OutputManager, PostProcessManager +from sympy.functions.special.error_functions import erf + +def run_sim(): + ## Minimal example for a PML implementation of the Time-Domain Maxwells equation + nc = 10 + # ncells = np.array([[nc, nc, nc], + # [nc, 2*nc, nc], + # [nc, nc, nc]]) + + ncells = np.array([[2*nc, 2*nc, 2*nc], + [2*nc, nc, 2*nc], + [2*nc, 2*nc, 2*nc]]) + + degree = [3,3] + plot_dir = "plots/PML/pml_test2" + bc = 'pml' #'none', 'abc' #'pml' + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + x_lim = np.pi + y_lim = np.pi + final_time = 3 + + domain = create_square_domain(ncells, [0, x_lim], [0, y_lim]) + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings_list = list(mappings.values()) + + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + domain_h = discretize(domain, ncells=ncells_h) + derham_h = discretize(derham, domain_h, degree=degree) + + nquads = [4*(d + 1) for d in degree] + P0, P1, P2 = derham_h.projectors(nquads=nquads) + + + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + + I1 = IdLinearOperator(V1h) + I1_m = I1.to_sparse_matrix() + + backend = 'pyccel-gcc' + + H0 = HodgeOperator(V0h, domain_h) + H1 = HodgeOperator(V1h, domain_h) + H2 = HodgeOperator(V2h, domain_h) + + dH0_m = H0.to_sparse_matrix() + H0_m = H0.get_dual_Hodge_sparse_matrix() + dH1_m = H1.to_sparse_matrix() + H1_m = H1.get_dual_Hodge_sparse_matrix() + dH2_m = H2.to_sparse_matrix() + H2_m = H2.get_dual_Hodge_sparse_matrix() + cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + + ## PML + u, v = elements_of(derham.V1, names='u, v') + x,y = domain.coordinates + + u1 = dot(Tuple(1,0),u) + u2 = dot(Tuple(0,1),u) + v1 = dot(Tuple(1,0),v) + v2 = dot(Tuple(0,1),v) + + def heaviside(x_direction, xmin, xmax, delta, sign, domain): + x,y = domain.coordinates + + if sign == -1: + d = xmax - delta + else: + d = xmin + delta + + if x_direction == True: + return 1/2*(erf(-sign*(x-d) *1000)+1) + else: + return 1/2*(erf(-sign*(y-d) *1000)+1) + + def parabola(x_direction, xmin, xmax, delta, sign, domain): + x,y = domain.coordinates + + if sign == -1: + d = xmax - delta + else: + d = xmin + delta + + if x_direction == True: + return ((x - d)/delta)**2 + else: + return ((y - d)/delta)**2 + + def sigma_fun(x, xmin, xmax, delta, sign, sigma_m, domain): + return sigma_m * heaviside(x, xmin, xmax, delta, sign, domain) * parabola(x, xmin, xmax, delta, sign, domain) + + def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): + return sigma_fun(x, xmin, xmax, delta, 1, sigma_m, domain) + sigma_fun(x, xmin, xmax, delta, -1, sigma_m, domain) + + delta = np.pi/10 + xmin = 0 + xmax = x_lim + ymin = 0 + ymax = y_lim + sigma_0 = 15 + + sigma_x = sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) + sigma_y = sigma_fun_sym(False, ymin, ymax, delta, sigma_0, domain) + if bc == 'pml': + mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) + massh = discretize(mass, domain_h, [V1h, V1h]) + M = massh.assemble().tosparse() + + u, v = elements_of(derham.V2, names='u, v') + mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) + massh = discretize(mass, domain_h, [V2h, V2h]) + M2 = massh.assemble().tosparse() + + elif bc == 'abc': + ### Silvermueller ABC + + u, v = elements_of(derham.V1, names='u, v') + nn = NormalVector('nn') + boundary = domain.boundary + expr_b = cross(nn, u)*cross(nn, v) + + a = BilinearForm((u,v), integral(boundary, expr_b)) + ah = discretize(a, domain_h, [V1h, V1h], backend=PSYDAC_BACKENDS[backend],) + A_eps = ah.assemble().tosparse() + ### + + + # conf_proj = GSP + K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) + cP0_m = K0_inv @ cP0_m @ K0 + K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) + cP1_m = K1_inv @ cP1_m @ K1 + + bD0, bD1 = derham_h.broken_derivatives_as_operators + bD0_m = bD0.to_sparse_matrix() + bD1_m = bD1.to_sparse_matrix() + + + dH1_m = dH1_m.tocsr() + H2_m = H2_m.tocsr() + cP1_m = cP1_m.tocsr() + bD1_m = bD1_m.tocsr() + + C_m = bD1_m @ cP1_m + dC_m = dH1_m @ C_m.transpose() @ H2_m + + + div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m + + jump_penal_m = I1_m - cP1_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m + + f0_c = np.zeros(V1h.nbasis) + + + E0, B0 = get_Gaussian_beam(x_0=np.pi * 1/2 , y_0=np.pi * 1/2, domain=domain) + #E0, B0 = get_Berenger_wave(x_0=3.14/2 , y_0=3.14/2, domain=domain) + + E0_h = P1_phys(E0, P1, domain, mappings_list) + E_c = E0_h.coeffs.toarray() + + B0_h = P2_phys(B0, P2, domain, mappings_list) + B_c = B0_h.coeffs.toarray() + + #plot_field(numpy_coeffs=E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='amplitude', filename="E_amp_before") + + #plot_field(numpy_coeffs=E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='components', filename="E_comp_before") + #plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, filename="B_before") + + + # E_c_ = dC_m @ B_c + # B_c[:] = 0 + # plot_field(numpy_coeffs=E_c_, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='components', filename="E_comp_after") + # plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, filename="B_after") + + # E_c_ = E_c + #B_c = C_m @ E_c + # plot_field(numpy_coeffs=E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='components', filename="E_comp_after_after") + #plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, filename="B_after_after") + #B_c[:] = 0 + + + #exit() + + OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') + OM1.add_spaces(V1h=V1h) + OM1.export_space_info() + + OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM2.add_spaces(V2h=V2h) + OM2.export_space_info() + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=0 , ts=0) + OM1.export_fields(Eh=Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=0 , ts=0) + OM2.export_fields(Bh=Bh) + + dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=0.8, dt_max=None) + Nt = int(np.ceil(final_time/dt)) + dt = final_time / Nt + if bc == 'pml': + Epml = sp.sparse.linalg.spsolve(H1_m, M) + Bpml = sp.sparse.linalg.spsolve(H2_m, M2) + elif bc == 'abc': + H1A = H1_m + dt * A_eps + A_eps = sp.sparse.linalg.spsolve(H1A, H1_m) + dC_m = sp.sparse.linalg.spsolve(H1A, C_m.transpose() @ H2_m) + elif bc == 'none': + A_eps = sp.sparse.linalg.spsolve(H1_m, H1_m) + + f_c = np.copy(f0_c) + for nt in range(Nt): + print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) + + # 1/2 faraday: Bn -> Bn+1/2 + if bc == 'pml': + B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c + E_c[:] += -dt*Epml @ E_c + dt * (dC_m @ B_c - f_c) + B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c + + else: + B_c[:] -= (dt/2) * C_m @ E_c + E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) + B_c[:] -= (dt/2) * C_m @ E_c + + #plot_field(numpy_coeffs=cP1_m @ E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='amplitude', filename=plot_dir+"/E_{}".format(nt)) + + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=nt*dt, ts=nt) + OM1.export_fields(Eh = Eh) + + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=nt*dt, ts=nt) + OM2.export_fields(Bh=Bh) + + OM1.close() + + print("Do some PP") + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) + PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Eh' ) + PM.close() + + PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) + PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Bh' ) + PM.close() + + +#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): +def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): + """ + Compute a stable time step size based on the maximum CFL parameter in the + domain. To this end we estimate the operator norm of + + `dC_m @ C_m: V1h -> V1h`, + + find the largest stable time step compatible with Strang splitting, and + rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the + scheme exactly at its stability limit, which is not safe because of the + unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. + + Optionally the user can provide a maximum time step size in order to + properly resolve some time scales of interest (e.g. a time-dependent + current source). + + Parameters + ---------- + C_m : scipy.sparse.spmatrix + Matrix of the Curl operator. + + dC_m : scipy.sparse.spmatrix + Matrix of the dual Curl operator. + + cfl_max : float + Maximum Courant parameter in the domain, intended as a stability + parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. + + dt_max : float, optional + If not None, restrict the computed dt by this value in order to + properly resolve time scales of interest. Must be > 0. + + Returns + ------- + dt : float + Largest stable dt which satisfies the provided constraints. + + """ + + print (" .. compute_stable_dt by estimating the operator norm of ") + print (" .. dC_m @ C_m: V1h -> V1h ") + print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) + + if not (0 < cfl_max < 1): + print(' ****** ****** ****** ****** ****** ****** ') + print(' WARNING !!! cfl = {} '.format(cfl)) + print(' ****** ****** ****** ****** ****** ****** ') + + def vect_norm_2 (vv): + return np.sqrt(np.dot(vv,vv)) + + t_stamp = time_count() + vv = np.random.random(C_m.shape[1]) + norm_vv = vect_norm_2(vv) + max_ncfl = 500 + ncfl = 0 + spectral_rho = 1 + conv = False + CC_m = dC_m @ C_m + + while not( conv or ncfl > max_ncfl ): + + vv[:] = (1./norm_vv)*vv + ncfl += 1 + vv[:] = CC_m.dot(vv) + + norm_vv = vect_norm_2(vv) + old_spectral_rho = spectral_rho + spectral_rho = vect_norm_2(vv) # approximation + conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 + print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) + t_stamp = time_count(t_stamp) + + norm_op = np.sqrt(spectral_rho) + c_dt_max = 2./norm_op + + light_c = 1 + dt = cfl_max * c_dt_max / light_c + + if dt_max is not None: + dt = min(dt, dt_max) + + print( " Time step dt computed for Maxwell solver:") + print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") + print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") + print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") + + return dt + + +if __name__ == '__main__': + run_sim() \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py index 7a645c668..a78a24f5e 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py @@ -45,9 +45,11 @@ def run_sim(): ## Minimal example for a PML implementation of the Time-Domain Maxwells equation - ncells = [16, 16, 16, 16] + ncells = [8, 8, 8, 8] degree = [3,3] - plot_dir = "plots/PML/further" + plot_dir = "plots/PML/test2" + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) final_time = 3 domain = build_multipatch_domain(domain_name='square_4') @@ -227,12 +229,12 @@ def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) # 1/2 faraday: Bn -> Bn+1/2 - B_c[:] -= dt/2*Bpml*B_c + (dt/2) * C_m @ E_c + B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c E_c[:] += -dt*Epml @ E_c + dt * (dC_m @ B_c - f_c) #E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) - B_c[:] -= dt/2*Bpml*B_c + (dt/2) * C_m @ E_c + B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) @@ -246,7 +248,8 @@ def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): OM2.export_fields(Bh=Bh) OM1.close() - + OM2.close() + print("Do some PP") PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Eh' ) diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index b6ea734d4..7213cc397 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -1406,8 +1406,6 @@ def get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_spa ER_1D = E_1D @ R_1D - - # id_err = np.linalg.norm(R_1D @ E_1D - sparse_eye( coarse_space_1d.nbasis, format="lil")) else: ER_1D = R_1D = E_1D = sparse_eye( @@ -1471,7 +1469,7 @@ def calculate_mass_matrix(space_1d, spl_type): # #domain_name = 'square_6' # #domain_name = '2patch_nc_mapped' -# domain_name = '4patch_nc' +# domain_name = '2patch_nc' # #domain_name = "curved_L_shape" # if domain_name == '2patch_nc_mapped': From a14799096a029a62a215002da25a8c3b1071777b Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 5 Mar 2024 14:48:00 +0100 Subject: [PATCH 19/88] add docs and readability --- psydac/api/postprocessing.py | 10 +- .../examples_nc/timedomain_maxwell_min.py | 14 +- .../feec/multipatch/non_matching_operators.py | 785 ++++-------------- .../test_feec_conf_projectors_cart_2d.py | 20 +- 4 files changed, 196 insertions(+), 633 deletions(-) diff --git a/psydac/api/postprocessing.py b/psydac/api/postprocessing.py index 121ba4509..52e8fd0ae 100644 --- a/psydac/api/postprocessing.py +++ b/psydac/api/postprocessing.py @@ -953,13 +953,9 @@ def _reconstruct_spaces(self): for subdomain_names, space_dict in subdomains_to_spaces.items(): if space_dict == {}: continue - ncells_dict = {interior_name: interior_names_to_ncells[interior_name] for interior_name in subdomain_names} - # No need for a a dict until PR about non-conforming meshes is merged - # Check for conformity - ncells = ncells_dict#list(ncells_dict.values())[0] - #try non conforming - #assert all(ncells_patch == ncells for ncells_patch in ncells_dict.values()) - + + ncells = {interior_name: interior_names_to_ncells[interior_name] for interior_name in subdomain_names} + subdomain = domain.get_subdomain(subdomain_names) space_name_0 = list(space_dict.keys())[0] periodic = space_dict[space_name_0][2].get('periodic', None) diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py index f429d804e..ccdb47b88 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py @@ -50,12 +50,18 @@ def run_sim(): # [nc, 2*nc, nc], # [nc, nc, nc]]) - ncells = np.array([[2*nc, 2*nc, 2*nc], - [2*nc, nc, 2*nc], - [2*nc, 2*nc, 2*nc]]) + ncells = np.array([[nc, nc, nc, nc], + [nc, 2*nc, 2*nc, nc], + [nc, 2*nc, 2*nc, nc], + [nc, nc, nc, nc]]) + + # ncells = np.array([[2*nc, 2*nc, 2*nc, 2*nc], + # [2*nc, nc, nc, 2*nc], + # [2*nc, nc, nc, 2*nc], + # [2*nc, 2*nc, 2*nc, 2*nc]]) degree = [3,3] - plot_dir = "plots/PML/pml_test2" + plot_dir = "plots/PML/pml_test3" bc = 'pml' #'none', 'abc' #'pml' if not os.path.exists(plot_dir): os.makedirs(plot_dir) diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index 7213cc397..f062b7e70 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -2,32 +2,16 @@ import numpy as np from scipy.sparse import eye as sparse_eye from scipy.sparse import csr_matrix -from scipy.sparse.linalg import inv, norm - -from sympde.topology import Derham, Square -from sympde.topology import IdentityMapping -from sympde.topology import Boundary, Interface, Union -from scipy.sparse.linalg import norm as sp_norm - -from psydac.feec.multipatch.utilities import time_count -from psydac.linalg.utilities import array_to_psydac -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS +from sympde.topology import Boundary, Interface from psydac.fem.splines import SplineSpace - -from psydac.fem.basic import FemField -from psydac.feec.multipatch.plotting_utilities import plot_field - -from sympde.topology import IdentityMapping, PolarMapping -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain - from psydac.utilities.quadratures import gauss_legendre from psydac.core.bsplines import breakpoints, quadrature_grid, basis_ders_on_quad_grid, find_spans, elements_spans from copy import deepcopy def get_patch_index_from_face(domain, face): - """ Return the patch index of subdomain/boundary + """ + Return the patch index of subdomain/boundary Parameters ---------- @@ -61,7 +45,6 @@ def get_patch_index_from_face(domain, face): class Local2GlobalIndexMap: def __init__(self, ndim, n_patches, n_components): - # A[patch_index][component_index][i1,i2] self._shapes = [None]*n_patches self._ndofs = [None]*n_patches self._ndim = ndim @@ -72,7 +55,7 @@ def set_patch_shapes(self, patch_index, *shapes): assert len(shapes) == self._n_components assert all(len(s) == self._ndim for s in shapes) self._shapes[patch_index] = shapes - self._ndofs[patch_index] = sum(np.product(s) for s in shapes) + self._ndofs[patch_index] = sum(np.prod(s) for s in shapes) def get_index(self, k, d, cartesian_index): """ Return a global scalar index. @@ -93,7 +76,7 @@ def get_index(self, k, d, cartesian_index): I : int The global scalar index. """ - sizes = [np.product(s) for s in self._shapes[k][:d]] + sizes = [np.prod(s) for s in self._shapes[k][:d]] Ipc = np.ravel_multi_index( cartesian_index, dims=self._shapes[k][d], order='C') Ip = sum(sizes) + Ipc @@ -102,7 +85,7 @@ def get_index(self, k, d, cartesian_index): def knots_to_insert(coarse_grid, fine_grid, tol=1e-14): - # assert len(coarse_grid)*2-2 == len(fine_grid)-1 + intersection = coarse_grid[( np.abs(fine_grid[:, None] - coarse_grid) < tol).any(0)] assert abs(intersection-coarse_grid).max() < tol @@ -112,372 +95,28 @@ def knots_to_insert(coarse_grid, fine_grid, tol=1e-14): def construct_extension_operator_1D(domain, codomain): """ - - compute the matrix of the extension operator on the interface space (1D space if global space is 2D) - - domain: 1d spline space on the interface (coarse grid) - codomain: 1d spline space on the interface (fine grid) + Compute the matrix of the extension operator on the interface. + + Parameters + ---------- + domain : 1d spline space on the interface (coarse grid) + codomain : 1d spline space on the interface (fine grid) """ - #from psydac.core.interface import matrix_multi_stages + from psydac.core.bsplines import hrefinement_matrix ops = [] assert domain.ncells <= codomain.ncells Ts = knots_to_insert(domain.breaks, codomain.breaks) - #P = matrix_multi_stages(Ts, domain.nbasis, domain.degree, domain.knots) P = hrefinement_matrix(Ts, domain.degree, domain.knots) + if domain.basis == 'M': assert codomain.basis == 'M' P = np.diag( 1/codomain._scaling_array) @ P @ np.diag(domain._scaling_array) - return csr_matrix(P) # kronecker of 1 term... - -# Legacy code -# def construct_V0_conforming_projection(V0h, hom_bc=None): -# dim_tot = V0h.nbasis -# domain = V0h.symbolic_space.domain -# ndim = 2 -# n_components = 1 -# n_patches = len(domain) - -# l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) -# for k in range(n_patches): -# Vk = V0h.spaces[k] -# # T is a TensorFemSpace and S is a 1D SplineSpace -# shapes = [S.nbasis for S in Vk.spaces] -# l2g.set_patch_shapes(k, shapes) - -# Proj = sparse_eye(dim_tot, format="lil") -# Proj_vertex = sparse_eye(dim_tot, format="lil") - -# Interfaces = domain.interfaces -# if isinstance(Interfaces, Interface): -# Interfaces = (Interfaces, ) - -# corner_indices = set() -# stored_indices = [] -# corners = get_corners(domain, False) -# for (bd,co) in corners.items(): - -# c = 0 -# indices = set() -# for patch in co: -# c += 1 -# multi_index_i = [None]*ndim - -# nbasis0 = V0h.spaces[patch].spaces[co[patch][0]].nbasis-1 -# nbasis1 = V0h.spaces[patch].spaces[co[patch][1]].nbasis-1 - -# multi_index_i[0] = 0 if co[patch][0] == 0 else nbasis0 -# multi_index_i[1] = 0 if co[patch][1] == 0 else nbasis1 -# ig = l2g.get_index(patch, 0, multi_index_i) -# indices.add(ig) - - -# corner_indices.add(ig) - -# stored_indices.append(indices) -# for j in indices: -# for i in indices: -# Proj_vertex[j,i] = 1/c - -# # First make all interfaces conforming -# # We also touch the vertices here, but change them later again -# for I in Interfaces: - -# axis = I.axis -# direction = I.ornt - -# k_minus = get_patch_index_from_face(domain, I.minus) -# k_plus = get_patch_index_from_face(domain, I.plus) -# # logical directions normal to interface -# minus_axis, plus_axis = I.minus.axis, I.plus.axis -# # logical directions along the interface - -# #d_minus, d_plus = 1-minus_axis, 1-plus_axis -# I_minus_ncells = V0h.spaces[k_minus].ncells -# I_plus_ncells = V0h.spaces[k_plus].ncells - -# matching_interfaces = (I_minus_ncells == I_plus_ncells) - -# if I_minus_ncells <= I_plus_ncells: -# k_fine, k_coarse = k_plus, k_minus -# fine_axis, coarse_axis = I.plus.axis, I.minus.axis -# fine_ext, coarse_ext = I.plus.ext, I.minus.ext - -# else: -# k_fine, k_coarse = k_minus, k_plus -# fine_axis, coarse_axis = I.minus.axis, I.plus.axis -# fine_ext, coarse_ext = I.minus.ext, I.plus.ext - -# d_fine = 1-fine_axis -# d_coarse = 1-coarse_axis - -# space_fine = V0h.spaces[k_fine] -# space_coarse = V0h.spaces[k_coarse] - - -# coarse_space_1d = space_coarse.spaces[d_coarse] - -# fine_space_1d = space_fine.spaces[d_fine] -# grid = np.linspace( -# fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) -# coarse_space_1d_k_plus = SplineSpace( -# degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) - -# if not matching_interfaces: -# E_1D = construct_extension_operator_1D( -# domain=coarse_space_1d_k_plus, codomain=fine_space_1d) - -# product = (E_1D.T) @ E_1D -# R_1D = inv(product.tocsc()) @ E_1D.T -# ER_1D = E_1D @ R_1D -# else: -# ER_1D = R_1D = E_1D = sparse_eye( -# fine_space_1d.nbasis, format="lil") - -# # P_k_minus_k_minus -# multi_index = [None]*ndim -# multi_index[coarse_axis] = 0 if coarse_ext == - \ -# 1 else space_coarse.spaces[coarse_axis].nbasis-1 -# for i in range(coarse_space_1d.nbasis): -# multi_index[d_coarse] = i -# ig = l2g.get_index(k_coarse, 0, multi_index) -# if not corner_indices.issuperset({ig}): -# Proj[ig, ig] = 0.5 - -# # P_k_plus_k_plus -# multi_index_i = [None]*ndim -# multi_index_j = [None]*ndim -# multi_index_i[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine.spaces[fine_axis].nbasis-1 -# multi_index_j[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine.spaces[fine_axis].nbasis-1 - -# for i in range(fine_space_1d.nbasis): -# multi_index_i[d_fine] = i -# ig = l2g.get_index(k_fine, 0, multi_index_i) -# for j in range(fine_space_1d.nbasis): -# multi_index_j[d_fine] = j -# jg = l2g.get_index(k_fine, 0, multi_index_j) -# if not corner_indices.issuperset({ig}): -# Proj[ig, jg] = 0.5*ER_1D[i, j] - -# # P_k_plus_k_minus -# multi_index_i = [None]*ndim -# multi_index_j = [None]*ndim -# multi_index_i[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine .spaces[fine_axis] .nbasis-1 -# multi_index_j[coarse_axis] = 0 if coarse_ext == - \ -# 1 else space_coarse.spaces[coarse_axis].nbasis-1 - -# for i in range(fine_space_1d.nbasis): -# multi_index_i[d_fine] = i -# ig = l2g.get_index(k_fine, 0, multi_index_i) -# for j in range(coarse_space_1d.nbasis): -# multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 -# jg = l2g.get_index(k_coarse, 0, multi_index_j) -# if not corner_indices.issuperset({ig}): -# Proj[ig, jg] = 0.5*E_1D[i, j]*direction - -# # P_k_minus_k_plus -# multi_index_i = [None]*ndim -# multi_index_j = [None]*ndim -# multi_index_i[coarse_axis] = 0 if coarse_ext == - \ -# 1 else space_coarse.spaces[coarse_axis].nbasis-1 -# multi_index_j[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine .spaces[fine_axis] .nbasis-1 - -# for i in range(coarse_space_1d.nbasis): -# multi_index_i[d_coarse] = i -# ig = l2g.get_index(k_coarse, 0, multi_index_i) -# for j in range(fine_space_1d.nbasis): -# multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 -# jg = l2g.get_index(k_fine, 0, multi_index_j) -# if not corner_indices.issuperset({ig}): -# Proj[ig, jg] = 0.5*R_1D[i, j]*direction - - -# if hom_bc: -# bd_co_indices = set() -# for bn in domain.boundary: -# k = get_patch_index_from_face(domain, bn) -# space_k = V0h.spaces[k] -# axis = bn.axis -# d = 1-axis -# ext = bn.ext -# space_k_1d = space_k.spaces[d] # t -# multi_index_i = [None]*ndim -# multi_index_i[axis] = 0 if ext == - \ -# 1 else space_k.spaces[axis].nbasis-1 - -# for i in range(space_k_1d.nbasis): -# multi_index_i[d] = i -# ig = l2g.get_index(k, 0, multi_index_i) -# bd_co_indices.add(ig) -# Proj[ig, ig] = 0 - -# # properly ensure vertex continuity -# for ig in bd_co_indices: -# for jg in bd_co_indices: -# Proj_vertex[ig, jg] = 0 - - -# return Proj @ Proj_vertex - -# def construct_V1_conforming_projection(V1h, hom_bc=None): -# dim_tot = V1h.nbasis -# domain = V1h.symbolic_space.domain -# ndim = 2 -# n_components = 2 -# n_patches = len(domain) - -# l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) -# for k in range(n_patches): -# Vk = V1h.spaces[k] -# # T is a TensorFemSpace and S is a 1D SplineSpace -# shapes = [[S.nbasis for S in T.spaces] for T in Vk.spaces] -# l2g.set_patch_shapes(k, *shapes) - -# Proj = sparse_eye(dim_tot, format="lil") - -# Interfaces = domain.interfaces -# if isinstance(Interfaces, Interface): -# Interfaces = (Interfaces, ) - -# for I in Interfaces: -# axis = I.axis -# direction = I.ornt - -# k_minus = get_patch_index_from_face(domain, I.minus) -# k_plus = get_patch_index_from_face(domain, I.plus) -# # logical directions normal to interface -# minus_axis, plus_axis = I.minus.axis, I.plus.axis -# # logical directions along the interface -# d_minus, d_plus = 1-minus_axis, 1-plus_axis -# I_minus_ncells = V1h.spaces[k_minus].spaces[d_minus].ncells[d_minus] -# I_plus_ncells = V1h.spaces[k_plus] .spaces[d_plus] .ncells[d_plus] - -# matching_interfaces = (I_minus_ncells == I_plus_ncells) - -# if I_minus_ncells <= I_plus_ncells: -# k_fine, k_coarse = k_plus, k_minus -# fine_axis, coarse_axis = I.plus.axis, I.minus.axis -# fine_ext, coarse_ext = I.plus.ext, I.minus.ext - -# else: -# k_fine, k_coarse = k_minus, k_plus -# fine_axis, coarse_axis = I.minus.axis, I.plus.axis -# fine_ext, coarse_ext = I.minus.ext, I.plus.ext - -# d_fine = 1-fine_axis -# d_coarse = 1-coarse_axis - -# space_fine = V1h.spaces[k_fine] -# space_coarse = V1h.spaces[k_coarse] - -# #print("coarse = \n", space_coarse.spaces[d_coarse]) -# #print("coarse 2 = \n", space_coarse.spaces[d_coarse].spaces[d_coarse]) -# # todo: merge with first test above -# coarse_space_1d = space_coarse.spaces[d_coarse].spaces[d_coarse] - -# #print("fine = \n", space_fine.spaces[d_fine]) -# #print("fine 2 = \n", space_fine.spaces[d_fine].spaces[d_fine]) - -# fine_space_1d = space_fine.spaces[d_fine].spaces[d_fine] -# grid = np.linspace( -# fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) -# coarse_space_1d_k_plus = SplineSpace( -# degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) - -# if not matching_interfaces: -# E_1D = construct_extension_operator_1D( -# domain=coarse_space_1d_k_plus, codomain=fine_space_1d) -# product = (E_1D.T) @ E_1D -# R_1D = inv(product.tocsc()) @ E_1D.T -# ER_1D = E_1D @ R_1D -# else: -# ER_1D = R_1D = E_1D = sparse_eye( -# fine_space_1d.nbasis, format="lil") - -# # P_k_minus_k_minus -# multi_index = [None]*ndim -# multi_index[coarse_axis] = 0 if coarse_ext == - \ -# 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 -# for i in range(coarse_space_1d.nbasis): -# multi_index[d_coarse] = i -# ig = l2g.get_index(k_coarse, d_coarse, multi_index) -# Proj[ig, ig] = 0.5 - -# # P_k_plus_k_plus -# multi_index_i = [None]*ndim -# multi_index_j = [None]*ndim -# multi_index_i[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 -# multi_index_j[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 - -# for i in range(fine_space_1d.nbasis): -# multi_index_i[d_fine] = i -# ig = l2g.get_index(k_fine, d_fine, multi_index_i) -# for j in range(fine_space_1d.nbasis): -# multi_index_j[d_fine] = j -# jg = l2g.get_index(k_fine, d_fine, multi_index_j) -# Proj[ig, jg] = 0.5*ER_1D[i, j] - -# # P_k_plus_k_minus -# multi_index_i = [None]*ndim -# multi_index_j = [None]*ndim -# multi_index_i[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 -# multi_index_j[coarse_axis] = 0 if coarse_ext == - \ -# 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 - -# for i in range(fine_space_1d.nbasis): -# multi_index_i[d_fine] = i -# ig = l2g.get_index(k_fine, d_fine, multi_index_i) -# for j in range(coarse_space_1d.nbasis): -# multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 -# jg = l2g.get_index(k_coarse, d_coarse, multi_index_j) -# Proj[ig, jg] = 0.5*E_1D[i, j]*direction - -# # P_k_minus_k_plus -# multi_index_i = [None]*ndim -# multi_index_j = [None]*ndim -# multi_index_i[coarse_axis] = 0 if coarse_ext == - \ -# 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 -# multi_index_j[fine_axis] = 0 if fine_ext == - \ -# 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 - -# for i in range(coarse_space_1d.nbasis): -# multi_index_i[d_coarse] = i -# ig = l2g.get_index(k_coarse, d_coarse, multi_index_i) -# for j in range(fine_space_1d.nbasis): -# multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 -# jg = l2g.get_index(k_fine, d_fine, multi_index_j) -# Proj[ig, jg] = 0.5*R_1D[i, j]*direction - -# if hom_bc: -# for bn in domain.boundary: -# k = get_patch_index_from_face(domain, bn) -# space_k = V1h.spaces[k] -# axis = bn.axis -# d = 1-axis -# ext = bn.ext -# space_k_1d = space_k.spaces[d].spaces[d] # t -# multi_index_i = [None]*ndim -# multi_index_i[axis] = 0 if ext == - \ -# 1 else space_k.spaces[d].spaces[axis].nbasis-1 - -# for i in range(space_k_1d.nbasis): -# multi_index_i[d] = i -# ig = l2g.get_index(k, d, multi_index_i) -# Proj[ig, ig] = 0 - -# return Proj - + return csr_matrix(P) def get_corners(domain, boundary_only): """ @@ -531,7 +170,31 @@ def get_corners(domain, boundary_only): def construct_scalar_conforming_projection(Vh, reg_orders=(0,0), p_moments=(-1,-1), nquads=None, hom_bc=(False, False)): - #construct conforming projection for a 2-dimensional scalar space + """ Construct the conforming projection for a scalar space for a given regularity (0 continuous, -1 discontinuous). + The conservation of p-moments only works for a matching TensorFemSpace. + + Parameters + ---------- + Vh : TensorFemSpace + Finite Element Space coming from the discrete de Rham sequence. + + reg_orders : tuple-like (int) + Regularity in each space direction -1 or 0. + + p_moments : tuple-like (int) + Number of moments to be preserved. + + nquads : int | None + Number of quadrature points. + + hom_bc : tuple-like (bool) + Homogeneous boundary conditions. + + Returns + ------- + cP : scipy.sparse.csr_array + Conforming projection as a sparse matrix. + """ dim_tot = Vh.nbasis @@ -793,8 +456,6 @@ def construct_scalar_conforming_projection(Vh, reg_orders=(0,0), p_moments=(-1,- Proj_edge[pg, jg] += corrections[coarse_axis][1][p_ind] *R_1D[i, j]*direction - # boundary conditions - # interface correction bd_co_indices = set() for bn in domain.boundary: @@ -918,6 +579,32 @@ def construct_scalar_conforming_projection(Vh, reg_orders=(0,0), p_moments=(-1,- return Proj_edge @ Proj_vertex def construct_vector_conforming_projection(Vh, reg_orders= (0,0), p_moments=(-1,-1), nquads=None, hom_bc=(False, False)): + """ Construct the conforming projection for a scalar space for a given regularity (0 continuous, -1 discontinuous). + The conservation of p-moments only works for a matching VectorFemSpace. + + Parameters + ---------- + Vh : VectorFemSpace + Finite Element Space coming from the discrete de Rham sequence. + + reg_orders : tuple-like (int) + Regularity in each space direction -1 or 0. + + p_moments : tuple-like (int) + Number of moments to be preserved. + + nquads : int | None + Number of quadrature points. + + hom_bc : tuple-like (bool) + Homogeneous boundary conditions. + + Returns + ------- + cP : scipy.sparse.csr_array + Conforming projection as a sparse matrix. + """ + dim_tot = Vh.nbasis # fully discontinuous space @@ -1123,7 +810,34 @@ def construct_vector_conforming_projection(Vh, reg_orders= (0,0), p_moments=(-1, def get_scalar_moment_correction(patch_space, conf_axis, reg=0, p_moments=-1, nquads=None, hom_bc=False): + """ + Calculate the coefficients for the one-dimensional moment correction. + + Parameters + ---------- + patch_space : TensorFemSpace + Finite Element Space of an adjacent patch. + conf_axis : {0, 1} + Coefficients for which axis. + + reg : {-1, 0} + Regularity -1 or 0. + + p_moments : int + Number of moments to be preserved. + + nquads : int | None + Number of quadrature points. + + hom_bc : tuple-like (bool) + Homogeneous boundary conditions. + + Returns + ------- + coeffs : list of arrays + Collection of the different coefficients. + """ proj_op = 0 #patch_space = Vh.spaces[0] local_shape = [patch_space.spaces[0].nbasis,patch_space.spaces[1].nbasis] @@ -1250,7 +964,37 @@ def get_scalar_moment_correction(patch_space, conf_axis, reg=0, p_moments=-1, nq return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 def get_vector_moment_correction(patch_space, conf_comp, conf_axis, reg=([0,0], [0,0]), p_moments=([-1,-1], [-1,-1]), nquads=None, hom_bc=([False, False],[False, False])): + """ + Calculate the coefficients for the vector-valued moment correction. + + Parameters + ---------- + patch_space : VectorFemSpace + Finite Element Space of an adjacent patch. + + conf_comp : {0, 1} + Coefficients for which vector component. + + conf_axis : {0, 1} + Coefficients for which axis. + + reg : tuple-like + Regularity -1 or 0. + + p_moments : tuple-like + Number of moments to be preserved. + nquads : int | None + Number of quadrature points. + + hom_bc : tuple-like (bool) + Homogeneous boundary conditions. + + Returns + ------- + coeffs : list of arrays + Collection of the different coefficients. + """ proj_op = 0 local_shape = [[patch_space.spaces[comp].spaces[axis].nbasis for axis in range(2)] for comp in range(2)] @@ -1383,6 +1127,34 @@ def get_vector_moment_correction(patch_space, conf_comp, conf_axis, reg=([0,0], return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 def get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, spl_type): + """ + Calculate the extension and restriction matrices for refining along an interface. + + Parameters + ---------- + matching_interfaces : bool + Do both patches have the same number of cells? + + coarse_space_1d : SplineSpace + Spline space of the coarse space. + + fine_space_1d : SplineSpace + Spline space of the fine space. + + spl_type : {'B', 'M'} + Spline type. + + Returns + ------- + E_1D : numpy array + Extension matrix. + + R_1D : numpy array + Restriction matrix. + + ER_1D : numpy array + Extension-restriction matrix. + """ grid = np.linspace(fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) coarse_space_1d_k_plus = SplineSpace(degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) @@ -1406,14 +1178,32 @@ def get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_spa ER_1D = E_1D @ R_1D - # id_err = np.linalg.norm(R_1D @ E_1D - sparse_eye( coarse_space_1d.nbasis, format="lil")) else: ER_1D = R_1D = E_1D = sparse_eye( fine_space_1d.nbasis, format="lil") return E_1D, R_1D, ER_1D +# Didn't find this utility in the code base. def calculate_mass_matrix(space_1d, spl_type): + """ + Calculate the mass-matrix of a 1d spline-space. + + Parameters + ---------- + + space_1d : SplineSpace + Spline space of the fine space. + + spl_type : {'B', 'M'} + Spline type. + + Returns + ------- + + Mass_mat : numpy array + Mass matrix. + """ Nel = space_1d.ncells deg = space_1d.degree knots = space_1d.knots @@ -1445,237 +1235,4 @@ def calculate_mass_matrix(space_1d, spl_type): locind2 = il2 + spans[ie1] - deg Mass_mat[locind1,locind2] += val - return Mass_mat - - -# if __name__ == '__main__': - -# nc = 5 -# deg = 3 -# nonconforming = True -# plot_dir = 'run_plots_nc={}_deg={}'.format(nc, deg) - -# if plot_dir is not None and not os.path.exists(plot_dir): -# os.makedirs(plot_dir) - -# ncells = [nc, nc] -# degree = [deg, deg] -# reg_orders=[0,0] -# p_moments=[3,3] - -# nquads=None -# hom_bc=[False, False] -# print(' .. multi-patch domain...') - -# #domain_name = 'square_6' -# #domain_name = '2patch_nc_mapped' -# domain_name = '2patch_nc' -# #domain_name = "curved_L_shape" - -# if domain_name == '2patch_nc_mapped': - -# A = Square('A', bounds1=(0.5, 1), bounds2=(0, np.pi/2)) -# B = Square('B', bounds1=(0.5, 1), bounds2=(np.pi/2, np.pi)) -# M1 = PolarMapping('M1', 2, c1=0, c2=0, rmin=0., rmax=1.) -# M2 = PolarMapping('M2', 2, c1=0, c2=0, rmin=0., rmax=1.) -# A = M1(A) -# B = M2(B) - -# domain = create_domain([A, B], [[A.get_boundary(axis=1, ext=1), B.get_boundary(axis=1, ext=-1), 1]], name='domain') - -# elif domain_name == '2patch_nc': - -# A = Square('A', bounds1=(0, 0.5), bounds2=(0, 1)) -# B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 1)) -# M1 = IdentityMapping('M1', dim=2) -# M2 = IdentityMapping('M2', dim=2) -# A = M1(A) -# B = M2(B) - -# domain = create_domain([A, B], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1]], name='domain') -# elif domain_name == '4patch_nc': - -# A = Square('A', bounds1=(0, 0.5), bounds2=(0, 0.5)) -# B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 0.5)) -# C = Square('C', bounds1=(0, 0.5), bounds2=(0.5, 1)) -# D = Square('D', bounds1=(0.5, 1.), bounds2=(0.5, 1)) -# M1 = IdentityMapping('M1', dim=2) -# M2 = IdentityMapping('M2', dim=2) -# M3 = IdentityMapping('M3', dim=2) -# M4 = IdentityMapping('M4', dim=2) -# A = M1(A) -# B = M2(B) -# C = M3(C) -# D = M4(D) - -# domain = create_domain([A, B, C, D], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], -# [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], -# [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], -# [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1] ], name='domain') -# else: -# domain = build_multipatch_domain(domain_name=domain_name) - - -# n_patches = len(domain) - -# def levelof(k): -# # some random refinement level (1 or 2 here) -# return 1+((2*k) % 3) % 2 -# if nonconforming: -# if len(domain) == 1: -# ncells_h = { -# 'M1(A)': [nc, nc], -# } - -# elif len(domain) == 2: -# ncells_h = { -# 'M1(A)': [nc, nc], -# 'M2(B)': [2*nc, 2*nc], -# } - -# else: -# ncells_h = {} -# for k, D in enumerate(domain.interior): -# print(k, D.name) -# ncells_h[D.name] = [2**k *nc, 2**k * nc ] -# else: -# ncells_h = {} -# for k, D in enumerate(domain.interior): -# ncells_h[D.name] = [nc, nc] - -# print('ncells_h = ', ncells_h) -# backend_language = 'python' - -# t_stamp = time_count() -# print(' .. derham sequence...') -# derham = Derham(domain, ["H1", "Hcurl", "L2"]) - -# t_stamp = time_count(t_stamp) -# print(' .. discrete domain...') - -# domain_h = discretize(domain, ncells=ncells_h) # Vh space -# derham_h = discretize(derham, domain_h, degree=degree) -# V0h = derham_h.V0 -# V1h = derham_h.V1 - -# # test_extension_restriction(V1h, domain) - - -# #cP1_m_old = construct_V1_conforming_projection(V1h, True) -# # cP0_m_old = construct_V0_conforming_projection(V0h,hom_bc[0]) -# cP0_m = construct_scalar_conforming_projection(V0h, reg_orders, p_moments, nquads, hom_bc) -# cP1_m = construct_vector_conforming_projection(V1h, reg_orders, p_moments, nquads, hom_bc) - -# #print("Error:") -# #print( norm(cP1_m - conf_cP1_m) ) -# np.set_printoptions(linewidth=100000, precision=2, -# threshold=100000, suppress=True) -# #print(cP0_m.toarray()) - -# # apply cP1 on some discontinuous G - -# # G_sol_log = [[lambda xi1, xi2, ii=i : ii+xi1+xi2**2 for d in [0,1]] for i in range(len(domain))] -# # G_sol_log = [[lambda xi1, xi2, kk=k : levelof(kk)-1 for d in [0,1]] for k in range(len(domain))] -# G_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0, 1]] -# for k in range(len(domain))] -# #G_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0, 1]] -# # for k in range(len(domain))] -# P0, P1, P2 = derham_h.projectors() - -# G1h = P1(G_sol_log) -# G1h_coeffs = G1h.coeffs.toarray() - -# #G1h_coeffs = np.zeros(G1h_coeffs.size) -# #183, 182, 184 -# #G1h_coeffs[27] = 1 - -# plot_field(numpy_coeffs=G1h_coeffs, Vh=V1h, space_kind='hcurl', -# plot_type='components', -# domain=domain, title='G1h', cmap='viridis', -# filename=plot_dir+'/G.png') - - - -# G1h_conf_coeffs = cP1_m @ G1h_coeffs - -# plot_field(numpy_coeffs=G1h_conf_coeffs, Vh=V1h, space_kind='hcurl', -# plot_type='components', -# domain=domain, title='PG', cmap='viridis', -# filename=plot_dir+'/PG.png') - - - -# #G0_sol_log = [[lambda xi1, xi2, kk=k: kk for d in [0]] -# # for k in range(len(domain))] -# G0_sol_log = [[lambda xi1, xi2, kk=k:kk for d in [0]] -# for k in range(len(domain))] -# #G0_sol_log = [[lambda xi1, xi2, kk=k: np.cos(xi1)*np.sin(xi2) for d in [0]] -# # for k in range(len(domain))] -# G0h = P0(G0_sol_log) -# G0h_coeffs = G0h.coeffs.toarray() - -# #G0h_coeffs = np.zeros(G0h_coeffs.size) -# #183, 182, 184 -# #conforming -# # 30 - 24 -# # 28 - 23 -# #nc = 4, co: 59, co_ed:45, fi_ed:54 -# #G0h_coeffs[54] = 1 -# #G0h_coeffs[23] = 1 - -# plot_field(numpy_coeffs=G0h_coeffs, Vh=V0h, space_kind='h1', -# domain=domain, title='G0h', cmap='viridis', -# filename=plot_dir+'/G0.png') - -# G0h_conf_coeffs = (cP0_m@cP0_m-cP0_m) @ G0h_coeffs - -# plot_field(numpy_coeffs=G0h_conf_coeffs, Vh=V0h, space_kind='h1', -# domain=domain, title='PG0', cmap='viridis', -# filename=plot_dir+'/PG0.png') - -# plot_field(numpy_coeffs=cP0_m @ G0h_coeffs, Vh=V0h, space_kind='h1', -# domain=domain, title='PG00', cmap='viridis', -# filename=plot_dir+'/PG00.png') - -# if not nonconforming: -# cP0_martin = conf_proj_scalar_space(V0h, reg_orders, p_moments, nquads, hom_bc) - -# G0h_conf_coeffs_martin = cP0_martin @ G0h_coeffs -# #plot_field(numpy_coeffs=G0h_conf_coeffs_martin, Vh=V0h, space_kind='h1', -# # domain=domain, title='PG0_martin', cmap='viridis', -# # filename=plot_dir+'/PG0_martin.png') - -# import numpy as np -# import matplotlib.pyplot as plt -# reg = 0 -# reg_orders = [[reg-1, reg ], [reg, reg-1]] -# hom_bc_list = [[False, hom_bc[1]], [hom_bc[0], False]] -# deg_moments = [p_moments,p_moments] -# V1h = derham_h.V1 -# V1 = V1h.symbolic_space -# cP1_martin = conf_proj_vector_space(V1h, reg_orders=reg_orders, deg_moments=deg_moments, nquads=None, hom_bc_list=hom_bc_list) - -# #cP0_martin, cP1_martin, cP2_martin = conf_projectors_scipy(derham_h, single_space=None, reg=0, mom_pres=True, nquads=None, hom_bc=False) - -# G1h_conf_martin = cP1_martin @ G1h_coeffs - -# # plot_field(numpy_coeffs=G1h_conf_martin, Vh=V1h, space_kind='hcurl', -# # plot_type='components', -# # domain=domain, title='PG_martin', cmap='viridis', -# # filename=plot_dir+'/PG_martin.png') - - -# plt.matshow((cP1_m - cP1_martin).toarray()) -# plt.colorbar() -# print(sp_norm(cP1_m - cP1_martin)) -# #plt.matshow((cP0_m).toarray()) - -# #plt.matshow((cP0_martin).toarray()) -# #plt.show() - -# #print( np.sum(cP0_m - cP0_martin)) -# # print( cP0_m - cP0_martin) - -# print(sp_norm(cP0_m- cP0_m @ cP0_m)) -# print(sp_norm(cP1_m- cP1_m @ cP1_m)) - + return Mass_mat \ No newline at end of file diff --git a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py index bae7682e8..b8c759b6b 100644 --- a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py @@ -49,6 +49,8 @@ def get_polynomial_function(degree, hom_bc_axes, domain): @pytest.mark.parametrize('hom_bc', [[False, False]]) @pytest.mark.parametrize('mom_pres', [[-1, -1]]) @pytest.mark.parametrize('domain_name', ["4patch_nc"]) +@pytest.mark.parametrize('nonconforming', [True]) + def test_conf_projectors_2d( V1_type, @@ -57,11 +59,11 @@ def test_conf_projectors_2d( reg, hom_bc, mom_pres, - domain_name, + domain_name, + nonconforming ): nquads=None - nonconforming=True print(' .. multi-patch domain...') @@ -168,7 +170,7 @@ def levelof(k): p_V2h = p_derham_h.V2 # full moment preservation only possible if enough interior functions in a patch (<=> enough cells) - full_mom_pres = mom_pres and (nc >= 3 + 2*reg[0]) and (nc >= 3 + 2*reg[1]) + full_mom_pres = (mom_pres[0] >= degree[0] and mom_pres[1] >= degree[1]) and (nc >= 3 + 2*reg[0]) and (nc >= 3 + 2*reg[1]) # NOTE: if mom_pres but not full_mom_pres we could test reduced order moment preservation... # geometric projections (operators) @@ -272,7 +274,7 @@ def levelof(k): G1_star_c = M1_inv @ cP1.transpose() @ tilde_G1_c np.allclose(G1_c, G1_star_c, 1e-12, 1e-12) # (P1_geom - P1_star) polynomial = 0 print(np.linalg.norm(G1_c- G1_star_c)) - + # tests on cP2 (non trivial for reg = 1): g2 = get_polynomial_function(degree=[degree[0]-1,degree[1]-1], hom_bc_axes=[False,False], domain=domain) g2h = P_phys_l2(g2, p_geomP2, domain, mappings_list) @@ -293,17 +295,18 @@ def levelof(k): # if __name__ == '__main__': # V1_type = "Hcurl" # nc = 7 -# deg = 2 +# deg = 3 +# nonconforming = False # degree = [deg, deg] # reg=[0,0] -# mom_pres=[5,5] +# mom_pres=[4,4] # hom_bc = [False, False] # # domain_name = 'square_6' # # domain_name = 'curved_L_shape' # # domain_name = '2patch_nc_mapped' -# domain_name = '4patch_nc' +# domain_name = '2patch_nc' # test_conf_projectors_2d( # V1_type, @@ -312,5 +315,6 @@ def levelof(k): # reg, # hom_bc, # mom_pres, -# domain_name +# domain_name, +# nonconforming # ) \ No newline at end of file From 567ce21cbb9ed382c4a63bd5da090b4ee3b9a65d Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 5 Mar 2024 14:53:48 +0100 Subject: [PATCH 20/88] move pml experiments to its own branch --- .../multipatch/examples_nc/interface_pml.py | 467 ------------------ .../examples_nc/timedomain_maxwell_min.py | 401 --------------- .../examples_nc/timedomain_maxwell_pml.py | 355 ------------- 3 files changed, 1223 deletions(-) delete mode 100644 psydac/feec/multipatch/examples_nc/interface_pml.py delete mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py delete mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py diff --git a/psydac/feec/multipatch/examples_nc/interface_pml.py b/psydac/feec/multipatch/examples_nc/interface_pml.py deleted file mode 100644 index 172db3cfb..000000000 --- a/psydac/feec/multipatch/examples_nc/interface_pml.py +++ /dev/null @@ -1,467 +0,0 @@ -from pytest import param -from mpi4py import MPI - -import os -import numpy as np -import scipy as sp -from collections import OrderedDict -import matplotlib.pyplot as plt - -from sympy import lambdify, Matrix - -from scipy.sparse.linalg import spsolve -from scipy import special - -from sympde.calculus import dot -from sympde.topology import element_of -from sympde.expr.expr import LinearForm -from sympde.expr.expr import integral, Norm -from sympde.topology import Derham - -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.pull_push import pull_2d_hcurl - -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv -from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam, get_diag_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 -from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for -from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain - -from sympde.calculus import grad, dot, curl, cross -from sympde.topology import NormalVector -from sympde.expr.expr import BilinearForm -from sympde.topology import elements_of -from sympde import Tuple -from sympde.topology import Square, Domain -from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping #TransposedPolarMapping - -from psydac.api.postprocessing import OutputManager, PostProcessManager -from sympy.functions.special.error_functions import erf - -from psydac.feec.multipatch.non_matching_operators import get_moment_pres_scalar_extension_restriction -from psydac.fem.splines import SplineSpace - -def run_sim(): - ## Minimal example for a PML implementation of the Time-Domain Maxwells equation - ncells = [8, 16] - degree = [3,3] - plot_dir = "plots/PML/interface_diffusion" - final_time = 5 - - OmegaLog1 = Square('OmegaLog1',bounds1=(0., 2*np.pi), bounds2=(0., np.pi)) - mapping_1 = IdentityMapping('M1',2) - domain_1 = mapping_1(OmegaLog1) - - OmegaLog2 = Square('OmegaLog2',bounds1=(0., 2*np.pi), bounds2=(np.pi, 2*np.pi)) - mapping_2 = IdentityMapping('M2',2) - domain_2 = mapping_2(OmegaLog2) - - patches = [domain_1, domain_2] - - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1] - ] - domain = create_domain(patches, interfaces, name='domain') - - ncells_h = {patch.name: [2 * ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) - mappings_list = list(mappings.values()) - - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - domain_h = discretize(domain, ncells=ncells_h) - derham_h = discretize(derham, domain_h, degree=degree) - - nquads = [4*(d + 1) for d in degree] - P0, P1, P2 = derham_h.projectors(nquads=nquads) - - - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - - I1 = IdLinearOperator(V1h) - I1_m = I1.to_sparse_matrix() - - I2 = IdLinearOperator(V2h) - I2_m = I2.to_sparse_matrix() - - I0 = IdLinearOperator(V0h) - I0_m = I0.to_sparse_matrix() - - backend = 'pyccel-gcc' - - H0 = HodgeOperator(V0h, domain_h) - H1 = HodgeOperator(V1h, domain_h) - H2 = HodgeOperator(V2h, domain_h) - - dH0_m = H0.to_sparse_matrix() - H0_m = H0.get_dual_Hodge_sparse_matrix() - dH1_m = H1.to_sparse_matrix() - H1_m = H1.get_dual_Hodge_sparse_matrix() - dH2_m = H2.to_sparse_matrix() - H2_m = H2.get_dual_Hodge_sparse_matrix() - cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - - def patch_extension_restriction(coarse_space, fine_space): - # coarse patch, fine patch -> Matrix: fine -> coarse - E_xy = [] - R_xy = [] - for k in range(2): - cs_k = coarse_space.spaces[k] - knots = [ (k - cs_k.knots[0])/(cs_k.knots[-1] - cs_k.knots[0]) for k in cs_k.knots] - css_k = SplineSpace(cs_k.degree, knots = knots, basis=cs_k.basis) - - fs_k = fine_space.spaces[k] - knots = [ (k - fs_k.knots[0])/(fs_k.knots[-1] - fs_k.knots[0]) for k in fs_k.knots] - fss_k = SplineSpace(fs_k.degree, knots=knots, basis=fs_k.basis) - - matching = (css_k.ncells == fss_k.ncells) - E_k, R_k, ER_k = get_moment_pres_scalar_extension_restriction(matching, css_k, fss_k, css_k.basis) - E_xy.append(E_k) - R_xy.append(R_k) - - E = np.kron(E_xy[0].toarray(), E_xy[1].toarray()) - if matching: - R = np.kron(R_xy[0].toarray(), R_xy[1].toarray()) - else: - R = np.kron(R_xy[0], R_xy[1]) - - return E, R - - def global_matrices(V0h, V1h, V2h): - - E, R = patch_extension_restriction(V0h.spaces[0], V0h.spaces[1]) - n0 = V0h.spaces[0].nbasis - n1 = V0h.spaces[1].nbasis - R0_global = np.block([[np.eye(n0), np.zeros((n0, n1))], - [np.zeros((n1, n0)), E@R]]) - I0_global = np.eye(n0+n0) - - # first component - # E, R = patch_extension_restriction(V1h.spaces[0].spaces[0], V1h.spaces[1].spaces[0]) - n = V1h.spaces[0].nbasis - #10 = V1h.spaces[1].spaces[0].nbasis - R00_global = np.eye(n) #np.block([[np.eye(n00), np.zeros((n00, n10))], - #[np.zeros((n10, n00)), E@R]]) - - #second component - E, R = patch_extension_restriction(V1h.spaces[0].spaces[0], V1h.spaces[1].spaces[0]) - R0 = E@R - E, R = patch_extension_restriction(V1h.spaces[0].spaces[1], V1h.spaces[1].spaces[1]) - R1 = E@R - - n01 = V1h.spaces[1].spaces[0].nbasis - n11 = V1h.spaces[1].spaces[1].nbasis - - R11_global = np.block([[R0, np.zeros((n01, n11))], - [np.zeros((n11, n01)), R1]]) - - m11 = n11 + n01 - R1_global = np.block([[R00_global, np.zeros((n, m11))], - [np.zeros((m11, n)), R11_global]]) - I1_global = np.eye(n+n01+m11) - - E, R = patch_extension_restriction(V2h.spaces[0], V2h.spaces[1]) - n0 = V2h.spaces[0].nbasis - n1 = V2h.spaces[1].nbasis - R2_global = np.block([[np.eye(n0), np.zeros((n0, n1))], - [np.zeros((n1, n0)), E@R]]) - I2_global = np.eye(n0+n0) - - return R0_global, R1_global, R2_global - - R0_global, R1_global, R2_global = global_matrices(V0h, V1h, V2h) - - - ## boundary PML - u, v = elements_of(derham.V1, names='u, v') - x,y = domain.coordinates - - u1 = dot(Tuple(1,0),u) - u2 = dot(Tuple(0,1),u) - v1 = dot(Tuple(1,0),v) - v2 = dot(Tuple(0,1),v) - - def heaviside(x_direction, xmin, xmax, delta, sign, domain, fact): - x,y = domain.coordinates - - if sign == -1: - d = xmax - delta - else: - d = xmin + delta - - if x_direction == True: - return 1/2*(erf(-sign*(x-d) *fact)+1) - else: - return 1/2*(erf(-sign*(y-d) *fact)+1) - - def parabola(x_direction, xmin, xmax, delta, sign, domain): - x,y = domain.coordinates - - if sign == -1: - d = xmax - delta - else: - d = xmin + delta - - if x_direction == True: - return ((x - d)/delta)**2 - else: - return ((y - d)/delta)**2 - - def sigma_fun(x, xmin, xmax, delta, sign, sigma_m, domain): - return sigma_m * heaviside(x, xmin, xmax, delta, sign, domain, 1000) * parabola(x, xmin, xmax, delta, sign, domain) - - def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): - return sigma_fun(x, xmin, xmax, delta, 1, sigma_m, domain) + sigma_fun(x, xmin, xmax, delta, -1, sigma_m, domain) - - delta = np.pi/6 - xmin = 0 - xmax = 2*np.pi - ymin = 0 - ymax = 2*np.pi - sigma_0 = 20 - - sigma_x = sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) - sigma_y = sigma_fun_sym(False, ymin, ymax, delta, sigma_0, domain) - - mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) - massh = discretize(mass, domain_h, [V1h, V1h]) - M = massh.assemble().tosparse() - - u, v = elements_of(derham.V2, names='u, v') - mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) - massh = discretize(mass, domain_h, [V2h, V2h]) - M2 = massh.assemble().tosparse() - - # interface PML at y = pi - - u, v = elements_of(derham.V1, names='u, v') - x,y = domain.coordinates - - u1 = dot(Tuple(1,0),u) - u2 = dot(Tuple(0,1),u) - v1 = dot(Tuple(1,0),v) - v2 = dot(Tuple(0,1),v) - - delta = np.pi/6 - ycenter = np.pi + 3/2*delta - - sigma_0 = 0.5 - - sigma_x = 0#sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) - sigma_y = sigma_0 * heaviside(False, ycenter-delta, ycenter, delta, -1, domain, 10) * heaviside(False, ycenter, ycenter+delta, delta, 1, domain, 10) - - mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) - massh = discretize(mass, domain_h, [V1h, V1h]) - M_int = massh.assemble().tosparse() - - u, v = elements_of(derham.V2, names='u, v') - mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) - massh = discretize(mass, domain_h, [V2h, V2h]) - M2_int = massh.assemble().tosparse() - - # conf_proj = GSP - K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) - cP0_m = K0_inv @ cP0_m @ K0 - K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) - cP1_m = K1_inv @ cP1_m @ K1 - - bD0, bD1 = derham_h.broken_derivatives_as_operators - bD0_m = bD0.to_sparse_matrix() - bD1_m = bD1.to_sparse_matrix() - - - dH1_m = dH1_m.tocsr() - H2_m = H2_m.tocsr() - cP1_m = cP1_m.tocsr() - bD1_m = bD1_m.tocsr() - - C_m = bD1_m @ cP1_m - dC_m = dH1_m @ C_m.transpose() @ H2_m - - - div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m - - jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m - - f0_c = np.zeros(V1h.nbasis) - - - #E0, B0 = get_Gaussian_beam(x_0=3.14 , y_0=0.5*3.14, domain=domain) - E0, B0 = get_diag_Gaussian_beam(x_0=2/3 * np.pi + np.pi, y_0=np.pi/2, domain=domain) - E0_h = P1_phys(E0, P1, domain, mappings_list) - E_c = E0_h.coeffs.toarray() - - B0_h = P2_phys(B0, P2, domain, mappings_list) - B_c = B0_h.coeffs.toarray() - - E_c = dC_m @ B_c - B_c[:] = 0 - - OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') - OM1.add_spaces(V1h=V1h) - OM1.export_space_info() - - OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') - OM2.add_spaces(V2h=V2h) - OM2.export_space_info() - - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=0 , ts=0) - OM1.export_fields(Eh=Eh) - - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=0 , ts=0) - OM2.export_fields(Bh=Bh) - - dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=0.8, dt_max=None) - Nt = int(np.ceil(final_time/dt)) - dt = final_time / Nt - Epml = sp.sparse.linalg.spsolve(H1_m, M) - Bpml = sp.sparse.linalg.spsolve(H2_m, M2) - - Epml_int = sp.sparse.linalg.spsolve(H1_m, (I1_m - R1_global).transpose()@M_int@(I1_m - R1_global)) - Bpml_int = sp.sparse.linalg.spsolve(H2_m, (I2_m - R2_global).transpose()@M2_int@(I2_m - R2_global)) - - #Epml_int = sp.sparse.linalg.spsolve(H1_m, (I1_m - R1_global).transpose()@C_m.transpose()@M2_int@C_m@(I1_m - R1_global)) - - - f_c = np.copy(f0_c) - for nt in range(Nt): - print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) - - # 1/2 faraday: Bn -> Bn+1/2 - B_c[:] -= dt/2*(Bpml @ B_c + Bpml_int@B_c) + (dt/2) * C_m @ E_c - - E_c[:] += -dt*(Epml @ E_c + Epml_int @ E_c) + dt * (dC_m @ B_c - f_c) - #E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) - - B_c[:] -= dt/2*(Bpml @ B_c + Bpml_int@B_c) + (dt/2) * C_m @ E_c - - - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=nt*dt, ts=nt) - OM1.export_fields(Eh = Eh) - - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=nt*dt, ts=nt) - OM2.export_fields(Bh=Bh) - - OM1.close() - - print("Do some PP") - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) - PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=6,snapshots='all', fields = 'Eh' ) - PM.close() - - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=6,snapshots='all', fields = 'Bh' ) - PM.close() - - -#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): -def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): - """ - Compute a stable time step size based on the maximum CFL parameter in the - domain. To this end we estimate the operator norm of - - `dC_m @ C_m: V1h -> V1h`, - - find the largest stable time step compatible with Strang splitting, and - rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the - scheme exactly at its stability limit, which is not safe because of the - unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. - - Optionally the user can provide a maximum time step size in order to - properly resolve some time scales of interest (e.g. a time-dependent - current source). - - Parameters - ---------- - C_m : scipy.sparse.spmatrix - Matrix of the Curl operator. - - dC_m : scipy.sparse.spmatrix - Matrix of the dual Curl operator. - - cfl_max : float - Maximum Courant parameter in the domain, intended as a stability - parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. - - dt_max : float, optional - If not None, restrict the computed dt by this value in order to - properly resolve time scales of interest. Must be > 0. - - Returns - ------- - dt : float - Largest stable dt which satisfies the provided constraints. - - """ - - print (" .. compute_stable_dt by estimating the operator norm of ") - print (" .. dC_m @ C_m: V1h -> V1h ") - print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) - - if not (0 < cfl_max < 1): - print(' ****** ****** ****** ****** ****** ****** ') - print(' WARNING !!! cfl = {} '.format(cfl)) - print(' ****** ****** ****** ****** ****** ****** ') - - def vect_norm_2 (vv): - return np.sqrt(np.dot(vv,vv)) - - t_stamp = time_count() - vv = np.random.random(C_m.shape[1]) - norm_vv = vect_norm_2(vv) - max_ncfl = 500 - ncfl = 0 - spectral_rho = 1 - conv = False - CC_m = dC_m @ C_m - - while not( conv or ncfl > max_ncfl ): - - vv[:] = (1./norm_vv)*vv - ncfl += 1 - vv[:] = CC_m.dot(vv) - - norm_vv = vect_norm_2(vv) - old_spectral_rho = spectral_rho - spectral_rho = vect_norm_2(vv) # approximation - conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 - print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) - t_stamp = time_count(t_stamp) - - norm_op = np.sqrt(spectral_rho) - c_dt_max = 2./norm_op - - light_c = 1 - dt = cfl_max * c_dt_max / light_c - - if dt_max is not None: - dt = min(dt, dt_max) - - print( " Time step dt computed for Maxwell solver:") - print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") - print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") - print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") - - return dt - - -if __name__ == '__main__': - run_sim() \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py deleted file mode 100644 index ccdb47b88..000000000 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_min.py +++ /dev/null @@ -1,401 +0,0 @@ -from pytest import param -from mpi4py import MPI - -import os -import numpy as np -import scipy as sp -from collections import OrderedDict -import matplotlib.pyplot as plt - -from sympy import lambdify, Matrix - -from scipy.sparse.linalg import spsolve -from scipy import special - -from sympde.calculus import dot -from sympde.topology import element_of -from sympde.expr.expr import LinearForm -from sympde.expr.expr import integral, Norm -from sympde.topology import Derham - -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.pull_push import pull_2d_hcurl - -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv -from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 -from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for -from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain - -from sympde.calculus import grad, dot, curl, cross -from sympde.topology import NormalVector -from sympde.expr.expr import BilinearForm -from sympde.topology import elements_of -from sympde import Tuple - -from psydac.api.postprocessing import OutputManager, PostProcessManager -from sympy.functions.special.error_functions import erf - -def run_sim(): - ## Minimal example for a PML implementation of the Time-Domain Maxwells equation - nc = 10 - # ncells = np.array([[nc, nc, nc], - # [nc, 2*nc, nc], - # [nc, nc, nc]]) - - ncells = np.array([[nc, nc, nc, nc], - [nc, 2*nc, 2*nc, nc], - [nc, 2*nc, 2*nc, nc], - [nc, nc, nc, nc]]) - - # ncells = np.array([[2*nc, 2*nc, 2*nc, 2*nc], - # [2*nc, nc, nc, 2*nc], - # [2*nc, nc, nc, 2*nc], - # [2*nc, 2*nc, 2*nc, 2*nc]]) - - degree = [3,3] - plot_dir = "plots/PML/pml_test3" - bc = 'pml' #'none', 'abc' #'pml' - if not os.path.exists(plot_dir): - os.makedirs(plot_dir) - - x_lim = np.pi - y_lim = np.pi - final_time = 3 - - domain = create_square_domain(ncells, [0, x_lim], [0, y_lim]) - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) - mappings_list = list(mappings.values()) - - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - domain_h = discretize(domain, ncells=ncells_h) - derham_h = discretize(derham, domain_h, degree=degree) - - nquads = [4*(d + 1) for d in degree] - P0, P1, P2 = derham_h.projectors(nquads=nquads) - - - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - - I1 = IdLinearOperator(V1h) - I1_m = I1.to_sparse_matrix() - - backend = 'pyccel-gcc' - - H0 = HodgeOperator(V0h, domain_h) - H1 = HodgeOperator(V1h, domain_h) - H2 = HodgeOperator(V2h, domain_h) - - dH0_m = H0.to_sparse_matrix() - H0_m = H0.get_dual_Hodge_sparse_matrix() - dH1_m = H1.to_sparse_matrix() - H1_m = H1.get_dual_Hodge_sparse_matrix() - dH2_m = H2.to_sparse_matrix() - H2_m = H2.get_dual_Hodge_sparse_matrix() - cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - - ## PML - u, v = elements_of(derham.V1, names='u, v') - x,y = domain.coordinates - - u1 = dot(Tuple(1,0),u) - u2 = dot(Tuple(0,1),u) - v1 = dot(Tuple(1,0),v) - v2 = dot(Tuple(0,1),v) - - def heaviside(x_direction, xmin, xmax, delta, sign, domain): - x,y = domain.coordinates - - if sign == -1: - d = xmax - delta - else: - d = xmin + delta - - if x_direction == True: - return 1/2*(erf(-sign*(x-d) *1000)+1) - else: - return 1/2*(erf(-sign*(y-d) *1000)+1) - - def parabola(x_direction, xmin, xmax, delta, sign, domain): - x,y = domain.coordinates - - if sign == -1: - d = xmax - delta - else: - d = xmin + delta - - if x_direction == True: - return ((x - d)/delta)**2 - else: - return ((y - d)/delta)**2 - - def sigma_fun(x, xmin, xmax, delta, sign, sigma_m, domain): - return sigma_m * heaviside(x, xmin, xmax, delta, sign, domain) * parabola(x, xmin, xmax, delta, sign, domain) - - def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): - return sigma_fun(x, xmin, xmax, delta, 1, sigma_m, domain) + sigma_fun(x, xmin, xmax, delta, -1, sigma_m, domain) - - delta = np.pi/10 - xmin = 0 - xmax = x_lim - ymin = 0 - ymax = y_lim - sigma_0 = 15 - - sigma_x = sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) - sigma_y = sigma_fun_sym(False, ymin, ymax, delta, sigma_0, domain) - if bc == 'pml': - mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) - massh = discretize(mass, domain_h, [V1h, V1h]) - M = massh.assemble().tosparse() - - u, v = elements_of(derham.V2, names='u, v') - mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) - massh = discretize(mass, domain_h, [V2h, V2h]) - M2 = massh.assemble().tosparse() - - elif bc == 'abc': - ### Silvermueller ABC - - u, v = elements_of(derham.V1, names='u, v') - nn = NormalVector('nn') - boundary = domain.boundary - expr_b = cross(nn, u)*cross(nn, v) - - a = BilinearForm((u,v), integral(boundary, expr_b)) - ah = discretize(a, domain_h, [V1h, V1h], backend=PSYDAC_BACKENDS[backend],) - A_eps = ah.assemble().tosparse() - ### - - - # conf_proj = GSP - K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) - cP0_m = K0_inv @ cP0_m @ K0 - K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) - cP1_m = K1_inv @ cP1_m @ K1 - - bD0, bD1 = derham_h.broken_derivatives_as_operators - bD0_m = bD0.to_sparse_matrix() - bD1_m = bD1.to_sparse_matrix() - - - dH1_m = dH1_m.tocsr() - H2_m = H2_m.tocsr() - cP1_m = cP1_m.tocsr() - bD1_m = bD1_m.tocsr() - - C_m = bD1_m @ cP1_m - dC_m = dH1_m @ C_m.transpose() @ H2_m - - - div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m - - jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m - - f0_c = np.zeros(V1h.nbasis) - - - E0, B0 = get_Gaussian_beam(x_0=np.pi * 1/2 , y_0=np.pi * 1/2, domain=domain) - #E0, B0 = get_Berenger_wave(x_0=3.14/2 , y_0=3.14/2, domain=domain) - - E0_h = P1_phys(E0, P1, domain, mappings_list) - E_c = E0_h.coeffs.toarray() - - B0_h = P2_phys(B0, P2, domain, mappings_list) - B_c = B0_h.coeffs.toarray() - - #plot_field(numpy_coeffs=E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='amplitude', filename="E_amp_before") - - #plot_field(numpy_coeffs=E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='components', filename="E_comp_before") - #plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, filename="B_before") - - - # E_c_ = dC_m @ B_c - # B_c[:] = 0 - # plot_field(numpy_coeffs=E_c_, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='components', filename="E_comp_after") - # plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, filename="B_after") - - # E_c_ = E_c - #B_c = C_m @ E_c - # plot_field(numpy_coeffs=E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='components', filename="E_comp_after_after") - #plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, filename="B_after_after") - #B_c[:] = 0 - - - #exit() - - OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') - OM1.add_spaces(V1h=V1h) - OM1.export_space_info() - - OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') - OM2.add_spaces(V2h=V2h) - OM2.export_space_info() - - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=0 , ts=0) - OM1.export_fields(Eh=Eh) - - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=0 , ts=0) - OM2.export_fields(Bh=Bh) - - dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=0.8, dt_max=None) - Nt = int(np.ceil(final_time/dt)) - dt = final_time / Nt - if bc == 'pml': - Epml = sp.sparse.linalg.spsolve(H1_m, M) - Bpml = sp.sparse.linalg.spsolve(H2_m, M2) - elif bc == 'abc': - H1A = H1_m + dt * A_eps - A_eps = sp.sparse.linalg.spsolve(H1A, H1_m) - dC_m = sp.sparse.linalg.spsolve(H1A, C_m.transpose() @ H2_m) - elif bc == 'none': - A_eps = sp.sparse.linalg.spsolve(H1_m, H1_m) - - f_c = np.copy(f0_c) - for nt in range(Nt): - print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) - - # 1/2 faraday: Bn -> Bn+1/2 - if bc == 'pml': - B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c - E_c[:] += -dt*Epml @ E_c + dt * (dC_m @ B_c - f_c) - B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c - - else: - B_c[:] -= (dt/2) * C_m @ E_c - E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) - B_c[:] -= (dt/2) * C_m @ E_c - - #plot_field(numpy_coeffs=cP1_m @ E_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, plot_type='amplitude', filename=plot_dir+"/E_{}".format(nt)) - - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=nt*dt, ts=nt) - OM1.export_fields(Eh = Eh) - - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=nt*dt, ts=nt) - OM2.export_fields(Bh=Bh) - - OM1.close() - - print("Do some PP") - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) - PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Eh' ) - PM.close() - - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Bh' ) - PM.close() - - -#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): -def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): - """ - Compute a stable time step size based on the maximum CFL parameter in the - domain. To this end we estimate the operator norm of - - `dC_m @ C_m: V1h -> V1h`, - - find the largest stable time step compatible with Strang splitting, and - rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the - scheme exactly at its stability limit, which is not safe because of the - unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. - - Optionally the user can provide a maximum time step size in order to - properly resolve some time scales of interest (e.g. a time-dependent - current source). - - Parameters - ---------- - C_m : scipy.sparse.spmatrix - Matrix of the Curl operator. - - dC_m : scipy.sparse.spmatrix - Matrix of the dual Curl operator. - - cfl_max : float - Maximum Courant parameter in the domain, intended as a stability - parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. - - dt_max : float, optional - If not None, restrict the computed dt by this value in order to - properly resolve time scales of interest. Must be > 0. - - Returns - ------- - dt : float - Largest stable dt which satisfies the provided constraints. - - """ - - print (" .. compute_stable_dt by estimating the operator norm of ") - print (" .. dC_m @ C_m: V1h -> V1h ") - print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) - - if not (0 < cfl_max < 1): - print(' ****** ****** ****** ****** ****** ****** ') - print(' WARNING !!! cfl = {} '.format(cfl)) - print(' ****** ****** ****** ****** ****** ****** ') - - def vect_norm_2 (vv): - return np.sqrt(np.dot(vv,vv)) - - t_stamp = time_count() - vv = np.random.random(C_m.shape[1]) - norm_vv = vect_norm_2(vv) - max_ncfl = 500 - ncfl = 0 - spectral_rho = 1 - conv = False - CC_m = dC_m @ C_m - - while not( conv or ncfl > max_ncfl ): - - vv[:] = (1./norm_vv)*vv - ncfl += 1 - vv[:] = CC_m.dot(vv) - - norm_vv = vect_norm_2(vv) - old_spectral_rho = spectral_rho - spectral_rho = vect_norm_2(vv) # approximation - conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 - print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) - t_stamp = time_count(t_stamp) - - norm_op = np.sqrt(spectral_rho) - c_dt_max = 2./norm_op - - light_c = 1 - dt = cfl_max * c_dt_max / light_c - - if dt_max is not None: - dt = min(dt, dt_max) - - print( " Time step dt computed for Maxwell solver:") - print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") - print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") - print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") - - return dt - - -if __name__ == '__main__': - run_sim() \ No newline at end of file diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py deleted file mode 100644 index a78a24f5e..000000000 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_pml.py +++ /dev/null @@ -1,355 +0,0 @@ -from pytest import param -from mpi4py import MPI - -import os -import numpy as np -import scipy as sp -from collections import OrderedDict -import matplotlib.pyplot as plt - -from sympy import lambdify, Matrix - -from scipy.sparse.linalg import spsolve -from scipy import special - -from sympde.calculus import dot -from sympde.topology import element_of -from sympde.expr.expr import LinearForm -from sympde.expr.expr import integral, Norm -from sympde.topology import Derham - -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.pull_push import pull_2d_hcurl - -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv -from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 -from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for -from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain - -from sympde.calculus import grad, dot, curl, cross -from sympde.topology import NormalVector -from sympde.expr.expr import BilinearForm -from sympde.topology import elements_of -from sympde import Tuple - -from psydac.api.postprocessing import OutputManager, PostProcessManager -from sympy.functions.special.error_functions import erf - -def run_sim(): - ## Minimal example for a PML implementation of the Time-Domain Maxwells equation - ncells = [8, 8, 8, 8] - degree = [3,3] - plot_dir = "plots/PML/test2" - if not os.path.exists(plot_dir): - os.makedirs(plot_dir) - final_time = 3 - - domain = build_multipatch_domain(domain_name='square_4') - ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) - mappings_list = list(mappings.values()) - - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - domain_h = discretize(domain, ncells=ncells_h) - derham_h = discretize(derham, domain_h, degree=degree) - - nquads = [4*(d + 1) for d in degree] - P0, P1, P2 = derham_h.projectors(nquads=nquads) - - - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - - I1 = IdLinearOperator(V1h) - I1_m = I1.to_sparse_matrix() - - backend = 'pyccel-gcc' - - H0 = HodgeOperator(V0h, domain_h) - H1 = HodgeOperator(V1h, domain_h) - H2 = HodgeOperator(V2h, domain_h) - - dH0_m = H0.to_sparse_matrix() - H0_m = H0.get_dual_Hodge_sparse_matrix() - dH1_m = H1.to_sparse_matrix() - H1_m = H1.get_dual_Hodge_sparse_matrix() - dH2_m = H2.to_sparse_matrix() - H2_m = H2.get_dual_Hodge_sparse_matrix() - cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - - ## PML - u, v = elements_of(derham.V1, names='u, v') - x,y = domain.coordinates - - u1 = dot(Tuple(1,0),u) - u2 = dot(Tuple(0,1),u) - v1 = dot(Tuple(1,0),v) - v2 = dot(Tuple(0,1),v) - - def heaviside(x_direction, xmin, xmax, delta, sign, domain): - x,y = domain.coordinates - - if sign == -1: - d = xmax - delta - else: - d = xmin + delta - - if x_direction == True: - return 1/2*(erf(-sign*(x-d) *1000)+1) - else: - return 1/2*(erf(-sign*(y-d) *1000)+1) - - def parabola(x_direction, xmin, xmax, delta, sign, domain): - x,y = domain.coordinates - - if sign == -1: - d = xmax - delta - else: - d = xmin + delta - - if x_direction == True: - return ((x - d)/delta)**2 - else: - return ((y - d)/delta)**2 - - def sigma_fun(x, xmin, xmax, delta, sign, sigma_m, domain): - return sigma_m * heaviside(x, xmin, xmax, delta, sign, domain) * parabola(x, xmin, xmax, delta, sign, domain) - - def sigma_fun_sym(x, xmin, xmax, delta, sigma_m, domain): - return sigma_fun(x, xmin, xmax, delta, 1, sigma_m, domain) + sigma_fun(x, xmin, xmax, delta, -1, sigma_m, domain) - - delta = np.pi/8 - xmin = 0 - xmax = np.pi - ymin = 0 - ymax = np.pi - sigma_0 = 20 - - sigma_x = sigma_fun_sym(True, xmin, xmax, delta, sigma_0, domain) - sigma_y = sigma_fun_sym(False, ymin, ymax, delta, sigma_0, domain) - - mass = BilinearForm((v,u), integral(domain, u1*v1*sigma_y + u2*v2*sigma_x)) - massh = discretize(mass, domain_h, [V1h, V1h]) - M = massh.assemble().tosparse() - - u, v = elements_of(derham.V2, names='u, v') - mass = BilinearForm((v,u), integral(domain, u*v*(sigma_y + sigma_x))) - massh = discretize(mass, domain_h, [V2h, V2h]) - M2 = massh.assemble().tosparse() - #### - - ### Silvermueller ABC - # u, v = elements_of(derham.V1, names='u, v') - # nn = NormalVector('nn') - # boundary = domain.boundary - # expr_b = cross(nn, u)*cross(nn, v) - - # a = BilinearForm((u,v), integral(boundary, expr_b)) - # ah = discretize(a, domain_h, [V1h, V1h], backend=PSYDAC_BACKENDS[backend],) - # A_eps = ah.assemble().tosparse() - ### - - - # conf_proj = GSP - K0, K0_inv = get_K0_and_K0_inv(V0h, uniform_patches=False) - cP0_m = K0_inv @ cP0_m @ K0 - K1, K1_inv = get_K1_and_K1_inv(V1h, uniform_patches=False) - cP1_m = K1_inv @ cP1_m @ K1 - - bD0, bD1 = derham_h.broken_derivatives_as_operators - bD0_m = bD0.to_sparse_matrix() - bD1_m = bD1.to_sparse_matrix() - - - dH1_m = dH1_m.tocsr() - H2_m = H2_m.tocsr() - cP1_m = cP1_m.tocsr() - bD1_m = bD1_m.tocsr() - - C_m = bD1_m @ cP1_m - dC_m = dH1_m @ C_m.transpose() @ H2_m - - - div_m = dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m - - jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m - - f0_c = np.zeros(V1h.nbasis) - - - E0, B0 = get_Gaussian_beam(x_0=3.14/2 , y_0=1, domain=domain) - E0_h = P1_phys(E0, P1, domain, mappings_list) - E_c = E0_h.coeffs.toarray() - - B0_h = P2_phys(B0, P2, domain, mappings_list) - B_c = B0_h.coeffs.toarray() - - E_c = dC_m @ B_c - B_c[:] = 0 - - OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') - OM1.add_spaces(V1h=V1h) - OM1.export_space_info() - - OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') - OM2.add_spaces(V2h=V2h) - OM2.export_space_info() - - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=0 , ts=0) - OM1.export_fields(Eh=Eh) - - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=0 , ts=0) - OM2.export_fields(Bh=Bh) - - dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=0.8, dt_max=None) - Nt = int(np.ceil(final_time/dt)) - dt = final_time / Nt - Epml = sp.sparse.linalg.spsolve(H1_m, M) - Bpml = sp.sparse.linalg.spsolve(H2_m, M2) - #H1A = H1_m + dt * A_eps - #A_eps = sp.sparse.linalg.spsolve(H1A, H1_m) - - f_c = np.copy(f0_c) - for nt in range(Nt): - print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) - - # 1/2 faraday: Bn -> Bn+1/2 - B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c - - E_c[:] += -dt*Epml @ E_c + dt * (dC_m @ B_c - f_c) - #E_c[:] = A_eps @ E_c + dt * (dC_m @ B_c - f_c) - - B_c[:] -= dt/2*Bpml@B_c + (dt/2) * C_m @ E_c - - - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=nt*dt, ts=nt) - OM1.export_fields(Eh = Eh) - - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=nt*dt, ts=nt) - OM2.export_fields(Bh=Bh) - - OM1.close() - OM2.close() - - print("Do some PP") - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) - PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Eh' ) - PM.close() - - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=4,snapshots='all', fields = 'Bh' ) - PM.close() - - -#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): -def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): - """ - Compute a stable time step size based on the maximum CFL parameter in the - domain. To this end we estimate the operator norm of - - `dC_m @ C_m: V1h -> V1h`, - - find the largest stable time step compatible with Strang splitting, and - rescale it by the provided `cfl_max`. Setting `cfl_max = 1` would run the - scheme exactly at its stability limit, which is not safe because of the - unavoidable round-off errors. Hence we require `0 < cfl_max < 1`. - - Optionally the user can provide a maximum time step size in order to - properly resolve some time scales of interest (e.g. a time-dependent - current source). - - Parameters - ---------- - C_m : scipy.sparse.spmatrix - Matrix of the Curl operator. - - dC_m : scipy.sparse.spmatrix - Matrix of the dual Curl operator. - - cfl_max : float - Maximum Courant parameter in the domain, intended as a stability - parameter (=1 at the stability limit). Must be `0 < cfl_max < 1`. - - dt_max : float, optional - If not None, restrict the computed dt by this value in order to - properly resolve time scales of interest. Must be > 0. - - Returns - ------- - dt : float - Largest stable dt which satisfies the provided constraints. - - """ - - print (" .. compute_stable_dt by estimating the operator norm of ") - print (" .. dC_m @ C_m: V1h -> V1h ") - print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) - - if not (0 < cfl_max < 1): - print(' ****** ****** ****** ****** ****** ****** ') - print(' WARNING !!! cfl = {} '.format(cfl)) - print(' ****** ****** ****** ****** ****** ****** ') - - def vect_norm_2 (vv): - return np.sqrt(np.dot(vv,vv)) - - t_stamp = time_count() - vv = np.random.random(C_m.shape[1]) - norm_vv = vect_norm_2(vv) - max_ncfl = 500 - ncfl = 0 - spectral_rho = 1 - conv = False - CC_m = dC_m @ C_m - - while not( conv or ncfl > max_ncfl ): - - vv[:] = (1./norm_vv)*vv - ncfl += 1 - vv[:] = CC_m.dot(vv) - - norm_vv = vect_norm_2(vv) - old_spectral_rho = spectral_rho - spectral_rho = vect_norm_2(vv) # approximation - conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 - print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) - t_stamp = time_count(t_stamp) - - norm_op = np.sqrt(spectral_rho) - c_dt_max = 2./norm_op - - light_c = 1 - dt = cfl_max * c_dt_max / light_c - - if dt_max is not None: - dt = min(dt, dt_max) - - print( " Time step dt computed for Maxwell solver:") - print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") - print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") - print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") - - return dt - - -if __name__ == '__main__': - run_sim() \ No newline at end of file From 2af29a874eddae7ee8bac4bb2f676cfba2d09563 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 5 Mar 2024 16:25:49 +0100 Subject: [PATCH 21/88] adapt tests --- .../test_feec_conf_projectors_cart_2d.py | 95 +++++-------------- 1 file changed, 25 insertions(+), 70 deletions(-) diff --git a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py index b8c759b6b..23f1d4d73 100644 --- a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py @@ -11,6 +11,7 @@ from psydac.feec.multipatch.api import discretize from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain +from sympde.topology import IdentityMapping, PolarMapping from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection @@ -47,9 +48,8 @@ def get_polynomial_function(degree, hom_bc_axes, domain): @pytest.mark.parametrize('nc', [4]) @pytest.mark.parametrize('reg', [[0,0]]) @pytest.mark.parametrize('hom_bc', [[False, False]]) -@pytest.mark.parametrize('mom_pres', [[-1, -1]]) -@pytest.mark.parametrize('domain_name', ["4patch_nc"]) -@pytest.mark.parametrize('nonconforming', [True]) +@pytest.mark.parametrize('domain_name', ["4patch_nc", "2patch_nc"]) +@pytest.mark.parametrize("nonconforming, full_mom_pres", [(True, False), (False, True)]) def test_conf_projectors_2d( @@ -58,7 +58,7 @@ def test_conf_projectors_2d( nc, reg, hom_bc, - mom_pres, + full_mom_pres, domain_name, nonconforming ): @@ -67,18 +67,7 @@ def test_conf_projectors_2d( print(' .. multi-patch domain...') - if domain_name == '2patch_nc_mapped': - - A = Square('A', bounds1=(0.5, 1), bounds2=(0, np.pi/2)) - B = Square('B', bounds1=(0.5, 1), bounds2=(np.pi/2, np.pi)) - M1 = PolarMapping('M1', 2, c1=0, c2=0, rmin=0., rmax=1.) - M2 = PolarMapping('M2', 2, c1=0, c2=0, rmin=0., rmax=1.) - A = M1(A) - B = M2(B) - - domain = create_domain([A, B], [[A.get_boundary(axis=1, ext=1), B.get_boundary(axis=1, ext=-1), 1]], name='domain') - - elif domain_name == '2patch_nc': + if domain_name == '2patch_nc': A = Square('A', bounds1=(0, 0.5), bounds2=(0, 1)) B = Square('B', bounds1=(0.5, 1.), bounds2=(0, 1)) @@ -170,7 +159,11 @@ def levelof(k): p_V2h = p_derham_h.V2 # full moment preservation only possible if enough interior functions in a patch (<=> enough cells) - full_mom_pres = (mom_pres[0] >= degree[0] and mom_pres[1] >= degree[1]) and (nc >= 3 + 2*reg[0]) and (nc >= 3 + 2*reg[1]) + if full_mom_pres and (nc >= 3 + 2*reg[0]) and (nc >= 3 + 2*reg[1]): + mom_pres = degree + else: + mom_pres = [-1,-1] + # NOTE: if mom_pres but not full_mom_pres we could test reduced order moment preservation... # geometric projections (operators) @@ -200,17 +193,12 @@ def levelof(k): D0 = bD0 @ cP0 # Conga grad D1 = bD1 @ cP1 # Conga curl or div - np.allclose(sp_norm(cP0 - cP0@cP0), 0, 1e-12, 1e-12) # cP0 is a projection - print(sp_norm(cP0 - cP0@cP0)) - np.allclose(sp_norm(cP1 - cP1@cP1), 0, 1e-12, 1e-12) # cP1 is a projection - print(sp_norm(cP1 - cP1@cP1)) - np.allclose(sp_norm(cP2 - cP2@cP2), 0, 1e-12, 1e-12) # cP2 is a projection - print(sp_norm(cP2 - cP2@cP2)) + assert np.allclose(sp_norm(cP0 - cP0@cP0), 0, 1e-12, 1e-12) # cP0 is a projection + assert np.allclose(sp_norm(cP1 - cP1@cP1), 0, 1e-12, 1e-12) # cP1 is a projection + assert np.allclose(sp_norm(cP2 - cP2@cP2), 0, 1e-12, 1e-12) # cP2 is a projection - np.allclose(sp_norm( D0 - cP1@D0), 0, 1e-12, 1e-12) # D0 maps in the conforming V1 space (where cP1 coincides with Id) - print(sp_norm( D0 - cP1@D0)) - np.allclose(sp_norm( D1 - cP2@D1), 0, 1e-12, 1e-12) # D1 maps in the conforming V2 space (where cP2 coincides with Id) - print(sp_norm( D1 - cP2@D1)) + assert np.allclose(sp_norm( D0 - cP1@D0), 0, 1e-12, 1e-12) # D0 maps in the conforming V1 space (where cP1 coincides with Id) + assert np.allclose(sp_norm( D1 - cP2@D1), 0, 1e-12, 1e-12) # D1 maps in the conforming V2 space (where cP2 coincides with Id) # comparing projections of polynomials which should be exact @@ -222,10 +210,9 @@ def levelof(k): tilde_g0_c = p_derham_h.get_dual_dofs(space='V0', f=g0, return_format='numpy_array') g0_L2_c = M0_inv @ tilde_g0_c - np.allclose(g0_c, g0_L2_c, 1e-12, 1e-12) # (P0_geom - P0_L2) polynomial = 0 - np.allclose(g0_c, cP0@g0_L2_c, 1e-12, 1e-12) # (P0_geom - confP0 @ P0_L2) polynomial= 0 - print(np.linalg.norm(g0_c- g0_L2_c)) - print(np.linalg.norm(g0_c- cP0@g0_L2_c)) + assert np.allclose(g0_c, g0_L2_c, 1e-12, 1e-12) # (P0_geom - P0_L2) polynomial = 0 + assert np.allclose(g0_c, cP0@g0_L2_c, 1e-12, 1e-12) # (P0_geom - confP0 @ P0_L2) polynomial= 0 + if full_mom_pres: # testing that polynomial moments are preserved: # the following projection should be exact for polynomials of proper degree (no bc) @@ -236,8 +223,7 @@ def levelof(k): tilde_g0_c = p_derham_h.get_dual_dofs(space='V0', f=g0, return_format='numpy_array') g0_star_c = M0_inv @ cP0.transpose() @ tilde_g0_c - np.allclose(g0_c, g0_star_c, 1e-12, 1e-12) # (P10_geom - P0_star) polynomial = 0 - print(np.linalg.norm(g0_c- g0_star_c)) + assert np.allclose(g0_c, g0_star_c, 1e-12, 1e-12) # (P10_geom - P0_star) polynomial = 0 # tests on cP1: @@ -254,11 +240,8 @@ def levelof(k): tilde_G1_c = p_derham_h.get_dual_dofs(space='V1', f=G1, return_format='numpy_array') G1_L2_c = M1_inv @ tilde_G1_c - np.allclose(G1_c, G1_L2_c, 1e-12, 1e-12) - print(np.linalg.norm(G1_c- G1_L2_c))# (P1_geom - P1_L2) polynomial = 0 - np.allclose(G1_c, cP1 @ G1_L2_c, 1e-12, 1e-12) # (P1_geom - confP1 @ P1_L2) polynomial= 0 - print(np.linalg.norm(G1_c- cP1 @ G1_L2_c)) - + assert np.allclose(G1_c, G1_L2_c, 1e-12, 1e-12) + assert np.allclose(G1_c, cP1 @ G1_L2_c, 1e-12, 1e-12) # (P1_geom - confP1 @ P1_L2) polynomial= 0 if full_mom_pres: # as above @@ -272,8 +255,7 @@ def levelof(k): tilde_G1_c = p_derham_h.get_dual_dofs(space='V1', f=G1, return_format='numpy_array') G1_star_c = M1_inv @ cP1.transpose() @ tilde_G1_c - np.allclose(G1_c, G1_star_c, 1e-12, 1e-12) # (P1_geom - P1_star) polynomial = 0 - print(np.linalg.norm(G1_c- G1_star_c)) + assert np.allclose(G1_c, G1_star_c, 1e-12, 1e-12) # (P1_geom - P1_star) polynomial = 0 # tests on cP2 (non trivial for reg = 1): g2 = get_polynomial_function(degree=[degree[0]-1,degree[1]-1], hom_bc_axes=[False,False], domain=domain) @@ -283,38 +265,11 @@ def levelof(k): tilde_g2_c = p_derham_h.get_dual_dofs(space='V2', f=g2, return_format='numpy_array') g2_L2_c = M2_inv @ tilde_g2_c - np.allclose(g2_c, g2_L2_c, 1e-12, 1e-12) # (P2_geom - P2_L2) polynomial = 0 - np.allclose(g2_c, cP2 @ g2_L2_c, 1e-12, 1e-12) # (P2_geom - confP2 @ P2_L2) polynomial = 0 + assert np.allclose(g2_c, g2_L2_c, 1e-12, 1e-12) # (P2_geom - P2_L2) polynomial = 0 + assert np.allclose(g2_c, cP2 @ g2_L2_c, 1e-12, 1e-12) # (P2_geom - confP2 @ P2_L2) polynomial = 0 if full_mom_pres: # as above, here with same degree and bc as # tilde_g2_c = p_derham_h.get_dual_dofs(space='V2', f=g2, return_format='numpy_array', nquads=nquads) g2_star_c = M2_inv @ cP2.transpose() @ tilde_g2_c - np.allclose(g2_c, g2_star_c, 1e-12, 1e-12) # (P2_geom - P2_star) polynomial = 0 - -# if __name__ == '__main__': -# V1_type = "Hcurl" -# nc = 7 -# deg = 3 -# nonconforming = False - -# degree = [deg, deg] -# reg=[0,0] -# mom_pres=[4,4] -# hom_bc = [False, False] - -# # domain_name = 'square_6' -# # domain_name = 'curved_L_shape' -# # domain_name = '2patch_nc_mapped' -# domain_name = '2patch_nc' - -# test_conf_projectors_2d( -# V1_type, -# degree, -# nc, -# reg, -# hom_bc, -# mom_pres, -# domain_name, -# nonconforming -# ) \ No newline at end of file + assert np.allclose(g2_c, g2_star_c, 1e-12, 1e-12) # (P2_geom - P2_star) polynomial = 0 \ No newline at end of file From d4fbdbfc37e9f5b9f4519fed8a476892b98dfe73 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Thu, 7 Mar 2024 19:19:43 +0100 Subject: [PATCH 22/88] change Hodge matrix naming conventions --- .../examples/h1_source_pbms_conga_2d.py | 20 ++-- .../examples/hcurl_eigen_pbms_conga_2d.py | 23 ++--- .../examples/hcurl_source_pbms_conga_2d.py | 34 +++---- .../examples/mixed_source_pbms_conga_2d.py | 38 ++++---- .../examples_nc/h1_source_pbms_nc.py | 17 ++-- .../examples_nc/hcurl_eigen_pbms_nc.py | 16 +-- .../examples_nc/hcurl_source_pbms_nc.py | 16 +-- .../examples_nc/timedomain_maxwell_nc.py | 13 +-- psydac/feec/multipatch/operators.py | 97 +++++++++++-------- .../test_feec_conf_projectors_cart_2d.py | 12 +-- 10 files changed, 149 insertions(+), 137 deletions(-) diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index a1bc6fdfe..485900f14 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -119,10 +119,10 @@ def solve_h1_source_pbm( H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language) H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = mass matrix of V0 - H0_m = H0.to_sparse_matrix() # = inverse mass matrix of V0 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = mass matrix of V1 - # H1_m = H1.to_sparse_matrix() # = inverse mass matrix of V1 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) @@ -149,13 +149,13 @@ def lift_u_bc(u_bc): # Conga (projection-based) stiffness matrices: # div grad: - pre_DG_m = - bD0_m.transpose() @ dH1_m @ bD0_m + pre_DG_m = - bD0_m.transpose() @ H1_m @ bD0_m # jump penalization: jump_penal_m = I0_m - cP0_m - JP0_m = jump_penal_m.transpose() * dH0_m * jump_penal_m + JP0_m = jump_penal_m.transpose() * H0_m * jump_penal_m - pre_A_m = cP0_m.transpose() @ ( eta * dH0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) + pre_A_m = cP0_m.transpose() @ ( eta * H0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) A_m = pre_A_m @ cP0_m + gamma_h * JP0_m print('getting the source and ref solution...') @@ -175,7 +175,7 @@ def lift_u_bc(u_bc): f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] f_h = P0(f_log) f_c = f_h.coeffs.toarray() - b_c = dH0_m.dot(f_c) + b_c = H0_m.dot(f_c) elif source_proj == 'P_L2': print('projecting the source with L2 projection...') @@ -186,12 +186,12 @@ def lift_u_bc(u_bc): b = lh.assemble() b_c = b.toarray() if plot_source: - f_c = H0_m.dot(b_c) + f_c = dH0_m.dot(b_c) else: raise ValueError(source_proj) if plot_source: - plot_field(numpy_coeffs=f_c, Vh=V0h, space_kind='h1', domain=domain, title='f_h with P = '+source_proj, filename=plot_dir+'/fh_'+source_proj+'.png', hide_plot=hide_plots) + plot_field(numpy_coeffs=f_c, Vh=V0h, space_kind='h1', domain=domain, title='f_h with P = '+source_proj, filename=plot_dir+'fh_'+source_proj+'.png', hide_plot=hide_plots) ubc_c = lift_u_bc(u_bc) diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index f1cfc3d71..01e74d868 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -95,11 +95,12 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = mass matrix of V0 - H0_m = H0.to_sparse_matrix() # = inverse mass matrix of V0 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = mass matrix of V1 - H1_m = H1.to_sparse_matrix() # = inverse mass matrix of V1 - dH2_m = H2.get_dual_Hodge_sparse_matrix() # = mass matrix of V2 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + # dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) @@ -117,17 +118,17 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language # Conga (projection-based) stiffness matrices # curl curl: print('curl-curl stiffness matrix...') - pre_CC_m = bD1_m.transpose() @ dH2_m @ bD1_m + pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix # grad div: print('grad-div stiffness matrix...') - pre_GD_m = - dH1_m @ bD0_m @ cP0_m @ H0_m @ cP0_m.transpose() @ bD0_m.transpose() @ dH1_m + pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix # jump penalization in V1h: jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * dH1_m * jump_penal_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m print('computing the full operator matrix...') print('mu = {}'.format(mu)) @@ -136,9 +137,9 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language if False: #gneralized problen print('adding jump stabilization to RHS of generalized eigenproblem...') - B_m = cP1_m.transpose() @ dH1_m @ cP1_m + JS_m + B_m = cP1_m.transpose() @ H1_m @ cP1_m + JS_m else: - B_m = dH1_m + B_m = H1_m print('solving matrix eigenproblem...') all_eigenvalues, all_eigenvectors_transp = get_eigenvalues(nb_eigs, sigma, A_m, B_m) @@ -169,7 +170,7 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('looking at emode i = {}: {}... '.format(i, lambda_i)) emode_i = np.real(eigenvectors[i]) - norm_emode_i = np.dot(emode_i,dH1_m.dot(emode_i)) + norm_emode_i = np.dot(emode_i,H1_m.dot(emode_i)) print('norm of computed eigenmode: ', norm_emode_i) eh_c = emode_i/norm_emode_i # numpy coeffs of the normalized eigenmode plot_field(numpy_coeffs=eh_c, Vh=V1h, space_kind='hcurl', domain=domain, title='mode e_{}, lambda_{}={}'.format(i,i,lambda_i), diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index fd1760396..e9c6a8f7c 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -137,26 +137,26 @@ def solve_hcurl_source_pbm( H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) t_stamp = time_count(t_stamp) - print('building the dual Hodge matrix dH0_m = M0_m ...') - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = mass matrix of V0 + print('building the primal Hodge matrix H0_m = M0_m ...') + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 t_stamp = time_count(t_stamp) - print('building the primal Hodge matrix H0_m = inv_M0_m ...') - H0_m = H0.to_sparse_matrix() # = inverse mass matrix of V0 + print('building the dual Hodge matrix dH0_m = inv_M0_m ...') + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 t_stamp = time_count(t_stamp) - print('building the dual Hodge matrix dH1_m = M1_m ...') - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = mass matrix of V1 + print('building the primal Hodge matrix H1_m = M1_m ...') + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 t_stamp = time_count(t_stamp) - print('building the primal Hodge matrix H1_m = inv_M1_m ...') - H1_m = H1.to_sparse_matrix() # = inverse mass matrix of V1 + print('building the dual Hodge matrix dH1_m = inv_M1_m ...') + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 # print("dH1_m @ H1_m == I1_m: {}".format(np.allclose((dH1_m @ H1_m).todense(), I1_m.todense())) ) # CHECK: OK t_stamp = time_count(t_stamp) - print('building the dual Hodge matrix dH2_m = M2_m ...') - dH2_m = H2.get_dual_Hodge_sparse_matrix() # = mass matrix of V2 + print('building the primal Hodge matrix H2_m = M2_m ...') + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 t_stamp = time_count(t_stamp) print('building the conforming Projection operators and matrices...') @@ -194,28 +194,28 @@ def lift_u_bc(u_bc): # curl curl: t_stamp = time_count(t_stamp) print('computing the curl-curl stiffness matrix...') - print(bD1_m.shape, dH2_m.shape ) - pre_CC_m = bD1_m.transpose() @ dH2_m @ bD1_m + print(bD1_m.shape, H2_m.shape ) + pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix # grad div: t_stamp = time_count(t_stamp) print('computing the grad-div stiffness matrix...') - pre_GD_m = - dH1_m @ bD0_m @ cP0_m @ H0_m @ cP0_m.transpose() @ bD0_m.transpose() @ dH1_m + pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m # GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix # jump penalization: t_stamp = time_count(t_stamp) print('computing the jump penalization matrix...') jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * dH1_m * jump_penal_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m t_stamp = time_count(t_stamp) print('computing the full operator matrix...') print('eta = {}'.format(eta)) print('mu = {}'.format(mu)) print('nu = {}'.format(nu)) - pre_A_m = cP1_m.transpose() @ ( eta * dH1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) + pre_A_m = cP1_m.transpose() @ ( eta * H1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) A_m = pre_A_m @ cP1_m + gamma_h * JP_m # get exact source, bc's, ref solution... @@ -239,7 +239,7 @@ def lift_u_bc(u_bc): f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] f_h = P1(f_log) f_c = f_h.coeffs.toarray() - b_c = dH1_m.dot(f_c) + b_c = H1_m.dot(f_c) elif source_proj == 'P_L2': # f_h = L2 projection of f_vect @@ -251,7 +251,7 @@ def lift_u_bc(u_bc): b = lh.assemble() b_c = b.toarray() if plot_source: - f_c = H1_m.dot(b_c) + f_c = dH1_m.dot(b_c) else: raise ValueError(source_proj) diff --git a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py index 2887da855..ab4c17bb7 100644 --- a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py @@ -159,15 +159,15 @@ def P2_phys(f_phys): H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = mass matrix of V0 - H0_m = H0.to_sparse_matrix() # = inverse mass matrix of V0 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = mass matrix of V1 - H1_m = H1.to_sparse_matrix() # = inverse mass matrix of V1 - dH2_m = H2.get_dual_Hodge_sparse_matrix() # = mass matrix of V2 - H2_m = H2.to_sparse_matrix() # = inverse mass matrix of V2 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 - M0_m = dH0_m - M1_m = dH1_m # usual notation + M0_m = H0_m + M1_m = H1_m # usual notation hom_bc = (bc_type == 'pseudo-vacuum') # /!\ here u = B is in H(curl), not E /!\ print('with hom_bc = {}'.format(hom_bc)) @@ -189,18 +189,18 @@ def P2_phys(f_phys): # Conga (projection-based) operator matrices print('grad matrix...') G_m = bD0_m @ cP0_m - tG_m = dH1_m @ G_m # grad: V0h -> tV1h + tG_m = H1_m @ G_m # grad: V0h -> tV1h print('curl-curl stiffness matrix...') C_m = bD1_m @ cP1_m - CC_m = C_m.transpose() @ dH2_m @ C_m + CC_m = C_m.transpose() @ H2_m @ C_m # jump penalization and stabilization operators: JP0_m = I0_m - cP0_m - S0_m = JP0_m.transpose() @ dH0_m @ JP0_m + S0_m = JP0_m.transpose() @ H0_m @ JP0_m JP1_m = I1_m - cP1_m - S1_m = JP1_m.transpose() @ dH1_m @ JP1_m + S1_m = JP1_m.transpose() @ H1_m @ JP1_m if not hom_bc: # very small regularization to avoid constant p=1 in the kernel @@ -214,9 +214,9 @@ def P2_phys(f_phys): print('computing the harmonic fields...') gamma_Lh = 10 # penalization value should not change the kernel - GD_m = - tG_m @ H0_m @ G_m.transpose() @ dH1_m # todo: check with paper + GD_m = - tG_m @ dH0_m @ G_m.transpose() @ H1_m # todo: check with paper L_m = CC_m - GD_m + gamma_Lh * S1_m - eigenvalues, eigenvectors = get_eigenvalues(dim_harmonic_space+1, 1e-6, L_m, dH1_m) + eigenvalues, eigenvectors = get_eigenvalues(dim_harmonic_space+1, 1e-6, L_m, H1_m) for i in range(dim_harmonic_space): lambda_i = eigenvalues[i] @@ -269,7 +269,7 @@ def P2_phys(f_phys): if source_proj == 'P_geom': f0_h = P0_phys(f_scal) f0_c = f0_h.coeffs.toarray() - tilde_f0_c = dH0_m.dot(f0_c) + tilde_f0_c = H0_m.dot(f0_c) else: # L2 proj tilde_f0_c = derham_h.get_dual_dofs(space='V0', f=f_scal, backend_language=backend_language, return_format='numpy_array') @@ -289,23 +289,23 @@ def P2_phys(f_phys): if source_proj == 'P_geom': f1_h = P1_phys(f_vect) f1_c = f1_h.coeffs.toarray() - tilde_f1_c = dH1_m.dot(f1_c) + tilde_f1_c = H1_m.dot(f1_c) else: assert source_proj == 'P_L2' tilde_f1_c = derham_h.get_dual_dofs(space='V1', f=f_vect, backend_language=backend_language, return_format='numpy_array') if plot_source: if f0_c is None: - f0_c = H0_m.dot(tilde_f0_c) + f0_c = dH0_m.dot(tilde_f0_c) plot_field(numpy_coeffs=f0_c, Vh=V0h, space_kind='h1', domain=domain, title='f0_h with P = '+source_proj, filename=plot_dir+'f0h_'+source_proj+'.png', hide_plot=hide_plots) if f1_c is None: - f1_c = H1_m.dot(tilde_f1_c) + f1_c = dH1_m.dot(tilde_f1_c) plot_field(numpy_coeffs=f1_c, Vh=V1h, space_kind='hcurl', domain=domain, title='f1_h with P = '+source_proj, filename=plot_dir+'f1h_'+source_proj+'.png', hide_plot=hide_plots) if source_proj == 'P_L2_wcurl_J': if j2_c is None: - j2_c = H2_m.dot(tilde_j2_c) + j2_c = dH2_m.dot(tilde_j2_c) plot_field(numpy_coeffs=j2_c, Vh=V2h, space_kind='l2', domain=domain, title='P_L2 jh in V2h', filename=plot_dir+'j2h.png', hide_plot=hide_plots) diff --git a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py index a06204c83..b646e84d8 100644 --- a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py @@ -124,10 +124,9 @@ def solve_h1_source_pbm_nc( H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language) H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = mass matrix of V0 - H0_m = H0.to_sparse_matrix() # = inverse mass matrix of V0 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = mass matrix of V1 - # H1_m = H1.to_sparse_matrix() # = inverse mass matrix of V1 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) @@ -154,13 +153,13 @@ def lift_u_bc(u_bc): # Conga (projection-based) stiffness matrices: # div grad: - pre_DG_m = - bD0_m.transpose() @ dH1_m @ bD0_m + pre_DG_m = - bD0_m.transpose() @ H1_m @ bD0_m # jump penalization: jump_penal_m = I0_m - cP0_m - JP0_m = jump_penal_m.transpose() * dH0_m * jump_penal_m + JP0_m = jump_penal_m.transpose() * H0_m * jump_penal_m - pre_A_m = cP0_m.transpose() @ ( eta * dH0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) + pre_A_m = cP0_m.transpose() @ ( eta * H0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) A_m = pre_A_m @ cP0_m + gamma_h * JP0_m print('getting the source and ref solution...') @@ -180,7 +179,7 @@ def lift_u_bc(u_bc): f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] f_h = P0(f_log) f_c = f_h.coeffs.toarray() - b_c = dH0_m.dot(f_c) + b_c = H0_m.dot(f_c) elif source_proj == 'P_L2': print('projecting the source with L2 projection...') @@ -191,7 +190,7 @@ def lift_u_bc(u_bc): b = lh.assemble() b_c = b.toarray() if plot_source: - f_c = H0_m.dot(b_c) + f_c = dH0_m.dot(b_c) else: raise ValueError(source_proj) diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py index d62515534..a35b71dfb 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -118,10 +118,10 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do #H0_m = H0.to_sparse_matrix() # = mass matrix of V0 #dH0_m = H0.get_dual_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 - H2_m = H2.to_sparse_matrix() # = mass matrix of V2 - dH2_m = H2.get_dual_Hodge_sparse_matrix() + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix()# = inverse mass matrix of V1 + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + dH2_m = H2.get_dual_Hodge_sparse_matrix()# = inverse mass matrix of V2 t_stamp = time_count(t_stamp) print('conforming projection operators...') @@ -157,7 +157,7 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do print('mu = {}'.format(mu)) print('curl-curl stiffness matrix...') - pre_CC_m = bD1_m.transpose() @ dH2_m @ bD1_m + pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix A_m += mu * CC_m @@ -166,13 +166,13 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do t_stamp = time_count(t_stamp) print('jump stabilization matrix...') jump_stab_m = I1_m - cP1_m - JS_m = jump_stab_m.transpose() @ dH1_m @ jump_stab_m + JS_m = jump_stab_m.transpose() @ H1_m @ jump_stab_m if generalized_pbm: print('adding jump stabilization to RHS of generalized eigenproblem...') - B_m = cP1_m.transpose() @ dH1_m @ cP1_m + JS_m + B_m = cP1_m.transpose() @ H1_m @ cP1_m + JS_m else: - B_m = dH1_m + B_m = H1_m t_stamp = time_count(t_stamp) diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py index 30b1ca36c..7e25d6cf1 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py @@ -177,7 +177,7 @@ def solve_hcurl_source_pbm_nc( dH1_m = H1.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) - print(' .. Hodge matrix dH2_m = M2_m ...') + print(' .. Hodge matrix H2_m = M2_m ...') H2_m = H2.to_sparse_matrix() dH2_m = H2.get_dual_Hodge_sparse_matrix() @@ -220,20 +220,20 @@ def lift_u_bc(u_bc): t_stamp = time_count(t_stamp) print(' .. curl-curl stiffness matrix...') print(bD1_m.shape, H2_m.shape ) - pre_CC_m = bD1_m.transpose() @ dH2_m @ bD1_m + pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix # grad div: t_stamp = time_count(t_stamp) print(' .. grad-div stiffness matrix...') - pre_GD_m = - dH1_m @ bD0_m @ cP0_m @ H0_m @ cP0_m.transpose() @ bD0_m.transpose() @ dH1_m + pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m # GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix # jump stabilization: t_stamp = time_count(t_stamp) print(' .. jump stabilization matrix...') jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * dH1_m * jump_penal_m + JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m t_stamp = time_count(t_stamp) print(' .. full operator matrix...') @@ -241,7 +241,7 @@ def lift_u_bc(u_bc): print('mu = {}'.format(mu)) print('nu = {}'.format(nu)) print('STABILIZATION: gamma_h = {}'.format(gamma_h)) - pre_A_m = cP1_m.transpose() @ ( eta * dH1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) + pre_A_m = cP1_m.transpose() @ ( eta * H1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) A_m = pre_A_m @ cP1_m + gamma_h * JP_m t_stamp = time_count(t_stamp) @@ -263,7 +263,7 @@ def lift_u_bc(u_bc): print(' .. projecting the source with primal (geometric) commuting projection...') f_h = P1_phys(f_vect, P1, domain, mappings_list) f_c = f_h.coeffs.toarray() - tilde_f_c = dH1_m.dot(f_c) + tilde_f_c = H1_m.dot(f_c) elif source_proj in ['P_L2', 'tilde_Pi']: # f_h = L2 projection of f_vect, with filtering if tilde_Pi @@ -285,7 +285,7 @@ def lift_u_bc(u_bc): title = 'f_h with P = '+source_proj title_vf = 'f_h with P = '+source_proj if f_c is None: - f_c = H1_m.dot(tilde_f_c) + f_c = dH1_m.dot(tilde_f_c) plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title, filename=plot_dir+'/fh_'+source_proj+'.pdf', hide_plot=hide_plots) plot_field(numpy_coeffs=f_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, @@ -319,7 +319,7 @@ def lift_u_bc(u_bc): uh_c += ubc_c uh = FemField(V1h, coeffs=array_to_psydac(uh_c, V1h.vector_space)) - f_c = H1_m.dot(tilde_f_c) + f_c = dH1_m.dot(tilde_f_c) jh = FemField(V1h, coeffs=array_to_psydac(f_c, V1h.vector_space)) t_stamp = time_count(t_stamp) diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py index d9b5890fc..1a80c13e4 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py @@ -303,22 +303,23 @@ def solve_td_maxwell_pbm(*, t_stamp = time_count(t_stamp) print(' .. Hodge matrix H0_m = M0_m ...') - dH0_m = H0.to_sparse_matrix() + H0_m = H0.to_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') - H0_m = H0.get_dual_Hodge_sparse_matrix() + dH0_m = H0.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. Hodge matrix H1_m = M1_m ...') - dH1_m = H1.to_sparse_matrix() + H1_m = H1.to_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') - H1_m = H1.get_dual_Hodge_sparse_matrix() + dH1_m = H1.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. Hodge matrix dH2_m = M2_m ...') - dH2_m = H2.to_sparse_matrix() - H2_m = H2.get_dual_Hodge_sparse_matrix() + H2_m = H2.to_sparse_matrix() + print(' .. dual Hodge matrix dH2_m = inv_M2_m ...') + dH2_m = H2.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. conforming Projection operators...') diff --git a/psydac/feec/multipatch/operators.py b/psydac/feec/multipatch/operators.py index fe09f1fe3..57d0ac99b 100644 --- a/psydac/feec/multipatch/operators.py +++ b/psydac/feec/multipatch/operators.py @@ -844,8 +844,8 @@ class HodgeOperator( FemLinearOperator ): """ Change of basis operator: dual basis -> primal basis - self._matrix: matrix of the primal Hodge = this is the INVERSE mass matrix ! - self.dual_Hodge_matrix: this is the mass matrix + self._matrix: matrix of the primal Hodge = this is the mass matrix ! + self.dual_Hodge_matrix: this is the INVERSE mass matrix Parameters ---------- @@ -855,6 +855,9 @@ class HodgeOperator( FemLinearOperator ): domain_h: The discrete domain of the projector + metric : + the metric of the de Rham complex + backend_language: The backend used to accelerate the code @@ -868,17 +871,20 @@ class HodgeOperator( FemLinearOperator ): ----- Either we use a storage, or these matrices are only computed on demand # todo: we compute the sparse matrix when to_sparse_matrix is called -- but never the stencil matrix (should be fixed...) - + We only support the identity metric, this implies that the dual Hodge is the inverse of the primal one. + # todo: allow for non-identity metrics """ - def __init__( self, Vh, domain_h, backend_language='python', load_dir=None, load_space_index=''): - + + def __init__( self, Vh, domain_h, metric='identity', backend_language='python', load_dir=None, load_space_index=''): FemLinearOperator.__init__(self, fem_domain=Vh) self._domain_h = domain_h self._backend_language = backend_language - self._dual_Hodge_matrix = None self._dual_Hodge_sparse_matrix = None + assert metric == 'identity' + self._metric = metric + if load_dir and isinstance(load_dir, str): if not os.path.exists(load_dir): os.makedirs(load_dir) @@ -888,49 +894,29 @@ def __init__( self, Vh, domain_h, backend_language='python', load_dir=None, load primal_Hodge_is_stored = os.path.exists(primal_Hodge_storage_fn) dual_Hodge_is_stored = os.path.exists(dual_Hodge_storage_fn) - if primal_Hodge_is_stored: - assert dual_Hodge_is_stored + if dual_Hodge_is_stored: + assert primal_Hodge_is_stored print(" ... loading dual Hodge sparse matrix from "+dual_Hodge_storage_fn) self._dual_Hodge_sparse_matrix = load_npz(dual_Hodge_storage_fn) print("[HodgeOperator] loading primal Hodge sparse matrix from "+primal_Hodge_storage_fn) self._sparse_matrix = load_npz(primal_Hodge_storage_fn) else: - assert not dual_Hodge_is_stored + assert not primal_Hodge_is_stored print("[HodgeOperator] assembling both sparse matrices for storage...") - self.assemble_dual_Hodge_matrix() - print("[HodgeOperator] storing primal Hodge sparse matrix in "+dual_Hodge_storage_fn) - save_npz(dual_Hodge_storage_fn, self._dual_Hodge_sparse_matrix) self.assemble_primal_Hodge_matrix() print("[HodgeOperator] storing primal Hodge sparse matrix in "+primal_Hodge_storage_fn) save_npz(primal_Hodge_storage_fn, self._sparse_matrix) + self.assemble_dual_Hodge_matrix() + print("[HodgeOperator] storing dual Hodge sparse matrix in "+dual_Hodge_storage_fn) + save_npz(dual_Hodge_storage_fn, self._dual_Hodge_sparse_matrix) else: # matrices are not stored, we will probably compute them later pass - def assemble_primal_Hodge_matrix(self): - - if self._sparse_matrix is None: - if not self._dual_Hodge_matrix: - self.assemble_dual_Hodge_matrix() - - M = self._dual_Hodge_matrix # mass matrix of the (primal) basis - nrows = M.n_block_rows - ncols = M.n_block_cols - - inv_M_blocks = [] - for i in range(nrows): - Mii = M[i,i].tosparse() - inv_Mii = inv(Mii.tocsc()) - inv_Mii.eliminate_zeros() - inv_M_blocks.append(inv_Mii) - - inv_M = block_diag(inv_M_blocks) - self._sparse_matrix = inv_M - - def to_sparse_matrix( self ): + def to_sparse_matrix(self): """ - the Hodge matrix is the patch-wise inverse of the multi-patch mass matrix - it is not stored by default but computed on demand, by local (patch-wise) inversion of the mass matrix + the Hodge matrix is the patch-wise multi-patch mass matrix + it is not stored by default but assembled on demand """ if (self._sparse_matrix is not None) or (self._matrix is not None): @@ -940,12 +926,13 @@ def to_sparse_matrix( self ): return self._sparse_matrix - def assemble_dual_Hodge_matrix( self ): + def assemble_primal_Hodge_matrix(self): """ - the dual Hodge matrix is the patch-wise multi-patch mass matrix + the Hodge matrix is the patch-wise multi-patch mass matrix it is not stored by default but assembled on demand """ - if self._dual_Hodge_matrix is None: + + if self._matrix is None: Vh = self.fem_domain assert Vh == self.fem_codomain @@ -953,24 +940,48 @@ def assemble_dual_Hodge_matrix( self ): domain = V.domain # domain_h = V0h.domain: would be nice... u, v = elements_of(V, names='u, v') - # print(type(u)) - # exit() + if isinstance(u, ScalarFunction): expr = u*v else: expr = dot(u,v) + a = BilinearForm((u,v), integral(domain, expr)) ah = discretize(a, self._domain_h, [Vh, Vh], backend=PSYDAC_BACKENDS[self._backend_language]) - self._dual_Hodge_matrix = ah.assemble() # Mass matrix in stencil format - self._dual_Hodge_sparse_matrix = self._dual_Hodge_matrix.tosparse() + self._matrix = ah.assemble() # Mass matrix in stencil format + self._sparse_matrix = self._matrix.tosparse() - def get_dual_Hodge_sparse_matrix( self ): + def get_dual_Hodge_sparse_matrix(self): if self._dual_Hodge_sparse_matrix is None: self.assemble_dual_Hodge_matrix() return self._dual_Hodge_sparse_matrix + def assemble_dual_Hodge_matrix(self): + """ + the dual Hodge matrix is the patch-wise inverse of the multi-patch mass matrix + it is not stored by default but computed on demand, by local (patch-wise) inversion of the mass matrix + """ + + if self._dual_Hodge_sparse_matrix is None: + if not self._matrix: + self.assemble_primal_Hodge_matrix() + + M = self._matrix # mass matrix of the (primal) basis + nrows = M.n_block_rows + ncols = M.n_block_cols + + inv_M_blocks = [] + for i in range(nrows): + Mii = M[i,i].tosparse() + inv_Mii = inv(Mii.tocsc()) + inv_Mii.eliminate_zeros() + inv_M_blocks.append(inv_Mii) + + inv_M = block_diag(inv_M_blocks) + self._dual_Hodge_sparse_matrix = inv_M + #============================================================================== class BrokenGradient_2D(FemLinearOperator): diff --git a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py index 23f1d4d73..c10efd6b2 100644 --- a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py @@ -175,16 +175,16 @@ def levelof(k): cP2 = construct_scalar_conforming_projection(V2h, [reg[0]- 1, reg[1]-1], mom_pres, nquads, hom_bc) HOp0 = HodgeOperator(p_V0h, domain_h) - M0 = HOp0.get_dual_Hodge_sparse_matrix() # mass matrix - M0_inv = HOp0.to_sparse_matrix() # inverse mass matrix + M0 = HOp0.to_sparse_matrix() # mass matrix + M0_inv = HOp0.get_dual_Hodge_sparse_matrix() # inverse mass matrix HOp1 = HodgeOperator(p_V1h, domain_h) - M1 = HOp1.get_dual_Hodge_sparse_matrix() # mass matrix - M1_inv = HOp1.to_sparse_matrix() # inverse mass matrix + M1 = HOp1.to_sparse_matrix() # mass matrix + M1_inv = HOp1.get_dual_Hodge_sparse_matrix() # inverse mass matrix HOp2 = HodgeOperator(p_V2h, domain_h) - M2 = HOp2.get_dual_Hodge_sparse_matrix() # mass matrix - M2_inv = HOp2.to_sparse_matrix() # inverse mass matrix + M2 = HOp2.to_sparse_matrix() # mass matrix + M2_inv = HOp2.get_dual_Hodge_sparse_matrix() # inverse mass matrix bD0, bD1 = p_derham_h.broken_derivatives_as_operators From aa4fd8c7b5c275921489653ea867b563872e3b20 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 8 May 2024 09:35:21 +0200 Subject: [PATCH 23/88] functions stencil index to petsc index and viceversa --- psydac/linalg/topetsc.py | 523 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 505 insertions(+), 18 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 9ea42a5a2..01f896e21 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -2,12 +2,133 @@ from psydac.linalg.block import BlockVectorSpace, BlockVector from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix +from psydac.linalg.basic import VectorSpace from scipy.sparse import coo_matrix, bmat +from itertools import product as cartesian_prod from mpi4py import MPI __all__ = ('flatten_vec', 'vec_topetsc', 'mat_topetsc') + +def psydac_to_petsc_local( + V : VectorSpace, + block_indices : tuple[int], + ndarray_indices : tuple[int]) -> int : + """ + Convert the Psydac local index to a PETSc local index. + + Parameters + ----------- + V : VectorSpace + The vector space to which the Psydac vector belongs. + This defines the number of blocks, the size of each block, + and how each block is distributed across MPI processes. + + block_indices : tuple[int] + The indices which identify the block in a (possibly nested) block vector. + In the case of a StencilVector this is an empty tuple. + + ndarray_indices : tuple[int] + The multi-index which identifies an element in the _data array, + excluding the ghost regions. + + Returns + -------- + petsc_index : int + The local PETSc index, which is equivalent to the global PETSc index + but starts from 0. + """ + + ndim = V.ndim + starts = V.starts + ends = V.ends + pads = V.pads + shifts = V.shifts + shape = V.shape + + ii = ndarray_indices + + + npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. + + assert all([ii[d] >= pads[d]*shifts[d] and ii[d] < shape[d] - pads[d]*shifts[d] for d in range(ndim)]), 'ndarray_indices within the ghost region' + + if ndim == 1: + petsc_index = ii[0] - pads[0]*shifts[0] # global index starting from 0 in each process + elif ndim == 2: + petsc_index = npts_local[1] * (ii[0] - pads[0]*shifts[0]) + ii[1] - pads[1]*shifts[1] # global index starting from 0 in each process + elif ndim == 3: + petsc_index = npts_local[1] * npts_local[2] * (ii[0] - pads[0]*shifts[0]) + npts_local[2] * (ii[1] - pads[1]*shifts[1]) + ii[2] - pads[2]*shifts[2] + else: + raise NotImplementedError( "Cannot handle more than 3 dimensions." ) + + return petsc_index + +def get_petsc_local_to_global_shift(V : VectorSpace) -> int: + """ + Compute the correct integer shift (process dependent) in order to convert + a PETSc local index to the corresponding global index. + + Parameter + --------- + V : VectorSpace + The distributed Psydac vector space. + + Returns + -------- + int + The integer shift which must be added to a local index + in order to get a global index. + """ + + cart = V.cart + comm = cart.global_comm + gstarts = cart.global_starts # Global variable + gends = cart.global_ends # Global variable + + npts_local_perprocess = [ ge - gs + 1 for gs, ge in zip(gstarts, gends)] #Global variable + npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable + localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(comm.Get_size())] #Global variable + index_shift = 0 + np.sum(localsize_perprocess[0:comm.Get_rank()], dtype=int) #Global variable + + return index_shift + +def petsc_to_psydac_local( + V : VectorSpace, + petsc_index : int) :#-> tuple(tuple[int], tuple[int]) : + """ + Convert the PETSc local index to a Psydac local index. + This is the inverse of `psydac_to_petsc_local`. + """ + + ndim = V.ndim + starts = V.starts + ends = V.ends + pads = V.pads + shifts = V.shifts + + npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. + + ii = np.zeros((ndim,), dtype=int) + if ndim == 1: + ii[0] = petsc_index + pads[0]*shifts[0] # global index starting from 0 in each process + + elif ndim == 2: + ii[0] = petsc_index // npts_local[1] + pads[0]*shifts[0] + ii[1] = petsc_index % npts_local[1] + pads[1]*shifts[1] + + elif ndim == 3: + ii[0] = petsc_index // (npts_local[1]*npts_local[2]) + pads[0]*shifts[0] + ii[1] = petsc_index // npts_local[2] + pads[1]*shifts[1] - npts_local[1]*(ii[0] - pads[0]*shifts[0]) + ii[2] = petsc_index % npts_local[2] + pads[2]*shifts[2] + + else: + raise NotImplementedError( "Cannot handle more than 3 dimensions." ) + + return [tuple(ii)] + + def flatten_vec( vec ): """ Return the flattened 1D array values and indices owned by the process of the given vector. @@ -73,23 +194,166 @@ def vec_topetsc( vec ): from petsc4py import PETSc if isinstance(vec, StencilVector): - comm = vec.space.cart.global_comm + cart = vec.space.cart elif isinstance(vec.space.spaces[0], StencilVectorSpace): - comm = vec.space.spaces[0].cart.global_comm + cart = vec.space.spaces[0].cart elif isinstance(vec.space.spaces[0], BlockVectorSpace): - comm = vec.space.spaces[0][0].cart.global_comm + cart = vec.space.spaces[0][0].cart + + comm = cart.global_comm + globalsize = vec.space.dimension #integer + """ print('\n\nVEC:\nglobalsize=', globalsize) + gvec.setDM(Dmda) + + # Set local and global size + gvec.setSizes(size=(ownership_ranges[comm.Get_rank()], globalsize)) + + '''ownership_ranges = [comm.allgather(cart.domain_decomposition.local_ncells[k]) for k in range(cart.ndim)] + boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if cart.domain_decomposition.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(cart.ndim)] + + #ownership_ranges = [ dcart.global_ends[0][k] - dcart.global_starts[0][k] + 1 for k in range(dcart.global_starts[0].size)] + print('VECTOR: OWNership_ranges=', ownership_ranges) + #Dmda = PETSc.DMDA().create(dim=2, sizes=mat.shape, proc_sizes=(comm.Get_size(),1), ownership_ranges=(ownership_ranges, mat.shape[1]), comm=comm) + # proc_sizes = [ len] + Dmda = PETSc.DMDA().create(dim=cart.ndim, sizes=cart.domain_decomposition.ncells, proc_sizes=cart.domain_decomposition.nprocs, + ownership_ranges=ownership_ranges, comm=comm, stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type)''' + + ### SPLITTING COEFFS + ownership_ranges = [ 1 + cart.global_ends[0][k] - cart.global_starts[0][k] for k in range(cart.global_starts[0].size)] + #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] + + print('OWNership_ranges=', ownership_ranges) + print('dcart.domain_decomposition.nprocs=', *cart.domain_decomposition.nprocs) + + boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if cart.domain_decomposition.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(cart.ndim)] + + Dmda = PETSc.DMDA().create(dim=1, sizes=(globalsize,), proc_sizes=cart.domain_decomposition.nprocs, comm=comm, + ownership_ranges=[ownership_ranges], boundary_type=boundary_type) + - globalsize = vec.space.dimension indices, data = flatten_vec(vec) + for k in range(comm.Get_size()): + if comm.Get_rank() == k: + print('Rank ', k) + print('vec.toarray()=\n', vec.toarray()) + print('VEC_indices=', indices) + print('VEC_data=', data) + comm.Barrier() + + + gvec = PETSc.Vec().create(comm=comm) - # Set global size - gvec.setSizes(globalsize) + + gvec.setDM(Dmda) + + # Set local and global size + gvec.setSizes(size=(ownership_ranges[comm.Get_rank()], globalsize)) + + '''if comm: + cart_petsc = cart.topetsc() + gvec.setLGMap(cart_petsc.l2g_mapping)''' + + gvec.setFromOptions() + gvec.setUp() # Set values of the vector. They are stored in a cache, so the assembly is necessary to use the vector. - gvec.setValues(indices, data) + gvec.setValues(indices, data, addv=PETSc.InsertMode.ADD_VALUES)""" + + ndim = vec.space.ndim + starts = vec.space.starts + ends = vec.space.ends + pads = vec.space.pads + shifts = vec.space.shifts + #npts = vec.space.npts + + #cart = vec.space.cart + + npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. + '''npts_local_perprocess = [ ge - gs + 1 for gs, ge in zip(cart.global_starts, cart.global_ends)] #Global variable + npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable + localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(comm.Get_size())] #Global variable''' + index_shift = get_petsc_local_to_global_shift(vec.space) #Global variable + + '''for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('\nRank ', k) + print('starts=', starts) + print('ends=', ends) + print('npts=', npts) + print('pads=', pads) + print('shifts=', shifts) + print('npts_local=', npts_local) + print('cart.global_starts=', cart.global_starts) + print('cart.global_ends=', cart.global_ends) + print('npts_local_perprocess=', npts_local_perprocess) + print('localsize_perprocess=', localsize_perprocess) + print('index_shift=', index_shift) + + print('vec._data.shape=', vec._data.shape) + print('vec._data=', vec._data) + #print('vec.toarray()=', vec.toarray()) + comm.Barrier()''' + + gvec = PETSc.Vec().create(comm=comm) + + localsize = np.prod(npts_local) + gvec.setSizes(size=(localsize, globalsize))#size=(ownership_ranges[comm.Get_rank()], globalsize)) + gvec.setFromOptions() + gvec.setUp() + + petsc_indices = [] + petsc_data = [] + + if ndim == 1: + for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): + value = vec._data[i1] + if value != 0: + index = psydac_to_petsc_local(vec.space, [], (i1)) # global index starting from 0 in each process + index += index_shift #starts[0] # global index starting from NOT 0 in each process + petsc_indices.append(index) + petsc_data.append(value) + + elif ndim == 2: + for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): + for i2 in range(pads[1]*shifts[1], pads[1]*shifts[1] + npts_local[1]): + value = vec._data[i1,i2] + if value != 0: + #index = npts_local[1] * (i1 - pads[0]*shifts[0]) + i2 - pads[1]*shifts[1] # global index starting from 0 in each process + index = psydac_to_petsc_local(vec.space, [], (i1,i2)) # global index starting from 0 in each process + index += index_shift # global index starting from NOT 0 in each process + petsc_indices.append(index) + petsc_data.append(value) + + elif ndim == 3: + for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): + for i2 in range(pads[1]*shifts[1], pads[1]*shifts[1] + npts_local[1]): + for i3 in range(pads[2]*shifts[2], pads[2]*shifts[2] + npts_local[2]): + value = vec._data[i1, i2, i3] + if value != 0: + #index = npts_local[1] * npts_local[2] * (i1 - pads[0]*shifts[0]) + npts_local[2] * (i2 - pads[1]*shifts[1]) + i3 - pads[2]*shifts[2] + index = psydac_to_petsc_local(vec.space, [], (i1,i2,i3)) + index += index_shift # global index starting from NOT 0 in each process + petsc_indices.append(index) + petsc_data.append(value) + + gvec.setValues(petsc_indices, petsc_data, addv=PETSc.InsertMode.ADD_VALUES) # Assemble vector gvec.assemble() # Here PETSc exchanges global communication. The block corresponding to a certain process is not necessarily the same block in the Psydac StencilVector. + #diff = abs(vec.toarray() - gvec.array).max() + + '''vec_arr = vec.toarray() + + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('\nRank ', k) + print('petsc_indices=', petsc_indices) + print('petsc_data=', petsc_data) + print('\ngvec.array=', gvec.array.real) + print('vec.toarray()=', vec_arr) + comm.Barrier()''' + + return gvec def mat_topetsc( mat ): @@ -109,42 +373,262 @@ def mat_topetsc( mat ): from petsc4py import PETSc if isinstance(mat, StencilMatrix): - comm = mat.domain.cart.global_comm + dcart = mat.domain.cart + ccart = mat.codomain.cart elif isinstance(mat.domain.spaces[0], StencilVectorSpace): - comm = mat.domain.spaces[0].cart.global_comm + dcart = mat.domain.spaces[0].cart + ccart = mat.codomain.spaces[0].cart elif isinstance(mat.domain.spaces[0], BlockVectorSpace): - comm = mat.domain.spaces[0][0].cart.global_comm + dcart = mat.domain.spaces[0][0].cart + ccart = mat.codomain.spaces[0][0].cart + + comm = dcart.global_comm + + + + #print('mat.shape = ', mat.shape) + #print('rank: ', comm.Get_rank(), ', local_ncells=', dcart.domain_decomposition.local_ncells) + #print('rank: ', comm.Get_rank(), ', nprocs=', dcart.domain_decomposition.nprocs) - mat_coo = mat.tosparse() + #recvbuf = np.empty(shape=(dcart.domain_decomposition.nprocs[0],1)) + #comm.allgather(sendbuf=dcart.domain_decomposition.local_ncells, recvbuf=recvbuf) + + #################################### + + ### SPLITTING DOMAIN + #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] + + boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if mat.domain.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(dcart.ndim)] + + + dim = dcart.ndim + sizes = dcart.npts + proc_sizes = dcart.nprocs + #ownership_ranges = [[ 1 + dcart.global_ends[p][k] - dcart.global_starts[p][k] + # for k in range(dcart.global_starts[p].size)] + # for p in range(len(dcart.global_starts))] + ownership_ranges = [[e - s + 1 for s,e in zip(starts, ends)] for starts, ends in zip(dcart.global_starts, dcart.global_ends)] + print('OWNership_ranges=', ownership_ranges) + Dmda = PETSc.DMDA().create(dim=dim, sizes=sizes, proc_sizes=proc_sizes, + ownership_ranges=ownership_ranges, comm=comm, + stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type) + + + '''### SPLITTING COEFFS + ownership_ranges = [[ 1 + dcart.global_ends[p][k] - dcart.global_starts[p][k] for k in range(dcart.global_starts[p].size)] for p in range(len(dcart.global_starts))] + #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] + + print('MAT: ownership_ranges=', ownership_ranges) + print('MAT: dcart.domain_decomposition.nprocs=', *dcart.domain_decomposition.nprocs) + + boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if mat.domain.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(dcart.ndim)] + + Dmda = PETSc.DMDA().create(dim=1, sizes=mat.shape, proc_sizes=[*dcart.domain_decomposition.nprocs,1], comm=comm, + ownership_ranges=[ownership_ranges[0], [mat.shape[1]]], stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type) + ''' + + ''' if comm: + dcart_petsc = dcart.topetsc() + d_LG_map = dcart_petsc.l2g_mapping + + ccart_petsc = ccart.topetsc() + c_LG_map = ccart_petsc.l2g_mapping + + print('Rank', comm.Get_rank(), ': dcart_petsc.local_size = ', dcart_petsc.local_size) + print('Rank', comm.Get_rank(), ': dcart_petsc.local_shape = ', dcart_petsc.local_shape) + print('Rank', comm.Get_rank(), ': ccart_petsc.local_size = ', ccart_petsc.local_size) + print('Rank', comm.Get_rank(), ': ccart_petsc.local_shape = ', ccart_petsc.local_shape) + + if not comm: + print('') + else: + for k in range(comm.Get_size()): + if comm.Get_rank() == k: + print('\nRank ', k) + print('mat=\n', mat.tosparse().toarray()) + print('dcart.local_ncells=', dcart.domain_decomposition.local_ncells) + print('ccart.local_ncells=', ccart.domain_decomposition.local_ncells) + print('dcart._grids=', dcart._grids) + print('ccart._grids=', ccart._grids) + print('dcart.starts =', dcart.starts) + print('dcart.ends =', dcart.ends) + print('ccart.starts =', ccart.starts) + print('ccart.ends =', ccart.ends) + print('dcart.shape=', dcart.shape) + print('dcart.npts=', dcart.npts) + print('ccart.shape=', ccart.shape) + print('ccart.npts=', ccart.npts) + print('\ndcart.indices=', dcart_petsc.indices) + print('ccart.indices=', ccart_petsc.indices) + print('dcart.global_starts=', dcart.global_starts) + print('dcart.global_ends=', dcart.global_ends) + print('ccart.global_starts=', ccart.global_starts) + print('ccart.global_ends=', ccart.global_ends) + comm.Barrier() + ''' + + print('Dmda.getOwnershipRanges()=', Dmda.getOwnershipRanges()) + print('Dmda.getRanges()=', Dmda.getRanges()) + + #LGmap = PETSc.LGMap().create(indices=) + + #dm = PETSc.DM().create(comm=comm) gmat = PETSc.Mat().create(comm=comm) + gmat.setDM(Dmda) + # Set GLOBAL matrix size + #gmat.setSizes(mat.shape) + + #gmat.setSizes(size=((dcart.domain_decomposition.local_ncells[0],mat.shape[0]), (mat.shape[1],mat.shape[1])), + # bsize=None) + #gmat.setSizes([[dcart_petsc.local_size, mat.shape[0]], [ccart_petsc.local_size, mat.shape[1]]]) #mat.setSizes([[nrl, nrg], [ncl, ncg]]) + + local_rows = np.prod([e - s + 1 for s, e in zip(ccart.starts, ccart.ends)]) + #local_columns = np.prod([p*m for p, m in zip(mat.domain.pads, mat.domain.shifts)]) + local_columns = np.prod([e - s + 1 for s, e in zip(dcart.starts, dcart.ends)]) + rows = mat.shape[0] + columns = mat.shape[1] + gmat.setSizes(size=((local_rows, rows), (local_columns, columns))) #((local_rows, rows), (local_columns, columns)) + if comm: # Set PETSc sparse parallel matrix type gmat.setType("mpiaij") + #gmat.setLGMap(c_LG_map, d_LG_map) else: # Set PETSc sequential matrix type gmat.setType("seqaij") - # Set GLOBAL matrix size - gmat.setSizes(mat.shape) gmat.setFromOptions() + gmat.setUp() - rows, cols, data = mat_coo.row, mat_coo.col, mat_coo.data + print('gmat.getSizes()=', gmat.getSizes()) + + mat_coo = mat.tosparse() + rows_coo, cols_coo, data_coo = mat_coo.row, mat_coo.col, mat_coo.data + + mat_csr = mat_coo.tocsr() + mat_csr.eliminate_zeros() + data, indices, indptr = mat_csr.data, mat_csr.indices, mat_csr.indptr + #indptr_chunk = indptr[indptr >= dcart.starts[0] and indptr <= dcart.ends[0]] + #indices_chunk = indices[indices >= dcart.starts[1] and indices <= dcart.ends[1]] + + mat_coo_local = mat.tocoo_local() + rows_coo_local, cols_coo_local, data_coo_local = mat_coo_local.row, mat_coo_local.col, mat_coo_local.data + + local_petsc_index = psydac_to_petsc_local(mat.domain, [], [2,0]) + global_petsc_index = get_petsc_local_to_global_shift(mat.domain) + + + print('dcart.global_starts=', dcart.global_starts) + print('dcart.global_ends=', dcart.global_ends) + + '''for k in range(comm.Get_size()): + if comm.Get_rank() == k: + local_indptr = indptr[1 + dcart.global_starts[0][comm.Get_rank()]:2+dcart.global_ends[0][comm.Get_rank()]] + local_indptr = [row_pter - dcart.global_starts[0][comm.Get_rank()] for row_pter in local_indptr] + local_indptr = [0, *local_indptr] + comm.Barrier()''' + + '''if comm.Get_rank() == 0: + local_indptr = [3,6] + else: + local_indptr = [3]''' + #local_indptr = indptr[1+ dcart.global_starts[comm.Get_rank()][0]:dcart.global_ends[comm.Get_rank()][0]+2] + #local_indptr = [0, *local_indptr] + + if not comm: + print('178:indptr = ', indptr) + print('178:indices = ', indices) + print('178:data = ', data) + else: + for k in range(comm.Get_size()): + if comm.Get_rank() == k: + print('\nRank ', k) + print('mat=\n', mat_csr.toarray()) + print('CSR: indptr = ', indptr) + #print('local_indptr = ', local_indptr) + print('CSR: indices = ', indices) + print('CSR: data = ', data) + + + '''print('data_coo_local=', data_coo_local) + print('rows_coo_local=', rows_coo_local) + print('cols_coo_local=', cols_coo_local) + + print('data_coo=', data_coo) + print('rows_coo=', rows_coo) + print('cols_coo=', cols_coo)''' + + comm.Barrier() + + '''rows, cols, data = mat_coo.row, mat_coo.col, mat_coo.data + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('\nRank ', k, ': data.size =', data.size) + print('rows=', rows) + print('cols=', cols) + print('data=', data) + comm.Barrier()''' + + + import time + t_prev = time.time() + '''for k in range(len(rows_coo)): + gmat.setValues(rows_coo[k] - dcart.global_starts[0][comm.Get_rank()], cols_coo[k], data_coo[k]) + ''' + #gmat.setValuesCSR([r - dcart.global_starts[0][comm.Get_rank()] for r in indptr[1:]], indices, data) + #gmat.setValuesLocalCSR(local_indptr, indices, data)#, addv=PETSc.InsertMode.ADD_VALUES) + + r = gmat.Stencil(0,0,0) + c = gmat.Stencil(0,0,0) + s = np.prod([p*m for p, m in zip(mat.domain.pads, mat.domain.shifts)]) + print('r=', r) + for k in range(comm.Get_size()): + if comm.Get_rank() == k: + print('\nrank ', k) + print('mat._data=', mat._data) + + print('mat.domain.pads=', mat.domain.pads) + print('mat.domain.shifts=', mat.domain.shifts) + print('mat._data (without ghost)=', mat._data[s:-s]) + + comm.Barrier() + + gmat.setValuesStencil(mat._data[s:-s]) + + + print('Rank ', comm.Get_rank() if comm else '-', ': duration of setValuesCSR :', time.time()-t_prev) + + '''if comm: + # Preallocate number of nonzeros per row + #row_lengths = np.count_nonzero(rows[None,:] == np.unique(rows)[:,None], axis=1).max() + + ##very slow: + #row_lengths = 0 + #for r in np.unique(rows): + # row_lengths = max(row_lengths, np.nonzero(rows==r)[0].size) + + row_lengths = np.unique(rows, return_counts=True)[1].max() - if comm: - # Preallocate number of nonzeros - row_lengths = np.count_nonzero(rows[None,:] == np.unique(rows)[:,None], axis=1).max() # NNZ is the number of non-zeros per row for the local portion of the matrix + t_prev = time.time() NNZ = comm.allreduce(row_lengths, op=MPI.MAX) + print('Rank ', comm.Get_rank() , ': duration of comm.allreduce :', time.time()-t_prev) + + t_prev = time.time() gmat.setPreallocationNNZ(NNZ) + print('Rank ', comm.Get_rank() , ': duration of setPreallocationNNZ :', time.time()-t_prev) + t_prev = time.time() # Fill-in matrix values for i in range(rows.size): # The values have to be set in "addition mode", otherwise the default just takes the new value. # This is here necessary, since the COO format can contain repeated entries. - gmat.setValues(rows[i], cols[i], data[i], addv=PETSc.InsertMode.ADD_VALUES) + gmat.setValues(rows[i], cols[i], data[i])#, addv=PETSc.InsertMode.ADD_VALUES) + print('Rank ', comm.Get_rank() , ': duration of setValues :', time.time()-t_prev)''' + # Process inserted matrix entries ################################################ # Note 12.03.2024: @@ -153,5 +637,8 @@ def mat_topetsc( mat ): # In the future we would like that PETSc uses the partition from Psydac, # which might involve passing a DM Object. ################################################ + t_prev = time.time() gmat.assemble() + print('Rank ', comm.Get_rank() if comm else '-', ': duration of Mat assembly :', time.time()-t_prev) + return gmat From 678de4a68e3a633a8ab21b7fb19880fa4467e0bc Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 8 May 2024 10:52:06 +0200 Subject: [PATCH 24/88] efficient conversion to PETSc of StencilVectors --- psydac/linalg/tests/test_stencil_vector.py | 102 ++++++++++++++++++++- psydac/linalg/topetsc.py | 34 ++++--- psydac/linalg/utilities.py | 40 +++++++- 3 files changed, 157 insertions(+), 19 deletions(-) diff --git a/psydac/linalg/tests/test_stencil_vector.py b/psydac/linalg/tests/test_stencil_vector.py index 6d74c0e6d..cf15ba58f 100644 --- a/psydac/linalg/tests/test_stencil_vector.py +++ b/psydac/linalg/tests/test_stencil_vector.py @@ -455,7 +455,7 @@ def test_stencil_vector_2d_serial_topetsc(dtype, n1, n2, p1, p2, s1, s2, P1, P2) assert v._data.shape == (n1 + 2 * p1 * s1, n2 + 2 * p2 * s2) assert v._data.dtype == dtype assert np.array_equal(x.toarray(), v.toarray()) - +#test_stencil_vector_2d_serial_topetsc(float, 4,5,1,1,1,1,True,True) # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) @pytest.mark.parametrize('n1', [5, 7]) @@ -619,7 +619,6 @@ def test_stencil_vector_2d_parallel_topetsc(dtype, n1, n2, p1, p2, s1, s2, P1, P x = StencilVector(V) # Fill the vector with data - if dtype == complex: f = lambda i1, i2: 10j * i1 + i2 else: @@ -638,6 +637,105 @@ def test_stencil_vector_2d_parallel_topetsc(dtype, n1, n2, p1, p2, s1, s2, P1, P assert np.array_equal(x.toarray(), v.toarray()) +#test_stencil_vector_2d_parallel_topetsc(float, 4, 5, 1, 1, 1, 1, True, True) +# =============================================================================== +@pytest.mark.parametrize('dtype', [float, complex]) +@pytest.mark.parametrize('n1', [20, 32]) +@pytest.mark.parametrize('p1', [1, 3]) +@pytest.mark.parametrize('s1', [1, 2]) +@pytest.mark.parametrize('P1', [True, False]) +@pytest.mark.parallel +@pytest.mark.petsc +def test_stencil_vector_1d_parallel_topetsc(dtype, n1, p1, s1, P1): + from mpi4py import MPI + comm = MPI.COMM_WORLD + + # Create domain decomposition + D = DomainDecomposition([n1], periods=[P1], comm=comm) + + # Partition the points + npts = [n1] + global_starts, global_ends = compute_global_starts_ends(D, npts) + C = CartDecomposition(D, npts, global_starts, global_ends, pads=[p1], shifts=[s1]) + + # Create vector space and stencil vector + V = StencilVectorSpace(C, dtype=dtype) + x = StencilVector(V) + + # Fill the vector with data + if dtype == complex: + f = lambda i1: 10j * i1 + 3 + else: + f = lambda i1: 10 * i1 + 3 + + # Initialize distributed 2D stencil vector + for i1 in range(V.starts[0], V.ends[0] + 1): + x[i1] = f(i1) + + # Convert vector to PETSc.Vec + v = x.topetsc() + + # Convert PETSc.Vec to StencilVector of V + v = petsc_to_psydac(v, V) + + assert np.array_equal(x.toarray(), v.toarray()) +#test_stencil_vector_1d_parallel_topetsc(float, 7, 2, 2, False) + +# =============================================================================== +@pytest.mark.parametrize('dtype', [float, complex]) +@pytest.mark.parametrize('n1', [20, 32]) +@pytest.mark.parametrize('n2', [24, 40]) +@pytest.mark.parametrize('n3', [7, 12]) +@pytest.mark.parametrize('p1', [1, 3]) +@pytest.mark.parametrize('p2', [2]) +@pytest.mark.parametrize('p3', [1]) +@pytest.mark.parametrize('s1', [1, 2]) +@pytest.mark.parametrize('s2', [2]) +@pytest.mark.parametrize('s3', [1]) +@pytest.mark.parametrize('P1', [True, False]) +@pytest.mark.parametrize('P2', [True]) +@pytest.mark.parametrize('P3', [False]) + +@pytest.mark.parallel +@pytest.mark.petsc +def test_stencil_vector_3d_parallel_topetsc(dtype, n1, n2, n3, p1, p2, p3, s1, s2, s3, P1, P2, P3): + from mpi4py import MPI + comm = MPI.COMM_WORLD + + # Create domain decomposition + D = DomainDecomposition([n1, n2, n3], periods=[P1, P2, P3], comm=comm) + + # Partition the points + npts = [n1, n2, n3] + global_starts, global_ends = compute_global_starts_ends(D, npts) + C = CartDecomposition(D, npts, global_starts, global_ends, pads=[p1, p2, p3], shifts=[s1, s2, s3]) + + # Create vector space and stencil vector + V = StencilVectorSpace(C, dtype=dtype) + x = StencilVector(V) + + # Fill the vector with data + + if dtype == complex: + f = lambda i1, i2, i3: 10j * i1 + i2 - i3 + else: + f = lambda i1, i2, i3: 10 * i1 + i2 - i3 + + # Initialize distributed 2D stencil vector + for i1 in range(V.starts[0], V.ends[0] + 1): + for i2 in range(V.starts[1], V.ends[1] + 1): + for i3 in range(V.starts[2], V.ends[2] + 1): + x[i1, i2, i3] = f(i1, i2, i3) + + # Convert vector to PETSc.Vec + v = x.topetsc() + + # Convert PETSc.Vec to StencilVector of V + v = petsc_to_psydac(v, V) + + assert np.array_equal(x.toarray(), v.toarray()) +#test_stencil_vector_3d_parallel_topetsc(float, 4, 10, 5, 1, 1, 3, 1, 2, 1, True, True, True) + # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) @pytest.mark.parametrize('n1', [6, 15]) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 01f896e21..c87748a78 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -49,7 +49,6 @@ def psydac_to_petsc_local( ii = ndarray_indices - npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. assert all([ii[d] >= pads[d]*shifts[d] and ii[d] < shape[d] - pads[d]*shifts[d] for d in range(ndim)]), 'ndarray_indices within the ghost region' @@ -84,6 +83,10 @@ def get_petsc_local_to_global_shift(V : VectorSpace) -> int: cart = V.cart comm = cart.global_comm + + if comm is None: + return 0 + gstarts = cart.global_starts # Global variable gends = cart.global_ends # Global variable @@ -126,7 +129,7 @@ def petsc_to_psydac_local( else: raise NotImplementedError( "Cannot handle more than 3 dimensions." ) - return [tuple(ii)] + return tuple(tuple(ii)) def flatten_vec( vec ): @@ -309,7 +312,7 @@ def vec_topetsc( vec ): for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): value = vec._data[i1] if value != 0: - index = psydac_to_petsc_local(vec.space, [], (i1)) # global index starting from 0 in each process + index = psydac_to_petsc_local(vec.space, [], (i1,)) # global index starting from 0 in each process index += index_shift #starts[0] # global index starting from NOT 0 in each process petsc_indices.append(index) petsc_data.append(value) @@ -337,21 +340,22 @@ def vec_topetsc( vec ): petsc_indices.append(index) petsc_data.append(value) - gvec.setValues(petsc_indices, petsc_data, addv=PETSc.InsertMode.ADD_VALUES) + gvec.setValues(petsc_indices, petsc_data)#, addv=PETSc.InsertMode.ADD_VALUES) # Assemble vector gvec.assemble() # Here PETSc exchanges global communication. The block corresponding to a certain process is not necessarily the same block in the Psydac StencilVector. - #diff = abs(vec.toarray() - gvec.array).max() - '''vec_arr = vec.toarray() - - for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('\nRank ', k) - print('petsc_indices=', petsc_indices) - print('petsc_data=', petsc_data) - print('\ngvec.array=', gvec.array.real) - print('vec.toarray()=', vec_arr) - comm.Barrier()''' + if comm is not None: + vec_arr = vec.toarray() + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('\nRank ', k) + #print('petsc_indices=', petsc_indices) + #print('petsc_data=', petsc_data) + #print('\ngvec.array=', gvec.array.real) + print('vec.toarray()=', vec_arr) + #print('gvec.getSizes()=', gvec.getSizes()) + comm.Barrier() + print('================================') return gvec diff --git a/psydac/linalg/utilities.py b/psydac/linalg/utilities.py index ed91e1013..d38d5b112 100644 --- a/psydac/linalg/utilities.py +++ b/psydac/linalg/utilities.py @@ -6,6 +6,7 @@ from psydac.linalg.basic import Vector from psydac.linalg.stencil import StencilVectorSpace, StencilVector from psydac.linalg.block import BlockVector, BlockVectorSpace +from psydac.linalg.topetsc import psydac_to_petsc_local, get_petsc_local_to_global_shift, petsc_to_psydac_local __all__ = ( 'array_to_psydac', @@ -164,7 +165,29 @@ def petsc_to_psydac(x, Xh): u = StencilVector(Xh) comm = u.space.cart.global_comm dtype = u.space.dtype - sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) + localsize, globalsize = x.getSizes() + assert globalsize == u.shape[0], 'Sizes of global vectors do not match' + + index_shift = get_petsc_local_to_global_shift(Xh) + petsc_local_indices = np.arange(localsize) + petsc_indices = petsc_local_indices #+ index_shift + psydac_indices = [petsc_to_psydac_local(Xh, petsc_index) for petsc_index in petsc_indices] + + if comm is not None: + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('\nRank ', k) + print('petsc_indices=\n', petsc_indices) + print('psydac_indices=\n', psydac_indices) + print('index_shift=', index_shift) + comm.Barrier() + + for psydac_index, petsc_index in zip(psydac_indices, petsc_indices): + value = x.getValue(petsc_index + index_shift) + if value != 0: + u._data[psydac_index] = value if dtype is complex else value.real + + '''sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors if comm: @@ -185,12 +208,25 @@ def petsc_to_psydac(x, Xh): # With PETSc installation configuration for complex, all the numbers are by default complex. # In the float case, the imaginary part must be truncated to avoid warnings. - u._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) + u._data[idx] = (vals if dtype is complex else vals.real).reshape(shape)''' else: raise ValueError('Xh must be a StencilVectorSpace or a BlockVectorSpace') u.update_ghost_regions() + + if comm is not None: + u_arr = u.toarray() + x_arr = x.array.real + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('\nRank ', k) + print('u.toarray()=\n', u_arr) + #print('x.array=\n', x_arr) + #print('u._data=\n', u._data) + + comm.Barrier() + return u #============================================================================== From bf176a28ea5f13369f8e9bf30e4e4962f837ce73 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Mon, 13 May 2024 17:07:01 +0200 Subject: [PATCH 25/88] works for 1D stencil matrix with multiple processes --- psydac/linalg/topetsc.py | 267 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 3 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index c87748a78..ab9f15d96 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -360,6 +360,7 @@ def vec_topetsc( vec ): return gvec + def mat_topetsc( mat ): """ Convert operator from Psydac format to a PETSc.Mat object. @@ -386,8 +387,268 @@ def mat_topetsc( mat ): dcart = mat.domain.spaces[0][0].cart ccart = mat.codomain.spaces[0][0].cart - comm = dcart.global_comm + dcomm = dcart.global_comm + ccomm = ccart.global_comm + + + dndim = dcart.ndim + dstarts = dcart.starts + dends = dcart.ends + dpads = dcart.pads + dshifts = dcart.shifts + dnpts = dcart.npts + + cndim = ccart.ndim + cstarts = ccart.starts + cends = ccart.ends + #cpads = ccart.pads + #cshifts = ccart.shifts + cnpts = ccart.npts + + dnpts_local = [ e - s + 1 for s, e in zip(dstarts, dends)] #Number of points in each dimension within each process. Different for each process. + cnpts_local = [ e - s + 1 for s, e in zip(cstarts, cends)] + + + dindex_shift = get_petsc_local_to_global_shift(mat.domain) #Global variable + cindex_shift = get_petsc_local_to_global_shift(mat.codomain) #Global variable + + + mat_dense = mat.tosparse().todense() + for k in range(dcomm.Get_size()): + if k == dcomm.Get_rank(): + print('\nRank ', k) + print('dstarts=', dstarts) + print('dends=', dends) + print('cstarts=', cstarts) + print('cends=', cends) + print('dnpts=', dnpts) + print('cnpts=', cnpts) + print('dnpts_local=', dnpts_local) + print('cnpts_local=', cnpts_local) + #print('mat_dense=\n', mat_dense) + print('mat._data.shape=\n', mat._data.shape) + #print('mat._data=\n', mat._data) + + dcomm.Barrier() + ccomm.Barrier() + + + globalsize = (np.prod(dnpts), np.prod(cnpts)) #Tuple of integers + localsize = (np.prod(dnpts_local), np.prod(cnpts_local)) + + gmat = PETSc.Mat().create(comm=dcomm) + + gmat.setSizes(size=((localsize[0], globalsize[0]), (localsize[1], globalsize[1]))) #((local_rows, rows), (local_columns, columns)) + + if dcomm: + # Set PETSc sparse parallel matrix type + gmat.setType("mpiaij") + else: + gmat.setType("seqaij") + + gmat.setFromOptions() + gmat.setUp() + + print('gmat.getSizes()=', gmat.getSizes()) + + '''mat_coo = mat.tosparse() + rows_coo, cols_coo, data_coo = mat_coo.row, mat_coo.col, mat_coo.data + + mat_csr = mat_coo.tocsr() + mat_csr.eliminate_zeros() + data, indices, indptr = mat_csr.data, mat_csr.indices, mat_csr.indptr + #indptr_chunk = indptr[indptr >= dcart.starts[0] and indptr <= dcart.ends[0]] + #indices_chunk = indices[indices >= dcart.starts[1] and indices <= dcart.ends[1]] + + mat_coo_local = mat.tocoo_local() + rows_coo_local, cols_coo_local, data_coo_local = mat_coo_local.row, mat_coo_local.col, mat_coo_local.data''' + + + petsc_row_indices = [] + petsc_col_indices = [] + petsc_data = [] + #mat.remove_spurious_entries() + + I = [0] + J = [] + V = [] + rowmap = [] + + dindices = [np.arange(p*m, p*m + n) for p, m, n in zip(dpads, dshifts, dnpts_local)] + #cindices = [np.arange(2*p*m + 1) for p, m in zip(dpads, dshifts)] + + #prod_indices = np.empty((max(dnpts_local) * max(cnpts_local), 3)) + '''prod_indices = [] + for d in range(len(dindices)): + #prod_indices[:, d] = [*cartesian_prod(dindices[d], cindices[d])] + prod_indices.append([*cartesian_prod(dindices[d], cindices[d])]) + ''' + + + if dndim == 1 and cndim == 1: + for id1 in dindices[0]: + nnz_in_row = 0 + for ic1 in range(2*dpads[0]*dshifts[0] + 1): + value = mat._data[id1, ic1] + + if value != 0: + #print('id1, ic1 = ', id1, ic1) + #print('value=', value) + '''cindex_petsc = (id1 + ic1 - 2*dpads[0]*dshifts[0]) % (2*dpads[0]*dshifts[0] + 1) + dindex_petsc = globalsize[1] * (id1 - dpads[0]*dshifts[0]) + cindex_petsc + #dindex_petsc = psydac_to_petsc_local(mat.domain, [], (id1*(2*dpads[0] + 1) + ic1,)) # global index starting from 0 in each process + + #cindex_petsc = psydac_to_petsc_local(mat.codomain, [], (ic1 + cpads[0]*cshifts[0],)) # global index starting from 0 in each process + dindex_petsc += dindex_shift # global index NOT starting from 0 in each process + cindex_petsc += cindex_shift # global index NOT starting from 0 in each process + petsc_row_indices.append(dindex_petsc) + petsc_col_indices.append(cindex_petsc)''' + #petsc_col_indices.append((id1 + ic1 - 2*dpads[0]*dshifts[0])%dnpts_local[0]) + if nnz_in_row == 0: + rowmap.append(dindex_shift + psydac_to_petsc_local(mat.domain, [], (id1,))) + J.append((dindex_shift + id1 + ic1 - 2*dpads[0]*dshifts[0])%dnpts[0]) + V.append(value) + + nnz_in_row += 1 + #J.append(petsc_col_indices[-1]) + #V.append(value) + + I.append(I[-1] + nnz_in_row) + '''if nnz_in_row > 0: + #rowmap.append(id1 - dpads[0]*dshifts[0]) + rowmap.append(petsc_row_indices[-1]) + I.append(I[-1] + nnz_in_row)''' + + elif dndim == 2 and cndim == 2: + for id1 in dindices[0]: + for id2 in dindices[1]: + + nnz_in_row = 0 + local_row = psydac_to_petsc_local(mat.domain, [], (id1, id2)) + #band_width = 4*np.prod(dpads)*np.prod(dshifts) + #cindices1 = [np.arange(max(0, (4*np.prod(dpads)*np.prod(dshifts) - local_row), ) for p, m, n in zip(dpads, dshifts, dnpts_local)] + + for ic1 in range(2*dpads[0]*dshifts[0] + 1): + for ic2 in range(2*dpads[1]*dshifts[1] + 1): + + value = mat._data[id1, id2, ic1, ic2] + + if value != 0: + '''dindex_petsc = psydac_to_petsc_local(mat.domain, [], (id1,id2)) # global index starting from 0 in each process + cindex_petsc = (id1 + ic1 - 2*dpads[0]*dshifts[0]) % (2*dpads[0]*dshifts[0]) + + + dindex_petsc += dindex_shift # global index NOT starting from 0 in each process + cindex_petsc += cindex_shift # global index NOT starting from 0 in each process + petsc_row_indices.append(dindex_petsc) + petsc_col_indices.append(cindex_petsc) + petsc_data.append(value) + + nnz_in_row += 1 + J.append(cindex_petsc) + V.append(value)''' + + #local_row = psydac_to_petsc_local(mat.domain, [], (id1, id2)) + + if nnz_in_row == 0: + rowmap.append(dindex_shift + local_row) + #J.append( (dindex_shift + local_row + ic1*(2*dpads[1]*dshifts[1] + 1) + ic2 - 2*dpads[0]*dshifts[0] - 2*dpads[1]*dshifts[1] ) \ + # % np.prod(dnpts) ) + num_zeros_0row = (2*dpads[0]*dshifts[0] + 1)*(2*dpads[1]*dshifts[1] + 1) // 2 + J.append( (dindex_shift + local_row \ + + (ic1*(2*dpads[1]*dshifts[1] + 1) + ic2) + - num_zeros_0row \ + ) \ + % (np.prod(dnpts)-1) ) + V.append(value) + + nnz_in_row += 1 + + I.append(I[-1] + nnz_in_row) + + + import time + t_prev = time.time() + '''for k in range(len(rows_coo)): + gmat.setValues(rows_coo[k] - dcart.global_starts[0][comm.Get_rank()], cols_coo[k], data_coo[k]) + ''' + #gmat.setValuesCSR([r - dcart.global_starts[0][comm.Get_rank()] for r in indptr[1:]], indices, data) + #gmat.setValuesLocalCSR(local_indptr, indices, data)#, addv=PETSc.InsertMode.ADD_VALUES) + gmat.setValuesIJV(I, J, V, rowmap=rowmap)#, addv=PETSc.InsertMode.ADD_VALUES) + + + print('Rank ', dcomm.Get_rank() if dcomm else '-', ': duration of setValuesIJV :', time.time()-t_prev) + + + + # Process inserted matrix entries + ################################################ + # Note 12.03.2024: + # In the assembly PETSc uses global communication to distribute the matrix in a different way than Psydac. + # For this reason, at the moment we cannot compare directly each distributed 'chunck' of the Psydac and the PETSc matrices. + # In the future we would like that PETSc uses the partition from Psydac, + # which might involve passing a DM Object. + ################################################ + t_prev = time.time() + gmat.assemble() + print('Rank ', dcomm.Get_rank() if dcomm else '-', ': duration of Mat assembly :', time.time()-t_prev) + + + ''' + if not dcomm: + gmat_dense = gmat.getDenseArray() + else: + gmat_dense = gmat.getDenseLocalMatrix() + dcomm.Barrier() + ''' + if not dcomm: + print('mat_dense=', mat_dense) + #print('gmat_dense=', gmat_dense) + else: + for k in range(dcomm.Get_size()): + if k == dcomm.Get_rank(): + print('\n\nRank ', k) + print('mat_dense=\n', mat_dense) + #print('petsc_row_indices=\n', petsc_row_indices) + #print('petsc_col_indices=\n', petsc_col_indices) + #print('petsc_data=\n', petsc_data) + print('I=', I) + print('rowmap=', rowmap) + print('J=\n', J) + print('V=\n', V) + #print('gmat_dense=\n', gmat_dense) + print + dcomm.Barrier() + return gmat + + +def mat_topetsc_old( mat ): + """ Convert operator from Psydac format to a PETSc.Mat object. + + Parameters + ---------- + mat : psydac.linalg.stencil.StencilMatrix | psydac.linalg.basic.LinearOperator | psydac.linalg.block.BlockLinearOperator + Psydac operator + + Returns + ------- + gmat : PETSc.Mat + PETSc Matrix + """ + + from petsc4py import PETSc + if isinstance(mat, StencilMatrix): + dcart = mat.domain.cart + ccart = mat.codomain.cart + elif isinstance(mat.domain.spaces[0], StencilVectorSpace): + dcart = mat.domain.spaces[0].cart + ccart = mat.codomain.spaces[0].cart + elif isinstance(mat.domain.spaces[0], BlockVectorSpace): + dcart = mat.domain.spaces[0][0].cart + ccart = mat.codomain.spaces[0][0].cart + + comm = dcart.global_comm #print('mat.shape = ', mat.shape) @@ -403,7 +664,7 @@ def mat_topetsc( mat ): ### SPLITTING DOMAIN #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] - boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if mat.domain.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(dcart.ndim)] + '''boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if mat.domain.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(dcart.ndim)] dim = dcart.ndim @@ -416,7 +677,7 @@ def mat_topetsc( mat ): print('OWNership_ranges=', ownership_ranges) Dmda = PETSc.DMDA().create(dim=dim, sizes=sizes, proc_sizes=proc_sizes, ownership_ranges=ownership_ranges, comm=comm, - stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type) + stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type)''' '''### SPLITTING COEFFS From c7ac7ce770521feaff89a86c20a7b70a64bfbca8 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 15 May 2024 15:53:12 +0200 Subject: [PATCH 26/88] correct indexing for 2D stencilmatrix --- psydac/linalg/topetsc.py | 284 +++++++++++++++++++++++++++++++-------- 1 file changed, 227 insertions(+), 57 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index ab9f15d96..c2c06ef6e 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -131,6 +131,90 @@ def petsc_to_psydac_local( return tuple(tuple(ii)) +def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: + '''From Psydac natural multi-index (grid coordinates) to global PETSc single-index. + Performs a search to find the process owning the multi-index.''' + ndim = V.ndim + s = V.starts + e = V.ends + p = V.pads + m = V.shifts + dnpts = V.cart.npts + nprocs = V.cart.nprocs + #dnpts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. + + gs = V.cart.global_starts # Global variable + ge = V.cart.global_ends # Global variable + + npts_local_perprocess = [ ge_i - gs_i + 1 for gs_i, ge_i in zip(gs, ge)] #Global variable + npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable + localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(V.cart.comm.Get_size())] #Global variable + + jj = ndarray_indices + if ndim == 1: + global_index = (jj[0] - p[0]*m[0])%dnpts[0] + elif ndim == 2: + proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] + + proc_index = proc_y + proc_x*nprocs[1]#proc_x + proc_y*nprocs[0] + index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + #global_index = jj[0] - gs[0][proc_x] + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][0] + index_shift + global_index = jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_perprocess[proc_index][1] + index_shift + + #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) + + #hola = np.where(jj[0] in x_proc_ranges)#, gs[0], -1) + '''for k in range(V.cart.comm.Get_size()): + if k == V.cart.comm.Get_rank(): + print('\nRank', k, '\njj=', jj) + print('proc_x=', proc_x) + print('proc_y=', proc_y) + print('proc_index=', proc_index) + print('index_shift=', index_shift) + print('global_index=', global_index) + print('npts_local_perprocess=', npts_local_perprocess) + V.cart.comm.Barrier()''' + + + else: + raise NotImplementedError( "Cannot handle more than 3 dimensions." ) + + + return global_index + +def psydac_to_singlenatural(V : VectorSpace, ndarray_indices : tuple[int]) -> int: + ndim = V.ndim + dnpts = V.cart.npts + + + jj = ndarray_indices + if ndim == 1: + singlenatural_index = 0 + elif ndim == 2: + singlenatural_index = jj[1] + jj[0] * dnpts[1] + + + #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) + + #hola = np.where(jj[0] in x_proc_ranges)#, gs[0], -1) + '''for k in range(V.cart.comm.Get_size()): + if k == V.cart.comm.Get_rank(): + print('\nRank', k, '\njj=', jj) + print('proc_x=', proc_x) + print('proc_y=', proc_y) + print('proc_index=', proc_index) + print('index_shift=', index_shift) + print('global_index=', global_index) + print('npts_local_perprocess=', npts_local_perprocess) + V.cart.comm.Barrier()''' + + + else: + raise NotImplementedError( "Cannot handle more than 3 dimensions." ) + + + return singlenatural_index def flatten_vec( vec ): """ Return the flattened 1D array values and indices owned by the process of the given vector. @@ -344,7 +428,7 @@ def vec_topetsc( vec ): # Assemble vector gvec.assemble() # Here PETSc exchanges global communication. The block corresponding to a certain process is not necessarily the same block in the Psydac StencilVector. - if comm is not None: + '''if comm is not None: vec_arr = vec.toarray() for k in range(comm.Get_size()): if k == comm.Get_rank(): @@ -355,7 +439,7 @@ def vec_topetsc( vec ): print('vec.toarray()=', vec_arr) #print('gvec.getSizes()=', gvec.getSizes()) comm.Barrier() - print('================================') + print('================================')''' return gvec @@ -390,6 +474,7 @@ def mat_topetsc( mat ): dcomm = dcart.global_comm ccomm = ccart.global_comm + mat.update_ghost_regions() dndim = dcart.ndim dstarts = dcart.starts @@ -403,7 +488,7 @@ def mat_topetsc( mat ): cends = ccart.ends #cpads = ccart.pads #cshifts = ccart.shifts - cnpts = ccart.npts + cnpts = ccart.npts dnpts_local = [ e - s + 1 for s, e in zip(dstarts, dends)] #Number of points in each dimension within each process. Different for each process. cnpts_local = [ e - s + 1 for s, e in zip(cstarts, cends)] @@ -425,8 +510,12 @@ def mat_topetsc( mat ): print('cnpts=', cnpts) print('dnpts_local=', dnpts_local) print('cnpts_local=', cnpts_local) - #print('mat_dense=\n', mat_dense) + #print('mat_dense=\n', mat_dense[:3]) print('mat._data.shape=\n', mat._data.shape) + print('dindex_shift=', dindex_shift) + print('cindex_shift=', cindex_shift) + print('ccart.global_starts=', ccart.global_starts) + print('ccart.global_ends=', ccart.global_ends) #print('mat._data=\n', mat._data) dcomm.Barrier() @@ -464,17 +553,19 @@ def mat_topetsc( mat ): rows_coo_local, cols_coo_local, data_coo_local = mat_coo_local.row, mat_coo_local.col, mat_coo_local.data''' - petsc_row_indices = [] - petsc_col_indices = [] - petsc_data = [] #mat.remove_spurious_entries() I = [0] J = [] V = [] + J2 = [] rowmap = [] + rowmap2 = [] dindices = [np.arange(p*m, p*m + n) for p, m, n in zip(dpads, dshifts, dnpts_local)] + + #[[ dcomm.Get_rank()*dnpts_local[1] + n2 + dnpts[1]*n1 for n2 in np.arange(dnpts_local[1])] for n1 in np.arange(dnpts_local[0])] + #cindices = [np.arange(2*p*m + 1) for p, m in zip(dpads, dshifts)] #prod_indices = np.empty((max(dnpts_local) * max(cnpts_local), 3)) @@ -484,29 +575,29 @@ def mat_topetsc( mat ): prod_indices.append([*cartesian_prod(dindices[d], cindices[d])]) ''' + #matd = mat.tosparse().todense() + s = dstarts + p = dpads + m = dshifts + if dndim == 1 and cndim == 1: - for id1 in dindices[0]: + for i1 in dindices[0]: nnz_in_row = 0 - for ic1 in range(2*dpads[0]*dshifts[0] + 1): - value = mat._data[id1, ic1] + for k1 in range(2*dpads[0]*dshifts[0] + 1): + value = mat._data[i1, k1] if value != 0: - #print('id1, ic1 = ', id1, ic1) - #print('value=', value) - '''cindex_petsc = (id1 + ic1 - 2*dpads[0]*dshifts[0]) % (2*dpads[0]*dshifts[0] + 1) - dindex_petsc = globalsize[1] * (id1 - dpads[0]*dshifts[0]) + cindex_petsc - #dindex_petsc = psydac_to_petsc_local(mat.domain, [], (id1*(2*dpads[0] + 1) + ic1,)) # global index starting from 0 in each process - - #cindex_petsc = psydac_to_petsc_local(mat.codomain, [], (ic1 + cpads[0]*cshifts[0],)) # global index starting from 0 in each process - dindex_petsc += dindex_shift # global index NOT starting from 0 in each process - cindex_petsc += cindex_shift # global index NOT starting from 0 in each process - petsc_row_indices.append(dindex_petsc) - petsc_col_indices.append(cindex_petsc)''' - #petsc_col_indices.append((id1 + ic1 - 2*dpads[0]*dshifts[0])%dnpts_local[0]) if nnz_in_row == 0: - rowmap.append(dindex_shift + psydac_to_petsc_local(mat.domain, [], (id1,))) - J.append((dindex_shift + id1 + ic1 - 2*dpads[0]*dshifts[0])%dnpts[0]) + rowmap.append(dindex_shift + psydac_to_petsc_local(mat.domain, [], (i1,))) + + i1_n = s[0] + i1 + j1_n = i1_n + k1 - p[0]*m[0] + + global_col = psydac_to_global(mat.domain, (j1_n,)) + #J.append((j1_n - p[0]*m[0])%dnpts[0]) + J.append(global_col) + #J.append((dindex_shift + i1 + k1 - 2*p[0]*m[0])%dnpts[0]) V.append(value) nnz_in_row += 1 @@ -520,20 +611,80 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row)''' elif dndim == 2 and cndim == 2: - for id1 in dindices[0]: - for id2 in dindices[1]: + ghost_size = (p[0]*m[0], p[1]*m[1]) + for i1 in np.arange(dnpts_local[0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): + for i2 in np.arange(dnpts_local[1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): nnz_in_row = 0 - local_row = psydac_to_petsc_local(mat.domain, [], (id1, id2)) - #band_width = 4*np.prod(dpads)*np.prod(dshifts) + #local_row = psydac_to_petsc_local(mat.domain, [], (i1, i2)) + #local_row += (local_row // dnpts_local[1])*dnpts_local[1] + + #cindices1 = np.arange( max(0, id1 - dindices[0][0] - dpads[0]*dshifts[0]), min(2*dpads[0]*dshifts[0], id1 - dindices[0][0] + dpads[0]*dshifts[0]) + 1) + #cindices2 = np.arange( max(0, id2 - dindices[1][0] - dpads[1]*dshifts[1]), min(2*dpads[1]*dshifts[1], id2 - dindices[1][0] + dpads[1]*dshifts[1]) + 1) + #cindices1 = np.arange( max(dpads[0]*dshifts[0], id1), min(2*dpads[0]*dshifts[0] + 1, id1 + 2*dpads[0]*dshifts[0]) + 1) + #cindices2 = np.arange( max(dpads[1]*dshifts[1], id2), min(2*dpads[1]*dshifts[1] + 1, id2 + 2*dpads[1]*dshifts[1]) + 1) + + #cindices = [*cartesian_prod(cindices1, cindices2)] + #cindices = [[(ic1, ic2) for ic2 in np.arange(id2 - int(np.ceil(dpads[1]*dshifts[1]/2)), id2 + int(np.floor(dpads[1]*dshifts[1]/2)) + 1) - dpads[1]*dshifts[1] ] + # for ic1 in np.arange(id1 - int(np.ceil(dpads[0]*dshifts[0]/2)), id1 + int(np.floor(dpads[0]*dshifts[0]/2)) + 1) - dpads[0]*dshifts[0] ] + + #ravel_ind_0_col = 2*dpads[1]*dshifts[1] + 1 + 2*dpads[0]*dshifts[0] - local_row #becomes negative for large row index + #ravel_ind_0_col = ((2*dpads[1]*dshifts[1] + 1) * (2*dpads[0]*dshifts[0] + 1) ) // 2 - local_row #becomes negative for large row index + #cindices1 = [np.arange(max(0, (4*np.prod(dpads)*np.prod(dshifts) - local_row), ) for p, m, n in zip(dpads, dshifts, dnpts_local)] - for ic1 in range(2*dpads[0]*dshifts[0] + 1): - for ic2 in range(2*dpads[1]*dshifts[1] + 1): + '''if dcomm.Get_rank() == 0: + print('Rank 0: mat._data[',id1, ',' , id2 , ']=\n', mat._data[id1, id2]) + elif dcomm.Get_rank() == 1: + print('Rank 1: mat._data[',id1, ',' , id2 , ']=\n', mat._data[id1, id2]) + #dcomm.Barrier()''' + + i1_n = s[0] + i1 + i2_n = s[1] + i2 + i_g = psydac_to_global(mat.codomain, (i1_n, i2_n)) + i_n = psydac_to_singlenatural(mat.codomain, (i1_n,i2_n)) + + for k in range(dcomm.Get_size()): + if k == dcomm.Get_rank(): + print(f'Rank {k}: ({i1_n}, {i2_n}), i_n= {i_n}, i_g= {i_g}') + #print(f'global_row= {global_row}') + #dcomm.Barrier() + #ccomm.Barrier() + + + + + for k1 in range(2*p[0]*m[0] + 1): + for k2 in range(2*p[1]*m[1] + 1): + #for ic1, ic2 in cindices: + + value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], k1, k2] + + '''i1_n = s[0] + i1 + i2_n = s[1] + i2 + #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) + j1_n = i1_n + k1 - p[0] + j2_n = i2_n + k2 - p[1]''' + - value = mat._data[id1, id2, ic1, ic2] + #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) + j1_n = i1_n + k1 - p[0]*m[0] + j2_n = i2_n + k2 - p[1]*m[1] - if value != 0: + + + #print('i1,i2,k1,k2=', i1,i2,k1,k2) + #print('i1_n,i2_n,j1_n,j2_n,value=', i1_n,i2_n,j1_n,j2_n,value) + + + + + if (value != 0 and j1_n < dnpts[0] and j2_n < dnpts[1]): + + j_g = psydac_to_global(mat.domain, (j1_n, j2_n)) + + global_col = psydac_to_global(mat.domain, (j1_n, j2_n)) + #print('row,id1,id2,ic1,ic2=', local_row, id1, id2, ic1, ic2) '''dindex_petsc = psydac_to_petsc_local(mat.domain, [], (id1,id2)) # global index starting from 0 in each process cindex_petsc = (id1 + ic1 - 2*dpads[0]*dshifts[0]) % (2*dpads[0]*dshifts[0]) @@ -551,15 +702,27 @@ def mat_topetsc( mat ): #local_row = psydac_to_petsc_local(mat.domain, [], (id1, id2)) if nnz_in_row == 0: - rowmap.append(dindex_shift + local_row) + #rowmap.append(dindex_shift + local_row) + #rowmap.append(dindex_shift + local_row) + rowmap.append(i_g) + rowmap2.append(psydac_to_singlenatural(mat.domain, (i1_n,i2_n))) #J.append( (dindex_shift + local_row + ic1*(2*dpads[1]*dshifts[1] + 1) + ic2 - 2*dpads[0]*dshifts[0] - 2*dpads[1]*dshifts[1] ) \ # % np.prod(dnpts) ) - num_zeros_0row = (2*dpads[0]*dshifts[0] + 1)*(2*dpads[1]*dshifts[1] + 1) // 2 - J.append( (dindex_shift + local_row \ - + (ic1*(2*dpads[1]*dshifts[1] + 1) + ic2) - - num_zeros_0row \ - ) \ - % (np.prod(dnpts)-1) ) + #num_zeros_0row = (2*dpads[0]*dshifts[0] + 1)*(2*dpads[1]*dshifts[1] + 1) // 2 + #J.append( (dindex_shift + local_row \ + # + (ic1*(2*dpads[1]*dshifts[1] + 1) + ic2) + # - num_zeros_0row \ + # ) \ + # % (np.prod(dnpts)) ) + + #ravel_ind = ic2 + (2*dpads[1]*dshifts[1] + 1) * ic1 + #col_index = ic2 - dpads[1]*dshifts[1] + (ic1 - dpads[0]*dshifts[0])*dnpts[1] + #col_index = ic2 - dpads[1]*dshifts[1] + (dindex_shift+local_row) % dnpts[1] \ + # + (ic1 - dpads[0]*dshifts[0] + (dindex_shift+local_row) // dnpts[1]) * dnpts[1] + + J.append(j_g) + J2.append(psydac_to_singlenatural(mat.domain, (j1_n,j2_n))) + V.append(value) nnz_in_row += 1 @@ -567,6 +730,30 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row) + if not dcomm: + print() + #print('mat_dense=', mat_dense) + #print('gmat_dense=', gmat_dense) + else: + for k in range(dcomm.Get_size()): + if k == dcomm.Get_rank(): + print('\n\nRank ', k) + #print('mat_dense=\n', mat_dense) + #print('petsc_row_indices=\n', petsc_row_indices) + #print('petsc_col_indices=\n', petsc_col_indices) + #print('petsc_data=\n', petsc_data) + #print('owned_rows=', owned_rows) + print('mat_dense=\n', mat_dense) + print('I=', I) + print('rowmap=', rowmap) + print('rowmap2=', rowmap2) + print('J=\n', J) + print('J2=\n', J2) + #print('V=\n', V) + #print('gmat_dense=\n', gmat_dense) + print('\n\n============') + dcomm.Barrier() + import time t_prev = time.time() '''for k in range(len(rows_coo)): @@ -601,24 +788,7 @@ def mat_topetsc( mat ): gmat_dense = gmat.getDenseLocalMatrix() dcomm.Barrier() ''' - if not dcomm: - print('mat_dense=', mat_dense) - #print('gmat_dense=', gmat_dense) - else: - for k in range(dcomm.Get_size()): - if k == dcomm.Get_rank(): - print('\n\nRank ', k) - print('mat_dense=\n', mat_dense) - #print('petsc_row_indices=\n', petsc_row_indices) - #print('petsc_col_indices=\n', petsc_col_indices) - #print('petsc_data=\n', petsc_data) - print('I=', I) - print('rowmap=', rowmap) - print('J=\n', J) - print('V=\n', V) - #print('gmat_dense=\n', gmat_dense) - print - dcomm.Barrier() + return gmat From 657947842a4184bc84b0027b49b4586e7d08f620 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 15 May 2024 18:51:59 +0200 Subject: [PATCH 27/88] works 1d,2d,3d stencilmatrix without periodic BC --- psydac/linalg/tests/test_stencil_matrix.py | 230 ++++++++++++++++++++- psydac/linalg/topetsc.py | 62 +++++- 2 files changed, 282 insertions(+), 10 deletions(-) diff --git a/psydac/linalg/tests/test_stencil_matrix.py b/psydac/linalg/tests/test_stencil_matrix.py index 1cd59410b..aef0b9d33 100644 --- a/psydac/linalg/tests/test_stencil_matrix.py +++ b/psydac/linalg/tests/test_stencil_matrix.py @@ -2773,7 +2773,7 @@ def test_stencil_matrix_2d_parallel_topetsc(dtype, n1, n2, p1, p2, sh1, sh2, P1, # Convert stencil matrix to PETSc.Mat Mp = M.topetsc() # Create Vec to allocate the result of the dot product - y_petsc = Mp.createVecRight() + y_petsc = Mp.createVecLeft() # Compute dot product Mp.mult(x.topetsc(), y_petsc) # Cast result back to Psydac StencilVector format @@ -2789,13 +2789,94 @@ def test_stencil_matrix_2d_parallel_topetsc(dtype, n1, n2, p1, p2, sh1, sh2, P1, assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) # =============================================================================== + +@pytest.mark.parametrize('dtype', [float, complex]) +@pytest.mark.parametrize('n1', [7, 11]) +@pytest.mark.parametrize('p1', [1, 3]) +@pytest.mark.parametrize('sh1', [1]) +@pytest.mark.parametrize('P1', [True, False]) +@pytest.mark.parallel +@pytest.mark.petsc + +def test_stencil_matrix_1d_parallel_topetsc(dtype, n1, p1, sh1, P1): + from mpi4py import MPI + + # Select non-zero values based on diagonal index + nonzero_values = dict() + if dtype==complex: + for k1 in range(-p1, p1 + 1): + nonzero_values[k1] = 10j * k1 + 7 + else: + for k1 in range(-p1, p1 + 1): + nonzero_values[k1] = 10 * k1 + 7 + + # Create domain decomposition: decomposes the coefficients + comm = MPI.COMM_WORLD + D = DomainDecomposition([n1], periods=[P1], comm=comm) + + # Partition the coefficients + npts = [n1] #Number of cells + global_starts, global_ends = compute_global_starts_ends(D, npts, [p1]) + + # In cart, npts must be the number of coefficients. + cart = CartDecomposition(D, npts, global_starts, global_ends, pads=[p1], shifts=[sh1]) + + # Create vector space and stencil matrix + V = StencilVectorSpace(cart, dtype=dtype) + M = StencilMatrix(V, V) + x = StencilVector(V) + + s1, = V.starts + e1, = V.ends + + # Fill in stencil matrix values + for i1 in range(s1, e1 + 1): + for k1 in range(-p1, p1 + 1): + M[i1, k1] = (i1+1)*nonzero_values[k1] + + # Fill in vector with random values, then update ghost regions + if dtype == complex: + for i1 in range(s1, e1 + 1): + x[i1] = 2.0j * random() - 1.0 + else: + for i1 in range(s1, e1 + 1): + x[i1] = 2.0 * random() - 1.0 + x.update_ghost_regions() + + # If any dimension is not periodic, set corresponding periodic corners to zero + M.remove_spurious_entries() + + # Convert stencil matrix to PETSc.Mat + Mp = M.topetsc() + + y = M.dot(x) + # Convert stencil matrix to PETSc.Mat + Mp = M.topetsc() + # Create Vec to allocate the result of the dot product + y_petsc = Mp.createVecLeft() + # Compute dot product + Mp.mult(x.topetsc(), y_petsc) + # Cast result back to Psydac StencilVector format + y_p = petsc_to_psydac(y_petsc, V) + + ################################################ + # Note 12.03.2024: + # Another possibility would be to compare y_petsc.array and y.toarray(). + # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. + # In the future we would like that PETSc uses the partition from Psydac, + # which might involve passing a DM Object. + ################################################ + assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) +#test_stencil_matrix_1d_parallel_topetsc(float, 5, 2, 1, True) +# =============================================================================== + @pytest.mark.parametrize('n1', [4,7]) @pytest.mark.parametrize('n2', [3,5]) @pytest.mark.parametrize('p1', [2]) @pytest.mark.parametrize('p2', [1]) -@pytest.mark.parametrize('P1', [True]) -@pytest.mark.parametrize('P2', [True]) +@pytest.mark.parametrize('P1', [True, False]) +@pytest.mark.parametrize('P2', [True, False]) @pytest.mark.parallel @pytest.mark.petsc @@ -2835,7 +2916,7 @@ def test_mass_matrix_2d_parallel_topetsc(n1, n2, p1, p2, P1, P2): # Convert stencil matrix to PETSc.Mat Mp = M.topetsc() # Create Vec to allocate the result of the dot product - y_petsc = Mp.createVecRight() + y_petsc = Mp.createVecLeft() # Compute dot product Mp.mult(x.topetsc(), y_petsc) # Cast result back to Psydac StencilVector format @@ -2848,9 +2929,150 @@ def test_mass_matrix_2d_parallel_topetsc(n1, n2, p1, p2, P1, P2): # In the future we would like that PETSc uses the partition from Psydac, # which might involve passing a DM Object. ################################################ + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + print('rank ', comm.Get_rank(), ':y_p.toarray()=\n', y_p.toarray()) + print('rank ', comm.Get_rank(), ': y.toarray()=\n', y.toarray()) + comm.Barrier() + + assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) + +test_mass_matrix_2d_parallel_topetsc(2, 3, 1, 1, True, False) + +# =============================================================================== + +@pytest.mark.parametrize('n1', [4,7]) +@pytest.mark.parametrize('n2', [3,5]) +@pytest.mark.parametrize('n3', [3,4]) +@pytest.mark.parametrize('p1', [2]) +@pytest.mark.parametrize('p2', [1]) +@pytest.mark.parametrize('p3', [1]) +@pytest.mark.parametrize('P1', [False]) +@pytest.mark.parametrize('P2', [True]) +@pytest.mark.parametrize('P3', [True, False]) +@pytest.mark.parallel +@pytest.mark.petsc + +def test_mass_matrix_3d_parallel_topetsc(n1, n2, n3, p1, p2, p3, P1, P2, P3): + from sympde.topology import Cube, ScalarFunctionSpace, element_of + from sympde.expr import BilinearForm, integral + from psydac.api.settings import PSYDAC_BACKENDS + from psydac.api.discretization import discretize + from mpi4py import MPI + + domain = Cube() + V = ScalarFunctionSpace('V', domain) + + u = element_of(V, name='u') + v = element_of(V, name='v') + + a = BilinearForm((u, v), integral(domain, u * v)) + comm = MPI.COMM_WORLD + domain_h = discretize(domain, ncells=[n1,n2,n3], periodic=[P1,P2,P3], comm=comm) + Vh = discretize(V, domain_h, degree=[p1,p2,p3]) + ah = discretize(a, domain_h, [Vh, Vh], backend=PSYDAC_BACKENDS['pyccel-gcc']) + M = ah.assemble() + + x = Vh.vector_space.zeros() + + s1, s2, s3 = Vh.vector_space.starts + e1, e2, e3 = Vh.vector_space.ends + + # Fill in vector with random values, then update ghost regions + for i1 in range(s1, e1 + 1): + for i2 in range(s2, e2 + 1): + for i3 in range(s3, e3 + 1): + x[i1, i2, i3] = 2.0 * random() - 1.0 + x.update_ghost_regions() + + y = M.dot(x) + + # Convert stencil matrix to PETSc.Mat + Mp = M.topetsc() + # Create Vec to allocate the result of the dot product + y_petsc = Mp.createVecLeft() + # Compute dot product + Mp.mult(x.topetsc(), y_petsc) + # Cast result back to Psydac StencilVector format + y_p = petsc_to_psydac(y_petsc, Vh.vector_space) + + assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) + +#test_mass_matrix_3d_parallel_topetsc(7, 3, 5, 1, 1, 2, True, True, True) + +# =============================================================================== + +@pytest.mark.parametrize('n1', [4,7]) +@pytest.mark.parametrize('p1', [2]) +@pytest.mark.parametrize('P1', [True]) +@pytest.mark.parallel +@pytest.mark.petsc + +def test_mass_matrix_1d_parallel_topetsc(n1, p1, P1): + from sympde.topology import Line, ScalarFunctionSpace, element_of + from sympde.expr import BilinearForm, integral + from psydac.api.settings import PSYDAC_BACKENDS + from psydac.api.discretization import discretize + from mpi4py import MPI + + domain = Line() + V = ScalarFunctionSpace('V', domain) + + u = element_of(V, name='u') + v = element_of(V, name='v') + + a = BilinearForm((u, v), integral(domain, u * v)) + comm = MPI.COMM_WORLD + domain_h = discretize(domain, ncells=[n1], periodic=[P1], comm=comm) + Vh = discretize(V, domain_h, degree=[p1]) + ah = discretize(a, domain_h, [Vh, Vh], backend=PSYDAC_BACKENDS['pyccel-gcc']) + M = ah.assemble() + + x = Vh.vector_space.zeros() + + s1, = Vh.vector_space.starts + e1, = Vh.vector_space.ends + + # Fill in vector with random values, then update ghost regions + for i1 in range(s1, e1 + 1): + x[i1] = 2.0 * random() - 1.0 + x.update_ghost_regions() + + y = M.dot(x) + + # Convert stencil matrix to PETSc.Mat + Mp = M.topetsc() + + #print('\nMp.getSizes()=', Mp.getSizes()) + # Create Vec to allocate the result of the dot product + y_petsc = Mp.createVecLeft() + #print('y_petsc.getSizes()=', y_petsc.getSizes()) + + x_petsc = x.topetsc() + # Compute dot product + Mp.mult(x_petsc, y_petsc) + # Cast result back to Psydac StencilVector format + y_p = petsc_to_psydac(y_petsc, Vh.vector_space) + + ################################################ + # Note 12.03.2024: + # Another possibility would be to compare y_petsc.array and y.toarray(). + # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. + # In the future we would like that PETSc uses the partition from Psydac, + # which might involve passing a DM Object. + ################################################ + '''for k in range(comm.Get_size()): + if comm.Get_rank() == k: + print('\n\nRank ', k) + print('x=\n', x.toarray()) + print('x_petsc=\n', x_petsc.array) + + print('MAX_DIFF=', abs((y-y_p).toarray()).max()) + comm.Barrier()''' assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) +#test_mass_matrix_1d_parallel_topetsc(12, 3, True) # =============================================================================== # PARALLEL BACKENDS TESTS # =============================================================================== diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index c2c06ef6e..de0f7047a 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -152,7 +152,11 @@ def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: jj = ndarray_indices if ndim == 1: - global_index = (jj[0] - p[0]*m[0])%dnpts[0] + #global_index = (jj[0] - p[0]*m[0])%dnpts[0] + proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + global_index = index_shift + jj[0] - gs[0][proc_x] + elif ndim == 2: proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] @@ -160,7 +164,7 @@ def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: proc_index = proc_y + proc_x*nprocs[1]#proc_x + proc_y*nprocs[0] index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable #global_index = jj[0] - gs[0][proc_x] + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][0] + index_shift - global_index = jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_perprocess[proc_index][1] + index_shift + global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_perprocess[proc_index][1] #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) @@ -175,8 +179,19 @@ def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: print('global_index=', global_index) print('npts_local_perprocess=', npts_local_perprocess) V.cart.comm.Barrier()''' + elif ndim == 3: + proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] + proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0] + + proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2] #proc_x + proc_y*nprocs[0] + index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + global_index = index_shift \ + + jj[2] - gs[2][proc_z] \ + + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][2] \ + + (jj[0] - gs[0][proc_x]) * npts_local_perprocess[proc_index][1] * npts_local_perprocess[proc_index][2] + - else: raise NotImplementedError( "Cannot handle more than 3 dimensions." ) @@ -475,6 +490,7 @@ def mat_topetsc( mat ): ccomm = ccart.global_comm mat.update_ghost_regions() + mat.remove_spurious_entries() dndim = dcart.ndim dstarts = dcart.starts @@ -562,7 +578,7 @@ def mat_topetsc( mat ): rowmap = [] rowmap2 = [] - dindices = [np.arange(p*m, p*m + n) for p, m, n in zip(dpads, dshifts, dnpts_local)] + #dindices = [np.arange(p*m, p*m + n) for p, m, n in zip(dpads, dshifts, dnpts_local)] #[[ dcomm.Get_rank()*dnpts_local[1] + n2 + dnpts[1]*n1 for n2 in np.arange(dnpts_local[1])] for n1 in np.arange(dnpts_local[0])] @@ -579,6 +595,7 @@ def mat_topetsc( mat ): s = dstarts p = dpads m = dshifts + ghost_size = [pi*mi for pi,mi in zip(p,m)] if dndim == 1 and cndim == 1: @@ -611,7 +628,7 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row)''' elif dndim == 2 and cndim == 2: - ghost_size = (p[0]*m[0], p[1]*m[1]) + #ghost_size = (p[0]*m[0], p[1]*m[1]) for i1 in np.arange(dnpts_local[0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): for i2 in np.arange(dnpts_local[1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): @@ -679,7 +696,7 @@ def mat_topetsc( mat ): - if (value != 0 and j1_n < dnpts[0] and j2_n < dnpts[1]): + if value != 0 and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): j_g = psydac_to_global(mat.domain, (j1_n, j2_n)) @@ -729,6 +746,39 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row) + elif dndim == 3 and cndim == 3: + for i1 in np.arange(dnpts_local[0]): + for i2 in np.arange(dnpts_local[1]): + for i3 in np.arange(dnpts_local[2]): + nnz_in_row = 0 + i1_n = s[0] + i1 + i2_n = s[1] + i2 + i3_n = s[2] + i3 + i_g = psydac_to_global(mat.codomain, (i1_n, i2_n, i3_n)) + + for k1 in range(2*p[0]*m[0] + 1): + for k2 in range(2*p[1]*m[1] + 1): + for k3 in range(2*p[2]*m[2] + 1): + value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], k1, k2, k3] + + j1_n = i1_n + k1 - ghost_size[0] + j2_n = i2_n + k2 - ghost_size[1] + j3_n = i3_n + k3 - ghost_size[2] + + if value != 0 and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): + j_g = psydac_to_global(mat.domain, (j1_n, j2_n, j3_n)) + + if nnz_in_row == 0: + rowmap.append(i_g) + + J.append(j_g) + V.append(value) + nnz_in_row += 1 + + I.append(I[-1] + nnz_in_row) + + + if not dcomm: print() From a9e0315a8e9d228a7cbd7295ebf383efcb1d2d74 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Thu, 16 May 2024 15:34:25 +0200 Subject: [PATCH 28/88] efficient conversion of stencilmatrix to PETSc.Mat for 1,2,3D and periodic or not BC --- psydac/linalg/tests/test_stencil_matrix.py | 35 ++----------- psydac/linalg/topetsc.py | 61 ++++++++++------------ 2 files changed, 32 insertions(+), 64 deletions(-) diff --git a/psydac/linalg/tests/test_stencil_matrix.py b/psydac/linalg/tests/test_stencil_matrix.py index aef0b9d33..c022f3dc7 100644 --- a/psydac/linalg/tests/test_stencil_matrix.py +++ b/psydac/linalg/tests/test_stencil_matrix.py @@ -2779,19 +2779,12 @@ def test_stencil_matrix_2d_parallel_topetsc(dtype, n1, n2, p1, p2, sh1, sh2, P1, # Cast result back to Psydac StencilVector format y_p = petsc_to_psydac(y_petsc, V) - ################################################ - # Note 12.03.2024: - # Another possibility would be to compare y_petsc.array and y.toarray(). - # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) -@pytest.mark.parametrize('n1', [7, 11]) +@pytest.mark.parametrize('n1', [13, 15]) @pytest.mark.parametrize('p1', [1, 3]) @pytest.mark.parametrize('sh1', [1]) @pytest.mark.parametrize('P1', [True, False]) @@ -2860,13 +2853,6 @@ def test_stencil_matrix_1d_parallel_topetsc(dtype, n1, p1, sh1, P1): # Cast result back to Psydac StencilVector format y_p = petsc_to_psydac(y_petsc, V) - ################################################ - # Note 12.03.2024: - # Another possibility would be to compare y_petsc.array and y.toarray(). - # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) #test_stencil_matrix_1d_parallel_topetsc(float, 5, 2, 1, True) # =============================================================================== @@ -2922,22 +2908,9 @@ def test_mass_matrix_2d_parallel_topetsc(n1, n2, p1, p2, P1, P2): # Cast result back to Psydac StencilVector format y_p = petsc_to_psydac(y_petsc, Vh.vector_space) - ################################################ - # Note 12.03.2024: - # Another possibility would be to compare y_petsc.array and y.toarray(). - # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ - for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('rank ', comm.Get_rank(), ':y_p.toarray()=\n', y_p.toarray()) - print('rank ', comm.Get_rank(), ': y.toarray()=\n', y.toarray()) - comm.Barrier() - assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) -test_mass_matrix_2d_parallel_topetsc(2, 3, 1, 1, True, False) +#test_mass_matrix_2d_parallel_topetsc(10, 13, 3, 2, True, True) # =============================================================================== @@ -3002,7 +2975,7 @@ def test_mass_matrix_3d_parallel_topetsc(n1, n2, n3, p1, p2, p3, P1, P2, P3): # =============================================================================== -@pytest.mark.parametrize('n1', [4,7]) +@pytest.mark.parametrize('n1', [15,17]) @pytest.mark.parametrize('p1', [2]) @pytest.mark.parametrize('P1', [True]) @pytest.mark.parallel @@ -3072,7 +3045,7 @@ def test_mass_matrix_1d_parallel_topetsc(n1, p1, P1): assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) -#test_mass_matrix_1d_parallel_topetsc(12, 3, True) +#test_mass_matrix_1d_parallel_topetsc(2, 1, False) # =============================================================================== # PARALLEL BACKENDS TESTS # =============================================================================== diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index de0f7047a..4eb3eff4d 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -155,7 +155,7 @@ def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: #global_index = (jj[0] - p[0]*m[0])%dnpts[0] proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable - global_index = index_shift + jj[0] - gs[0][proc_x] + global_index = index_shift + jj[0] - gs[0][proc_index] elif ndim == 2: proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] @@ -599,33 +599,28 @@ def mat_topetsc( mat ): if dndim == 1 and cndim == 1: - for i1 in dindices[0]: + for i1 in np.arange(dnpts_local[0]): nnz_in_row = 0 - for k1 in range(2*dpads[0]*dshifts[0] + 1): - value = mat._data[i1, k1] + i1_n = s[0] + i1 + i_g = psydac_to_global(mat.codomain, (i1_n,)) + + for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): + value = mat._data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] + j1_n = (i1_n + k1)%dnpts[0] if value != 0: - if nnz_in_row == 0: - rowmap.append(dindex_shift + psydac_to_petsc_local(mat.domain, [], (i1,))) - i1_n = s[0] + i1 - j1_n = i1_n + k1 - p[0]*m[0] + j_g = psydac_to_global(mat.domain, (j1_n, )) + + if nnz_in_row == 0: + rowmap.append(i_g) - global_col = psydac_to_global(mat.domain, (j1_n,)) - #J.append((j1_n - p[0]*m[0])%dnpts[0]) - J.append(global_col) - #J.append((dindex_shift + i1 + k1 - 2*p[0]*m[0])%dnpts[0]) + J.append(j_g) V.append(value) nnz_in_row += 1 - #J.append(petsc_col_indices[-1]) - #V.append(value) I.append(I[-1] + nnz_in_row) - '''if nnz_in_row > 0: - #rowmap.append(id1 - dpads[0]*dshifts[0]) - rowmap.append(petsc_row_indices[-1]) - I.append(I[-1] + nnz_in_row)''' elif dndim == 2 and cndim == 2: #ghost_size = (p[0]*m[0], p[1]*m[1]) @@ -671,11 +666,11 @@ def mat_topetsc( mat ): - for k1 in range(2*p[0]*m[0] + 1): - for k2 in range(2*p[1]*m[1] + 1): + for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): + for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): #for ic1, ic2 in cindices: - value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], k1, k2] + value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] '''i1_n = s[0] + i1 i2_n = s[1] + i2 @@ -685,8 +680,8 @@ def mat_topetsc( mat ): #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) - j1_n = i1_n + k1 - p[0]*m[0] - j2_n = i2_n + k2 - p[1]*m[1] + j1_n = (i1_n + k1)%dnpts[0] #- p[0]*m[0] + j2_n = (i2_n + k2)%dnpts[1] #- p[1]*m[1] @@ -696,7 +691,7 @@ def mat_topetsc( mat ): - if value != 0 and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): + if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): j_g = psydac_to_global(mat.domain, (j1_n, j2_n)) @@ -756,16 +751,16 @@ def mat_topetsc( mat ): i3_n = s[2] + i3 i_g = psydac_to_global(mat.codomain, (i1_n, i2_n, i3_n)) - for k1 in range(2*p[0]*m[0] + 1): - for k2 in range(2*p[1]*m[1] + 1): - for k3 in range(2*p[2]*m[2] + 1): - value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], k1, k2, k3] + for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): + for k2 in range(-p[1]*m[1], p[1]*m[1] + 1): + for k3 in range(-p[2]*m[2], p[2]*m[2] + 1): + value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] - j1_n = i1_n + k1 - ghost_size[0] - j2_n = i2_n + k2 - ghost_size[1] - j3_n = i3_n + k3 - ghost_size[2] + j1_n = (i1_n + k1)%dnpts[0] #- ghost_size[0] + j2_n = (i2_n + k2)%dnpts[1] # - ghost_size[1] + j3_n = (i3_n + k3)%dnpts[2] # - ghost_size[2] - if value != 0 and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): + if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): j_g = psydac_to_global(mat.domain, (j1_n, j2_n, j3_n)) if nnz_in_row == 0: @@ -811,7 +806,7 @@ def mat_topetsc( mat ): ''' #gmat.setValuesCSR([r - dcart.global_starts[0][comm.Get_rank()] for r in indptr[1:]], indices, data) #gmat.setValuesLocalCSR(local_indptr, indices, data)#, addv=PETSc.InsertMode.ADD_VALUES) - gmat.setValuesIJV(I, J, V, rowmap=rowmap)#, addv=PETSc.InsertMode.ADD_VALUES) + gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC print('Rank ', dcomm.Get_rank() if dcomm else '-', ': duration of setValuesIJV :', time.time()-t_prev) From 1579d31d22e77493f2ccdd411dc6bcdd976e383d Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Fri, 17 May 2024 19:54:44 +0200 Subject: [PATCH 29/88] Fixed conversion from 2D BlockStencilVector to Petsc.Vec --- psydac/linalg/topetsc.py | 671 +++++++++++++++++++++------------------ 1 file changed, 367 insertions(+), 304 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 4eb3eff4d..ee2b7ceca 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -1,6 +1,6 @@ import numpy as np -from psydac.linalg.block import BlockVectorSpace, BlockVector +from psydac.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix from psydac.linalg.basic import VectorSpace from scipy.sparse import coo_matrix, bmat @@ -131,30 +131,68 @@ def petsc_to_psydac_local( return tuple(tuple(ii)) -def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: +def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indices : tuple[int]) -> int: '''From Psydac natural multi-index (grid coordinates) to global PETSc single-index. Performs a search to find the process owning the multi-index.''' - ndim = V.ndim - s = V.starts - e = V.ends - p = V.pads - m = V.shifts - dnpts = V.cart.npts - nprocs = V.cart.nprocs - #dnpts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. - gs = V.cart.global_starts # Global variable - ge = V.cart.global_ends # Global variable + + #nonzero_block_indices = ((0,0)) if not isinstance(V, BlockVectorSpace) else V. + #s = V.starts + #e = V.ends + #p = V.pads + #m = V.shifts + #dnpts = V.cart.npts + + + + #block_shift = 0 + + #for b in bb: + '''if isinstance(V, StencilVectorSpace): + cart = V.cart + elif isinstance(V, BlockVectorSpace): + cart = V.spaces[bb[0]].cart''' + + '''# compute the block shift: + for b1 in range(min(len(V.spaces), bb[0])): + prev_npts_local = 0#np.sum(np.prod([ e - s + 1 for s, e in zip(V.spaces[b1].starts, V.spaces[b1].ends)], axis=1)) + #for b2 in range(max(0, bb[1])): + block_shift += prev_npts_local''' + + bb = block_indices[0] + npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + #print(f'npts_local_per_block_per_process={npts_local_per_block_per_process}') + #print(f'local_sizes_per_block_per_process={local_sizes_per_block_per_process}') + + #shift_per_block_per_process = np.sum(local_sizes_per_block_per_process[:][:]) + if isinstance(V, BlockVectorSpace): + V = V.spaces[bb] + + cart = V.cart + # block_local_shift = get_block_local_shift(V) + # block_shift = block_local_shift[bb[0]] + + nprocs = cart.nprocs + ndim = cart.ndim + gs = cart.global_starts # Global variable + ge = cart.global_ends # Global variable + + '''#dnpts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. + + + npts_local_perprocess = [ ge_i - gs_i + 1 for gs_i, ge_i in zip(gs, ge)] #Global variable npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable - localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(V.cart.comm.Get_size())] #Global variable + localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(cart.comm.Get_size())] #Global variable''' jj = ndarray_indices if ndim == 1: - #global_index = (jj[0] - p[0]*m[0])%dnpts[0] - proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] - index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + #proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + #index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + + index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable global_index = index_shift + jj[0] - gs[0][proc_index] elif ndim == 2: @@ -162,10 +200,15 @@ def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] proc_index = proc_y + proc_x*nprocs[1]#proc_x + proc_y*nprocs[0] - index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + index_shift = 0#0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable #global_index = jj[0] - gs[0][proc_x] + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][0] + index_shift - global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_perprocess[proc_index][1] + #global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] + #print(f'np.sum(local_sizes_per_block_per_process[:,:proc_index])={np.sum(local_sizes_per_block_per_process[:,:proc_index])}') + #print(f'np.sum(local_sizes_per_block_per_process[:bb,proc_index])={np.sum(local_sizes_per_block_per_process[:bb,proc_index])}') + shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) + global_index = shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] + #print(f'shift={shift}') #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) #hola = np.where(jj[0] in x_proc_ranges)#, gs[0], -1) @@ -185,18 +228,17 @@ def psydac_to_global(V : VectorSpace, ndarray_indices : tuple[int]) -> int: proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0] proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2] #proc_x + proc_y*nprocs[0] - index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable + index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable global_index = index_shift \ - + jj[2] - gs[2][proc_z] \ - + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][2] \ - + (jj[0] - gs[0][proc_x]) * npts_local_perprocess[proc_index][1] * npts_local_perprocess[proc_index][2] - + + jj[2] - gs[2][proc_z] \ + + (jj[1] - gs[1][proc_y]) * npts_local_per_block_per_process[bb][proc_index][2] \ + + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb][proc_index][1] * npts_local_per_block_per_process[bb][proc_index][2] else: raise NotImplementedError( "Cannot handle more than 3 dimensions." ) + return global_index - return global_index def psydac_to_singlenatural(V : VectorSpace, ndarray_indices : tuple[int]) -> int: ndim = V.ndim @@ -231,6 +273,150 @@ def psydac_to_singlenatural(V : VectorSpace, ndarray_indices : tuple[int]) -> in return singlenatural_index +def get_npts_local(V : VectorSpace) -> list: + """ + Compute the local number of nodes per dimension owned by the actual process. + This is a local variable, its value will be different for each process. + + Parameter + --------- + V : VectorSpace + The distributed Psydac vector space. + + Returns + -------- + list + Local number of nodes per dimension owned by the actual process. + In case of a StencilVectorSpace the list has length equal the number of dimensions in the domain. + In case of a BlockVectorSpace the list has length equal the number of blocks. + """ + if isinstance(V, StencilVectorSpace): + s = V.starts + e = V.ends + npts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. + return npts_local + + npts_local_per_block = [ get_npts_local(V.spaces[b]) for b in range(V.n_blocks) ] + return npts_local_per_block + +def get_block_local_shift(V : VectorSpace) -> np.ndarray: + """ + Compute the local block shift per block. + This is a local variable, its value will be different for each process. + + Parameter + --------- + V : VectorSpace + The distributed Psydac vector space. + + Returns + -------- + Numpy.ndarray + Local block shift per block. + In case of a StencilVectorSpace it returns the total local number of points in the space. + In case of a BlockVectorSpace the returned array has the same shape as the space block structure. + """ + if isinstance(V, StencilVectorSpace): + return np.prod(get_npts_local(V)) + + block_local_shift_per_block = [] + for b in range(V.n_blocks): + block_local_shift_per_block.append(get_block_local_shift(V.spaces[b])) + + block_local_shift_per_block = np.array(block_local_shift_per_block) + + block_local_shift_per_block = np.reshape(np.cumsum(block_local_shift_per_block), block_local_shift_per_block.shape) + + return block_local_shift_per_block + +def get_npts_per_block(V : VectorSpace) -> list: + + if isinstance(V, StencilVectorSpace): + gs = V.cart.global_starts # Global variable + ge = V.cart.global_ends # Global variable + npts_local_perprocess = [ ge_i - gs_i + 1 for gs_i, ge_i in zip(gs, ge)] #Global variable + + if V.cart.comm: + npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable + #localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(V.cart.comm.Get_size())] #Global variable + #else: + # #localsize_perprocess = [np.prod(npts_local_perprocess)] + return [npts_local_perprocess] + + npts_local_per_block = [] #[ get_npts_per_block(V.spaces[b]) for b in range(V.n_blocks) ] + for b in range(V.n_blocks): + npts_b = get_npts_per_block(V.spaces[b]) + if isinstance(V.spaces[b], StencilVectorSpace): + npts_b = npts_b[0] + npts_local_per_block.append(npts_b) + + return npts_local_per_block + + +def get_block_shift_per_process(V : VectorSpace) -> list: + #shift_per_process = [0] + npts_local_per_block = get_npts_per_block(V) + local_sizes_per_block = np.prod(npts_local_per_block, axis=-1) + + local_sizes_per_block = np.array(local_sizes_per_block) + + # Get nested block structure: + n_blocks = local_sizes_per_block.shape[:-1] + # Assume that all the blocks have the same number of processes: + n_procs = local_sizes_per_block.shape[-1] + print(f'n_procs={n_procs}') + local_sizes_per_process = np.array([local_sizes_per_block[:,k] for k in range(n_procs)]) + print(f'local_sizes_per_process={local_sizes_per_process}') + + #print(f'np.sum(local_sizes_per_process[:k,1:])={np.sum(local_sizes_per_process[:1,1:])}') + shift_per_process = [0]+[np.sum(local_sizes_per_process[:k,1:]) for k in range(1,n_procs)] + + + + #local_sizes_per_process = np.sum(local_sizes_per_process[1:], axis=1) + print(f'shift_per_process={shift_per_process}') + + #shift_per_process = [0] + [ np.sum(local_sizes_per_process[:k-1]) for k in range(1, n_procs)] + + + + '''if isinstance(V, StencilVectorSpace): + n_procs = 1 if not V.cart.comm else V.cart.comm.Get_size() + + if V.cart.comm: + localsize_perprocess = [np.prod(npts_local_per_block[0][k]) for k in range(n_procs)] #Global variable + else: + localsize_perprocess = [np.prod(npts_local_per_block[k]) for k in range(n_procs)] #Global variable''' + + #for b_lvl in range(len(n_blocks)): + # for b in range(n_blocks[b_lvl]): + + '''for k in range(n_procs): + shift_k = 0 + for b in range(n_blocks[0]): + #npts_local_per_process = npts_local_per_block[b] + #shift_k += np.prod(npts_local_per_process[k]) + #if b != len(shift_per_process): + shift_k += local_sizes_per_block[b][k] + shift_per_process.append(shift_k)''' + '''print(f'n_blocks={n_blocks}') + for k in range(n_procs): + shift_k = 0 + for b in range(n_blocks[0]): + print(f'k={k}, b={b}, local_sizes_per_block={local_sizes_per_block[:,k]}') + #accumulated_local_size = local_sizes_per_block[:b]#[k] + + if b == 1: + accumulated_local_size = local_sizes_per_block[0][k] + else: + accumulated_local_size = local_sizes_per_block[b][k] + shift_k += np.sum(accumulated_local_size) + shift_per_process.append(shift_k)''' + + return shift_per_process + + + def flatten_vec( vec ): """ Return the flattened 1D array values and indices owned by the process of the given vector. @@ -296,167 +482,116 @@ def vec_topetsc( vec ): from petsc4py import PETSc if isinstance(vec, StencilVector): - cart = vec.space.cart - elif isinstance(vec.space.spaces[0], StencilVectorSpace): - cart = vec.space.spaces[0].cart - elif isinstance(vec.space.spaces[0], BlockVectorSpace): - cart = vec.space.spaces[0][0].cart - - comm = cart.global_comm - globalsize = vec.space.dimension #integer - """ print('\n\nVEC:\nglobalsize=', globalsize) - gvec.setDM(Dmda) - - # Set local and global size - gvec.setSizes(size=(ownership_ranges[comm.Get_rank()], globalsize)) - - '''ownership_ranges = [comm.allgather(cart.domain_decomposition.local_ncells[k]) for k in range(cart.ndim)] - boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if cart.domain_decomposition.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(cart.ndim)] - - #ownership_ranges = [ dcart.global_ends[0][k] - dcart.global_starts[0][k] + 1 for k in range(dcart.global_starts[0].size)] - print('VECTOR: OWNership_ranges=', ownership_ranges) - #Dmda = PETSc.DMDA().create(dim=2, sizes=mat.shape, proc_sizes=(comm.Get_size(),1), ownership_ranges=(ownership_ranges, mat.shape[1]), comm=comm) - # proc_sizes = [ len] - Dmda = PETSc.DMDA().create(dim=cart.ndim, sizes=cart.domain_decomposition.ncells, proc_sizes=cart.domain_decomposition.nprocs, - ownership_ranges=ownership_ranges, comm=comm, stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type)''' + carts = [vec.space.cart] + elif isinstance(vec.space, BlockVectorSpace): + carts = [] + for b in range(vec.n_blocks): + if isinstance(vec.space.spaces[b], StencilVectorSpace): + carts.append(vec.space.spaces[b].cart) + + elif isinstance(vec.space.spaces[b], BlockVectorSpace): + carts2 = [] + for b2 in range(vec.space.spaces[b].n_blocks): + if isinstance(vec.space.spaces[b][b2], StencilVectorSpace): + carts2.append(vec.space.spaces[b][b2].cart) + else: + raise NotImplementedError( "Cannot handle more than block of a block." ) + carts.append(carts2) + + + '''elif isinstance(vec.space.spaces[0], StencilVectorSpace): + carts = [vec.space.spaces[b1].cart for b1 in range(len(vec.space.spaces))] - ### SPLITTING COEFFS - ownership_ranges = [ 1 + cart.global_ends[0][k] - cart.global_starts[0][k] for k in range(cart.global_starts[0].size)] - #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] - - print('OWNership_ranges=', ownership_ranges) - print('dcart.domain_decomposition.nprocs=', *cart.domain_decomposition.nprocs) - - boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if cart.domain_decomposition.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(cart.ndim)] - - Dmda = PETSc.DMDA().create(dim=1, sizes=(globalsize,), proc_sizes=cart.domain_decomposition.nprocs, comm=comm, - ownership_ranges=[ownership_ranges], boundary_type=boundary_type) - - - indices, data = flatten_vec(vec) - for k in range(comm.Get_size()): - if comm.Get_rank() == k: - print('Rank ', k) - print('vec.toarray()=\n', vec.toarray()) - print('VEC_indices=', indices) - print('VEC_data=', data) - comm.Barrier() - + elif isinstance(vec.space.spaces[0], BlockVectorSpace): + carts = [[vec.space.spaces[b1][b2].cart for b2 in range(len(vec.space.spaces[b1]))] for b1 in range(len(vec.space.spaces))] + ''' + npts_local = get_npts_local(vec.space) #[[ e - s + 1 for s, e in zip(cart.starts, cart.ends)] for cart in carts] #Number of points in each dimension within each process. Different for each process. - gvec = PETSc.Vec().create(comm=comm) + comms = [cart.global_comm for cart in carts] - gvec.setDM(Dmda) + ndims = [cart.ndim for cart in carts] - # Set local and global size - gvec.setSizes(size=(ownership_ranges[comm.Get_rank()], globalsize)) + + #index_shift = get_petsc_local_to_global_shift(vec.space) #Global variable - '''if comm: - cart_petsc = cart.topetsc() - gvec.setLGMap(cart_petsc.l2g_mapping)''' + gvec = PETSc.Vec().create(comm=comms[0]) + globalsize = vec.space.dimension + localsize = np.sum(np.prod(npts_local, axis=1)) # Sum over all the blocks + gvec.setSizes(size=(localsize, globalsize)) gvec.setFromOptions() gvec.setUp() - # Set values of the vector. They are stored in a cache, so the assembly is necessary to use the vector. - gvec.setValues(indices, data, addv=PETSc.InsertMode.ADD_VALUES)""" - - ndim = vec.space.ndim - starts = vec.space.starts - ends = vec.space.ends - pads = vec.space.pads - shifts = vec.space.shifts - #npts = vec.space.npts - #cart = vec.space.cart + petsc_indices = [] + petsc_data = [] - npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. - '''npts_local_perprocess = [ ge - gs + 1 for gs, ge in zip(cart.global_starts, cart.global_ends)] #Global variable - npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable - localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(comm.Get_size())] #Global variable''' - index_shift = get_petsc_local_to_global_shift(vec.space) #Global variable + s = [cart.starts for cart in carts] + #p = [cart.pads for cart in carts] + #m = [cart.shifts for cart in carts] + ghost_size = [[pi*mi for pi,mi in zip(cart.pads, cart.shifts)] for cart in carts] - '''for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('\nRank ', k) - print('starts=', starts) - print('ends=', ends) - print('npts=', npts) - print('pads=', pads) - print('shifts=', shifts) - print('npts_local=', npts_local) - print('cart.global_starts=', cart.global_starts) - print('cart.global_ends=', cart.global_ends) - print('npts_local_perprocess=', npts_local_perprocess) - print('localsize_perprocess=', localsize_perprocess) - print('index_shift=', index_shift) - - print('vec._data.shape=', vec._data.shape) - print('vec._data=', vec._data) - #print('vec.toarray()=', vec.toarray()) - comm.Barrier()''' + n_blocks = 1 if isinstance(vec, StencilVector) else vec.n_blocks - gvec = PETSc.Vec().create(comm=comm) + vec_block = vec - localsize = np.prod(npts_local) - gvec.setSizes(size=(localsize, globalsize))#size=(ownership_ranges[comm.Get_rank()], globalsize)) - - gvec.setFromOptions() - gvec.setUp() + block_shift_per_process = get_block_shift_per_process(vec.space) + #global_npts_per_block_per_proc = get_npts_per_block(vec.space) + print(f'blocks_shift={block_shift_per_process}') - petsc_indices = [] - petsc_data = [] + for b in range(n_blocks): + if isinstance(vec, BlockVector): + vec_block = vec.blocks[b] - if ndim == 1: - for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): - value = vec._data[i1] - if value != 0: - index = psydac_to_petsc_local(vec.space, [], (i1,)) # global index starting from 0 in each process - index += index_shift #starts[0] # global index starting from NOT 0 in each process - petsc_indices.append(index) - petsc_data.append(value) + index_shift = block_shift_per_process[comms[b].Get_rank()] - elif ndim == 2: - for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): - for i2 in range(pads[1]*shifts[1], pads[1]*shifts[1] + npts_local[1]): - value = vec._data[i1,i2] + if ndims[b] == 1: + for i1 in range(npts_local[b][0]): + value = vec_block._data[i1 + ghost_size[b][0]] if value != 0: - #index = npts_local[1] * (i1 - pads[0]*shifts[0]) + i2 - pads[1]*shifts[1] # global index starting from 0 in each process - index = psydac_to_petsc_local(vec.space, [], (i1,i2)) # global index starting from 0 in each process - index += index_shift # global index starting from NOT 0 in each process - petsc_indices.append(index) - petsc_data.append(value) - - elif ndim == 3: - for i1 in range(pads[0]*shifts[0], pads[0]*shifts[0] + npts_local[0]): - for i2 in range(pads[1]*shifts[1], pads[1]*shifts[1] + npts_local[1]): - for i3 in range(pads[2]*shifts[2], pads[2]*shifts[2] + npts_local[2]): - value = vec._data[i1, i2, i3] + i1_n = s[b][0] + i1 + i_g = psydac_to_global(vec.space.spaces[b], (), (i1_n,)) + index_shift + petsc_indices.append(i_g) + petsc_data.append(value) + + elif ndims[b] == 2: + for i1 in range(npts_local[b][0]): + for i2 in range(npts_local[b][1]): + value = vec_block._data[i1 + ghost_size[b][0], i2 + ghost_size[b][1]] if value != 0: - #index = npts_local[1] * npts_local[2] * (i1 - pads[0]*shifts[0]) + npts_local[2] * (i2 - pads[1]*shifts[1]) + i3 - pads[2]*shifts[2] - index = psydac_to_petsc_local(vec.space, [], (i1,i2,i3)) - index += index_shift # global index starting from NOT 0 in each process - petsc_indices.append(index) - petsc_data.append(value) + i1_n = s[b][0] + i1 + i2_n = s[b][1] + i2 + i_g = psydac_to_global(vec.space, (b,), (i1_n, i2_n)) #+ index_shift + print(f'Rank {comms[b].Get_rank()}, Block {b}: i1_n = {i1_n}, i2_n = {i2_n}, i_g = {i_g}') + petsc_indices.append(i_g) + petsc_data.append(value) + + elif ndims[b] == 3: + for i1 in np.arange(npts_local[b][0]): + for i2 in np.arange(npts_local[b][1]): + for i3 in np.arange(npts_local[b][2]): + value = vec_block._data[i1 + ghost_size[b][0], i2 + ghost_size[b][1], i3 + ghost_size[b][2]] + if value != 0: + i1_n = s[b][0] + i1 + i2_n = s[b][1] + i2 + i3_n = s[b][2] + i3 + i_g = psydac_to_global(vec.space, (b,), (i1_n, i2_n, i3_n)) + petsc_indices.append(i_g) + petsc_data.append(value) + + + gvec.setValues(petsc_indices, petsc_data, addv=PETSc.InsertMode.ADD_VALUES) #Adding the values is necessary when periodic BC - gvec.setValues(petsc_indices, petsc_data)#, addv=PETSc.InsertMode.ADD_VALUES) # Assemble vector gvec.assemble() # Here PETSc exchanges global communication. The block corresponding to a certain process is not necessarily the same block in the Psydac StencilVector. - '''if comm is not None: - vec_arr = vec.toarray() - for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('\nRank ', k) - #print('petsc_indices=', petsc_indices) - #print('petsc_data=', petsc_data) - #print('\ngvec.array=', gvec.array.real) - print('vec.toarray()=', vec_arr) - #print('gvec.getSizes()=', gvec.getSizes()) - comm.Barrier() - print('================================')''' + vec_arr = vec.toarray() + for k in range(comms[0].Get_size()): + if k == comms[0].Get_rank(): + print(f'Rank {k}: vec={vec_arr}, petsc_indices={petsc_indices}, data={petsc_data}, s={s}, npts_local={npts_local}, gvec={gvec.array.real}') + comms[k].Barrier() - return gvec @@ -481,10 +616,15 @@ def mat_topetsc( mat ): ccart = mat.codomain.cart elif isinstance(mat.domain.spaces[0], StencilVectorSpace): dcart = mat.domain.spaces[0].cart - ccart = mat.codomain.spaces[0].cart elif isinstance(mat.domain.spaces[0], BlockVectorSpace): dcart = mat.domain.spaces[0][0].cart - ccart = mat.codomain.spaces[0][0].cart + + if isinstance(mat._codomain, StencilVectorSpace): + ccart = mat.codomain.cart + elif isinstance(mat.codomain.spaces[0], StencilVectorSpace): + ccart = mat.codomain.spaces[0].cart + elif isinstance(mat.codomain.spaces[0], BlockVectorSpace): + ccart = mat.codomain.spaces[0][0].cart dcomm = dcart.global_comm ccomm = ccart.global_comm @@ -510,8 +650,8 @@ def mat_topetsc( mat ): cnpts_local = [ e - s + 1 for s, e in zip(cstarts, cends)] - dindex_shift = get_petsc_local_to_global_shift(mat.domain) #Global variable - cindex_shift = get_petsc_local_to_global_shift(mat.codomain) #Global variable + #dindex_shift = get_petsc_local_to_global_shift(mat.domain) #Global variable + #cindex_shift = get_petsc_local_to_global_shift(mat.codomain) #Global variable mat_dense = mat.tosparse().todense() @@ -527,19 +667,26 @@ def mat_topetsc( mat ): print('dnpts_local=', dnpts_local) print('cnpts_local=', cnpts_local) #print('mat_dense=\n', mat_dense[:3]) - print('mat._data.shape=\n', mat._data.shape) - print('dindex_shift=', dindex_shift) - print('cindex_shift=', cindex_shift) + #print('mat._data.shape=\n', mat._data.shape) + #print('dindex_shift=', dindex_shift) + #print('cindex_shift=', cindex_shift) print('ccart.global_starts=', ccart.global_starts) print('ccart.global_ends=', ccart.global_ends) #print('mat._data=\n', mat._data) dcomm.Barrier() - ccomm.Barrier() + ccomm.Barrier() - globalsize = (np.prod(dnpts), np.prod(cnpts)) #Tuple of integers - localsize = (np.prod(dnpts_local), np.prod(cnpts_local)) + n_block_rows = 1 if not isinstance(mat, BlockLinearOperator) else mat.n_block_rows + n_block_cols = 1 if not isinstance(mat, BlockLinearOperator) else mat.n_block_cols + if isinstance(mat, StencilMatrix): + nonzero_block_indices = ((0,0),) + else: + nonzero_block_indices = mat.nonzero_block_indices + + globalsize = mat.shape #equivalent to (np.prod(dnpts), np.prod(cnpts)) #Tuple of integers + localsize = (np.prod(cnpts_local)*n_block_rows, np.prod(dnpts_local)*n_block_cols) gmat = PETSc.Mat().create(comm=dcomm) @@ -578,170 +725,86 @@ def mat_topetsc( mat ): rowmap = [] rowmap2 = [] - #dindices = [np.arange(p*m, p*m + n) for p, m, n in zip(dpads, dshifts, dnpts_local)] - - #[[ dcomm.Get_rank()*dnpts_local[1] + n2 + dnpts[1]*n1 for n2 in np.arange(dnpts_local[1])] for n1 in np.arange(dnpts_local[0])] - - #cindices = [np.arange(2*p*m + 1) for p, m in zip(dpads, dshifts)] - - #prod_indices = np.empty((max(dnpts_local) * max(cnpts_local), 3)) - '''prod_indices = [] - for d in range(len(dindices)): - #prod_indices[:, d] = [*cartesian_prod(dindices[d], cindices[d])] - prod_indices.append([*cartesian_prod(dindices[d], cindices[d])]) - ''' - - #matd = mat.tosparse().todense() s = dstarts p = dpads m = dshifts ghost_size = [pi*mi for pi,mi in zip(p,m)] - - - if dndim == 1 and cndim == 1: - for i1 in np.arange(dnpts_local[0]): - nnz_in_row = 0 - i1_n = s[0] + i1 - i_g = psydac_to_global(mat.codomain, (i1_n,)) - - for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): - value = mat._data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] - j1_n = (i1_n + k1)%dnpts[0] - - if value != 0: - j_g = psydac_to_global(mat.domain, (j1_n, )) - if nnz_in_row == 0: - rowmap.append(i_g) - J.append(j_g) - V.append(value) + if dndim == 1 and cndim == 1: + for bb in nonzero_block_indices: - nnz_in_row += 1 - - I.append(I[-1] + nnz_in_row) - - elif dndim == 2 and cndim == 2: - #ghost_size = (p[0]*m[0], p[1]*m[1]) - for i1 in np.arange(dnpts_local[0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): - for i2 in np.arange(dnpts_local[1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): + if isinstance(mat, StencilMatrix): + data = mat._data + elif isinstance(mat, BlockLinearOperator): + data = mat.blocks[bb[0]][bb[1]]._data + for i1 in range(dnpts_local[0]): nnz_in_row = 0 - #local_row = psydac_to_petsc_local(mat.domain, [], (i1, i2)) - #local_row += (local_row // dnpts_local[1])*dnpts_local[1] - - #cindices1 = np.arange( max(0, id1 - dindices[0][0] - dpads[0]*dshifts[0]), min(2*dpads[0]*dshifts[0], id1 - dindices[0][0] + dpads[0]*dshifts[0]) + 1) - #cindices2 = np.arange( max(0, id2 - dindices[1][0] - dpads[1]*dshifts[1]), min(2*dpads[1]*dshifts[1], id2 - dindices[1][0] + dpads[1]*dshifts[1]) + 1) - #cindices1 = np.arange( max(dpads[0]*dshifts[0], id1), min(2*dpads[0]*dshifts[0] + 1, id1 + 2*dpads[0]*dshifts[0]) + 1) - #cindices2 = np.arange( max(dpads[1]*dshifts[1], id2), min(2*dpads[1]*dshifts[1] + 1, id2 + 2*dpads[1]*dshifts[1]) + 1) - - #cindices = [*cartesian_prod(cindices1, cindices2)] - #cindices = [[(ic1, ic2) for ic2 in np.arange(id2 - int(np.ceil(dpads[1]*dshifts[1]/2)), id2 + int(np.floor(dpads[1]*dshifts[1]/2)) + 1) - dpads[1]*dshifts[1] ] - # for ic1 in np.arange(id1 - int(np.ceil(dpads[0]*dshifts[0]/2)), id1 + int(np.floor(dpads[0]*dshifts[0]/2)) + 1) - dpads[0]*dshifts[0] ] - - #ravel_ind_0_col = 2*dpads[1]*dshifts[1] + 1 + 2*dpads[0]*dshifts[0] - local_row #becomes negative for large row index - #ravel_ind_0_col = ((2*dpads[1]*dshifts[1] + 1) * (2*dpads[0]*dshifts[0] + 1) ) // 2 - local_row #becomes negative for large row index - - #cindices1 = [np.arange(max(0, (4*np.prod(dpads)*np.prod(dshifts) - local_row), ) for p, m, n in zip(dpads, dshifts, dnpts_local)] - - '''if dcomm.Get_rank() == 0: - print('Rank 0: mat._data[',id1, ',' , id2 , ']=\n', mat._data[id1, id2]) - elif dcomm.Get_rank() == 1: - print('Rank 1: mat._data[',id1, ',' , id2 , ']=\n', mat._data[id1, id2]) - #dcomm.Barrier()''' - i1_n = s[0] + i1 - i2_n = s[1] + i2 - i_g = psydac_to_global(mat.codomain, (i1_n, i2_n)) - i_n = psydac_to_singlenatural(mat.codomain, (i1_n,i2_n)) - - for k in range(dcomm.Get_size()): - if k == dcomm.Get_rank(): - print(f'Rank {k}: ({i1_n}, {i2_n}), i_n= {i_n}, i_g= {i_g}') - #print(f'global_row= {global_row}') - #dcomm.Barrier() - #ccomm.Barrier() - - + i_g = psydac_to_global(mat.codomain, (bb[0],), (i1_n,)) + for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): + value = data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] + j1_n = (i1_n + k1)%dnpts[0] + + if value != 0: - for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): - for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): - #for ic1, ic2 in cindices: + j_g = psydac_to_global(mat.domain, (bb[1],), (j1_n, )) - value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] + if nnz_in_row == 0: + rowmap.append(i_g) - '''i1_n = s[0] + i1 - i2_n = s[1] + i2 - #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) - j1_n = i1_n + k1 - p[0] - j2_n = i2_n + k2 - p[1]''' - + J.append(j_g) + V.append(value) - #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) - j1_n = (i1_n + k1)%dnpts[0] #- p[0]*m[0] - j2_n = (i2_n + k2)%dnpts[1] #- p[1]*m[1] + nnz_in_row += 1 - + I.append(I[-1] + nnz_in_row) + + elif dndim == 2 and cndim == 2: + for b1,b2 in nonzero_block_indices: + for i1 in np.arange(dnpts_local[0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): + for i2 in np.arange(dnpts_local[1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): - #print('i1,i2,k1,k2=', i1,i2,k1,k2) - #print('i1_n,i2_n,j1_n,j2_n,value=', i1_n,i2_n,j1_n,j2_n,value) + nnz_in_row = 0 + i1_n = s[0] + i1 + i2_n = s[1] + i2 + i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n, i2_n)) + #i_n = psydac_to_singlenatural(mat.codomain, (i1_n,i2_n)) - + for k in range(dcomm.Get_size()): + if k == dcomm.Get_rank(): + print(f'Rank {k}: ({i1_n}, {i2_n}), i_n= {i_n}, i_g= {i_g}') - if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): + for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): + for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): - j_g = psydac_to_global(mat.domain, (j1_n, j2_n)) + value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] - global_col = psydac_to_global(mat.domain, (j1_n, j2_n)) - #print('row,id1,id2,ic1,ic2=', local_row, id1, id2, ic1, ic2) - '''dindex_petsc = psydac_to_petsc_local(mat.domain, [], (id1,id2)) # global index starting from 0 in each process - cindex_petsc = (id1 + ic1 - 2*dpads[0]*dshifts[0]) % (2*dpads[0]*dshifts[0]) + #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) + j1_n = (i1_n + k1)%dnpts[0] #- p[0]*m[0] + j2_n = (i2_n + k2)%dnpts[1] #- p[1]*m[1] + if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): + j_g = psydac_to_global(mat.domain, (b1, b2), (j1_n, j2_n)) - dindex_petsc += dindex_shift # global index NOT starting from 0 in each process - cindex_petsc += cindex_shift # global index NOT starting from 0 in each process - petsc_row_indices.append(dindex_petsc) - petsc_col_indices.append(cindex_petsc) - petsc_data.append(value) + if nnz_in_row == 0: + rowmap.append(i_g) + rowmap2.append(psydac_to_singlenatural(mat.domain, (i1_n,i2_n))) - nnz_in_row += 1 - J.append(cindex_petsc) - V.append(value)''' - - #local_row = psydac_to_petsc_local(mat.domain, [], (id1, id2)) - - if nnz_in_row == 0: - #rowmap.append(dindex_shift + local_row) - #rowmap.append(dindex_shift + local_row) - rowmap.append(i_g) - rowmap2.append(psydac_to_singlenatural(mat.domain, (i1_n,i2_n))) - #J.append( (dindex_shift + local_row + ic1*(2*dpads[1]*dshifts[1] + 1) + ic2 - 2*dpads[0]*dshifts[0] - 2*dpads[1]*dshifts[1] ) \ - # % np.prod(dnpts) ) - #num_zeros_0row = (2*dpads[0]*dshifts[0] + 1)*(2*dpads[1]*dshifts[1] + 1) // 2 - #J.append( (dindex_shift + local_row \ - # + (ic1*(2*dpads[1]*dshifts[1] + 1) + ic2) - # - num_zeros_0row \ - # ) \ - # % (np.prod(dnpts)) ) - - #ravel_ind = ic2 + (2*dpads[1]*dshifts[1] + 1) * ic1 - #col_index = ic2 - dpads[1]*dshifts[1] + (ic1 - dpads[0]*dshifts[0])*dnpts[1] - #col_index = ic2 - dpads[1]*dshifts[1] + (dindex_shift+local_row) % dnpts[1] \ - # + (ic1 - dpads[0]*dshifts[0] + (dindex_shift+local_row) // dnpts[1]) * dnpts[1] - - J.append(j_g) - J2.append(psydac_to_singlenatural(mat.domain, (j1_n,j2_n))) + J.append(j_g) + J2.append(psydac_to_singlenatural(mat.domain, (j1_n,j2_n))) - V.append(value) + V.append(value) - nnz_in_row += 1 + nnz_in_row += 1 - I.append(I[-1] + nnz_in_row) + I.append(I[-1] + nnz_in_row) - elif dndim == 3 and cndim == 3: + elif dndim == 3 and cndim == 3: for i1 in np.arange(dnpts_local[0]): for i2 in np.arange(dnpts_local[1]): for i3 in np.arange(dnpts_local[2]): From a78bb93cbb2528bb7bccfb947c9395c2ab22905b Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Fri, 17 May 2024 20:13:23 +0200 Subject: [PATCH 30/88] fix general case also for stencilvector 2D --- psydac/linalg/topetsc.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index ee2b7ceca..c00457610 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -294,9 +294,15 @@ def get_npts_local(V : VectorSpace) -> list: s = V.starts e = V.ends npts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. - return npts_local + return [npts_local] - npts_local_per_block = [ get_npts_local(V.spaces[b]) for b in range(V.n_blocks) ] + npts_local_per_block = [] + for b in range(V.n_blocks): + npts_local_b = get_npts_local(V.spaces[b]) + if isinstance(V.spaces[b], StencilVectorSpace): + npts_local_b = npts_local_b[0] + npts_local_per_block.append(npts_local_b) + #npts_local_per_block = [ get_npts_local(V.spaces[b]) for b in range(V.n_blocks) ] return npts_local_per_block def get_block_local_shift(V : VectorSpace) -> np.ndarray: @@ -590,7 +596,7 @@ def vec_topetsc( vec ): for k in range(comms[0].Get_size()): if k == comms[0].Get_rank(): print(f'Rank {k}: vec={vec_arr}, petsc_indices={petsc_indices}, data={petsc_data}, s={s}, npts_local={npts_local}, gvec={gvec.array.real}') - comms[k].Barrier() + comms[0].Barrier() return gvec From bb05e725062651565dc3ceb327923ea72d39abaa Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Tue, 21 May 2024 13:40:09 +0200 Subject: [PATCH 31/88] fixed petsc_to_psydac for BlockVectors --- psydac/linalg/topetsc.py | 107 +++++++++++++++++++- psydac/linalg/utilities.py | 194 ++++++++++++++++++++++++------------- 2 files changed, 235 insertions(+), 66 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index c00457610..ee08dfa35 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -105,13 +105,23 @@ def petsc_to_psydac_local( This is the inverse of `psydac_to_petsc_local`. """ + npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + + if isinstance(V, BlockVectorSpace): + V = V.spaces[bb] + + accumulated_local_sizes_per_block_per_process = np.cumsum(local_sizes_per_block_per_process, axis=0) #indexed [b,k] for block b and process k + bb = np.nonzero(np.array([petsc_index in range(accumulated_local_sizes_per_block_per_process[b-1][comm.Get_rank()], accumulated_local_sizes_per_block_per_process[b][comm.Get_rank()])]))[0][0] + + ndim = V.ndim starts = V.starts ends = V.ends pads = V.pads shifts = V.shifts - npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. + npts_local = npts_local_per_block_per_process[bb] #Number of points in each dimension within each process. Different for each process. ii = np.zeros((ndim,), dtype=int) if ndim == 1: @@ -131,6 +141,101 @@ def petsc_to_psydac_local( return tuple(tuple(ii)) +def global_to_psydac( + V : VectorSpace, + petsc_index : int) :#-> tuple(tuple[int], tuple[int]) : + """ + Convert the PETSc local index to a Psydac local index. + This is the inverse of `psydac_to_petsc_local`. + """ + + '''npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + accumulated_local_sizes_per_block_per_process = np.concatenate((np.zeros_like(local_sizes_per_block_per_process), np.cumsum(local_sizes_per_block_per_process, axis=0))) #indexed [b+1,k] for block b and process k + print(f'accumulated_local_sizes_per_block_per_process = {accumulated_local_sizes_per_block_per_process}' ) + n_blocks = local_sizes_per_block_per_process.shape[0] + rk = comm.Get_rank() + bb = np.nonzero( + np.array( + [petsc_index in range(accumulated_local_sizes_per_block_per_process[b][rk], accumulated_local_sizes_per_block_per_process[b+1][rk]) + for b in range(n_blocks)] + ))[0][0] + print(f'rk={rk}, bb={bb}') + + npts_local_per_process = npts_local_per_block_per_process[bb] #indexed [k,d] for process k + local_sizes_per_process = np.prod(npts_local_per_process, axis=-1) #indexed [k] for process k + accumulated_local_sizes_per_process = np.concatenate((np.zeros((1,), dtype=int), np.cumsum(local_sizes_per_process, axis=0))) #indexed [k+1] for process k + + n_procs = local_sizes_per_process.size + + print(f'n_procs={n_procs}, accumulated_local_sizes_per_process={accumulated_local_sizes_per_process}') + + rank = np.nonzero( + np.array( + [petsc_index in range(accumulated_local_sizes_per_process[k], accumulated_local_sizes_per_process[k+1]) + for k in range(n_procs)] + ))[0][0] + + + npts_local = npts_local_per_block_per_process[bb][rank] #Number of points in each dimension within each process. Different for each process. + ''' + + + npts_local_per_block = np.array(get_npts_local(V)) #indexed [b,d] for block b and dimension d + local_sizes_per_block = np.prod(npts_local_per_block, axis=-1) #indexed [b] for block b + accumulated_local_sizes_per_block = np.concatenate((np.zeros((1,), dtype=int), np.cumsum(local_sizes_per_block, axis=0))) #indexed [b+1] for block b + + n_blocks = local_sizes_per_block.size + # Find the block where the index belongs to: + bb = np.nonzero( + np.array( + [petsc_index in range(accumulated_local_sizes_per_block[b], accumulated_local_sizes_per_block[b+1]) + for b in range(n_blocks)] + ))[0][0] + + #print(f'bb={bb}') + + if isinstance(V, BlockVectorSpace): + V = V.spaces[bb] + + ndim = V.ndim + p = V.pads + m = V.shifts + + npts_local = npts_local_per_block[bb] #Number of points in each dimension within each process. Different for each process. + + # Get the PETSc index LOCAL in the block: + petsc_index -= accumulated_local_sizes_per_block[bb] + + #npts_local = npts_local_per_block_per_process[bb][rk] + #print(f'npts_local={npts_local}') + + '''# Find shift for process k: + npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + assert local_sizes_per_block_per_process[:,comm.Get_rank()] == np.prod(npts_local) + index_proc_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:comm.Get_rank()], dtype=int) #Global variable''' + + + ii = np.zeros((ndim,), dtype=int) + if ndim == 1: + ii[0] = petsc_index + p[0]*m[0] # global index starting from 0 in each process + + elif ndim == 2: + ii[0] = petsc_index // npts_local[1] + p[0]*m[0] + ii[1] = petsc_index % npts_local[1] + p[1]*m[1] + #print(f'rank={comm.Get_rank()}, bb={bb}, npts_local={npts_local}, local_petsc_index={petsc_index}, ii={ii}') + + elif ndim == 3: + ii[0] = petsc_index // (npts_local[1]*npts_local[2]) + p[0]*m[0] + ii[1] = petsc_index // npts_local[2] + p[1]*m[1] - npts_local[1]*(ii[0] - p[0]*m[0]) + ii[2] = petsc_index % npts_local[2] + p[2]*m[2] + + else: + raise NotImplementedError( "Cannot handle more than 3 dimensions." ) + + return (bb,), tuple(ii) + def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indices : tuple[int]) -> int: '''From Psydac natural multi-index (grid coordinates) to global PETSc single-index. Performs a search to find the process owning the multi-index.''' diff --git a/psydac/linalg/utilities.py b/psydac/linalg/utilities.py index d38d5b112..8cb07c6c3 100644 --- a/psydac/linalg/utilities.py +++ b/psydac/linalg/utilities.py @@ -6,7 +6,7 @@ from psydac.linalg.basic import Vector from psydac.linalg.stencil import StencilVectorSpace, StencilVector from psydac.linalg.block import BlockVector, BlockVectorSpace -from psydac.linalg.topetsc import psydac_to_petsc_local, get_petsc_local_to_global_shift, petsc_to_psydac_local +from psydac.linalg.topetsc import psydac_to_petsc_local, get_petsc_local_to_global_shift, petsc_to_psydac_local, global_to_psydac, get_npts_per_block __all__ = ( 'array_to_psydac', @@ -89,77 +89,115 @@ def petsc_to_psydac(x, Xh): u : psydac.linalg.stencil.StencilVector | psydac.linalg.block.BlockVector Psydac vector """ - + if isinstance(Xh, BlockVectorSpace): u = BlockVector(Xh) - if isinstance(Xh.spaces[0], BlockVectorSpace): - - comm = u[0][0].space.cart.global_comm - dtype = u[0][0].space.dtype - sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) - recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors - - if comm: - # Gather the global array in all the processors - ################################################ - # Note 12.03.2024: - # This global communication is at the moment necessary since PETSc distributes matrices and vectors different than Psydac. - # In order to avoid it, we would need that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ - comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) - else: - recvbuf[:] = x.array - - inds = 0 - for d in range(len(Xh.spaces)): - starts = [np.array(V.starts) for V in Xh.spaces[d].spaces] - ends = [np.array(V.ends) for V in Xh.spaces[d].spaces] - - for i in range(len(starts)): - idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.spaces[d].spaces[i].pads, u.space.spaces[d].spaces[i].shifts) ) - shape = tuple(ends[i]-starts[i]+1) - npts = Xh.spaces[d].spaces[i].npts - # compute the global indices of the coefficents owned by the process using starts and ends - indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts[i], xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) - vals = recvbuf[indices+inds] - - # With PETSc installation configuration for complex, all the numbers are by default complex. - # In the float case, the imaginary part must be truncated to avoid warnings. - u[d][i]._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) - - inds += np.prod(npts) + comm = x.comm#u[0][0].space.cart.global_comm + dtype = Xh._dtype#u[0][0].space.dtype + n_blocks = Xh.n_blocks + #sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) + #recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors + localsize, globalsize = x.getSizes() + #assert globalsize == u.shape[0], 'Sizes of global vectors do not match' + + + # Find shifts for process k: + npts_local_per_block_per_process = np.array(get_npts_per_block(Xh)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + + index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:comm.Get_rank()]) #+ np.sum(local_sizes_per_block_per_process[:b,comm.Get_rank()]) for b in range(n_blocks)] + + #index_shift_per_block = [0 + np.sum(local_sizes_per_block_per_process[b][0:x.comm.Get_rank()], dtype=int) for b in range(n_blocks)] #Global variable + + print(f'rk={comm.Get_rank()}, local_sizes_per_block_per_process={local_sizes_per_block_per_process}, index_shift={index_shift}, u[0]._data={u[0]._data.shape}, u[1]._data={u[1]._data.shape}') + + + local_petsc_indices = np.arange(localsize) + global_petsc_indices = [] + psydac_indices = [] + block_indices = [] + for petsc_index in local_petsc_indices: + + block_index, psydac_index = global_to_psydac(Xh, petsc_index)#, comm=x.comm) + psydac_indices.append(psydac_index) + block_indices.append(block_index) + + + global_petsc_indices.append(petsc_index + index_shift) + + print(f'rank={comm.Get_rank()}, psydac_index = {psydac_index}, local_petsc_index={petsc_index}, petsc_global_index={global_petsc_indices[-1]}') + + + for block_index, psydac_index, petsc_index in zip(block_indices, psydac_indices, global_petsc_indices): + value = x.getValue(petsc_index) # Global index + if value != 0: + u[block_index[0]]._data[psydac_index] = value if dtype is complex else value.real + + + + + '''if comm: + # Gather the global array in all the processors + ################################################ + # Note 12.03.2024: + # This global communication is at the moment necessary since PETSc distributes matrices and vectors different than Psydac. + # In order to avoid it, we would need that PETSc uses the partition from Psydac, + # which might involve passing a DM Object. + ################################################ + comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) else: - comm = u[0].space.cart.global_comm - dtype = u[0].space.dtype - sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) - recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors - - if comm: - # Gather the global array in all the procs - # TODO: Avoid this global communication with a DM Object (see note above). - comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) - else: - recvbuf[:] = x.array - - inds = 0 - starts = [np.array(V.starts) for V in Xh.spaces] - ends = [np.array(V.ends) for V in Xh.spaces] + recvbuf[:] = x.array + + inds = 0 + for d in range(len(Xh.spaces)): + starts = [np.array(V.starts) for V in Xh.spaces[d].spaces] + ends = [np.array(V.ends) for V in Xh.spaces[d].spaces] + for i in range(len(starts)): - idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.spaces[i].pads, u.space.spaces[i].shifts) ) + idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.spaces[d].spaces[i].pads, u.space.spaces[d].spaces[i].shifts) ) shape = tuple(ends[i]-starts[i]+1) - npts = Xh.spaces[i].npts + npts = Xh.spaces[d].spaces[i].npts # compute the global indices of the coefficents owned by the process using starts and ends indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts[i], xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) vals = recvbuf[indices+inds] # With PETSc installation configuration for complex, all the numbers are by default complex. # In the float case, the imaginary part must be truncated to avoid warnings. - u[i]._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) + u[d][i]._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) inds += np.prod(npts) + else: + comm = u[0].space.cart.global_comm + dtype = u[0].space.dtype + sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) + recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors + + if comm: + # Gather the global array in all the procs + # TODO: Avoid this global communication with a DM Object (see note above). + comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) + else: + recvbuf[:] = x.array + + inds = 0 + starts = [np.array(V.starts) for V in Xh.spaces] + ends = [np.array(V.ends) for V in Xh.spaces] + for i in range(len(starts)): + idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.spaces[i].pads, u.space.spaces[i].shifts) ) + shape = tuple(ends[i]-starts[i]+1) + npts = Xh.spaces[i].npts + # compute the global indices of the coefficents owned by the process using starts and ends + indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts[i], xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) + vals = recvbuf[indices+inds] + + # With PETSc installation configuration for complex, all the numbers are by default complex. + # In the float case, the imaginary part must be truncated to avoid warnings. + u[i]._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) + + inds += np.prod(npts)''' + elif isinstance(Xh, StencilVectorSpace): u = StencilVector(Xh) @@ -168,22 +206,48 @@ def petsc_to_psydac(x, Xh): localsize, globalsize = x.getSizes() assert globalsize == u.shape[0], 'Sizes of global vectors do not match' - index_shift = get_petsc_local_to_global_shift(Xh) + '''index_shift = get_petsc_local_to_global_shift(Xh) petsc_local_indices = np.arange(localsize) petsc_indices = petsc_local_indices #+ index_shift - psydac_indices = [petsc_to_psydac_local(Xh, petsc_index) for petsc_index in petsc_indices] + psydac_indices = [petsc_to_psydac_local(Xh, petsc_index) for petsc_index in petsc_indices]''' + + + # Find shifts for process k: + npts_local_per_block_per_process = np.array(get_npts_per_block(Xh)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + + index_shift = 0 + np.sum(local_sizes_per_block_per_process[0][0:x.comm.Get_rank()], dtype=int) #Global variable + + + + local_petsc_indices = np.arange(localsize) + global_petsc_indices = [] + psydac_indices = [] + block_indices = [] + for petsc_index in local_petsc_indices: + + block_index, psydac_index = global_to_psydac(Xh, petsc_index)#, comm=x.comm) + psydac_indices.append(psydac_index) + block_indices.append(block_index) + global_petsc_indices.append(petsc_index + index_shift) + + + + + #psydac_indices = [global_to_psydac(Xh, petsc_index, comm=x.comm) for petsc_index in petsc_indices] + - if comm is not None: + '''if comm is not None: for k in range(comm.Get_size()): if k == comm.Get_rank(): print('\nRank ', k) print('petsc_indices=\n', petsc_indices) print('psydac_indices=\n', psydac_indices) print('index_shift=', index_shift) - comm.Barrier() + comm.Barrier()''' - for psydac_index, petsc_index in zip(psydac_indices, petsc_indices): - value = x.getValue(petsc_index + index_shift) + for block_index, psydac_index, petsc_index in zip(block_indices, psydac_indices, global_petsc_indices): + value = x.getValue(petsc_index) # Global index if value != 0: u._data[psydac_index] = value if dtype is complex else value.real @@ -215,7 +279,7 @@ def petsc_to_psydac(x, Xh): u.update_ghost_regions() - if comm is not None: + '''if comm is not None: u_arr = u.toarray() x_arr = x.array.real for k in range(comm.Get_size()): @@ -225,7 +289,7 @@ def petsc_to_psydac(x, Xh): #print('x.array=\n', x_arr) #print('u._data=\n', u._data) - comm.Barrier() + comm.Barrier()''' return u From 4174b66ffad571e99ed22d3e87559732f7a40baa Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Tue, 21 May 2024 13:54:47 +0200 Subject: [PATCH 32/88] PETSc conversion works for StencilVector and BlockStencilVector of 1 level --- psydac/linalg/topetsc.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index ee08dfa35..bdc600cf1 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -281,7 +281,9 @@ def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indice nprocs = cart.nprocs ndim = cart.ndim gs = cart.global_starts # Global variable - ge = cart.global_ends # Global variable + ge = cart.global_ends # Global variable + + '''#dnpts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. @@ -294,10 +296,11 @@ def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indice jj = ndarray_indices if ndim == 1: - #proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] #index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable - index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + #index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) global_index = index_shift + jj[0] - gs[0][proc_index] elif ndim == 2: @@ -305,14 +308,15 @@ def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indice proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] proc_index = proc_y + proc_x*nprocs[1]#proc_x + proc_y*nprocs[0] - index_shift = 0#0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) + #index_shift = 0#0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable #global_index = jj[0] - gs[0][proc_x] + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][0] + index_shift #global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] #print(f'np.sum(local_sizes_per_block_per_process[:,:proc_index])={np.sum(local_sizes_per_block_per_process[:,:proc_index])}') #print(f'np.sum(local_sizes_per_block_per_process[:bb,proc_index])={np.sum(local_sizes_per_block_per_process[:bb,proc_index])}') - shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) - global_index = shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] + #index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) + global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] #print(f'shift={shift}') #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) @@ -333,7 +337,8 @@ def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indice proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0] proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2] #proc_x + proc_y*nprocs[0] - index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + #index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) global_index = index_shift \ + jj[2] - gs[2][proc_z] \ + (jj[1] - gs[1][proc_y]) * npts_local_per_block_per_process[bb][proc_index][2] \ @@ -647,22 +652,20 @@ def vec_topetsc( vec ): vec_block = vec - block_shift_per_process = get_block_shift_per_process(vec.space) + #block_shift_per_process = get_block_shift_per_process(vec.space) #global_npts_per_block_per_proc = get_npts_per_block(vec.space) - print(f'blocks_shift={block_shift_per_process}') + #print(f'blocks_shift={block_shift_per_process}') for b in range(n_blocks): if isinstance(vec, BlockVector): vec_block = vec.blocks[b] - index_shift = block_shift_per_process[comms[b].Get_rank()] - if ndims[b] == 1: for i1 in range(npts_local[b][0]): value = vec_block._data[i1 + ghost_size[b][0]] if value != 0: i1_n = s[b][0] + i1 - i_g = psydac_to_global(vec.space.spaces[b], (), (i1_n,)) + index_shift + i_g = psydac_to_global(vec.space, (b,), (i1_n,)) petsc_indices.append(i_g) petsc_data.append(value) @@ -673,8 +676,8 @@ def vec_topetsc( vec ): if value != 0: i1_n = s[b][0] + i1 i2_n = s[b][1] + i2 - i_g = psydac_to_global(vec.space, (b,), (i1_n, i2_n)) #+ index_shift - print(f'Rank {comms[b].Get_rank()}, Block {b}: i1_n = {i1_n}, i2_n = {i2_n}, i_g = {i_g}') + i_g = psydac_to_global(vec.space, (b,), (i1_n, i2_n)) + #print(f'Rank {comms[b].Get_rank()}, Block {b}: i1_n = {i1_n}, i2_n = {i2_n}, i_g = {i_g}') petsc_indices.append(i_g) petsc_data.append(value) From d019388c095aa4943cf039c1965e2fadedd038f4 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Tue, 21 May 2024 14:24:20 +0200 Subject: [PATCH 33/88] conversion works for StencilMatrix --- psydac/linalg/topetsc.py | 91 +++++++++++++++++--------------------- psydac/linalg/utilities.py | 2 + 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index bdc600cf1..5f29d71cc 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -597,6 +597,9 @@ def vec_topetsc( vec ): """ from petsc4py import PETSc + if isinstance(vec.space, BlockVectorSpace) and any([isinstance(vec.space.spaces[b], BlockVectorSpace) for b in range(len(vec.space.spaces))]): + raise NotImplementedError('Block of blocks not implemented.') + if isinstance(vec, StencilVector): carts = [vec.space.cart] elif isinstance(vec.space, BlockVectorSpace): @@ -615,12 +618,7 @@ def vec_topetsc( vec ): carts.append(carts2) - '''elif isinstance(vec.space.spaces[0], StencilVectorSpace): - carts = [vec.space.spaces[b1].cart for b1 in range(len(vec.space.spaces))] - - elif isinstance(vec.space.spaces[0], BlockVectorSpace): - carts = [[vec.space.spaces[b1][b2].cart for b2 in range(len(vec.space.spaces[b1]))] for b1 in range(len(vec.space.spaces))] - ''' + npts_local = get_npts_local(vec.space) #[[ e - s + 1 for s, e in zip(cart.starts, cart.ends)] for cart in carts] #Number of points in each dimension within each process. Different for each process. @@ -628,8 +626,6 @@ def vec_topetsc( vec ): ndims = [cart.ndim for cart in carts] - - #index_shift = get_petsc_local_to_global_shift(vec.space) #Global variable gvec = PETSc.Vec().create(comm=comms[0]) @@ -644,18 +640,12 @@ def vec_topetsc( vec ): petsc_data = [] s = [cart.starts for cart in carts] - #p = [cart.pads for cart in carts] - #m = [cart.shifts for cart in carts] ghost_size = [[pi*mi for pi,mi in zip(cart.pads, cart.shifts)] for cart in carts] n_blocks = 1 if isinstance(vec, StencilVector) else vec.n_blocks vec_block = vec - #block_shift_per_process = get_block_shift_per_process(vec.space) - #global_npts_per_block_per_proc = get_npts_per_block(vec.space) - #print(f'blocks_shift={block_shift_per_process}') - for b in range(n_blocks): if isinstance(vec, BlockVector): vec_block = vec.blocks[b] @@ -847,7 +837,7 @@ def mat_topetsc( mat ): if dndim == 1 and cndim == 1: - for bb in nonzero_block_indices: + for b1,b2 in nonzero_block_indices: if isinstance(mat, StencilMatrix): data = mat._data @@ -857,7 +847,7 @@ def mat_topetsc( mat ): for i1 in range(dnpts_local[0]): nnz_in_row = 0 i1_n = s[0] + i1 - i_g = psydac_to_global(mat.codomain, (bb[0],), (i1_n,)) + i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n,)) for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): value = data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] @@ -865,7 +855,7 @@ def mat_topetsc( mat ): if value != 0: - j_g = psydac_to_global(mat.domain, (bb[1],), (j1_n, )) + j_g = psydac_to_global(mat.domain, (b1, b2), (j1_n, )) if nnz_in_row == 0: rowmap.append(i_g) @@ -889,9 +879,9 @@ def mat_topetsc( mat ): i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n, i2_n)) #i_n = psydac_to_singlenatural(mat.codomain, (i1_n,i2_n)) - for k in range(dcomm.Get_size()): + '''for k in range(dcomm.Get_size()): if k == dcomm.Get_rank(): - print(f'Rank {k}: ({i1_n}, {i2_n}), i_n= {i_n}, i_g= {i_g}') + print(f'Rank {k}: ({i1_n}, {i2_n}), i_n= {i_n}, i_g= {i_g}')''' for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): @@ -907,10 +897,10 @@ def mat_topetsc( mat ): if nnz_in_row == 0: rowmap.append(i_g) - rowmap2.append(psydac_to_singlenatural(mat.domain, (i1_n,i2_n))) + #rowmap2.append(psydac_to_singlenatural(mat.domain, (i1_n,i2_n))) J.append(j_g) - J2.append(psydac_to_singlenatural(mat.domain, (j1_n,j2_n))) + #J2.append(psydac_to_singlenatural(mat.domain, (j1_n,j2_n))) V.append(value) @@ -919,35 +909,36 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row) elif dndim == 3 and cndim == 3: - for i1 in np.arange(dnpts_local[0]): - for i2 in np.arange(dnpts_local[1]): - for i3 in np.arange(dnpts_local[2]): - nnz_in_row = 0 - i1_n = s[0] + i1 - i2_n = s[1] + i2 - i3_n = s[2] + i3 - i_g = psydac_to_global(mat.codomain, (i1_n, i2_n, i3_n)) - - for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): - for k2 in range(-p[1]*m[1], p[1]*m[1] + 1): - for k3 in range(-p[2]*m[2], p[2]*m[2] + 1): - value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] - - j1_n = (i1_n + k1)%dnpts[0] #- ghost_size[0] - j2_n = (i2_n + k2)%dnpts[1] # - ghost_size[1] - j3_n = (i3_n + k3)%dnpts[2] # - ghost_size[2] - - if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): - j_g = psydac_to_global(mat.domain, (j1_n, j2_n, j3_n)) - - if nnz_in_row == 0: - rowmap.append(i_g) - - J.append(j_g) - V.append(value) - nnz_in_row += 1 - - I.append(I[-1] + nnz_in_row) + for b1,b2 in nonzero_block_indices: + for i1 in np.arange(dnpts_local[0]): + for i2 in np.arange(dnpts_local[1]): + for i3 in np.arange(dnpts_local[2]): + nnz_in_row = 0 + i1_n = s[0] + i1 + i2_n = s[1] + i2 + i3_n = s[2] + i3 + i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n, i2_n, i3_n)) + + for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): + for k2 in range(-p[1]*m[1], p[1]*m[1] + 1): + for k3 in range(-p[2]*m[2], p[2]*m[2] + 1): + value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] + + j1_n = (i1_n + k1)%dnpts[0] #- ghost_size[0] + j2_n = (i2_n + k2)%dnpts[1] # - ghost_size[1] + j3_n = (i3_n + k3)%dnpts[2] # - ghost_size[2] + + if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): + j_g = psydac_to_global(mat.domain, (b1, b2), (j1_n, j2_n, j3_n)) + + if nnz_in_row == 0: + rowmap.append(i_g) + + J.append(j_g) + V.append(value) + nnz_in_row += 1 + + I.append(I[-1] + nnz_in_row) diff --git a/psydac/linalg/utilities.py b/psydac/linalg/utilities.py index 8cb07c6c3..127d97132 100644 --- a/psydac/linalg/utilities.py +++ b/psydac/linalg/utilities.py @@ -91,6 +91,8 @@ def petsc_to_psydac(x, Xh): """ if isinstance(Xh, BlockVectorSpace): + if any([isinstance(Xh.spaces[b], BlockVectorSpace) for b in range(len(Xh.spaces))]): + raise NotImplementedError('Block of blocks not implemented.') u = BlockVector(Xh) comm = x.comm#u[0][0].space.cart.global_comm From 40667462532845e4bcfb5cc614ef17b0b6955f72 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Tue, 21 May 2024 16:18:28 +0200 Subject: [PATCH 34/88] works for BlockLinearOperators, the blocks of which are Stencilmatrices --- psydac/linalg/topetsc.py | 167 ++++++++++++++++++++++++--------------- 1 file changed, 105 insertions(+), 62 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 5f29d71cc..aaf652b3f 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -715,7 +715,11 @@ def mat_topetsc( mat ): from petsc4py import PETSc - if isinstance(mat, StencilMatrix): + if (isinstance(mat.domain, BlockVectorSpace) and any([isinstance(mat.domain.spaces[b], BlockVectorSpace) for b in range(len(mat.domain.spaces))]))\ + or (isinstance(mat.codomain, BlockVectorSpace) and any([isinstance(mat.codomain.spaces[b], BlockVectorSpace) for b in range(len(mat.codomain.spaces))])): + raise NotImplementedError('Block of blocks not implemented.') + + '''if isinstance(mat, StencilMatrix): dcart = mat.domain.cart ccart = mat.codomain.cart elif isinstance(mat.domain.spaces[0], StencilVectorSpace): @@ -728,37 +732,76 @@ def mat_topetsc( mat ): elif isinstance(mat.codomain.spaces[0], StencilVectorSpace): ccart = mat.codomain.spaces[0].cart elif isinstance(mat.codomain.spaces[0], BlockVectorSpace): - ccart = mat.codomain.spaces[0][0].cart + ccart = mat.codomain.spaces[0][0].cart ''' + + if isinstance(mat.domain, StencilVectorSpace): + dcarts = [mat.domain.cart] + elif isinstance(mat.domain, BlockVectorSpace): + dcarts = [] + for b in range(len(mat.domain.spaces)): + dcarts.append(mat.domain.spaces[b].cart) + + if isinstance(mat.codomain, StencilVectorSpace): + ccarts = [mat.codomain.cart] + elif isinstance(mat.codomain, BlockVectorSpace): + ccarts = [] + for b in range(len(mat.codomain.spaces)): + ccarts.append(mat.codomain.spaces[b].cart) + + n_blocks = (len(ccarts), len(dcarts)) + nonzero_block_indices = ((0,0),) if isinstance(mat, StencilMatrix) else mat.nonzero_block_indices + + + '''if isinstance(mat, StencilMatrix): + dcarts = [mat.domain.cart] + ccarts = [mat.codomain.cart] + nonzero_block_indices = ((0,0),) + n_blocks = (1,1) + elif isinstance(mat, BlockLinearOperator): + dcarts = [] + ccarts = [] + for b in range(len(mat.domain.spaces)): + dcarts.append(mat.domain.spaces[b].cart) + for b in range(len(mat.codomain.spaces)): + ccarts.append(mat.codomain.spaces[b].cart) + + nonzero_block_indices = mat.nonzero_block_indices + n_blocks = (len(dcarts), len(ccarts)) ''' + + - dcomm = dcart.global_comm - ccomm = ccart.global_comm + dcomms = [dcart.global_comm for dcart in dcarts] + dcomm = dcomms[0] + #ccomms = [ccart.global_comm for ccart in ccarts] mat.update_ghost_regions() mat.remove_spurious_entries() - dndim = dcart.ndim + dndims = [dcart.ndim for dcart in dcarts] + cndims = [ccart.ndim for ccart in ccarts] + dnpts = [dcart.npts for dcart in dcarts] + cnpts = [ccart.npts for ccart in ccarts] + + ''' dstarts = dcart.starts dends = dcart.ends dpads = dcart.pads dshifts = dcart.shifts - dnpts = dcart.npts + cndim = ccart.ndim cstarts = ccart.starts cends = ccart.ends #cpads = ccart.pads #cshifts = ccart.shifts - cnpts = ccart.npts + cnpts = ccart.npts ''' - dnpts_local = [ e - s + 1 for s, e in zip(dstarts, dends)] #Number of points in each dimension within each process. Different for each process. - cnpts_local = [ e - s + 1 for s, e in zip(cstarts, cends)] + dnpts_local = get_npts_local(mat.domain) #[ e - s + 1 for s, e in zip(dstarts, dends)] #Number of points in each dimension within each process. Different for each process. + cnpts_local = get_npts_local(mat.codomain) #[ e - s + 1 for s, e in zip(cstarts, cends)] - - #dindex_shift = get_petsc_local_to_global_shift(mat.domain) #Global variable - #cindex_shift = get_petsc_local_to_global_shift(mat.codomain) #Global variable - mat_dense = mat.tosparse().todense() + '''mat_dense = mat.tosparse().todense() for k in range(dcomm.Get_size()): if k == dcomm.Get_rank(): print('\nRank ', k) @@ -779,18 +822,11 @@ def mat_topetsc( mat ): #print('mat._data=\n', mat._data) dcomm.Barrier() - ccomm.Barrier() - + ccomm.Barrier() ''' - n_block_rows = 1 if not isinstance(mat, BlockLinearOperator) else mat.n_block_rows - n_block_cols = 1 if not isinstance(mat, BlockLinearOperator) else mat.n_block_cols - if isinstance(mat, StencilMatrix): - nonzero_block_indices = ((0,0),) - else: - nonzero_block_indices = mat.nonzero_block_indices globalsize = mat.shape #equivalent to (np.prod(dnpts), np.prod(cnpts)) #Tuple of integers - localsize = (np.prod(cnpts_local)*n_block_rows, np.prod(dnpts_local)*n_block_cols) + localsize = (np.sum(np.prod(cnpts_local, axis=1)), np.sum(np.prod(dnpts_local, axis=1))) #(np.prod(cnpts_local)*n_block_rows, np.prod(dnpts_local)*n_block_cols) gmat = PETSc.Mat().create(comm=dcomm) @@ -825,37 +861,46 @@ def mat_topetsc( mat ): I = [0] J = [] V = [] - J2 = [] + #J2 = [] rowmap = [] - rowmap2 = [] + #rowmap2 = [] - s = dstarts - p = dpads - m = dshifts - ghost_size = [pi*mi for pi,mi in zip(p,m)] + #s = [dcart.starts for dcart in dcarts] + #p = [dcart.pads for dcart in dcarts] + #m = [dcart.shifts for dcart in dcarts] + ghost_size = [[pi*mi for pi,mi in zip(dcart.pads, dcart.shifts)] for dcart in dcarts] + mat_block = mat - if dndim == 1 and cndim == 1: - for b1,b2 in nonzero_block_indices: + for bc, bd in nonzero_block_indices: + if isinstance(mat, BlockLinearOperator): + mat_block = mat.blocks[bc][bd] - if isinstance(mat, StencilMatrix): - data = mat._data - elif isinstance(mat, BlockLinearOperator): - data = mat.blocks[bb[0]][bb[1]]._data + s = dcarts[bd].starts + p = dcarts[bd].pads + m = dcarts[bd].shifts + ghost_size = [pi*mi for pi,mi in zip(p, m)] - for i1 in range(dnpts_local[0]): + if dndims[bd] == 1 and cndims[bc] == 1: + + #if isinstance(mat, StencilMatrix): + # data = mat._data + #elif isinstance(mat, BlockLinearOperator): + # data = mat.blocks[bb[0]][bb[1]]._data + + for i1 in range(dnpts_local[bd][0]): nnz_in_row = 0 i1_n = s[0] + i1 - i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n,)) + i_g = psydac_to_global(mat.codomain, (bc,), (i1_n,)) for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): - value = data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] - j1_n = (i1_n + k1)%dnpts[0] + value = mat_block._data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] + j1_n = (i1_n + k1)%dnpts[bd][0] if value != 0: - j_g = psydac_to_global(mat.domain, (b1, b2), (j1_n, )) + j_g = psydac_to_global(mat.domain, (bd,), (j1_n, )) if nnz_in_row == 0: rowmap.append(i_g) @@ -867,16 +912,15 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row) - elif dndim == 2 and cndim == 2: - for b1,b2 in nonzero_block_indices: - for i1 in np.arange(dnpts_local[0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): - for i2 in np.arange(dnpts_local[1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): + elif dndims[bd] == 2 and cndims[bc] == 2: + for i1 in np.arange(dnpts_local[bd][0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): + for i2 in np.arange(dnpts_local[bd][1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): nnz_in_row = 0 i1_n = s[0] + i1 i2_n = s[1] + i2 - i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n, i2_n)) + i_g = psydac_to_global(mat.codomain, (bc,), (i1_n, i2_n)) #i_n = psydac_to_singlenatural(mat.codomain, (i1_n,i2_n)) '''for k in range(dcomm.Get_size()): @@ -886,14 +930,14 @@ def mat_topetsc( mat ): for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): - value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] + value = mat_block._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) - j1_n = (i1_n + k1)%dnpts[0] #- p[0]*m[0] - j2_n = (i2_n + k2)%dnpts[1] #- p[1]*m[1] + j1_n = (i1_n + k1)%dnpts[bd][0] #- p[0]*m[0] + j2_n = (i2_n + k2)%dnpts[bd][1] #- p[1]*m[1] if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): - j_g = psydac_to_global(mat.domain, (b1, b2), (j1_n, j2_n)) + j_g = psydac_to_global(mat.domain, (bd,), (j1_n, j2_n)) if nnz_in_row == 0: rowmap.append(i_g) @@ -908,28 +952,27 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row) - elif dndim == 3 and cndim == 3: - for b1,b2 in nonzero_block_indices: - for i1 in np.arange(dnpts_local[0]): - for i2 in np.arange(dnpts_local[1]): - for i3 in np.arange(dnpts_local[2]): + elif dndims[bd] == 3 and cndims[bc] == 3: + for i1 in np.arange(dnpts_local[bd][0]): + for i2 in np.arange(dnpts_local[bd][1]): + for i3 in np.arange(dnpts_local[bd][2]): nnz_in_row = 0 i1_n = s[0] + i1 i2_n = s[1] + i2 i3_n = s[2] + i3 - i_g = psydac_to_global(mat.codomain, (b1, b2), (i1_n, i2_n, i3_n)) + i_g = psydac_to_global(mat.codomain, (bc,), (i1_n, i2_n, i3_n)) for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): for k2 in range(-p[1]*m[1], p[1]*m[1] + 1): for k3 in range(-p[2]*m[2], p[2]*m[2] + 1): - value = mat._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] + value = mat_block._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] - j1_n = (i1_n + k1)%dnpts[0] #- ghost_size[0] - j2_n = (i2_n + k2)%dnpts[1] # - ghost_size[1] - j3_n = (i3_n + k3)%dnpts[2] # - ghost_size[2] + j1_n = (i1_n + k1)%dnpts[bd][0] #- ghost_size[0] + j2_n = (i2_n + k2)%dnpts[bd][1] # - ghost_size[1] + j3_n = (i3_n + k3)%dnpts[bd][2] # - ghost_size[2] if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): - j_g = psydac_to_global(mat.domain, (b1, b2), (j1_n, j2_n, j3_n)) + j_g = psydac_to_global(mat.domain, (bd,), (j1_n, j2_n, j3_n)) if nnz_in_row == 0: rowmap.append(i_g) @@ -956,12 +999,12 @@ def mat_topetsc( mat ): #print('petsc_col_indices=\n', petsc_col_indices) #print('petsc_data=\n', petsc_data) #print('owned_rows=', owned_rows) - print('mat_dense=\n', mat_dense) + #print('mat_dense=\n', mat_dense) print('I=', I) print('rowmap=', rowmap) - print('rowmap2=', rowmap2) + #print('rowmap2=', rowmap2) print('J=\n', J) - print('J2=\n', J2) + #print('J2=\n', J2) #print('V=\n', V) #print('gmat_dense=\n', gmat_dense) print('\n\n============') From 988ba35fb4ee13ddfe9e75214b2bb0b65a0ff5c0 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Tue, 21 May 2024 18:15:42 +0200 Subject: [PATCH 35/88] Clean up, docstrings --- psydac/linalg/topetsc.py | 1123 +++++------------------------------- psydac/linalg/utilities.py | 214 +------ 2 files changed, 182 insertions(+), 1155 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index aaf652b3f..564ea2702 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -8,15 +8,14 @@ from mpi4py import MPI -__all__ = ('flatten_vec', 'vec_topetsc', 'mat_topetsc') +__all__ = ('petsc_local_to_psydac', 'psydac_to_petsc_global', 'get_npts_local', 'get_npts_per_block', 'vec_topetsc', 'mat_topetsc') -def psydac_to_petsc_local( - V : VectorSpace, - block_indices : tuple[int], - ndarray_indices : tuple[int]) -> int : +def petsc_local_to_psydac( + V : VectorSpace, + petsc_index : int) -> tuple[tuple[int], tuple[int]]: """ - Convert the Psydac local index to a PETSc local index. + Convert the PETSc local index (starting from 0 in each process) to a Psydac local index (natural multi-index, as grid coordinates). Parameters ----------- @@ -25,167 +24,26 @@ def psydac_to_petsc_local( This defines the number of blocks, the size of each block, and how each block is distributed across MPI processes. - block_indices : tuple[int] - The indices which identify the block in a (possibly nested) block vector. - In the case of a StencilVector this is an empty tuple. - - ndarray_indices : tuple[int] - The multi-index which identifies an element in the _data array, - excluding the ghost regions. - - Returns - -------- petsc_index : int - The local PETSc index, which is equivalent to the global PETSc index - but starts from 0. - """ - - ndim = V.ndim - starts = V.starts - ends = V.ends - pads = V.pads - shifts = V.shifts - shape = V.shape - - ii = ndarray_indices - - npts_local = [ e - s + 1 for s, e in zip(starts, ends)] #Number of points in each dimension within each process. Different for each process. - - assert all([ii[d] >= pads[d]*shifts[d] and ii[d] < shape[d] - pads[d]*shifts[d] for d in range(ndim)]), 'ndarray_indices within the ghost region' - - if ndim == 1: - petsc_index = ii[0] - pads[0]*shifts[0] # global index starting from 0 in each process - elif ndim == 2: - petsc_index = npts_local[1] * (ii[0] - pads[0]*shifts[0]) + ii[1] - pads[1]*shifts[1] # global index starting from 0 in each process - elif ndim == 3: - petsc_index = npts_local[1] * npts_local[2] * (ii[0] - pads[0]*shifts[0]) + npts_local[2] * (ii[1] - pads[1]*shifts[1]) + ii[2] - pads[2]*shifts[2] - else: - raise NotImplementedError( "Cannot handle more than 3 dimensions." ) - - return petsc_index - -def get_petsc_local_to_global_shift(V : VectorSpace) -> int: - """ - Compute the correct integer shift (process dependent) in order to convert - a PETSc local index to the corresponding global index. - - Parameter - --------- - V : VectorSpace - The distributed Psydac vector space. + The local PETSc index. The 0 index is only owned by every process. Returns -------- - int - The integer shift which must be added to a local index - in order to get a global index. - """ - - cart = V.cart - comm = cart.global_comm - - if comm is None: - return 0 - - gstarts = cart.global_starts # Global variable - gends = cart.global_ends # Global variable - - npts_local_perprocess = [ ge - gs + 1 for gs, ge in zip(gstarts, gends)] #Global variable - npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable - localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(comm.Get_size())] #Global variable - index_shift = 0 + np.sum(localsize_perprocess[0:comm.Get_rank()], dtype=int) #Global variable - - return index_shift - -def petsc_to_psydac_local( - V : VectorSpace, - petsc_index : int) :#-> tuple(tuple[int], tuple[int]) : + block: tuple + The block where the Psydac multi-index belongs to. + psydac_index : tuple + The Psydac local multi-index. This index is local the block. """ - Convert the PETSc local index to a Psydac local index. - This is the inverse of `psydac_to_petsc_local`. - """ - - npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d - local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k - - if isinstance(V, BlockVectorSpace): - V = V.spaces[bb] - - accumulated_local_sizes_per_block_per_process = np.cumsum(local_sizes_per_block_per_process, axis=0) #indexed [b,k] for block b and process k - bb = np.nonzero(np.array([petsc_index in range(accumulated_local_sizes_per_block_per_process[b-1][comm.Get_rank()], accumulated_local_sizes_per_block_per_process[b][comm.Get_rank()])]))[0][0] - - - ndim = V.ndim - starts = V.starts - ends = V.ends - pads = V.pads - shifts = V.shifts - - npts_local = npts_local_per_block_per_process[bb] #Number of points in each dimension within each process. Different for each process. - - ii = np.zeros((ndim,), dtype=int) - if ndim == 1: - ii[0] = petsc_index + pads[0]*shifts[0] # global index starting from 0 in each process - - elif ndim == 2: - ii[0] = petsc_index // npts_local[1] + pads[0]*shifts[0] - ii[1] = petsc_index % npts_local[1] + pads[1]*shifts[1] - - elif ndim == 3: - ii[0] = petsc_index // (npts_local[1]*npts_local[2]) + pads[0]*shifts[0] - ii[1] = petsc_index // npts_local[2] + pads[1]*shifts[1] - npts_local[1]*(ii[0] - pads[0]*shifts[0]) - ii[2] = petsc_index % npts_local[2] + pads[2]*shifts[2] - - else: - raise NotImplementedError( "Cannot handle more than 3 dimensions." ) - - return tuple(tuple(ii)) - -def global_to_psydac( - V : VectorSpace, - petsc_index : int) :#-> tuple(tuple[int], tuple[int]) : - """ - Convert the PETSc local index to a Psydac local index. - This is the inverse of `psydac_to_petsc_local`. - """ - - '''npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d - local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k - accumulated_local_sizes_per_block_per_process = np.concatenate((np.zeros_like(local_sizes_per_block_per_process), np.cumsum(local_sizes_per_block_per_process, axis=0))) #indexed [b+1,k] for block b and process k - print(f'accumulated_local_sizes_per_block_per_process = {accumulated_local_sizes_per_block_per_process}' ) - n_blocks = local_sizes_per_block_per_process.shape[0] - rk = comm.Get_rank() - bb = np.nonzero( - np.array( - [petsc_index in range(accumulated_local_sizes_per_block_per_process[b][rk], accumulated_local_sizes_per_block_per_process[b+1][rk]) - for b in range(n_blocks)] - ))[0][0] - print(f'rk={rk}, bb={bb}') - - npts_local_per_process = npts_local_per_block_per_process[bb] #indexed [k,d] for process k - local_sizes_per_process = np.prod(npts_local_per_process, axis=-1) #indexed [k] for process k - accumulated_local_sizes_per_process = np.concatenate((np.zeros((1,), dtype=int), np.cumsum(local_sizes_per_process, axis=0))) #indexed [k+1] for process k - - n_procs = local_sizes_per_process.size - - print(f'n_procs={n_procs}, accumulated_local_sizes_per_process={accumulated_local_sizes_per_process}') - - rank = np.nonzero( - np.array( - [petsc_index in range(accumulated_local_sizes_per_process[k], accumulated_local_sizes_per_process[k+1]) - for k in range(n_procs)] - ))[0][0] - - npts_local = npts_local_per_block_per_process[bb][rank] #Number of points in each dimension within each process. Different for each process. - ''' - - - npts_local_per_block = np.array(get_npts_local(V)) #indexed [b,d] for block b and dimension d - local_sizes_per_block = np.prod(npts_local_per_block, axis=-1) #indexed [b] for block b + # Get the number of points for each block and each dimension local to the current process: + npts_local_per_block = np.array(get_npts_local(V)) # indexed [b,d] for block b and dimension d + # Get the local size of the current process for each block: + local_sizes_per_block = np.prod(npts_local_per_block, axis=-1) # indexed [b] for block b + # Compute the accumulated local size of the current process for each block: accumulated_local_sizes_per_block = np.concatenate((np.zeros((1,), dtype=int), np.cumsum(local_sizes_per_block, axis=0))) #indexed [b+1] for block b n_blocks = local_sizes_per_block.size + # Find the block where the index belongs to: bb = np.nonzero( np.array( @@ -193,8 +51,6 @@ def global_to_psydac( for b in range(n_blocks)] ))[0][0] - #print(f'bb={bb}') - if isinstance(V, BlockVectorSpace): V = V.spaces[bb] @@ -202,29 +58,19 @@ def global_to_psydac( p = V.pads m = V.shifts - npts_local = npts_local_per_block[bb] #Number of points in each dimension within each process. Different for each process. + # Get the number of points for each dimension local to the current process and block: + npts_local = npts_local_per_block[bb] - # Get the PETSc index LOCAL in the block: + # Get the PETSc index local within the block: petsc_index -= accumulated_local_sizes_per_block[bb] - #npts_local = npts_local_per_block_per_process[bb][rk] - #print(f'npts_local={npts_local}') - - '''# Find shift for process k: - npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d - local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k - assert local_sizes_per_block_per_process[:,comm.Get_rank()] == np.prod(npts_local) - index_proc_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:comm.Get_rank()], dtype=int) #Global variable''' - - ii = np.zeros((ndim,), dtype=int) if ndim == 1: - ii[0] = petsc_index + p[0]*m[0] # global index starting from 0 in each process + ii[0] = petsc_index + p[0]*m[0] elif ndim == 2: ii[0] = petsc_index // npts_local[1] + p[0]*m[0] ii[1] = petsc_index % npts_local[1] + p[1]*m[1] - #print(f'rank={comm.Get_rank()}, bb={bb}, npts_local={npts_local}, local_petsc_index={petsc_index}, ii={ii}') elif ndim == 3: ii[0] = petsc_index // (npts_local[1]*npts_local[2]) + p[0]*m[0] @@ -236,109 +82,88 @@ def global_to_psydac( return (bb,), tuple(ii) -def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indices : tuple[int]) -> int: - '''From Psydac natural multi-index (grid coordinates) to global PETSc single-index. - Performs a search to find the process owning the multi-index.''' +def psydac_to_petsc_global( + V : VectorSpace, + block_indices : tuple[int], + ndarray_indices : tuple[int]) -> int: + """ + Convert the Psydac local index (natural multi-index, as grid coordinates) to a PETSc global index. Performs a search to find the process owning the multi-index. - - #nonzero_block_indices = ((0,0)) if not isinstance(V, BlockVectorSpace) else V. - #s = V.starts - #e = V.ends - #p = V.pads - #m = V.shifts - #dnpts = V.cart.npts - + Parameters + ----------- + V : VectorSpace + The vector space to which the Psydac vector belongs. + This defines the number of blocks, the size of each block, + and how each block is distributed across MPI processes. - - #block_shift = 0 + block_indices : tuple[int] + The indices which identify the block in a (possibly nested) block vector. + In the case of a StencilVector this is an empty tuple. - #for b in bb: - '''if isinstance(V, StencilVectorSpace): - cart = V.cart - elif isinstance(V, BlockVectorSpace): - cart = V.spaces[bb[0]].cart''' + ndarray_indices : tuple[int] + The multi-index which identifies an element in the _data array, + excluding the ghost regions. - '''# compute the block shift: - for b1 in range(min(len(V.spaces), bb[0])): - prev_npts_local = 0#np.sum(np.prod([ e - s + 1 for s, e in zip(V.spaces[b1].starts, V.spaces[b1].ends)], axis=1)) - #for b2 in range(max(0, bb[1])): - block_shift += prev_npts_local''' + Returns + -------- + petsc_index : int + The global PETSc index. The 0 index is only owned by the first process. + """ bb = block_indices[0] + # Get the number of points per block, per process and per dimension: npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d + # Get the local sizes per block and per process: local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k - #print(f'npts_local_per_block_per_process={npts_local_per_block_per_process}') - #print(f'local_sizes_per_block_per_process={local_sizes_per_block_per_process}') - #shift_per_block_per_process = np.sum(local_sizes_per_block_per_process[:][:]) + # Extract Cartesian decomposition of the Block where the node is: if isinstance(V, BlockVectorSpace): V = V.spaces[bb] cart = V.cart - # block_local_shift = get_block_local_shift(V) - # block_shift = block_local_shift[bb[0]] - nprocs = cart.nprocs + nprocs = cart.nprocs # Number of processes in each dimension ndim = cart.ndim + + # Get global starts and ends to find process owning the node. gs = cart.global_starts # Global variable ge = cart.global_ends # Global variable - - - '''#dnpts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process. - - - - - npts_local_perprocess = [ ge_i - gs_i + 1 for gs_i, ge_i in zip(gs, ge)] #Global variable - npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable - localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(cart.comm.Get_size())] #Global variable''' - jj = ndarray_indices + if ndim == 1: + # Find to which process the node belongs to: proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] - #index_shift = 0 + np.sum(localsize_perprocess[0:proc_index], dtype=int) #Global variable - - #index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + + # Find the index shift corresponding to the block and the owner process: index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) + + # Compute the global PETSc index: global_index = index_shift + jj[0] - gs[0][proc_index] elif ndim == 2: + # Find to which process the node belongs to: proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] + proc_index = proc_y + proc_x*nprocs[1] - proc_index = proc_y + proc_x*nprocs[1]#proc_x + proc_y*nprocs[0] + # Find the index shift corresponding to the block and the owner process: index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) - #index_shift = 0#0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable - #global_index = jj[0] - gs[0][proc_x] + (jj[1] - gs[1][proc_y]) * npts_local_perprocess[proc_index][0] + index_shift - #global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] - #print(f'np.sum(local_sizes_per_block_per_process[:,:proc_index])={np.sum(local_sizes_per_block_per_process[:,:proc_index])}') - #print(f'np.sum(local_sizes_per_block_per_process[:bb,proc_index])={np.sum(local_sizes_per_block_per_process[:bb,proc_index])}') - #index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) + # Compute the global PETSc index: global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] - #print(f'shift={shift}') - #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) - - #hola = np.where(jj[0] in x_proc_ranges)#, gs[0], -1) - '''for k in range(V.cart.comm.Get_size()): - if k == V.cart.comm.Get_rank(): - print('\nRank', k, '\njj=', jj) - print('proc_x=', proc_x) - print('proc_y=', proc_y) - print('proc_index=', proc_index) - print('index_shift=', index_shift) - print('global_index=', global_index) - print('npts_local_perprocess=', npts_local_perprocess) - V.cart.comm.Barrier()''' + elif ndim == 3: + # Find to which process the node belongs to: proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0] + proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2] - proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2] #proc_x + proc_y*nprocs[0] - #index_shift = 0 + np.sum(local_sizes_per_block_per_process[bb][0:proc_index], dtype=int) #Global variable + # Find the index shift corresponding to the block and the owner process: index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) + + # Compute the global PETSc index: global_index = index_shift \ + jj[2] - gs[2][proc_z] \ + (jj[1] - gs[1][proc_y]) * npts_local_per_block_per_process[bb][proc_index][2] \ @@ -349,40 +174,6 @@ def psydac_to_global(V : VectorSpace, block_indices : tuple[int], ndarray_indice return global_index - -def psydac_to_singlenatural(V : VectorSpace, ndarray_indices : tuple[int]) -> int: - ndim = V.ndim - dnpts = V.cart.npts - - - jj = ndarray_indices - if ndim == 1: - singlenatural_index = 0 - elif ndim == 2: - singlenatural_index = jj[1] + jj[0] * dnpts[1] - - - #x_proc_ranges = np.array([range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]) - - #hola = np.where(jj[0] in x_proc_ranges)#, gs[0], -1) - '''for k in range(V.cart.comm.Get_size()): - if k == V.cart.comm.Get_rank(): - print('\nRank', k, '\njj=', jj) - print('proc_x=', proc_x) - print('proc_y=', proc_y) - print('proc_index=', proc_index) - print('index_shift=', index_shift) - print('global_index=', global_index) - print('npts_local_perprocess=', npts_local_perprocess) - V.cart.comm.Barrier()''' - - - else: - raise NotImplementedError( "Cannot handle more than 3 dimensions." ) - - - return singlenatural_index - def get_npts_local(V : VectorSpace) -> list: """ Compute the local number of nodes per dimension owned by the actual process. @@ -412,38 +203,8 @@ def get_npts_local(V : VectorSpace) -> list: if isinstance(V.spaces[b], StencilVectorSpace): npts_local_b = npts_local_b[0] npts_local_per_block.append(npts_local_b) - #npts_local_per_block = [ get_npts_local(V.spaces[b]) for b in range(V.n_blocks) ] - return npts_local_per_block - -def get_block_local_shift(V : VectorSpace) -> np.ndarray: - """ - Compute the local block shift per block. - This is a local variable, its value will be different for each process. - Parameter - --------- - V : VectorSpace - The distributed Psydac vector space. - - Returns - -------- - Numpy.ndarray - Local block shift per block. - In case of a StencilVectorSpace it returns the total local number of points in the space. - In case of a BlockVectorSpace the returned array has the same shape as the space block structure. - """ - if isinstance(V, StencilVectorSpace): - return np.prod(get_npts_local(V)) - - block_local_shift_per_block = [] - for b in range(V.n_blocks): - block_local_shift_per_block.append(get_block_local_shift(V.spaces[b])) - - block_local_shift_per_block = np.array(block_local_shift_per_block) - - block_local_shift_per_block = np.reshape(np.cumsum(block_local_shift_per_block), block_local_shift_per_block.shape) - - return block_local_shift_per_block + return npts_local_per_block def get_npts_per_block(V : VectorSpace) -> list: @@ -454,12 +215,10 @@ def get_npts_per_block(V : VectorSpace) -> list: if V.cart.comm: npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable - #localsize_perprocess = [np.prod(npts_local_perprocess[k]) for k in range(V.cart.comm.Get_size())] #Global variable - #else: - # #localsize_perprocess = [np.prod(npts_local_perprocess)] + return [npts_local_perprocess] - npts_local_per_block = [] #[ get_npts_per_block(V.spaces[b]) for b in range(V.n_blocks) ] + npts_local_per_block = [] for b in range(V.n_blocks): npts_b = get_npts_per_block(V.spaces[b]) if isinstance(V.spaces[b], StencilVectorSpace): @@ -468,127 +227,13 @@ def get_npts_per_block(V : VectorSpace) -> list: return npts_local_per_block - -def get_block_shift_per_process(V : VectorSpace) -> list: - #shift_per_process = [0] - npts_local_per_block = get_npts_per_block(V) - local_sizes_per_block = np.prod(npts_local_per_block, axis=-1) - - local_sizes_per_block = np.array(local_sizes_per_block) - - # Get nested block structure: - n_blocks = local_sizes_per_block.shape[:-1] - # Assume that all the blocks have the same number of processes: - n_procs = local_sizes_per_block.shape[-1] - print(f'n_procs={n_procs}') - local_sizes_per_process = np.array([local_sizes_per_block[:,k] for k in range(n_procs)]) - print(f'local_sizes_per_process={local_sizes_per_process}') - - #print(f'np.sum(local_sizes_per_process[:k,1:])={np.sum(local_sizes_per_process[:1,1:])}') - shift_per_process = [0]+[np.sum(local_sizes_per_process[:k,1:]) for k in range(1,n_procs)] - - - - #local_sizes_per_process = np.sum(local_sizes_per_process[1:], axis=1) - print(f'shift_per_process={shift_per_process}') - - #shift_per_process = [0] + [ np.sum(local_sizes_per_process[:k-1]) for k in range(1, n_procs)] - - - - '''if isinstance(V, StencilVectorSpace): - n_procs = 1 if not V.cart.comm else V.cart.comm.Get_size() - - if V.cart.comm: - localsize_perprocess = [np.prod(npts_local_per_block[0][k]) for k in range(n_procs)] #Global variable - else: - localsize_perprocess = [np.prod(npts_local_per_block[k]) for k in range(n_procs)] #Global variable''' - - #for b_lvl in range(len(n_blocks)): - # for b in range(n_blocks[b_lvl]): - - '''for k in range(n_procs): - shift_k = 0 - for b in range(n_blocks[0]): - #npts_local_per_process = npts_local_per_block[b] - #shift_k += np.prod(npts_local_per_process[k]) - #if b != len(shift_per_process): - shift_k += local_sizes_per_block[b][k] - shift_per_process.append(shift_k)''' - '''print(f'n_blocks={n_blocks}') - for k in range(n_procs): - shift_k = 0 - for b in range(n_blocks[0]): - print(f'k={k}, b={b}, local_sizes_per_block={local_sizes_per_block[:,k]}') - #accumulated_local_size = local_sizes_per_block[:b]#[k] - - if b == 1: - accumulated_local_size = local_sizes_per_block[0][k] - else: - accumulated_local_size = local_sizes_per_block[b][k] - shift_k += np.sum(accumulated_local_size) - shift_per_process.append(shift_k)''' - - return shift_per_process - - - -def flatten_vec( vec ): - """ Return the flattened 1D array values and indices owned by the process of the given vector. - - Parameters - ---------- - vec : psydac.linalg.stencil.StencilVector | psydac.linalg.block.BlockVector - Psydac vector to be flattened - - Returns - ------- - indices: numpy.ndarray - The global indices of the data array collapsed into one dimension. - - array : numpy.ndarray - A copy of the data array collapsed into one dimension. - - """ - - if isinstance(vec, StencilVector): - npts = vec.space.npts - idx = tuple( slice(m*p,-m*p) for m,p in zip(vec.pads, vec.space.shifts) ) - shape = vec._data[idx].shape - starts = vec.space.starts - indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts, xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) - data = vec._data[idx].flatten() - vec = coo_matrix( - (data,(indices,indices)), - shape = [vec.space.dimension,vec.space.dimension], - dtype = vec.space.dtype) - - elif isinstance(vec, BlockVector): - vecs = [flatten_vec(b) for b in vec.blocks] - vecs = [coo_matrix((v[1],(v[0],v[0])), - shape=[vs.space.dimension,vs.space.dimension], - dtype=vs.space.dtype) for v,vs in zip(vecs, vec.blocks)] - - blocks = [[None]*len(vecs) for v in vecs] - for i,v in enumerate(vecs): - blocks[i][i] = v - - vec = bmat(blocks,format='coo') - - else: - raise TypeError("Expected StencilVector or BlockVector, found instead {}".format(type(vec))) - - array = vec.data - indices = vec.row - return indices, array - def vec_topetsc( vec ): """ Convert vector from Psydac format to a PETSc.Vec object. Parameters ---------- vec : psydac.linalg.stencil.StencilVector | psydac.linalg.block.BlockVector - Psydac StencilVector or BlockVector. + Psydac StencilVector or BlockVector. In the case of a BlockVector, only the case where the blocks are StencilVector is implemented. Returns ------- @@ -598,39 +243,31 @@ def vec_topetsc( vec ): from petsc4py import PETSc if isinstance(vec.space, BlockVectorSpace) and any([isinstance(vec.space.spaces[b], BlockVectorSpace) for b in range(len(vec.space.spaces))]): - raise NotImplementedError('Block of blocks not implemented.') + raise NotImplementedError('Conversion for block of blocks not implemented.') if isinstance(vec, StencilVector): carts = [vec.space.cart] elif isinstance(vec.space, BlockVectorSpace): carts = [] for b in range(vec.n_blocks): - if isinstance(vec.space.spaces[b], StencilVectorSpace): - carts.append(vec.space.spaces[b].cart) - - elif isinstance(vec.space.spaces[b], BlockVectorSpace): - carts2 = [] - for b2 in range(vec.space.spaces[b].n_blocks): - if isinstance(vec.space.spaces[b][b2], StencilVectorSpace): - carts2.append(vec.space.spaces[b][b2].cart) - else: - raise NotImplementedError( "Cannot handle more than block of a block." ) - carts.append(carts2) + carts.append(vec.space.spaces[b].cart) + n_blocks = 1 if isinstance(vec, StencilVector) else vec.n_blocks + # Get the number of points local to the current process: + npts_local = get_npts_local(vec.space) # indexed [block, dimension]. Different for each process. - - npts_local = get_npts_local(vec.space) #[[ e - s + 1 for s, e in zip(cart.starts, cart.ends)] for cart in carts] #Number of points in each dimension within each process. Different for each process. - - comms = [cart.global_comm for cart in carts] - + # Number of dimensions for each cart: ndims = [cart.ndim for cart in carts] + globalsize = vec.space.dimension + + # Sum over the blocks to get the total local size + localsize = np.sum(np.prod(npts_local, axis=1)) - gvec = PETSc.Vec().create(comm=comms[0]) + gvec = PETSc.Vec().create(comm=carts[0].global_comm) - globalsize = vec.space.dimension - localsize = np.sum(np.prod(npts_local, axis=1)) # Sum over all the blocks + # Set global and local size: gvec.setSizes(size=(localsize, globalsize)) gvec.setFromOptions() @@ -639,35 +276,32 @@ def vec_topetsc( vec ): petsc_indices = [] petsc_data = [] - s = [cart.starts for cart in carts] - ghost_size = [[pi*mi for pi,mi in zip(cart.pads, cart.shifts)] for cart in carts] - - n_blocks = 1 if isinstance(vec, StencilVector) else vec.n_blocks - vec_block = vec for b in range(n_blocks): if isinstance(vec, BlockVector): vec_block = vec.blocks[b] + + s = carts[b].starts + ghost_size = [pi*mi for pi,mi in zip(carts[b].pads, carts[b].shifts)] if ndims[b] == 1: for i1 in range(npts_local[b][0]): - value = vec_block._data[i1 + ghost_size[b][0]] + value = vec_block._data[i1 + ghost_size[0]] if value != 0: - i1_n = s[b][0] + i1 - i_g = psydac_to_global(vec.space, (b,), (i1_n,)) + i1_n = s[0] + i1 + i_g = psydac_to_petsc_global(vec.space, (b,), (i1_n,)) petsc_indices.append(i_g) petsc_data.append(value) elif ndims[b] == 2: for i1 in range(npts_local[b][0]): for i2 in range(npts_local[b][1]): - value = vec_block._data[i1 + ghost_size[b][0], i2 + ghost_size[b][1]] + value = vec_block._data[i1 + ghost_size[0], i2 + ghost_size[1]] if value != 0: - i1_n = s[b][0] + i1 - i2_n = s[b][1] + i2 - i_g = psydac_to_global(vec.space, (b,), (i1_n, i2_n)) - #print(f'Rank {comms[b].Get_rank()}, Block {b}: i1_n = {i1_n}, i2_n = {i2_n}, i_g = {i_g}') + i1_n = s[0] + i1 + i2_n = s[1] + i2 + i_g = psydac_to_petsc_global(vec.space, (b,), (i1_n, i2_n)) petsc_indices.append(i_g) petsc_data.append(value) @@ -675,37 +309,30 @@ def vec_topetsc( vec ): for i1 in np.arange(npts_local[b][0]): for i2 in np.arange(npts_local[b][1]): for i3 in np.arange(npts_local[b][2]): - value = vec_block._data[i1 + ghost_size[b][0], i2 + ghost_size[b][1], i3 + ghost_size[b][2]] + value = vec_block._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2]] if value != 0: - i1_n = s[b][0] + i1 - i2_n = s[b][1] + i2 - i3_n = s[b][2] + i3 - i_g = psydac_to_global(vec.space, (b,), (i1_n, i2_n, i3_n)) + i1_n = s[0] + i1 + i2_n = s[1] + i2 + i3_n = s[2] + i3 + i_g = psydac_to_petsc_global(vec.space, (b,), (i1_n, i2_n, i3_n)) petsc_indices.append(i_g) petsc_data.append(value) + # Set the values. The values are stored in a cache memory. + gvec.setValues(petsc_indices, petsc_data, addv=PETSc.InsertMode.ADD_VALUES) #The addition mode the values is necessary when periodic BC - gvec.setValues(petsc_indices, petsc_data, addv=PETSc.InsertMode.ADD_VALUES) #Adding the values is necessary when periodic BC - - # Assemble vector - gvec.assemble() # Here PETSc exchanges global communication. The block corresponding to a certain process is not necessarily the same block in the Psydac StencilVector. - - vec_arr = vec.toarray() - for k in range(comms[0].Get_size()): - if k == comms[0].Get_rank(): - print(f'Rank {k}: vec={vec_arr}, petsc_indices={petsc_indices}, data={petsc_data}, s={s}, npts_local={npts_local}, gvec={gvec.array.real}') - comms[0].Barrier() + # Assemble vector with the values from the cache. Here it is where PETSc exchanges global communication. + gvec.assemble() return gvec - def mat_topetsc( mat ): """ Convert operator from Psydac format to a PETSc.Mat object. Parameters ---------- - mat : psydac.linalg.stencil.StencilMatrix | psydac.linalg.basic.LinearOperator | psydac.linalg.block.BlockLinearOperator - Psydac operator + mat : psydac.linalg.stencil.StencilMatrix | psydac.linalg.block.BlockLinearOperator + Psydac operator. In the case of a BlockLinearOperator, only the case where the blocks are StencilMatrix is implemented. Returns ------- @@ -715,24 +342,14 @@ def mat_topetsc( mat ): from petsc4py import PETSc + assert isinstance(mat, StencilMatrix) or isinstance(mat, BlockLinearOperator), 'Conversion only implemented for StencilMatrix and BlockLinearOperator.' + + if (isinstance(mat.domain, BlockVectorSpace) and any([isinstance(mat.domain.spaces[b], BlockVectorSpace) for b in range(len(mat.domain.spaces))]))\ or (isinstance(mat.codomain, BlockVectorSpace) and any([isinstance(mat.codomain.spaces[b], BlockVectorSpace) for b in range(len(mat.codomain.spaces))])): - raise NotImplementedError('Block of blocks not implemented.') - - '''if isinstance(mat, StencilMatrix): - dcart = mat.domain.cart - ccart = mat.codomain.cart - elif isinstance(mat.domain.spaces[0], StencilVectorSpace): - dcart = mat.domain.spaces[0].cart - elif isinstance(mat.domain.spaces[0], BlockVectorSpace): - dcart = mat.domain.spaces[0][0].cart - - if isinstance(mat._codomain, StencilVectorSpace): - ccart = mat.codomain.cart - elif isinstance(mat.codomain.spaces[0], StencilVectorSpace): - ccart = mat.codomain.spaces[0].cart - elif isinstance(mat.codomain.spaces[0], BlockVectorSpace): - ccart = mat.codomain.spaces[0][0].cart ''' + raise NotImplementedError('Conversion for block of blocks not implemented.') + + if isinstance(mat.domain, StencilVectorSpace): dcarts = [mat.domain.cart] @@ -748,131 +365,48 @@ def mat_topetsc( mat ): for b in range(len(mat.codomain.spaces)): ccarts.append(mat.codomain.spaces[b].cart) - n_blocks = (len(ccarts), len(dcarts)) nonzero_block_indices = ((0,0),) if isinstance(mat, StencilMatrix) else mat.nonzero_block_indices + mat.update_ghost_regions() + mat.remove_spurious_entries() - '''if isinstance(mat, StencilMatrix): - dcarts = [mat.domain.cart] - ccarts = [mat.codomain.cart] - nonzero_block_indices = ((0,0),) - n_blocks = (1,1) - elif isinstance(mat, BlockLinearOperator): - dcarts = [] - ccarts = [] - for b in range(len(mat.domain.spaces)): - dcarts.append(mat.domain.spaces[b].cart) - for b in range(len(mat.codomain.spaces)): - ccarts.append(mat.codomain.spaces[b].cart) - - nonzero_block_indices = mat.nonzero_block_indices - n_blocks = (len(dcarts), len(ccarts)) ''' + # Number of dimensions for each cart: + dndims = [dcart.ndim for dcart in dcarts] + cndims = [ccart.ndim for ccart in ccarts] + # Get global number of points per block: + dnpts = [dcart.npts for dcart in dcarts] # indexed [block, dimension]. Same for all processes. + # Get the number of points local to the current process: + dnpts_local = get_npts_local(mat.domain) # indexed [block, dimension]. Different for each process. + cnpts_local = get_npts_local(mat.codomain) # indexed [block, dimension]. Different for each process. + globalsize = mat.shape - dcomms = [dcart.global_comm for dcart in dcarts] - dcomm = dcomms[0] - #ccomms = [ccart.global_comm for ccart in ccarts] + # Sum over the blocks to get the total local size + localsize = (np.sum(np.prod(cnpts_local, axis=1)), np.sum(np.prod(dnpts_local, axis=1))) - mat.update_ghost_regions() - mat.remove_spurious_entries() + gmat = PETSc.Mat().create(comm=dcarts[0].global_comm) - dndims = [dcart.ndim for dcart in dcarts] - cndims = [ccart.ndim for ccart in ccarts] - dnpts = [dcart.npts for dcart in dcarts] - cnpts = [ccart.npts for ccart in ccarts] - - ''' - dstarts = dcart.starts - dends = dcart.ends - dpads = dcart.pads - dshifts = dcart.shifts - - - cndim = ccart.ndim - cstarts = ccart.starts - cends = ccart.ends - #cpads = ccart.pads - #cshifts = ccart.shifts - cnpts = ccart.npts ''' - - dnpts_local = get_npts_local(mat.domain) #[ e - s + 1 for s, e in zip(dstarts, dends)] #Number of points in each dimension within each process. Different for each process. - cnpts_local = get_npts_local(mat.codomain) #[ e - s + 1 for s, e in zip(cstarts, cends)] - - - - '''mat_dense = mat.tosparse().todense() - for k in range(dcomm.Get_size()): - if k == dcomm.Get_rank(): - print('\nRank ', k) - print('dstarts=', dstarts) - print('dends=', dends) - print('cstarts=', cstarts) - print('cends=', cends) - print('dnpts=', dnpts) - print('cnpts=', cnpts) - print('dnpts_local=', dnpts_local) - print('cnpts_local=', cnpts_local) - #print('mat_dense=\n', mat_dense[:3]) - #print('mat._data.shape=\n', mat._data.shape) - #print('dindex_shift=', dindex_shift) - #print('cindex_shift=', cindex_shift) - print('ccart.global_starts=', ccart.global_starts) - print('ccart.global_ends=', ccart.global_ends) - #print('mat._data=\n', mat._data) - - dcomm.Barrier() - ccomm.Barrier() ''' - - - globalsize = mat.shape #equivalent to (np.prod(dnpts), np.prod(cnpts)) #Tuple of integers - localsize = (np.sum(np.prod(cnpts_local, axis=1)), np.sum(np.prod(dnpts_local, axis=1))) #(np.prod(cnpts_local)*n_block_rows, np.prod(dnpts_local)*n_block_cols) - - gmat = PETSc.Mat().create(comm=dcomm) - - gmat.setSizes(size=((localsize[0], globalsize[0]), (localsize[1], globalsize[1]))) #((local_rows, rows), (local_columns, columns)) + # Set global and local sizes: size=((local_rows, rows), (local_columns, columns)) + gmat.setSizes(size=((localsize[0], globalsize[0]), (localsize[1], globalsize[1]))) - if dcomm: + if dcarts[0].global_comm: # Set PETSc sparse parallel matrix type gmat.setType("mpiaij") else: + # Set PETSc sparse sequential matrix type gmat.setType("seqaij") gmat.setFromOptions() gmat.setUp() - print('gmat.getSizes()=', gmat.getSizes()) - - '''mat_coo = mat.tosparse() - rows_coo, cols_coo, data_coo = mat_coo.row, mat_coo.col, mat_coo.data - - mat_csr = mat_coo.tocsr() - mat_csr.eliminate_zeros() - data, indices, indptr = mat_csr.data, mat_csr.indices, mat_csr.indptr - #indptr_chunk = indptr[indptr >= dcart.starts[0] and indptr <= dcart.ends[0]] - #indices_chunk = indices[indices >= dcart.starts[1] and indices <= dcart.ends[1]] - - mat_coo_local = mat.tocoo_local() - rows_coo_local, cols_coo_local, data_coo_local = mat_coo_local.row, mat_coo_local.col, mat_coo_local.data''' - - - #mat.remove_spurious_entries() - - I = [0] - J = [] - V = [] - #J2 = [] - rowmap = [] - #rowmap2 = [] - - #s = [dcart.starts for dcart in dcarts] - #p = [dcart.pads for dcart in dcarts] - #m = [dcart.shifts for dcart in dcarts] - ghost_size = [[pi*mi for pi,mi in zip(dcart.pads, dcart.shifts)] for dcart in dcarts] + I = [0] # Row pointers + J = [] # Column indices + V = [] # Values + rowmap = [] # Row indices of rows containing non-zeros mat_block = mat - for bc, bd in nonzero_block_indices: if isinstance(mat, BlockLinearOperator): mat_block = mat.blocks[bc][bd] @@ -883,24 +417,19 @@ def mat_topetsc( mat ): ghost_size = [pi*mi for pi,mi in zip(p, m)] if dndims[bd] == 1 and cndims[bc] == 1: - - #if isinstance(mat, StencilMatrix): - # data = mat._data - #elif isinstance(mat, BlockLinearOperator): - # data = mat.blocks[bb[0]][bb[1]]._data for i1 in range(dnpts_local[bd][0]): nnz_in_row = 0 i1_n = s[0] + i1 - i_g = psydac_to_global(mat.codomain, (bc,), (i1_n,)) + i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n,)) for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): value = mat_block._data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] - j1_n = (i1_n + k1)%dnpts[bd][0] + + j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC if value != 0: - - j_g = psydac_to_global(mat.domain, (bd,), (j1_n, )) + j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, )) if nnz_in_row == 0: rowmap.append(i_g) @@ -913,39 +442,29 @@ def mat_topetsc( mat ): I.append(I[-1] + nnz_in_row) elif dndims[bd] == 2 and cndims[bc] == 2: - for i1 in np.arange(dnpts_local[bd][0]):#dindices[0]: #range(dpads[0]*dshifts[0] + dnpts_local[0]): - for i2 in np.arange(dnpts_local[bd][1]):#dindices[1]: #range(dpads[1]*dshifts[1] + dnpts_local[1]): + for i1 in np.arange(dnpts_local[bd][0]): + for i2 in np.arange(dnpts_local[bd][1]): nnz_in_row = 0 i1_n = s[0] + i1 i2_n = s[1] + i2 - i_g = psydac_to_global(mat.codomain, (bc,), (i1_n, i2_n)) - #i_n = psydac_to_singlenatural(mat.codomain, (i1_n,i2_n)) - - '''for k in range(dcomm.Get_size()): - if k == dcomm.Get_rank(): - print(f'Rank {k}: ({i1_n}, {i2_n}), i_n= {i_n}, i_g= {i_g}')''' + i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n)) for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): - value = mat_block._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] - #(j1_n, j2_n) is the Psydac natural multi-index (like a grid) - j1_n = (i1_n + k1)%dnpts[bd][0] #- p[0]*m[0] - j2_n = (i2_n + k2)%dnpts[bd][1] #- p[1]*m[1] + j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC + j2_n = (i2_n + k2) % dnpts[bd][1] # modulus is necessary for periodic BC - if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]): - j_g = psydac_to_global(mat.domain, (bd,), (j1_n, j2_n)) + if value != 0: + j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n)) if nnz_in_row == 0: rowmap.append(i_g) - #rowmap2.append(psydac_to_singlenatural(mat.domain, (i1_n,i2_n))) J.append(j_g) - #J2.append(psydac_to_singlenatural(mat.domain, (j1_n,j2_n))) - V.append(value) nnz_in_row += 1 @@ -960,376 +479,34 @@ def mat_topetsc( mat ): i1_n = s[0] + i1 i2_n = s[1] + i2 i3_n = s[2] + i3 - i_g = psydac_to_global(mat.codomain, (bc,), (i1_n, i2_n, i3_n)) + i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n, i3_n)) for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): for k2 in range(-p[1]*m[1], p[1]*m[1] + 1): for k3 in range(-p[2]*m[2], p[2]*m[2] + 1): value = mat_block._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] - j1_n = (i1_n + k1)%dnpts[bd][0] #- ghost_size[0] - j2_n = (i2_n + k2)%dnpts[bd][1] # - ghost_size[1] - j3_n = (i3_n + k3)%dnpts[bd][2] # - ghost_size[2] + j1_n = (i1_n + k1)%dnpts[bd][0] # modulus is necessary for periodic BC + j2_n = (i2_n + k2)%dnpts[bd][1] # modulus is necessary for periodic BC + j3_n = (i3_n + k3)%dnpts[bd][2] # modulus is necessary for periodic BC - if value != 0: #and j1_n in range(dnpts[0]) and j2_n in range(dnpts[1]) and j3_n in range(dnpts[2]): - j_g = psydac_to_global(mat.domain, (bd,), (j1_n, j2_n, j3_n)) + if value != 0: + j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n, j3_n)) if nnz_in_row == 0: rowmap.append(i_g) J.append(j_g) V.append(value) + nnz_in_row += 1 I.append(I[-1] + nnz_in_row) - - - - if not dcomm: - print() - #print('mat_dense=', mat_dense) - #print('gmat_dense=', gmat_dense) - else: - for k in range(dcomm.Get_size()): - if k == dcomm.Get_rank(): - print('\n\nRank ', k) - #print('mat_dense=\n', mat_dense) - #print('petsc_row_indices=\n', petsc_row_indices) - #print('petsc_col_indices=\n', petsc_col_indices) - #print('petsc_data=\n', petsc_data) - #print('owned_rows=', owned_rows) - #print('mat_dense=\n', mat_dense) - print('I=', I) - print('rowmap=', rowmap) - #print('rowmap2=', rowmap2) - print('J=\n', J) - #print('J2=\n', J2) - #print('V=\n', V) - #print('gmat_dense=\n', gmat_dense) - print('\n\n============') - dcomm.Barrier() - - import time - t_prev = time.time() - '''for k in range(len(rows_coo)): - gmat.setValues(rows_coo[k] - dcart.global_starts[0][comm.Get_rank()], cols_coo[k], data_coo[k]) - ''' - #gmat.setValuesCSR([r - dcart.global_starts[0][comm.Get_rank()] for r in indptr[1:]], indices, data) - #gmat.setValuesLocalCSR(local_indptr, indices, data)#, addv=PETSc.InsertMode.ADD_VALUES) + # Set the values using IJV&rowmap format. The values are stored in a cache memory. gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC - - print('Rank ', dcomm.Get_rank() if dcomm else '-', ': duration of setValuesIJV :', time.time()-t_prev) - - - - # Process inserted matrix entries - ################################################ - # Note 12.03.2024: - # In the assembly PETSc uses global communication to distribute the matrix in a different way than Psydac. - # For this reason, at the moment we cannot compare directly each distributed 'chunck' of the Psydac and the PETSc matrices. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ - t_prev = time.time() + # Assemble the matrix with the values from the cache. Here it is where PETSc exchanges global communication. gmat.assemble() - print('Rank ', dcomm.Get_rank() if dcomm else '-', ': duration of Mat assembly :', time.time()-t_prev) - - - ''' - if not dcomm: - gmat_dense = gmat.getDenseArray() - else: - gmat_dense = gmat.getDenseLocalMatrix() - dcomm.Barrier() - ''' return gmat - - -def mat_topetsc_old( mat ): - """ Convert operator from Psydac format to a PETSc.Mat object. - - Parameters - ---------- - mat : psydac.linalg.stencil.StencilMatrix | psydac.linalg.basic.LinearOperator | psydac.linalg.block.BlockLinearOperator - Psydac operator - - Returns - ------- - gmat : PETSc.Mat - PETSc Matrix - """ - - from petsc4py import PETSc - - if isinstance(mat, StencilMatrix): - dcart = mat.domain.cart - ccart = mat.codomain.cart - elif isinstance(mat.domain.spaces[0], StencilVectorSpace): - dcart = mat.domain.spaces[0].cart - ccart = mat.codomain.spaces[0].cart - elif isinstance(mat.domain.spaces[0], BlockVectorSpace): - dcart = mat.domain.spaces[0][0].cart - ccart = mat.codomain.spaces[0][0].cart - - comm = dcart.global_comm - - - #print('mat.shape = ', mat.shape) - #print('rank: ', comm.Get_rank(), ', local_ncells=', dcart.domain_decomposition.local_ncells) - #print('rank: ', comm.Get_rank(), ', nprocs=', dcart.domain_decomposition.nprocs) - - - #recvbuf = np.empty(shape=(dcart.domain_decomposition.nprocs[0],1)) - #comm.allgather(sendbuf=dcart.domain_decomposition.local_ncells, recvbuf=recvbuf) - - #################################### - - ### SPLITTING DOMAIN - #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] - - '''boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if mat.domain.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(dcart.ndim)] - - - dim = dcart.ndim - sizes = dcart.npts - proc_sizes = dcart.nprocs - #ownership_ranges = [[ 1 + dcart.global_ends[p][k] - dcart.global_starts[p][k] - # for k in range(dcart.global_starts[p].size)] - # for p in range(len(dcart.global_starts))] - ownership_ranges = [[e - s + 1 for s,e in zip(starts, ends)] for starts, ends in zip(dcart.global_starts, dcart.global_ends)] - print('OWNership_ranges=', ownership_ranges) - Dmda = PETSc.DMDA().create(dim=dim, sizes=sizes, proc_sizes=proc_sizes, - ownership_ranges=ownership_ranges, comm=comm, - stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type)''' - - - '''### SPLITTING COEFFS - ownership_ranges = [[ 1 + dcart.global_ends[p][k] - dcart.global_starts[p][k] for k in range(dcart.global_starts[p].size)] for p in range(len(dcart.global_starts))] - #ownership_ranges = [comm.allgather(dcart.domain_decomposition.local_ncells[k]) for k in range(dcart.ndim)] - - print('MAT: ownership_ranges=', ownership_ranges) - print('MAT: dcart.domain_decomposition.nprocs=', *dcart.domain_decomposition.nprocs) - - boundary_type = [(PETSc.DM.BoundaryType.PERIODIC if mat.domain.periods[k] else PETSc.DM.BoundaryType.NONE) for k in range(dcart.ndim)] - - Dmda = PETSc.DMDA().create(dim=1, sizes=mat.shape, proc_sizes=[*dcart.domain_decomposition.nprocs,1], comm=comm, - ownership_ranges=[ownership_ranges[0], [mat.shape[1]]], stencil_type=PETSc.DMDA.StencilType.BOX, boundary_type=boundary_type) - ''' - - ''' if comm: - dcart_petsc = dcart.topetsc() - d_LG_map = dcart_petsc.l2g_mapping - - ccart_petsc = ccart.topetsc() - c_LG_map = ccart_petsc.l2g_mapping - - print('Rank', comm.Get_rank(), ': dcart_petsc.local_size = ', dcart_petsc.local_size) - print('Rank', comm.Get_rank(), ': dcart_petsc.local_shape = ', dcart_petsc.local_shape) - print('Rank', comm.Get_rank(), ': ccart_petsc.local_size = ', ccart_petsc.local_size) - print('Rank', comm.Get_rank(), ': ccart_petsc.local_shape = ', ccart_petsc.local_shape) - - if not comm: - print('') - else: - for k in range(comm.Get_size()): - if comm.Get_rank() == k: - print('\nRank ', k) - print('mat=\n', mat.tosparse().toarray()) - print('dcart.local_ncells=', dcart.domain_decomposition.local_ncells) - print('ccart.local_ncells=', ccart.domain_decomposition.local_ncells) - print('dcart._grids=', dcart._grids) - print('ccart._grids=', ccart._grids) - print('dcart.starts =', dcart.starts) - print('dcart.ends =', dcart.ends) - print('ccart.starts =', ccart.starts) - print('ccart.ends =', ccart.ends) - print('dcart.shape=', dcart.shape) - print('dcart.npts=', dcart.npts) - print('ccart.shape=', ccart.shape) - print('ccart.npts=', ccart.npts) - print('\ndcart.indices=', dcart_petsc.indices) - print('ccart.indices=', ccart_petsc.indices) - print('dcart.global_starts=', dcart.global_starts) - print('dcart.global_ends=', dcart.global_ends) - print('ccart.global_starts=', ccart.global_starts) - print('ccart.global_ends=', ccart.global_ends) - comm.Barrier() - ''' - - print('Dmda.getOwnershipRanges()=', Dmda.getOwnershipRanges()) - print('Dmda.getRanges()=', Dmda.getRanges()) - - #LGmap = PETSc.LGMap().create(indices=) - - #dm = PETSc.DM().create(comm=comm) - gmat = PETSc.Mat().create(comm=comm) - - gmat.setDM(Dmda) - # Set GLOBAL matrix size - #gmat.setSizes(mat.shape) - - #gmat.setSizes(size=((dcart.domain_decomposition.local_ncells[0],mat.shape[0]), (mat.shape[1],mat.shape[1])), - # bsize=None) - #gmat.setSizes([[dcart_petsc.local_size, mat.shape[0]], [ccart_petsc.local_size, mat.shape[1]]]) #mat.setSizes([[nrl, nrg], [ncl, ncg]]) - - local_rows = np.prod([e - s + 1 for s, e in zip(ccart.starts, ccart.ends)]) - #local_columns = np.prod([p*m for p, m in zip(mat.domain.pads, mat.domain.shifts)]) - local_columns = np.prod([e - s + 1 for s, e in zip(dcart.starts, dcart.ends)]) - rows = mat.shape[0] - columns = mat.shape[1] - gmat.setSizes(size=((local_rows, rows), (local_columns, columns))) #((local_rows, rows), (local_columns, columns)) - - if comm: - # Set PETSc sparse parallel matrix type - gmat.setType("mpiaij") - #gmat.setLGMap(c_LG_map, d_LG_map) - else: - # Set PETSc sequential matrix type - gmat.setType("seqaij") - - gmat.setFromOptions() - gmat.setUp() - - print('gmat.getSizes()=', gmat.getSizes()) - - mat_coo = mat.tosparse() - rows_coo, cols_coo, data_coo = mat_coo.row, mat_coo.col, mat_coo.data - - mat_csr = mat_coo.tocsr() - mat_csr.eliminate_zeros() - data, indices, indptr = mat_csr.data, mat_csr.indices, mat_csr.indptr - #indptr_chunk = indptr[indptr >= dcart.starts[0] and indptr <= dcart.ends[0]] - #indices_chunk = indices[indices >= dcart.starts[1] and indices <= dcart.ends[1]] - - mat_coo_local = mat.tocoo_local() - rows_coo_local, cols_coo_local, data_coo_local = mat_coo_local.row, mat_coo_local.col, mat_coo_local.data - - local_petsc_index = psydac_to_petsc_local(mat.domain, [], [2,0]) - global_petsc_index = get_petsc_local_to_global_shift(mat.domain) - - - print('dcart.global_starts=', dcart.global_starts) - print('dcart.global_ends=', dcart.global_ends) - - '''for k in range(comm.Get_size()): - if comm.Get_rank() == k: - local_indptr = indptr[1 + dcart.global_starts[0][comm.Get_rank()]:2+dcart.global_ends[0][comm.Get_rank()]] - local_indptr = [row_pter - dcart.global_starts[0][comm.Get_rank()] for row_pter in local_indptr] - local_indptr = [0, *local_indptr] - comm.Barrier()''' - - '''if comm.Get_rank() == 0: - local_indptr = [3,6] - else: - local_indptr = [3]''' - #local_indptr = indptr[1+ dcart.global_starts[comm.Get_rank()][0]:dcart.global_ends[comm.Get_rank()][0]+2] - #local_indptr = [0, *local_indptr] - - if not comm: - print('178:indptr = ', indptr) - print('178:indices = ', indices) - print('178:data = ', data) - else: - for k in range(comm.Get_size()): - if comm.Get_rank() == k: - print('\nRank ', k) - print('mat=\n', mat_csr.toarray()) - print('CSR: indptr = ', indptr) - #print('local_indptr = ', local_indptr) - print('CSR: indices = ', indices) - print('CSR: data = ', data) - - - '''print('data_coo_local=', data_coo_local) - print('rows_coo_local=', rows_coo_local) - print('cols_coo_local=', cols_coo_local) - - print('data_coo=', data_coo) - print('rows_coo=', rows_coo) - print('cols_coo=', cols_coo)''' - - comm.Barrier() - - '''rows, cols, data = mat_coo.row, mat_coo.col, mat_coo.data - for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('\nRank ', k, ': data.size =', data.size) - print('rows=', rows) - print('cols=', cols) - print('data=', data) - comm.Barrier()''' - - - import time - t_prev = time.time() - '''for k in range(len(rows_coo)): - gmat.setValues(rows_coo[k] - dcart.global_starts[0][comm.Get_rank()], cols_coo[k], data_coo[k]) - ''' - #gmat.setValuesCSR([r - dcart.global_starts[0][comm.Get_rank()] for r in indptr[1:]], indices, data) - #gmat.setValuesLocalCSR(local_indptr, indices, data)#, addv=PETSc.InsertMode.ADD_VALUES) - - r = gmat.Stencil(0,0,0) - c = gmat.Stencil(0,0,0) - s = np.prod([p*m for p, m in zip(mat.domain.pads, mat.domain.shifts)]) - print('r=', r) - for k in range(comm.Get_size()): - if comm.Get_rank() == k: - print('\nrank ', k) - print('mat._data=', mat._data) - - print('mat.domain.pads=', mat.domain.pads) - print('mat.domain.shifts=', mat.domain.shifts) - print('mat._data (without ghost)=', mat._data[s:-s]) - - comm.Barrier() - - gmat.setValuesStencil(mat._data[s:-s]) - - - print('Rank ', comm.Get_rank() if comm else '-', ': duration of setValuesCSR :', time.time()-t_prev) - - '''if comm: - # Preallocate number of nonzeros per row - #row_lengths = np.count_nonzero(rows[None,:] == np.unique(rows)[:,None], axis=1).max() - - ##very slow: - #row_lengths = 0 - #for r in np.unique(rows): - # row_lengths = max(row_lengths, np.nonzero(rows==r)[0].size) - - row_lengths = np.unique(rows, return_counts=True)[1].max() - - # NNZ is the number of non-zeros per row for the local portion of the matrix - t_prev = time.time() - NNZ = comm.allreduce(row_lengths, op=MPI.MAX) - print('Rank ', comm.Get_rank() , ': duration of comm.allreduce :', time.time()-t_prev) - - t_prev = time.time() - gmat.setPreallocationNNZ(NNZ) - print('Rank ', comm.Get_rank() , ': duration of setPreallocationNNZ :', time.time()-t_prev) - - t_prev = time.time() - # Fill-in matrix values - for i in range(rows.size): - # The values have to be set in "addition mode", otherwise the default just takes the new value. - # This is here necessary, since the COO format can contain repeated entries. - gmat.setValues(rows[i], cols[i], data[i])#, addv=PETSc.InsertMode.ADD_VALUES) - - print('Rank ', comm.Get_rank() , ': duration of setValues :', time.time()-t_prev)''' - - # Process inserted matrix entries - ################################################ - # Note 12.03.2024: - # In the assembly PETSc uses global communication to distribute the matrix in a different way than Psydac. - # For this reason, at the moment we cannot compare directly each distributed 'chunck' of the Psydac and the PETSc matrices. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ - t_prev = time.time() - gmat.assemble() - print('Rank ', comm.Get_rank() if comm else '-', ': duration of Mat assembly :', time.time()-t_prev) - - return gmat diff --git a/psydac/linalg/utilities.py b/psydac/linalg/utilities.py index 127d97132..824f8757c 100644 --- a/psydac/linalg/utilities.py +++ b/psydac/linalg/utilities.py @@ -6,7 +6,7 @@ from psydac.linalg.basic import Vector from psydac.linalg.stencil import StencilVectorSpace, StencilVector from psydac.linalg.block import BlockVector, BlockVectorSpace -from psydac.linalg.topetsc import psydac_to_petsc_local, get_petsc_local_to_global_shift, petsc_to_psydac_local, global_to_psydac, get_npts_per_block +from psydac.linalg.topetsc import petsc_local_to_psydac, get_npts_per_block __all__ = ( 'array_to_psydac', @@ -75,9 +75,9 @@ def _array_to_psydac_recursive(x, u): #============================================================================== def petsc_to_psydac(x, Xh): - """Convert a PETSc.Vec object to a StencilVector or BlockVector. It assumes that PETSc was installed with the configuration for complex numbers. - We gather the petsc global vector in all the processes and extract the chunk owned by the Psydac Vector. - .. warning: This function will not work if the global vector does not fit in the process memory. + """ + Convert a PETSc.Vec object to a StencilVector or BlockVector. It assumes that PETSc was installed with the configuration for complex numbers. + Uses the index conversion functions in psydac.linalg.topetsc.py. Parameters ---------- @@ -87,119 +87,34 @@ def petsc_to_psydac(x, Xh): Returns ------- u : psydac.linalg.stencil.StencilVector | psydac.linalg.block.BlockVector - Psydac vector + Psydac vector. In the case of a BlockVector, the blocks must be StencilVector. The general case is not yet implemented. """ if isinstance(Xh, BlockVectorSpace): if any([isinstance(Xh.spaces[b], BlockVectorSpace) for b in range(len(Xh.spaces))]): raise NotImplementedError('Block of blocks not implemented.') + u = BlockVector(Xh) - - comm = x.comm#u[0][0].space.cart.global_comm - dtype = Xh._dtype#u[0][0].space.dtype - n_blocks = Xh.n_blocks - #sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) - #recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors + comm = x.comm + dtype = Xh._dtype localsize, globalsize = x.getSizes() - #assert globalsize == u.shape[0], 'Sizes of global vectors do not match' - + assert globalsize == u.shape[0], 'Sizes of global vectors do not match' - # Find shifts for process k: + # Find shift for process k: + # ..get number of points for each block, each process and each dimension: npts_local_per_block_per_process = np.array(get_npts_per_block(Xh)) #indexed [b,k,d] for block b and process k and dimension d + # ..get local sizes for each block and each process: local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + # ..sum the sizes over all the blocks and the previous processes: + index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:comm.Get_rank()], dtype=int) #global variable - index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:comm.Get_rank()]) #+ np.sum(local_sizes_per_block_per_process[:b,comm.Get_rank()]) for b in range(n_blocks)] - - #index_shift_per_block = [0 + np.sum(local_sizes_per_block_per_process[b][0:x.comm.Get_rank()], dtype=int) for b in range(n_blocks)] #Global variable - - print(f'rk={comm.Get_rank()}, local_sizes_per_block_per_process={local_sizes_per_block_per_process}, index_shift={index_shift}, u[0]._data={u[0]._data.shape}, u[1]._data={u[1]._data.shape}') - - - local_petsc_indices = np.arange(localsize) - global_petsc_indices = [] - psydac_indices = [] - block_indices = [] - for petsc_index in local_petsc_indices: - - block_index, psydac_index = global_to_psydac(Xh, petsc_index)#, comm=x.comm) - psydac_indices.append(psydac_index) - block_indices.append(block_index) - - - global_petsc_indices.append(petsc_index + index_shift) - - print(f'rank={comm.Get_rank()}, psydac_index = {psydac_index}, local_petsc_index={petsc_index}, petsc_global_index={global_petsc_indices[-1]}') - - - for block_index, psydac_index, petsc_index in zip(block_indices, psydac_indices, global_petsc_indices): - value = x.getValue(petsc_index) # Global index + for local_petsc_index in range(localsize): + block_index, psydac_index = petsc_local_to_psydac(Xh, local_petsc_index) + # Get value of local PETSc vector passing the global PETSc index + value = x.getValue(local_petsc_index + index_shift) if value != 0: - u[block_index[0]]._data[psydac_index] = value if dtype is complex else value.real + u[block_index[0]]._data[psydac_index] = value if dtype is complex else value.real # PETSc always handles dtype specified in the installation configuration - - - - '''if comm: - # Gather the global array in all the processors - ################################################ - # Note 12.03.2024: - # This global communication is at the moment necessary since PETSc distributes matrices and vectors different than Psydac. - # In order to avoid it, we would need that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ - comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) - else: - recvbuf[:] = x.array - - inds = 0 - for d in range(len(Xh.spaces)): - starts = [np.array(V.starts) for V in Xh.spaces[d].spaces] - ends = [np.array(V.ends) for V in Xh.spaces[d].spaces] - - for i in range(len(starts)): - idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.spaces[d].spaces[i].pads, u.space.spaces[d].spaces[i].shifts) ) - shape = tuple(ends[i]-starts[i]+1) - npts = Xh.spaces[d].spaces[i].npts - # compute the global indices of the coefficents owned by the process using starts and ends - indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts[i], xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) - vals = recvbuf[indices+inds] - - # With PETSc installation configuration for complex, all the numbers are by default complex. - # In the float case, the imaginary part must be truncated to avoid warnings. - u[d][i]._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) - - inds += np.prod(npts) - - else: - comm = u[0].space.cart.global_comm - dtype = u[0].space.dtype - sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) - recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors - - if comm: - # Gather the global array in all the procs - # TODO: Avoid this global communication with a DM Object (see note above). - comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) - else: - recvbuf[:] = x.array - - inds = 0 - starts = [np.array(V.starts) for V in Xh.spaces] - ends = [np.array(V.ends) for V in Xh.spaces] - for i in range(len(starts)): - idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.spaces[i].pads, u.space.spaces[i].shifts) ) - shape = tuple(ends[i]-starts[i]+1) - npts = Xh.spaces[i].npts - # compute the global indices of the coefficents owned by the process using starts and ends - indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts[i], xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) - vals = recvbuf[indices+inds] - - # With PETSc installation configuration for complex, all the numbers are by default complex. - # In the float case, the imaginary part must be truncated to avoid warnings. - u[i]._data[idx] = (vals if dtype is complex else vals.real).reshape(shape) - - inds += np.prod(npts)''' - elif isinstance(Xh, StencilVectorSpace): u = StencilVector(Xh) @@ -208,91 +123,26 @@ def petsc_to_psydac(x, Xh): localsize, globalsize = x.getSizes() assert globalsize == u.shape[0], 'Sizes of global vectors do not match' - '''index_shift = get_petsc_local_to_global_shift(Xh) - petsc_local_indices = np.arange(localsize) - petsc_indices = petsc_local_indices #+ index_shift - psydac_indices = [petsc_to_psydac_local(Xh, petsc_index) for petsc_index in petsc_indices]''' - - - # Find shifts for process k: - npts_local_per_block_per_process = np.array(get_npts_per_block(Xh)) #indexed [b,k,d] for block b and process k and dimension d - local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k - - index_shift = 0 + np.sum(local_sizes_per_block_per_process[0][0:x.comm.Get_rank()], dtype=int) #Global variable - - - - local_petsc_indices = np.arange(localsize) - global_petsc_indices = [] - psydac_indices = [] - block_indices = [] - for petsc_index in local_petsc_indices: - - block_index, psydac_index = global_to_psydac(Xh, petsc_index)#, comm=x.comm) - psydac_indices.append(psydac_index) - block_indices.append(block_index) - global_petsc_indices.append(petsc_index + index_shift) - - - - - #psydac_indices = [global_to_psydac(Xh, petsc_index, comm=x.comm) for petsc_index in petsc_indices] - - - '''if comm is not None: - for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('\nRank ', k) - print('petsc_indices=\n', petsc_indices) - print('psydac_indices=\n', psydac_indices) - print('index_shift=', index_shift) - comm.Barrier()''' - - for block_index, psydac_index, petsc_index in zip(block_indices, psydac_indices, global_petsc_indices): - value = x.getValue(petsc_index) # Global index + # Find shift for process k: + # ..get number of points for each process and each dimension: + npts_local_per_block_per_process = np.array(get_npts_per_block(Xh))[0] #indexed [k,d] for process k and dimension d + # ..get local sizes for each process: + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [k] for process k + # ..sum the sizes over all the previous processes: + index_shift = 0 + np.sum(local_sizes_per_block_per_process[:comm.Get_rank()], dtype=int) #global variable + + for local_petsc_index in range(localsize): + block_index, psydac_index = petsc_local_to_psydac(Xh, local_petsc_index) + # Get value of local PETSc vector passing the global PETSc index + value = x.getValue(local_petsc_index + index_shift) if value != 0: - u._data[psydac_index] = value if dtype is complex else value.real - - '''sendcounts = np.array(comm.allgather(len(x.array))) if comm else np.array([len(x.array)]) - recvbuf = np.empty(sum(sendcounts), dtype='complex') # PETSc installed with complex configuration only handles complex vectors - - if comm: - # Gather the global array in all the procs - # TODO: Avoid this global communication with a DM Object (see note above). - comm.Allgatherv(sendbuf=x.array, recvbuf=(recvbuf, sendcounts)) - else: - recvbuf[:] = x.array - - # compute the global indices of the coefficents owned by the process using starts and ends - starts = np.array(Xh.starts) - ends = np.array(Xh.ends) - shape = tuple(ends-starts+1) - npts = Xh.npts - indices = np.array([np.ravel_multi_index( [s+x for s,x in zip(starts, xx)], dims=npts, order='C' ) for xx in np.ndindex(*shape)] ) - idx = tuple( slice(m*p,-m*p) for m,p in zip(u.space.pads, u.space.shifts) ) - vals = recvbuf[indices] - - # With PETSc installation configuration for complex, all the numbers are by default complex. - # In the float case, the imaginary part must be truncated to avoid warnings. - u._data[idx] = (vals if dtype is complex else vals.real).reshape(shape)''' + u._data[psydac_index] = value if dtype is complex else value.real # PETSc always handles dtype specified in the installation configuration else: raise ValueError('Xh must be a StencilVectorSpace or a BlockVectorSpace') u.update_ghost_regions() - '''if comm is not None: - u_arr = u.toarray() - x_arr = x.array.real - for k in range(comm.Get_size()): - if k == comm.Get_rank(): - print('\nRank ', k) - print('u.toarray()=\n', u_arr) - #print('x.array=\n', x_arr) - #print('u._data=\n', u._data) - - comm.Barrier()''' - return u #============================================================================== From 6e3a348e54dab4cdb00dae02c0091fce39e5fc9c Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 22 May 2024 12:00:15 +0200 Subject: [PATCH 36/88] fix bugs --- psydac/linalg/topetsc.py | 66 +++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 564ea2702..7254ec5f6 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -375,6 +375,7 @@ def mat_topetsc( mat ): cndims = [ccart.ndim for ccart in ccarts] # Get global number of points per block: dnpts = [dcart.npts for dcart in dcarts] # indexed [block, dimension]. Same for all processes. + cnpts = [ccart.npts for ccart in ccarts] # indexed [block, dimension]. Same for all processes. # Get the number of points local to the current process: dnpts_local = get_npts_local(mat.domain) # indexed [block, dimension]. Different for each process. @@ -411,20 +412,20 @@ def mat_topetsc( mat ): if isinstance(mat, BlockLinearOperator): mat_block = mat.blocks[bc][bd] - s = dcarts[bd].starts - p = dcarts[bd].pads - m = dcarts[bd].shifts - ghost_size = [pi*mi for pi,mi in zip(p, m)] + cs = ccarts[bc].starts + dp = dcarts[bd].pads + dm = dcarts[bd].shifts + cghost_size = [pi*mi for pi,mi in zip(ccarts[bc].pads, ccarts[bc].shifts)] if dndims[bd] == 1 and cndims[bc] == 1: - for i1 in range(dnpts_local[bd][0]): + for i1 in range(cnpts_local[bc][0]): nnz_in_row = 0 - i1_n = s[0] + i1 + i1_n = cs[0] + i1 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n,)) - for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): - value = mat_block._data[i1 + ghost_size[0], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1)] + for k1 in range(-dp[0]*dm[0], dp[0]*dm[0] + 1): + value = mat_block._data[i1 + cghost_size[0], (k1 + dp[0]*dm[0])%(2*dp[0]*dm[0] + 1)] j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC @@ -439,21 +440,22 @@ def mat_topetsc( mat ): nnz_in_row += 1 - I.append(I[-1] + nnz_in_row) + if nnz_in_row > 0: + I.append(I[-1] + nnz_in_row) elif dndims[bd] == 2 and cndims[bc] == 2: - for i1 in np.arange(dnpts_local[bd][0]): - for i2 in np.arange(dnpts_local[bd][1]): + for i1 in np.arange(cnpts_local[bc][0]): + for i2 in np.arange(cnpts_local[bc][1]): nnz_in_row = 0 - i1_n = s[0] + i1 - i2_n = s[1] + i2 + i1_n = cs[0] + i1 + i2_n = cs[1] + i2 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n)) - for k1 in range(- p[0]*m[0], p[0]*m[0] + 1): - for k2 in range(- p[1]*m[1], p[1]*m[1] + 1): - value = mat_block._data[i1 + ghost_size[0], i2 + ghost_size[1], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1)] + for k1 in range(- dp[0]*dm[0], dp[0]*dm[0] + 1): + for k2 in range(- dp[1]*dm[1], dp[1]*dm[1] + 1): + value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], (k1 + dp[0]*dm[0])%(2*dp[0]*dm[0] + 1), (k2 + dp[1]*dm[1])%(2*dp[1]*dm[1] + 1)] j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC j2_n = (i2_n + k2) % dnpts[bd][1] # modulus is necessary for periodic BC @@ -469,26 +471,27 @@ def mat_topetsc( mat ): nnz_in_row += 1 - I.append(I[-1] + nnz_in_row) + if nnz_in_row > 0: + I.append(I[-1] + nnz_in_row) elif dndims[bd] == 3 and cndims[bc] == 3: - for i1 in np.arange(dnpts_local[bd][0]): - for i2 in np.arange(dnpts_local[bd][1]): - for i3 in np.arange(dnpts_local[bd][2]): + for i1 in np.arange(cnpts_local[bc][0]): + for i2 in np.arange(cnpts_local[bc][1]): + for i3 in np.arange(cnpts_local[bc][2]): nnz_in_row = 0 - i1_n = s[0] + i1 - i2_n = s[1] + i2 - i3_n = s[2] + i3 + i1_n = cs[0] + i1 + i2_n = cs[1] + i2 + i3_n = cs[2] + i3 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n, i3_n)) - for k1 in range(-p[0]*m[0], p[0]*m[0] + 1): - for k2 in range(-p[1]*m[1], p[1]*m[1] + 1): - for k3 in range(-p[2]*m[2], p[2]*m[2] + 1): - value = mat_block._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2], (k1 + ghost_size[0])%(2*p[0]*m[0] + 1), (k2 + ghost_size[1])%(2*p[1]*m[1] + 1), (k3 + ghost_size[2])%(2*p[2]*m[2] + 1)] + for k1 in range(-dp[0]*dm[0], dp[0]*dm[0] + 1): + for k2 in range(-dp[1]*dm[1], dp[1]*dm[1] + 1): + for k3 in range(-dp[2]*dm[2], dp[2]*dm[2] + 1): + value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], i3 + cghost_size[2], (k1 + dp[0]*dm[0])%(2*dp[0]*dm[0] + 1), (k2 + dp[1]*dm[1])%(2*dp[1]*dm[1] + 1), (k3 + dp[2]*dm[2])%(2*dp[2]*dm[2] + 1)] - j1_n = (i1_n + k1)%dnpts[bd][0] # modulus is necessary for periodic BC - j2_n = (i2_n + k2)%dnpts[bd][1] # modulus is necessary for periodic BC - j3_n = (i3_n + k3)%dnpts[bd][2] # modulus is necessary for periodic BC + j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC + j2_n = (i2_n + k2) % dnpts[bd][1] # modulus is necessary for periodic BC + j3_n = (i3_n + k3) % dnpts[bd][2] # modulus is necessary for periodic BC if value != 0: j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n, j3_n)) @@ -501,7 +504,8 @@ def mat_topetsc( mat ): nnz_in_row += 1 - I.append(I[-1] + nnz_in_row) + if nnz_in_row > 0: + I.append(I[-1] + nnz_in_row) # Set the values using IJV&rowmap format. The values are stored in a cache memory. gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC From 448670bcad21b7e7a78f77a3b6c57b954f6fda86 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 22 May 2024 14:18:25 +0200 Subject: [PATCH 37/88] sequential case, docstrings --- psydac/linalg/tests/test_block.py | 126 ++++++++++++++------- psydac/linalg/tests/test_stencil_vector.py | 5 +- psydac/linalg/topetsc.py | 50 ++++++-- psydac/linalg/utilities.py | 4 +- 4 files changed, 129 insertions(+), 56 deletions(-) diff --git a/psydac/linalg/tests/test_block.py b/psydac/linalg/tests/test_block.py index 846b84472..e2e4c9640 100644 --- a/psydac/linalg/tests/test_block.py +++ b/psydac/linalg/tests/test_block.py @@ -811,32 +811,22 @@ def test_block_vector_2d_serial_topetsc( dtype, n1, n2, p1, p2, P1, P2 ): V2 = StencilVectorSpace( cart ,dtype=dtype) W = BlockVectorSpace(V1, V2) - W2 = BlockVectorSpace(W, W) x = BlockVector(W) - x2 = BlockVector(W2) # Fill in vector with random values, then update ghost regions for i1 in range(n1): for i2 in range(n2): x[0][i1,i2] = 2.0*factor*random() + 1.0 x[1][i1,i2] = 5.0*factor*random() - 1.0 - x2[0][0][i1,i2] = 2.0*factor*random() + 1.0 - x2[0][1][i1,i2] = 5.0*factor*random() - 1.0 - x2[1][0][i1,i2] = 2.0*factor*random() + 1.0 - x2[1][1][i1,i2] = 5.0*factor*random() - 1.0 x.update_ghost_regions() - x2.update_ghost_regions() v = x.topetsc() - v2 = x2.topetsc() v = petsc_to_psydac(v, W) - v2 = petsc_to_psydac(v2, W2) # The vectors can only be compared in the serial case assert np.allclose( x.toarray() , v.toarray() ) - assert np.allclose( x2.toarray() , v2.toarray() ) #=============================================================================== @pytest.mark.parametrize( 'dtype', [float, complex] ) @@ -1125,7 +1115,6 @@ def test_block_linear_operator_parallel_dot( dtype, n1, n2, p1, p2, P1, P2 ): assert np.allclose( Z.blocks[0].toarray(), y1.toarray(), rtol=1e-14, atol=1e-14 ) assert np.allclose( Z.blocks[1].toarray(), y2.toarray(), rtol=1e-14, atol=1e-14 ) - # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) @pytest.mark.parametrize('n1', [10, 17]) @@ -1243,36 +1232,102 @@ def test_block_vector_2d_parallel_topetsc( dtype, n1, n2, p1, p2, P1, P2 ): cart = CartDecomposition(D, npts, global_starts, global_ends, pads=[p1,p2], shifts=[1,1]) + D2 = DomainDecomposition([n1+1,n2+1], periods=[P1,P2], comm=comm) + npts2 = [n1+1,n2+1] + global_starts2, global_ends2 = compute_global_starts_ends(D2, npts2) + cart2 = CartDecomposition(D2, npts2, global_starts2, global_ends2, pads=[p1+1,p2+1], shifts=[1,1]) + # Create vector spaces, and stencil vectors V1 = StencilVectorSpace( cart ,dtype=dtype) - V2 = StencilVectorSpace( cart ,dtype=dtype) + V2 = StencilVectorSpace( cart2 ,dtype=dtype) W = BlockVectorSpace(V1, V2) - W2 = BlockVectorSpace(W, W) + #TODO: implement conversion to PETSc recursively to treat case of block of blocks x = BlockVector(W) - x2 = BlockVector(W2) # Fill in vector with random values, then update ghost regions for i0 in range(len(W.starts)): for i1 in range(W.starts[i0][0], W.ends[i0][0] + 1): for i2 in range(W.starts[i0][1], W.ends[i0][1] + 1): x[i0][i1,i2] = 2.0*factor*random() + 1.0 - x2[0][0][i1,i2] = 2.0*factor*random() + 1.0 - x2[0][1][i1,i2] = 5.0*factor*random() - 1.0 - x2[1][0][i1,i2] = 2.0*factor*random() + 1.0 - x2[1][1][i1,i2] = 5.0*factor*random() - 1.0 x.update_ghost_regions() - x2.update_ghost_regions() - v = x.topetsc() - v2 = x2.topetsc() - v = petsc_to_psydac(v, W) - v2 = petsc_to_psydac(v2, W2) + v = petsc_to_psydac(x.topetsc(), W) assert np.allclose( x.toarray() , v.toarray(), rtol=1e-12, atol=1e-12 ) - assert np.allclose( x2.toarray() , v2.toarray(), rtol=1e-12, atol=1e-12 ) + +#=============================================================================== +@pytest.mark.parametrize( 'dtype', [float, complex] ) +@pytest.mark.parametrize( 'n1', [8, 16] ) +@pytest.mark.parametrize( 'p1', [1, 2] ) +@pytest.mark.parametrize( 'P1', [True, False] ) +@pytest.mark.parallel +@pytest.mark.petsc + +def test_block_linear_operator_1d_parallel_topetsc( dtype, n1, p1, P1): + # set seed for reproducibility + seed(n1*p1) + from mpi4py import MPI + + D = DomainDecomposition([n1], periods=[P1], comm=MPI.COMM_WORLD) + + # Partition the points + npts = [n1] + global_starts, global_ends = compute_global_starts_ends(D, npts) + + cart = CartDecomposition(D, npts, global_starts, global_ends, pads=[p1], shifts=[1]) + + # Create vector spaces, stencil matrices, and stencil vectors + V = StencilVectorSpace( cart, dtype=dtype ) + M1 = StencilMatrix( V, V ) + M2 = StencilMatrix( V, V ) + + # Fill in stencil matrices based on diagonal index + if dtype==complex: + f=lambda k1: 10j*k1 + else: + f=lambda k1: 10*k1 + + for k1 in range(-p1, p1+1): + M1[:,k1] = f(k1) + M2[:,k1] = f(k1)+2. + + M1.remove_spurious_entries() + M2.remove_spurious_entries() + + W = BlockVectorSpace(V, V) + + # Construct a BlockLinearOperator object containing M1, M2, M3: + # |M1 M2| + # L = | | + # |M3 0 | + + dict_blocks = {(0,0):M1, (0,1):M2} + + L = BlockLinearOperator( W, V, blocks=dict_blocks ) + x = BlockVector(W) + + # Fill in vector with random values, then update ghost regions + for i0 in range(len(W.starts)): + for i1 in range(W.starts[i0][0], W.ends[i0][0] + 1): + x[i0][i1] = 2.0*random() + (1j if dtype==complex else 1.) + x.update_ghost_regions() + + y = L.dot(x) + + # Cast operator to PETSc Mat format + Lp = L.topetsc() + + # Create Vec to allocate the result of the dot product + y_petsc = Lp.createVecLeft() + # Compute dot product + Lp.mult(x.topetsc(), y_petsc) + # Cast result back to Psydac BlockVector format + y_p = petsc_to_psydac(y_petsc, V) + + assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) #=============================================================================== @pytest.mark.parametrize( 'dtype', [float, complex] ) @@ -1289,8 +1344,9 @@ def test_block_linear_operator_2d_parallel_topetsc( dtype, n1, n2, p1, p2, P1, P # set seed for reproducibility seed(n1*n2*p1*p2) from mpi4py import MPI + comm = MPI.COMM_WORLD - D = DomainDecomposition([n1,n2], periods=[P1,P2], comm=MPI.COMM_WORLD) + D = DomainDecomposition([n1,n2], periods=[P1,P2], comm=comm) # Partition the points npts = [n1,n2] @@ -1300,7 +1356,7 @@ def test_block_linear_operator_2d_parallel_topetsc( dtype, n1, n2, p1, p2, P1, P # Create vector spaces, stencil matrices, and stencil vectors V = StencilVectorSpace( cart, dtype=dtype ) - M1 = StencilMatrix( V, V) + M1 = StencilMatrix( V, V ) M2 = StencilMatrix( V, V ) M3 = StencilMatrix( V, V ) @@ -1314,7 +1370,7 @@ def test_block_linear_operator_2d_parallel_topetsc( dtype, n1, n2, p1, p2, P1, P for k2 in range(-p2,p2+1): M1[:,:,k1,k2] = f(k1,k2) M2[:,:,k1,k2] = f(k1,k2)+2. - M3[:,:,k1,k2] = f(k1,k2)+5. + M3[:,:,k1,k2] = -f(k1,k2)+1. M1.remove_spurious_entries() M2.remove_spurious_entries() @@ -1323,8 +1379,8 @@ def test_block_linear_operator_2d_parallel_topetsc( dtype, n1, n2, p1, p2, P1, P W = BlockVectorSpace(V, V) # Construct a BlockLinearOperator object containing M1, M2, M3: - # |M1 M2| - # L = | | + # + # L = |M1 M2| # |M3 0 | dict_blocks = {(0,0):M1, (0,1):M2, (1,0):M3} @@ -1345,22 +1401,16 @@ def test_block_linear_operator_2d_parallel_topetsc( dtype, n1, n2, p1, p2, P1, P Lp = L.topetsc() # Create Vec to allocate the result of the dot product - y_petsc = Lp.createVecRight() + y_petsc = Lp.createVecLeft() # Compute dot product Lp.mult(x.topetsc(), y_petsc) # Cast result back to Psydac BlockVector format - y_p = petsc_to_psydac(y_petsc, W) + y_p = petsc_to_psydac(y_petsc, L.codomain) - ################################################ - # Note 12.03.2024: - # Another possibility would be to compare y_petsc.array and y.toarray(). - # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) #=============================================================================== + @pytest.mark.parametrize( 'dtype', [float, complex] ) @pytest.mark.parametrize( 'n1', [8, 16] ) @pytest.mark.parametrize( 'n2', [8, 32] ) diff --git a/psydac/linalg/tests/test_stencil_vector.py b/psydac/linalg/tests/test_stencil_vector.py index cf15ba58f..dd884c7b8 100644 --- a/psydac/linalg/tests/test_stencil_vector.py +++ b/psydac/linalg/tests/test_stencil_vector.py @@ -431,7 +431,6 @@ def test_stencil_vector_2d_serial_topetsc(dtype, n1, n2, p1, p2, s1, s2, P1, P2) x = StencilVector(V) # Fill the vector with data - if dtype == complex: f = lambda i1, i2: 10j * i1 + i2 else: @@ -455,7 +454,7 @@ def test_stencil_vector_2d_serial_topetsc(dtype, n1, n2, p1, p2, s1, s2, P1, P2) assert v._data.shape == (n1 + 2 * p1 * s1, n2 + 2 * p2 * s2) assert v._data.dtype == dtype assert np.array_equal(x.toarray(), v.toarray()) -#test_stencil_vector_2d_serial_topetsc(float, 4,5,1,1,1,1,True,True) + # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) @pytest.mark.parametrize('n1', [5, 7]) @@ -637,7 +636,6 @@ def test_stencil_vector_2d_parallel_topetsc(dtype, n1, n2, p1, p2, s1, s2, P1, P assert np.array_equal(x.toarray(), v.toarray()) -#test_stencil_vector_2d_parallel_topetsc(float, 4, 5, 1, 1, 1, 1, True, True) # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) @pytest.mark.parametrize('n1', [20, 32]) @@ -679,7 +677,6 @@ def test_stencil_vector_1d_parallel_topetsc(dtype, n1, p1, s1, P1): v = petsc_to_psydac(v, V) assert np.array_equal(x.toarray(), v.toarray()) -#test_stencil_vector_1d_parallel_topetsc(float, 7, 2, 2, False) # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 7254ec5f6..65a8d94e4 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -132,8 +132,11 @@ def psydac_to_petsc_global( jj = ndarray_indices if ndim == 1: - # Find to which process the node belongs to: - proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + if cart.comm: + # Find to which process the node belongs to: + proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + else: + proc_index = 0 # Find the index shift corresponding to the block and the owner process: index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) @@ -142,11 +145,15 @@ def psydac_to_petsc_global( global_index = index_shift + jj[0] - gs[0][proc_index] elif ndim == 2: - # Find to which process the node belongs to: - proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] - proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] - proc_index = proc_y + proc_x*nprocs[1] + if cart.comm: + # Find to which process the node belongs to: + proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] + else: + proc_x = 0 + proc_y = 0 + proc_index = proc_y + proc_x*nprocs[1] # Find the index shift corresponding to the block and the owner process: index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index]) @@ -154,10 +161,16 @@ def psydac_to_petsc_global( global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1] elif ndim == 3: - # Find to which process the node belongs to: - proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] - proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] - proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0] + if cart.comm: + # Find to which process the node belongs to: + proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0] + proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0] + proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0] + else: + proc_x = 0 + proc_y = 0 + proc_z = 0 + proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2] # Find the index shift corresponding to the block and the owner process: @@ -207,14 +220,27 @@ def get_npts_local(V : VectorSpace) -> list: return npts_local_per_block def get_npts_per_block(V : VectorSpace) -> list: + """ + Compute the number of nodes per block, process and dimension. + This is a global variable, its value is the same for all processes. + + Parameter + --------- + V : VectorSpace + The distributed Psydac vector space. + Returns + -------- + list + Number of nodes per block, process and dimension. + """ if isinstance(V, StencilVectorSpace): gs = V.cart.global_starts # Global variable ge = V.cart.global_ends # Global variable npts_local_perprocess = [ ge_i - gs_i + 1 for gs_i, ge_i in zip(gs, ge)] #Global variable - if V.cart.comm: - npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable + #if V.cart.comm: + npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable return [npts_local_perprocess] diff --git a/psydac/linalg/utilities.py b/psydac/linalg/utilities.py index 824f8757c..eb150f138 100644 --- a/psydac/linalg/utilities.py +++ b/psydac/linalg/utilities.py @@ -118,8 +118,8 @@ def petsc_to_psydac(x, Xh): elif isinstance(Xh, StencilVectorSpace): u = StencilVector(Xh) - comm = u.space.cart.global_comm - dtype = u.space.dtype + comm = x.comm + dtype = Xh.dtype localsize, globalsize = x.getSizes() assert globalsize == u.shape[0], 'Sizes of global vectors do not match' From 22b7646489c9c8facf16bbbe984314b80ab697d3 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 22 May 2024 14:22:56 +0200 Subject: [PATCH 38/88] cleaning --- psydac/linalg/tests/test_stencil_matrix.py | 25 +--------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/psydac/linalg/tests/test_stencil_matrix.py b/psydac/linalg/tests/test_stencil_matrix.py index c022f3dc7..7b54d1ace 100644 --- a/psydac/linalg/tests/test_stencil_matrix.py +++ b/psydac/linalg/tests/test_stencil_matrix.py @@ -2854,7 +2854,7 @@ def test_stencil_matrix_1d_parallel_topetsc(dtype, n1, p1, sh1, P1): y_p = petsc_to_psydac(y_petsc, V) assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) -#test_stencil_matrix_1d_parallel_topetsc(float, 5, 2, 1, True) + # =============================================================================== @pytest.mark.parametrize('n1', [4,7]) @@ -2909,8 +2909,6 @@ def test_mass_matrix_2d_parallel_topetsc(n1, n2, p1, p2, P1, P2): y_p = petsc_to_psydac(y_petsc, Vh.vector_space) assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) - -#test_mass_matrix_2d_parallel_topetsc(10, 13, 3, 2, True, True) # =============================================================================== @@ -2971,8 +2969,6 @@ def test_mass_matrix_3d_parallel_topetsc(n1, n2, n3, p1, p2, p3, P1, P2, P3): assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) -#test_mass_matrix_3d_parallel_topetsc(7, 3, 5, 1, 1, 2, True, True, True) - # =============================================================================== @pytest.mark.parametrize('n1', [15,17]) @@ -3016,10 +3012,8 @@ def test_mass_matrix_1d_parallel_topetsc(n1, p1, P1): # Convert stencil matrix to PETSc.Mat Mp = M.topetsc() - #print('\nMp.getSizes()=', Mp.getSizes()) # Create Vec to allocate the result of the dot product y_petsc = Mp.createVecLeft() - #print('y_petsc.getSizes()=', y_petsc.getSizes()) x_petsc = x.topetsc() # Compute dot product @@ -3027,25 +3021,8 @@ def test_mass_matrix_1d_parallel_topetsc(n1, p1, P1): # Cast result back to Psydac StencilVector format y_p = petsc_to_psydac(y_petsc, Vh.vector_space) - ################################################ - # Note 12.03.2024: - # Another possibility would be to compare y_petsc.array and y.toarray(). - # However, we cannot do this because PETSc distributes matrices and vectors different than Psydac. - # In the future we would like that PETSc uses the partition from Psydac, - # which might involve passing a DM Object. - ################################################ - '''for k in range(comm.Get_size()): - if comm.Get_rank() == k: - print('\n\nRank ', k) - print('x=\n', x.toarray()) - print('x_petsc=\n', x_petsc.array) - - print('MAX_DIFF=', abs((y-y_p).toarray()).max()) - comm.Barrier()''' - assert np.allclose(y_p.toarray(), y.toarray(), rtol=1e-12, atol=1e-12) -#test_mass_matrix_1d_parallel_topetsc(2, 1, False) # =============================================================================== # PARALLEL BACKENDS TESTS # =============================================================================== From 32a6a134385e759ba2bfa7765e6ee72032f84975 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 22 May 2024 14:25:51 +0200 Subject: [PATCH 39/88] erase forgotten comments --- psydac/linalg/tests/test_stencil_vector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/psydac/linalg/tests/test_stencil_vector.py b/psydac/linalg/tests/test_stencil_vector.py index dd884c7b8..95200ba37 100644 --- a/psydac/linalg/tests/test_stencil_vector.py +++ b/psydac/linalg/tests/test_stencil_vector.py @@ -731,7 +731,6 @@ def test_stencil_vector_3d_parallel_topetsc(dtype, n1, n2, n3, p1, p2, p3, s1, s v = petsc_to_psydac(v, V) assert np.array_equal(x.toarray(), v.toarray()) -#test_stencil_vector_3d_parallel_topetsc(float, 4, 10, 5, 1, 1, 3, 1, 2, 1, True, True, True) # =============================================================================== @pytest.mark.parametrize('dtype', [float, complex]) From ff8c1c04619b2d28b54c9fbcf917de330e0b59ab Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Wed, 22 May 2024 16:00:28 +0200 Subject: [PATCH 40/88] adds revised Hcurl conforming projections and adapts the tests, prior review comments have been worked in --- .../feec/multipatch/non_matching_operators.py | 1781 ++++++++--------- .../test_feec_conf_projectors_cart_2d.py | 320 +-- 2 files changed, 1042 insertions(+), 1059 deletions(-) diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index f062b7e70..f98ca00dd 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -1,16 +1,24 @@ +""" +This module provides utilities for constructing the conforming projections +for a H1-Hcurl-L2 broken FEEC de Rham sequence. +""" + import os + import numpy as np + from scipy.sparse import eye as sparse_eye from scipy.sparse import csr_matrix + from sympde.topology import Boundary, Interface + from psydac.fem.splines import SplineSpace from psydac.utilities.quadratures import gauss_legendre -from psydac.core.bsplines import breakpoints, quadrature_grid, basis_ders_on_quad_grid, find_spans, elements_spans -from copy import deepcopy +from psydac.core.bsplines import quadrature_grid, basis_ders_on_quad_grid, find_spans, elements_spans, cell_index, basis_ders_on_irregular_grid def get_patch_index_from_face(domain, face): - """ + """ Return the patch index of subdomain/boundary Parameters @@ -45,8 +53,8 @@ def get_patch_index_from_face(domain, face): class Local2GlobalIndexMap: def __init__(self, ndim, n_patches, n_components): - self._shapes = [None]*n_patches - self._ndofs = [None]*n_patches + self._shapes = [None] * n_patches + self._ndofs = [None] * n_patches self._ndim = ndim self._n_patches = n_patches self._n_components = n_components @@ -85,42 +93,17 @@ def get_index(self, k, d, cartesian_index): def knots_to_insert(coarse_grid, fine_grid, tol=1e-14): - + """knot insertion for refinement of a 1d spline space.""" intersection = coarse_grid[( np.abs(fine_grid[:, None] - coarse_grid) < tol).any(0)] - assert abs(intersection-coarse_grid).max() < tol + assert abs(intersection - coarse_grid).max() < tol T = fine_grid[~(np.abs(coarse_grid[:, None] - fine_grid) < tol).any(0)] return T -def construct_extension_operator_1D(domain, codomain): - """ - Compute the matrix of the extension operator on the interface. - - Parameters - ---------- - domain : 1d spline space on the interface (coarse grid) - codomain : 1d spline space on the interface (fine grid) - """ - - from psydac.core.bsplines import hrefinement_matrix - ops = [] - - assert domain.ncells <= codomain.ncells - - Ts = knots_to_insert(domain.breaks, codomain.breaks) - P = hrefinement_matrix(Ts, domain.degree, domain.knots) - - if domain.basis == 'M': - assert codomain.basis == 'M' - P = np.diag( - 1/codomain._scaling_array) @ P @ np.diag(domain._scaling_array) - - return csr_matrix(P) - def get_corners(domain, boundary_only): """ - Given the domain, extract the vertices on their respective domains with local coordinates. + Given the domain, extract the vertices on their respective domains with local coordinates. Parameters ---------- @@ -140,22 +123,22 @@ def get_corners(domain, boundary_only): if boundary_only: for co in cos: - + corner_data[co] = dict() - c = 0 + c = False for cb in co.corners: axis = set() - #check if corner boundary is part of the domain boundary - for cbbd in cb.args: - if bd.has(cbbd): - axis.add(cbbd.axis) - c += 1 + # check if corner boundary is part of the domain boundary + for cbbd in cb.args: + if bd.has(cbbd): + c = True p_ind = patches.index(cb.domain) c_coord = cb.coordinates - corner_data[co][p_ind] = (c_coord, axis) - - if c == 0: corner_data.pop(co) + corner_data[co][p_ind] = c_coord + + if not c: + corner_data.pop(co) else: for co in cos: @@ -164,1075 +147,1041 @@ def get_corners(domain, boundary_only): for cb in co.corners: p_ind = patches.index(cb.domain) c_coord = cb.coordinates - corner_data[co][p_ind] = c_coord + corner_data[co][p_ind] = c_coord return corner_data -def construct_scalar_conforming_projection(Vh, reg_orders=(0,0), p_moments=(-1,-1), nquads=None, hom_bc=(False, False)): - """ Construct the conforming projection for a scalar space for a given regularity (0 continuous, -1 discontinuous). - The conservation of p-moments only works for a matching TensorFemSpace. +def construct_extension_operator_1D(domain, codomain): + """ + Compute the matrix of the extension operator on the interface. Parameters ---------- - Vh : TensorFemSpace - Finite Element Space coming from the discrete de Rham sequence. + domain : 1d spline space on the interface (coarse grid) + codomain : 1d spline space on the interface (fine grid) + """ - reg_orders : tuple-like (int) - Regularity in each space direction -1 or 0. + from psydac.core.bsplines import hrefinement_matrix + ops = [] - p_moments : tuple-like (int) - Number of moments to be preserved. + assert domain.ncells <= codomain.ncells - nquads : int | None - Number of quadrature points. + Ts = knots_to_insert(domain.breaks, codomain.breaks) + P = hrefinement_matrix(Ts, domain.degree, domain.knots) - hom_bc : tuple-like (bool) - Homogeneous boundary conditions. + if domain.basis == 'M': + assert codomain.basis == 'M' + P = np.diag( + 1 / codomain._scaling_array) @ P @ np.diag(domain._scaling_array) - Returns - ------- - cP : scipy.sparse.csr_array - Conforming projection as a sparse matrix. + return csr_matrix(P) + + +def construct_restriction_operator_1D( + coarse_space_1d, fine_space_1d, E, p_moments=-1): """ + Compute the matrix of the (moment preserving) restriction operator on the interface. - dim_tot = Vh.nbasis + Parameters + ---------- + coarse_space_1d : 1d spline space on the interface (coarse grid) + fine_space_1d : 1d spline space on the interface (fine grid) + E : Extension matrix + p_moments : Amount of moments to be preserved + """ + n_c = coarse_space_1d.nbasis + n_f = fine_space_1d.nbasis + R = np.zeros((n_c, n_f)) - # fully discontinuous space - if reg_orders[0] < 0 and reg_orders[1] < 0: - return sparse_eye(dim_tot, format="lil") + if coarse_space_1d.basis == 'B': - - # moment corrections perpendicular to interfaces - # a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, cc_0_ax - cor_x = get_scalar_moment_correction(Vh.spaces[0], 0, reg_orders[0], p_moments[0], nquads, hom_bc[0]) - cor_y = get_scalar_moment_correction(Vh.spaces[0], 1, reg_orders[1], p_moments[1], nquads, hom_bc[1]) - corrections = [cor_x, cor_y] - domain = Vh.symbolic_space.domain - ndim = 2 - n_components = 1 - n_patches = len(domain) + T = np.zeros((n_f, n_f)) + for i in range(1, n_f - 1): + for j in range(n_f): + T[i, j] = int(i == j) - E[i, 0] * int(0 == j) - \ + E[i, -1] * int(n_f - 1 == j) - l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) - for k in range(n_patches): - Vk = Vh.spaces[k] - # T is a TensorFemSpace and S is a 1D SplineSpace - shapes = [S.nbasis for S in Vk.spaces] - l2g.set_patch_shapes(k, shapes) + cf_mass_mat = calculate_mixed_mass_matrix(coarse_space_1d, fine_space_1d)[ + 1:-1, 1:-1].transpose() + c_mass_mat = calculate_mass_matrix(coarse_space_1d)[1:-1, 1:-1] - - # vertex correction matrix - Proj_vertex = sparse_eye(dim_tot, format="lil") + if p_moments > 0: - # edge correction matrix - Proj_edge = sparse_eye(dim_tot, format="lil") + if not p_moments % 2 == 0: + p_moments += 1 + c_poly_mat = calculate_poly_basis_integral( + coarse_space_1d, p_moments=p_moments - 1)[:, 1:-1] + f_poly_mat = calculate_poly_basis_integral( + fine_space_1d, p_moments=p_moments - 1)[:, 1:-1] - Interfaces = domain.interfaces - if isinstance(Interfaces, Interface): - Interfaces = (Interfaces, ) + c_mass_mat[0:p_moments // 2, :] = c_poly_mat[0:p_moments // 2, :] + c_mass_mat[-p_moments // 2:, :] = c_poly_mat[-p_moments // 2:, :] - corner_indices = set() - corners = get_corners(domain, False) + cf_mass_mat[0:p_moments // 2, :] = f_poly_mat[0:p_moments // 2, :] + cf_mass_mat[-p_moments // 2:, :] = f_poly_mat[-p_moments // 2:, :] + R0 = np.linalg.solve(c_mass_mat, cf_mass_mat) + R[1:-1, 1:-1] = R0 + R = R @ T - #loop over all vertices - for (bd,co) in corners.items(): + R[0, 0] += 1 + R[-1, -1] += 1 + else: - # len(co) is the number of adjacent patches at a vertex - corr = len(co) - for patch1 in co: + cf_mass_mat = calculate_mixed_mass_matrix( + coarse_space_1d, fine_space_1d).transpose() + c_mass_mat = calculate_mass_matrix(coarse_space_1d) - #local vertex coordinates in patch1 - coords1 = co[patch1] - nbasis01 = Vh.spaces[patch1].spaces[coords1[0]].nbasis-1 - nbasis11 = Vh.spaces[patch1].spaces[coords1[1]].nbasis-1 + if p_moments > 0: - #patch local index - multi_index_i = [None]*ndim - multi_index_i[0] = 0 if coords1[0] == 0 else nbasis01 - multi_index_i[1] = 0 if coords1[1] == 0 else nbasis11 + if not p_moments % 2 == 0: + p_moments += 1 + c_poly_mat = calculate_poly_basis_integral( + coarse_space_1d, p_moments=p_moments - 1) + f_poly_mat = calculate_poly_basis_integral( + fine_space_1d, p_moments=p_moments - 1) - #global index - ig = l2g.get_index(patch1, 0, multi_index_i) - corner_indices.add(ig) + c_mass_mat[0:p_moments // 2, :] = c_poly_mat[0:p_moments // 2, :] + c_mass_mat[-p_moments // 2:, :] = c_poly_mat[-p_moments // 2:, :] - for patch2 in co: - - # local vertex coordinates in patch2 - coords2 = co[patch2] - nbasis02 = Vh.spaces[patch2].spaces[coords2[0]].nbasis-1 - nbasis12 = Vh.spaces[patch2].spaces[coords2[1]].nbasis-1 + cf_mass_mat[0:p_moments // 2, :] = f_poly_mat[0:p_moments // 2, :] + cf_mass_mat[-p_moments // 2:, :] = f_poly_mat[-p_moments // 2:, :] - #patch local index - multi_index_j = [None]*ndim - multi_index_j[0] = 0 if coords2[0] == 0 else nbasis02 - multi_index_j[1] = 0 if coords2[1] == 0 else nbasis12 + R = np.linalg.solve(c_mass_mat, cf_mass_mat) - #global index - jg = l2g.get_index(patch2, 0, multi_index_j) + return R - #conformity constraint - Proj_vertex[jg,ig] = 1/corr - if patch1 == patch2: continue +def get_extension_restriction(coarse_space_1d, fine_space_1d, p_moments=-1): + """ + Calculate the extension and restriction matrices for refining along an interface. - if (p_moments[0] == -1 and p_moments[1] == -1): continue + Parameters + ---------- - #moment corrections from patch1 to patch2 - axis = 0 - d = 1 - multi_index_p = [None]*ndim - for pd in range(0, max(1, p_moments[d]+1)): - p_indd = pd+0+1 - multi_index_p[d] = p_indd if coords2[d] == 0 else Vh.spaces[patch2].spaces[coords2[d]].nbasis-1-p_indd + coarse_space_1d : SplineSpace + Spline space of the coarse space. - for p in range(0, max(1,p_moments[axis]+1)): + fine_space_1d : SplineSpace + Spline space of the fine space. - p_ind = p+0+1 # 0 = regularity - multi_index_p[axis] = p_ind if coords2[axis] == 0 else Vh.spaces[patch2].spaces[coords2[axis]].nbasis-1-p_ind - pg = l2g.get_index(patch2, 0, multi_index_p) - Proj_vertex[pg, ig] += - 1/corr * corrections[axis][5][p] * corrections[d][5][pd] + p_moments : {int} + Amount of moments to be preserved. - if (p_moments[0] == -1 and p_moments[1]) == -1: continue + Returns + ------- + E_1D : numpy array + Extension matrix. - #moment corrections from patch1 to patch1 - axis = 0 - d = 1 - multi_index_p = [None]*ndim - for pd in range(0, max(1, p_moments[d]+1)): - p_indd = pd+0+1 - multi_index_p[d] = p_indd if coords1[d] == 0 else Vh.spaces[patch1].spaces[coords1[d]].nbasis-1-p_indd - for p in range(0, max(1, p_moments[axis]+1)): - - p_ind = p+0+1 # 0 = regularity - multi_index_p[axis] = p_ind if coords1[axis] == 0 else Vh.spaces[patch1].spaces[coords1[axis]].nbasis-1-p_ind - pg = l2g.get_index(patch1, 0, multi_index_p) - Proj_vertex[pg,ig] += (1-1/corr) * corrections[axis][5][p] * corrections[d][5][pd] - + R_1D : numpy array + Restriction matrix. - # loop over all interfaces - for I in Interfaces: - - axis = I.axis - direction = I.ornt + ER_1D : numpy array + Extension-restriction matrix. + """ + matching_interfaces = (coarse_space_1d.ncells == fine_space_1d.ncells) + assert (coarse_space_1d.breaks[0] == fine_space_1d.breaks[0]) and ( + coarse_space_1d.breaks[-1] == fine_space_1d.breaks[-1]) + assert (coarse_space_1d.basis == fine_space_1d.basis) + spl_type = coarse_space_1d.basis - k_minus = get_patch_index_from_face(domain, I.minus) - k_plus = get_patch_index_from_face(domain, I.plus) + if not matching_interfaces: + grid = np.linspace( + fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells + 1) + coarse_space_1d_k_plus = SplineSpace( + degree=fine_space_1d.degree, + grid=grid, + basis=fine_space_1d.basis) - I_minus_ncells = Vh.spaces[k_minus].ncells - I_plus_ncells = Vh.spaces[k_plus].ncells + E_1D = construct_extension_operator_1D( + domain=coarse_space_1d_k_plus, codomain=fine_space_1d) - matching_interfaces = (I_minus_ncells == I_plus_ncells) + R_1D = construct_restriction_operator_1D( + coarse_space_1d, fine_space_1d, E_1D, p_moments) - # logical directions normal to interface - if I_minus_ncells <= I_plus_ncells: - k_fine, k_coarse = k_plus, k_minus - fine_axis, coarse_axis = I.plus.axis, I.minus.axis - fine_ext, coarse_ext = I.plus.ext, I.minus.ext + ER_1D = E_1D @ R_1D - else: - k_fine, k_coarse = k_minus, k_plus - fine_axis, coarse_axis = I.minus.axis, I.plus.axis - fine_ext, coarse_ext = I.minus.ext, I.plus.ext + else: + ER_1D = R_1D = E_1D = sparse_eye( + fine_space_1d.nbasis, format="lil") - # logical directions along the interface - d_fine = 1-fine_axis - d_coarse = 1-coarse_axis + # TODO remove later + assert ( + np.allclose( + np.linalg.norm( + R_1D @ E_1D - + np.eye( + coarse_space_1d.nbasis)), + 0, + 1e-12, + 1e-12)) + return E_1D, R_1D, ER_1D - space_fine = Vh.spaces[k_fine] - space_coarse = Vh.spaces[k_coarse] +# Didn't find this utility in the code base. +def calculate_mass_matrix(space_1d): + """ + Calculate the mass-matrix of a 1d spline-space. - coarse_space_1d = space_coarse.spaces[d_coarse] - fine_space_1d = space_fine.spaces[d_fine] + Parameters + ---------- - E_1D, R_1D, ER_1D = get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, 'B') + space_1d : SplineSpace + Spline space of the fine space. - # P_k_minus_k_minus - multi_index = [None]*ndim - multi_index_m = [None]*ndim - multi_index[coarse_axis] = 0 if coarse_ext == - 1 else space_coarse.spaces[coarse_axis].nbasis-1 + Returns + ------- + Mass_mat : numpy array + Mass matrix. + """ + Nel = space_1d.ncells + deg = space_1d.degree + knots = space_1d.knots + spl_type = space_1d.basis - for i in range(coarse_space_1d.nbasis): - multi_index[d_coarse] = i - multi_index_m[d_coarse] = i - ig = l2g.get_index(k_coarse, 0, multi_index) + u, w = gauss_legendre(deg + 1) - if not corner_indices.issuperset({ig}): - Proj_edge[ig, ig] = corrections[coarse_axis][0][0] + nquad = len(w) + quad_x, quad_w = quadrature_grid(space_1d.breaks, u, w) - for p in range(0, p_moments[coarse_axis]+1): + basis = basis_ders_on_quad_grid(knots, deg, quad_x, 0, spl_type) + spans = elements_spans(knots, deg) - p_ind = p+0+1 # 0 = regularity - multi_index_m[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[coarse_axis].nbasis-1-p_ind - mg = l2g.get_index(k_coarse, 0, multi_index_m) + Mass_mat = np.zeros((space_1d.nbasis, space_1d.nbasis)) - Proj_edge[mg, ig] += corrections[coarse_axis][0][p_ind] - - # P_k_plus_k_plus - multi_index_i = [None]*ndim - multi_index_j = [None]*ndim - multi_index_p = [None]*ndim + for ie1 in range(Nel): # loop on cells + for il1 in range(deg + 1): # loops on basis function in each cell + for il2 in range(deg + 1): # loops on basis function in each cell + val = 0. - multi_index_i[fine_axis] = 0 if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1 - multi_index_j[fine_axis] = 0 if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1 + for q1 in range(nquad): # loops on quadrature points + v0 = basis[ie1, il1, 0, q1] + w0 = basis[ie1, il2, 0, q1] + val += quad_w[ie1, q1] * v0 * w0 - for i in range(fine_space_1d.nbasis): - multi_index_i[d_fine] = i - ig = l2g.get_index(k_fine, 0, multi_index_i) - - multi_index_p[d_fine] = i + locind1 = il1 + spans[ie1] - deg + locind2 = il2 + spans[ie1] - deg + Mass_mat[locind1, locind2] += val - for j in range(fine_space_1d.nbasis): - multi_index_j[d_fine] = j - jg = l2g.get_index(k_fine, 0, multi_index_j) + return Mass_mat - if not corner_indices.issuperset({ig}): - Proj_edge[ig, jg] = corrections[fine_axis][0][0] * ER_1D[i,j] - for p in range(0, p_moments[fine_axis]+1): +# Didn't find this utility in the code base. +def calculate_mixed_mass_matrix(domain_space, codomain_space): + """ + Calculate the mixed mass-matrix of two 1d spline-spaces on the same domain. - p_ind = p+0+1 # 0 = regularity - multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1-p_ind - pg = l2g.get_index(k_fine, 0, multi_index_p) + Parameters + ---------- - Proj_edge[pg, jg] += corrections[fine_axis][0][p_ind] * ER_1D[i, j] + domain_space : SplineSpace + Spline space of the domain space. - # P_k_plus_k_minus - multi_index_i = [None]*ndim - multi_index_j = [None]*ndim - multi_index_p = [None]*ndim + codomain_space : SplineSpace + Spline space of the codomain space. - multi_index_i[fine_axis] = 0 if fine_ext == -1 else space_fine .spaces[fine_axis] .nbasis-1 - multi_index_j[coarse_axis] = 0 if coarse_ext == -1 else space_coarse.spaces[coarse_axis].nbasis-1 + Returns + ------- - for i in range(fine_space_1d.nbasis): - multi_index_i[d_fine] = i - multi_index_p[d_fine] = i - ig = l2g.get_index(k_fine, 0, multi_index_i) + Mass_mat : numpy array + Mass matrix. + """ + if domain_space.nbasis > codomain_space.nbasis: + coarse_space = codomain_space + fine_space = domain_space + else: + coarse_space = domain_space + fine_space = codomain_space - for j in range(coarse_space_1d.nbasis): - multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 - jg = l2g.get_index(k_coarse, 0, multi_index_j) + deg = coarse_space.degree + knots = coarse_space.knots + spl_type = coarse_space.basis + breaks = coarse_space.breaks - if not corner_indices.issuperset({ig}): - Proj_edge[ig, jg] = corrections[coarse_axis][1][0] *E_1D[i,j]*direction + fdeg = fine_space.degree + fknots = fine_space.knots + fbreaks = fine_space.breaks + fspl_type = fine_space.basis + fNel = fine_space.ncells - for p in range(0, p_moments[fine_axis]+1): + assert spl_type == fspl_type + assert deg == fdeg + assert ((knots[0] == fknots[0]) and (knots[-1] == fknots[-1])) - p_ind = p+0+1 # 0 = regularity - multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[fine_axis].nbasis-1-p_ind - pg = l2g.get_index(k_fine, 0, multi_index_p) - - Proj_edge[pg, jg] += corrections[fine_axis][1][p_ind] *E_1D[i, j]*direction + u, w = gauss_legendre(deg + 1) - # P_k_minus_k_plus - multi_index_i = [None]*ndim - multi_index_j = [None]*ndim - multi_index_p = [None]*ndim + nquad = len(w) + quad_x, quad_w = quadrature_grid(fbreaks, u, w) - multi_index_i[coarse_axis] = 0 if coarse_ext == -1 else space_coarse.spaces[coarse_axis].nbasis-1 - multi_index_j[fine_axis] = 0 if fine_ext == -1 else space_fine .spaces[fine_axis] .nbasis-1 + fine_basis = basis_ders_on_quad_grid(fknots, fdeg, quad_x, 0, spl_type) + coarse_basis = [ + basis_ders_on_irregular_grid( + knots, deg, q, cell_index( + breaks, q), 0, spl_type) for q in quad_x] - for i in range(coarse_space_1d.nbasis): - multi_index_i[d_coarse] = i - multi_index_p[d_coarse] = i - ig = l2g.get_index(k_coarse, 0, multi_index_i) + fine_spans = elements_spans(fknots, deg) + coarse_spans = [find_spans(knots, deg, q[0])[0] for q in quad_x] - for j in range(fine_space_1d.nbasis): - multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 - jg = l2g.get_index(k_fine, 0, multi_index_j) + Mass_mat = np.zeros((fine_space.nbasis, coarse_space.nbasis)) - if not corner_indices.issuperset({ig}): - Proj_edge[ig, jg] = corrections[fine_axis][1][0] *R_1D[i,j]*direction + for ie1 in range(fNel): # loop on cells + for il1 in range(deg + 1): # loops on basis function in each cell + for il2 in range(deg + 1): # loops on basis function in each cell + val = 0. - for p in range(0, p_moments[coarse_axis]+1): + for q1 in range(nquad): # loops on quadrature points + v0 = fine_basis[ie1, il1, 0, q1] + w0 = coarse_basis[ie1][q1, il2, 0] + val += quad_w[ie1, q1] * v0 * w0 - p_ind = p+0+1 # 0 = regularity - multi_index_p[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[coarse_axis].nbasis-1-p_ind - pg = l2g.get_index(k_coarse, 0, multi_index_p) + locind1 = il1 + fine_spans[ie1] - deg + locind2 = il2 + coarse_spans[ie1] - deg + Mass_mat[locind1, locind2] += val - Proj_edge[pg, jg] += corrections[coarse_axis][1][p_ind] *R_1D[i, j]*direction + return Mass_mat - # interface correction - bd_co_indices = set() - for bn in domain.boundary: - k = get_patch_index_from_face(domain, bn) - space_k = Vh.spaces[k] - axis = bn.axis - if not hom_bc[axis]: - continue - d = 1-axis - ext = bn.ext - space_k_1d = space_k.spaces[d] # t - multi_index_i = [None]*ndim - multi_index_i[axis] = 0 if ext == - \ - 1 else space_k.spaces[axis].nbasis-1 +def calculate_poly_basis_integral(space_1d, p_moments=-1): + """ + Calculate the "mixed mass-matrix" of a 1d spline-space with polynomials. - multi_index_p = [None]*ndim - multi_index_p[axis] = 0 if ext == - \ - 1 else space_k.spaces[axis].nbasis-1 + Parameters + ---------- - for i in range(0, space_k_1d.nbasis): - multi_index_i[d] = i - ig = l2g.get_index(k, 0, multi_index_i) - bd_co_indices.add(ig) - Proj_edge[ig, ig] = 0 + space_1d : SplineSpace + Spline space of the fine space. - multi_index_p[d] = i + p_moments : Int + Amount of moments to be preserved. - # interface correction - if (i != 0 and i != space_k_1d.nbasis-1): - for p in range(0, p_moments[axis]+1): + Returns + ------- - p_ind = p+0+1 # 0 = regularity - multi_index_p[axis] = p_ind if ext == - 1 else space_k.spaces[axis].nbasis-1-p_ind - pg = l2g.get_index(k, 0, multi_index_p) - #a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd - Proj_edge[pg, ig] = corrections[axis][4][p] #* corrections[d][4][p] + Mass_mat : numpy array + Mass matrix. + """ - - # vertex corrections - corners = get_corners(domain, True) - for (bd,co) in corners.items(): - - # len(co) is the number of adjacent patches at a vertex - corr = len(co) - for patch1 in co: - c = 0 - if hom_bc[0]: - if 0 in co[patch1][1]: c += 1 - if hom_bc[1]: - if 1 in co[patch1][1]: c+=1 - if c == 0: break - - #local vertex coordinates in patch1 - coords1 = co[patch1][0] - nbasis01 = Vh.spaces[patch1].spaces[coords1[0]].nbasis-1 - nbasis11 = Vh.spaces[patch1].spaces[coords1[1]].nbasis-1 - - #patch local index - multi_index_i = [None]*ndim - multi_index_i[0] = 0 if coords1[0] == 0 else nbasis01 - multi_index_i[1] = 0 if coords1[1] == 0 else nbasis11 - - #global index - ig = l2g.get_index(patch1, 0, multi_index_i) - corner_indices.add(ig) + Nel = space_1d.ncells + deg = space_1d.degree + knots = space_1d.knots + spl_type = space_1d.basis + breaks = space_1d.breaks + enddom = breaks[-1] + begdom = breaks[0] + denom = enddom - begdom - for patch2 in co: - - # local vertex coordinates in patch2 - coords2 = co[patch2][0] - nbasis02 = Vh.spaces[patch2].spaces[coords2[0]].nbasis-1 - nbasis12 = Vh.spaces[patch2].spaces[coords2[1]].nbasis-1 + order = max(p_moments + 1, deg + 1) + u, w = gauss_legendre(order) - #patch local index - multi_index_j = [None]*ndim - multi_index_j[0] = 0 if coords2[0] == 0 else nbasis02 - multi_index_j[1] = 0 if coords2[1] == 0 else nbasis12 + nquad = len(w) + quad_x, quad_w = quadrature_grid(space_1d.breaks, u, w) - #global index - jg = l2g.get_index(patch2, 0, multi_index_j) + coarse_basis = basis_ders_on_quad_grid(knots, deg, quad_x, 0, spl_type) + spans = elements_spans(knots, deg) - #conformity constraint - Proj_vertex[jg,ig] = 0 + Mass_mat = np.zeros((p_moments + 1, space_1d.nbasis)) - if patch1 == patch2: continue + for ie1 in range(Nel): # loop on cells + for pol in range( + p_moments + 1): # loops on basis function in each cell + for il2 in range(deg + 1): # loops on basis function in each cell + val = 0. - if (p_moments[0] == -1 and p_moments[1] == -1): continue + for q1 in range(nquad): # loops on quadrature points + v0 = coarse_basis[ie1, il2, 0, q1] + x = quad_x[ie1, q1] + # val += quad_w[ie1, q1] * v0 * ((enddom-x)/denom)**pol + val += quad_w[ie1, q1] * v0 * \ + ((enddom - x) / denom)**(p_moments - pol) * (x / denom)**pol + locind2 = il2 + spans[ie1] - deg + Mass_mat[pol, locind2] += val - #moment corrections from patch1 to patch2 - axis = 0 - d = 1 - multi_index_p = [None]*ndim - for pd in range(0, max(1, p_moments[d]+1)): - p_indd = pd+0+1 - multi_index_p[d] = p_indd if coords2[d] == 0 else Vh.spaces[patch2].spaces[coords2[d]].nbasis-1-p_indd + return Mass_mat - for p in range(0, max(1,p_moments[axis]+1)): - p_ind = p+0+1 # 0 = regularity - multi_index_p[axis] = p_ind if coords2[axis] == 0 else Vh.spaces[patch2].spaces[coords2[axis]].nbasis-1-p_ind - pg = l2g.get_index(patch2, 0, multi_index_p) - Proj_vertex[pg, ig] = 0 +def get_1d_moment_correction(space_1d, p_moments=-1): + """ + Calculate the coefficients for the one-dimensional moment correction. - if (p_moments[0] == -1 and p_moments[1]) == -1: continue + Parameters + ---------- + patch_space : SplineSpace + 1d spline space. - #moment corrections from patch1 to patch1 - axis = 0 - d = 1 - multi_index_p = [None]*ndim - for pd in range(0, max(1, p_moments[d]+1)): - p_indd = pd+0+1 - multi_index_p[d] = p_indd if coords1[d] == 0 else Vh.spaces[patch1].spaces[coords1[d]].nbasis-1-p_indd - for p in range(0, max(1, p_moments[axis]+1)): - - p_ind = p+0+1 # 0 = regularity - multi_index_p[axis] = p_ind if coords1[axis] == 0 else Vh.spaces[patch1].spaces[coords1[axis]].nbasis-1-p_ind - pg = l2g.get_index(patch1, 0, multi_index_p) - Proj_vertex[pg,ig] = corrections[axis][5][p] * corrections[d][5][pd] + p_moments : int + Number of moments to be preserved. - return Proj_edge @ Proj_vertex + Returns + ------- + gamma : array + Moment correction coefficients without the conformity factor. + """ + + if p_moments < 0: + return None -def construct_vector_conforming_projection(Vh, reg_orders= (0,0), p_moments=(-1,-1), nquads=None, hom_bc=(False, False)): - """ Construct the conforming projection for a scalar space for a given regularity (0 continuous, -1 discontinuous). - The conservation of p-moments only works for a matching VectorFemSpace. + if space_1d.ncells <= p_moments + 1: + print("Careful, the correction term is currently not independent of the mesh.") + + if p_moments >= 0: + # to preserve moments of degree p we need 1+p conforming basis functions in the patch (the "interior" ones) + # and for the given regularity constraint, there are + # local_shape[conf_axis]-2*(1+reg) such conforming functions + p_max = space_1d.nbasis - 3 + if p_max < p_moments: + print( + " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") + print(" ** WARNING -- WARNING -- WARNING ") + print( + f" ** conf. projection imposing C0 smoothness on scalar space along this axis :") + print( + f" ** there are not enough dofs in a patch to preserve moments of degree {p_moments} !") + print(f" ** Only able to preserve up to degree --> {p_max} <-- ") + print( + " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") + p_moments = p_max + + Mass_mat = calculate_poly_basis_integral(space_1d, p_moments) + gamma = np.linalg.solve(Mass_mat[:, 1:p_moments + 2], Mass_mat[:, 0]) + + return gamma + + +def construct_h1_conforming_projection( + Vh, reg_orders=0, p_moments=-1, hom_bc=False): + """ + Construct the conforming projection for a scalar space for a given regularity (0 continuous, -1 discontinuous). Parameters ---------- - Vh : VectorFemSpace + Vh : TensorFemSpace Finite Element Space coming from the discrete de Rham sequence. - reg_orders : tuple-like (int) - Regularity in each space direction -1 or 0. - - p_moments : tuple-like (int) - Number of moments to be preserved. + reg_orders : (int) + Regularity in each space direction -1 or 0. - nquads : int | None - Number of quadrature points. + p_moments : (int) + Number of moments to be preserved. - hom_bc : tuple-like (bool) - Homogeneous boundary conditions. + hom_bc : (bool) + Homogeneous boundary conditions. Returns ------- cP : scipy.sparse.csr_array Conforming projection as a sparse matrix. """ - + dim_tot = Vh.nbasis # fully discontinuous space - if reg_orders[0] < 0 and reg_orders[1] < 0: + if reg_orders < 0: return sparse_eye(dim_tot, format="lil") - #moment corrections - corrections_0 = get_vector_moment_correction(Vh.spaces[0], 0, 0, reg=reg_orders[0], p_moments=p_moments[0], nquads=nquads, hom_bc=hom_bc[0]) - corrections_1 = get_vector_moment_correction(Vh.spaces[0], 0, 1, reg=reg_orders[1], p_moments=p_moments[1], nquads=nquads, hom_bc=hom_bc[1]) - - corrections_00 = get_vector_moment_correction(Vh.spaces[0], 1, 0, reg=reg_orders[0], p_moments=p_moments[0], nquads=nquads, hom_bc=hom_bc[0]) - corrections_11 = get_vector_moment_correction(Vh.spaces[0], 1, 1, reg=reg_orders[1], p_moments=p_moments[1], nquads=nquads, hom_bc=hom_bc[1]) - - corrections = [[corrections_0, corrections_1], [corrections_00, corrections_11]] + # moment corrections perpendicular to interfaces + # assume same moments everywhere + gamma = get_1d_moment_correction( + Vh.spaces[0].spaces[0], p_moments=p_moments) domain = Vh.symbolic_space.domain ndim = 2 - n_components = 2 + n_components = 1 n_patches = len(domain) l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) for k in range(n_patches): Vk = Vh.spaces[k] # T is a TensorFemSpace and S is a 1D SplineSpace - shapes = [[S.nbasis for S in T.spaces] for T in Vk.spaces] - l2g.set_patch_shapes(k, *shapes) - - Proj = sparse_eye(dim_tot, format="lil") + shapes = [S.nbasis for S in Vk.spaces] + l2g.set_patch_shapes(k, shapes) - Interfaces = domain.interfaces - if isinstance(Interfaces, Interface): - Interfaces = (Interfaces, ) + # P vertex + # vertex correction matrix + Proj_vertex = sparse_eye(dim_tot, format="lil") - for I in Interfaces: - axis = I.axis - direction = I.ornt + corner_indices = set() + corners = get_corners(domain, False) - k_minus = get_patch_index_from_face(domain, I.minus) - k_plus = get_patch_index_from_face(domain, I.plus) - # logical directions normal to interface - minus_axis, plus_axis = I.minus.axis, I.plus.axis - # logical directions along the interface - d_minus, d_plus = 1-minus_axis, 1-plus_axis - I_minus_ncells = Vh.spaces[k_minus].spaces[d_minus].ncells[d_minus] - I_plus_ncells = Vh.spaces[k_plus] .spaces[d_plus] .ncells[d_plus] + def get_vertex_index_from_patch(patch, coords): + # coords = co[patch] + nbasis0 = Vh.spaces[patch].spaces[coords[0]].nbasis - 1 + nbasis1 = Vh.spaces[patch].spaces[coords[1]].nbasis - 1 - matching_interfaces = (I_minus_ncells == I_plus_ncells) + # patch local index + multi_index = [None] * ndim + multi_index[0] = 0 if coords[0] == 0 else nbasis0 + multi_index[1] = 0 if coords[1] == 0 else nbasis1 - if I_minus_ncells <= I_plus_ncells: - k_fine, k_coarse = k_plus, k_minus - fine_axis, coarse_axis = I.plus.axis, I.minus.axis - fine_ext, coarse_ext = I.plus.ext, I.minus.ext + # global index + return l2g.get_index(patch, 0, multi_index) + def vertex_moment_indices(axis, coords, patch, p_moments): + if coords[axis] == 0: + return range(1, p_moments + 2) else: - k_fine, k_coarse = k_minus, k_plus - fine_axis, coarse_axis = I.minus.axis, I.plus.axis - fine_ext, coarse_ext = I.minus.ext, I.plus.ext + return range(Vh.spaces[patch].spaces[coords[axis]].nbasis - 1 - 1, + Vh.spaces[patch].spaces[coords[axis]].nbasis - 1 - p_moments - 2, -1) - d_fine = 1-fine_axis - d_coarse = 1-coarse_axis + # loop over all vertices + for (bd, co) in corners.items(): - space_fine = Vh.spaces[k_fine] - space_coarse = Vh.spaces[k_coarse] + # len(co)=#v is the number of adjacent patches at a vertex + corr = len(co) - coarse_space_1d = space_coarse.spaces[d_coarse].spaces[d_coarse] - fine_space_1d = space_fine.spaces[d_fine].spaces[d_fine] + for patch1 in co: + # local vertex coordinates in patch1 + coords1 = co[patch1] + # global index + ig = get_vertex_index_from_patch(patch1, coords1) - E_1D, R_1D, ER_1D = get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, 'M') - - # P_k_minus_k_minus - multi_index = [None]*ndim - multi_index_m = [None]*ndim - multi_index[coarse_axis] = 0 if coarse_ext == - \ - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 - - for i in range(coarse_space_1d.nbasis): - multi_index[d_coarse] = i - multi_index_m[d_coarse] = i - ig = l2g.get_index(k_coarse, d_coarse, multi_index) - Proj[ig, ig] = corrections[d_coarse][coarse_axis][0][0] - - for p in range(0, p_moments[coarse_axis]+1): - - p_ind = p+0+1 # 0 = regularity - multi_index_m[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1-p_ind - mg = l2g.get_index(k_coarse, d_coarse, multi_index_m) - - Proj[mg, ig] = corrections[d_coarse][coarse_axis][0][p_ind] - - # P_k_plus_k_plus - multi_index_i = [None]*ndim - multi_index_j = [None]*ndim - multi_index_p = [None]*ndim - multi_index_i[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 - multi_index_j[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1 - - for i in range(fine_space_1d.nbasis): - multi_index_i[d_fine] = i - multi_index_p[d_fine] = i - ig = l2g.get_index(k_fine, d_fine, multi_index_i) - - for j in range(fine_space_1d.nbasis): - multi_index_j[d_fine] = j - jg = l2g.get_index(k_fine, d_fine, multi_index_j) - Proj[ig, jg] = corrections[d_fine][fine_axis][0][0] * ER_1D[i, j] - - for p in range(0, p_moments[fine_axis]+1): - - p_ind = p+0+1 # 0 = regularity - multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1-p_ind - pg = l2g.get_index(k_fine, d_fine, multi_index_p) - - Proj[pg, jg] = corrections[d_fine][fine_axis][0][p_ind] * ER_1D[i, j] - - # P_k_plus_k_minus - multi_index_i = [None]*ndim - multi_index_j = [None]*ndim - multi_index_p = [None]*ndim - multi_index_i[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 - multi_index_j[coarse_axis] = 0 if coarse_ext == - \ - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 - - for i in range(fine_space_1d.nbasis): - multi_index_i[d_fine] = i - multi_index_p[d_fine] = i - ig = l2g.get_index(k_fine, d_fine, multi_index_i) - - for j in range(coarse_space_1d.nbasis): - multi_index_j[d_coarse] = j if direction == 1 else coarse_space_1d.nbasis-j-1 - jg = l2g.get_index(k_coarse, d_coarse, multi_index_j) - Proj[ig, jg] = corrections[d_fine][fine_axis][1][0] *E_1D[i, j]*direction - - for p in range(0, p_moments[fine_axis]+1): - - p_ind = p+0+1 # 0 = regularity - multi_index_p[fine_axis] = p_ind if fine_ext == - 1 else space_fine.spaces[d_fine].spaces[fine_axis].nbasis-1-p_ind - pg = l2g.get_index(k_fine, d_fine, multi_index_p) - - Proj[pg, jg] = corrections[d_fine][fine_axis][1][p_ind] *E_1D[i, j]*direction - - # P_k_minus_k_plus - multi_index_i = [None]*ndim - multi_index_j = [None]*ndim - multi_index_p = [None]*ndim - multi_index_i[coarse_axis] = 0 if coarse_ext == - \ - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1 - multi_index_j[fine_axis] = 0 if fine_ext == - \ - 1 else space_fine .spaces[d_fine] .spaces[fine_axis] .nbasis-1 - - for i in range(coarse_space_1d.nbasis): - multi_index_i[d_coarse] = i - multi_index_p[d_coarse] = i - ig = l2g.get_index(k_coarse, d_coarse, multi_index_i) - for j in range(fine_space_1d.nbasis): - multi_index_j[d_fine] = j if direction == 1 else fine_space_1d.nbasis-j-1 - jg = l2g.get_index(k_fine, d_fine, multi_index_j) - Proj[ig, jg] = corrections[d_coarse][coarse_axis][1][0] *R_1D[i, j]*direction - - for p in range(0, p_moments[coarse_axis]+1): - - p_ind = p+0+1 # 0 = regularity - multi_index_p[coarse_axis] = p_ind if coarse_ext == - 1 else space_coarse.spaces[d_coarse].spaces[coarse_axis].nbasis-1-p_ind - pg = l2g.get_index(k_coarse, d_coarse, multi_index_p) - - Proj[pg, jg] = corrections[d_coarse][coarse_axis][1][p_ind] *R_1D[i, j]*direction - - #if hom_bc: - for bn in domain.boundary: - k = get_patch_index_from_face(domain, bn) - space_k = Vh.spaces[k] - axis = bn.axis - d = 1-axis - ext = bn.ext + corner_indices.add(ig) - if not hom_bc[axis]: - continue + for patch2 in co: - space_k_1d = space_k.spaces[d].spaces[d] # t - multi_index_i = [None]*ndim - multi_index_i[axis] = 0 if ext == - \ - 1 else space_k.spaces[d].spaces[axis].nbasis-1 - multi_index_p = [None]*ndim + # local vertex coordinates in patch2 + coords2 = co[patch2] + # global index + jg = get_vertex_index_from_patch(patch2, coords2) - for i in range(space_k_1d.nbasis): - multi_index_i[d] = i - multi_index_p[d] = i - ig = l2g.get_index(k, d, multi_index_i) - Proj[ig, ig] = 0 + # conformity constraint + Proj_vertex[jg, ig] = 1 / corr - for p in range(0, p_moments[axis]+1): + if patch1 == patch2: + continue - p_ind = p+0+1 # 0 = regularity - multi_index_p[axis] = p_ind if ext == - 1 else space_k.spaces[d].spaces[axis].nbasis-1-p_ind - pg = l2g.get_index(k, d, multi_index_p) - #a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd + if p_moments == -1: + continue - Proj[pg, ig] = corrections[d][axis][4][p] + # moment corrections from patch1 to patch2 + axis = 0 + d = 1 + multi_index_p = [None] * ndim - return Proj + d_moment_index = vertex_moment_indices( + d, coords2, patch2, p_moments) + axis_moment_index = vertex_moment_indices( + axis, coords2, patch2, p_moments) + for pd in range(0, p_moments + 1): + multi_index_p[d] = d_moment_index[pd] -def get_scalar_moment_correction(patch_space, conf_axis, reg=0, p_moments=-1, nquads=None, hom_bc=False): - """ - Calculate the coefficients for the one-dimensional moment correction. + for p in range(0, p_moments + 1): + multi_index_p[axis] = axis_moment_index[p] - Parameters - ---------- - patch_space : TensorFemSpace - Finite Element Space of an adjacent patch. + pg = l2g.get_index(patch2, 0, multi_index_p) + Proj_vertex[pg, ig] += - 1 / \ + corr * gamma[p] * gamma[pd] - conf_axis : {0, 1} - Coefficients for which axis. + if p_moments == -1: + continue - reg : {-1, 0} - Regularity -1 or 0. + # moment corrections from patch1 to patch1 + axis = 0 + d = 1 + multi_index_p = [None] * ndim - p_moments : int - Number of moments to be preserved. + d_moment_index = vertex_moment_indices( + d, coords1, patch1, p_moments) + axis_moment_index = vertex_moment_indices( + axis, coords1, patch1, p_moments) - nquads : int | None - Number of quadrature points. + for pd in range(0, p_moments + 1): + multi_index_p[d] = d_moment_index[pd] - hom_bc : tuple-like (bool) - Homogeneous boundary conditions. + for p in range(0, p_moments + 1): + multi_index_p[axis] = axis_moment_index[p] - Returns - ------- - coeffs : list of arrays - Collection of the different coefficients. - """ - proj_op = 0 - #patch_space = Vh.spaces[0] - local_shape = [patch_space.spaces[0].nbasis,patch_space.spaces[1].nbasis] - Nel = patch_space.ncells # number of elements - degree = patch_space.degree - breakpoints_xy = [breakpoints(patch_space.knots[axis],degree[axis]) for axis in range(2)] - - if nquads is None: - # default: Gauss-Legendre quadratures should be exact for polynomials of deg ≤ 2*degree - nquads = [ degree[axis]+1 for axis in range(2)] - - #Creating vector of weights for moments preserving - uw = [gauss_legendre( k-1 ) for k in nquads] - u = [u[::-1] for u,w in uw] - w = [w[::-1] for u,w in uw] - - grid = [np.array([deepcopy((0.5*(u[axis]+1)*(breakpoints_xy[axis][i+1]-breakpoints_xy[axis][i])+breakpoints_xy[axis][i])) - for i in range(Nel[axis])]) - for axis in range(2)] - _, basis, span, _ = patch_space.preprocess_regular_tensor_grid(grid,der=1) # todo: why not der=0 ? - - span = [deepcopy(span[k] + patch_space.vector_space.starts[k] - patch_space.vector_space.shifts[k] * patch_space.vector_space.pads[k]) for k in range(2)] - p_axis = degree[conf_axis] - enddom = breakpoints_xy[conf_axis][-1] - begdom = breakpoints_xy[conf_axis][0] - denom = enddom-begdom - - a_sm = np.zeros(p_moments+2+reg) # coefs of P B0 on same patch - a_nb = np.zeros(p_moments+2+reg) # coefs of P B0 on neighbor patch - b_sm = np.zeros(p_moments+3) # coefs of P B1 on same patch - b_nb = np.zeros(p_moments+3) # coefs of P B1 on neighbor patch - Correct_coef_bnd = np.zeros(p_moments+1) - Correct_coef_0 = np.zeros(p_moments+2+reg) - - if reg >= 0: - # projection coefs: - a_sm[0] = 1/2 - a_nb[0] = a_sm[0] - if reg == 1: - - if proj_op == 0: - # new slope is average of old ones - a_sm[1] = 0 - elif proj_op == 1: - # new slope is average of old ones after averaging of interface coef - a_sm[1] = 1/2 - elif proj_op == 2: - # new slope is average of reconstructed ones using local values and slopes - a_sm[1] = 1/(2*p_axis) - else: - # just to try something else - a_sm[1] = proj_op/2 - - a_nb[1] = 2*a_sm[0] - a_sm[1] - b_sm[0] = 0 - b_sm[1] = 1/2 - b_nb[0] = b_sm[0] - b_nb[1] = 2*b_sm[0] - b_sm[1] - - if p_moments >= 0: - # to preserve moments of degree p we need 1+p conforming basis functions in the patch (the "interior" ones) - # and for the given regularity constraint, there are local_shape[conf_axis]-2*(1+reg) such conforming functions - p_max = local_shape[conf_axis]-2*(1+reg) - 1 - if p_max < p_moments: - print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") - print( " ** WARNING -- WARNING -- WARNING ") - print(f" ** conf. projection imposing C{reg} smoothness on scalar space along axis {conf_axis}:") - print(f" ** there are not enough dofs in a patch to preserve moments of degree {p_moments} !") - print(f" ** Only able to preserve up to degree --> {p_max} <-- ") - print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") - p_moments = p_max + pg = l2g.get_index(patch1, 0, multi_index_p) + Proj_vertex[pg, ig] += (1 - 1 / corr) * \ + gamma[p] * gamma[pd] - # computing the contribution to every moment of the differents basis function - # for simplicity we assemble the full matrix with all basis functions (ok if patches not too large) - Mass_mat = np.zeros((p_moments+1,local_shape[conf_axis])) - for poldeg in range(p_moments+1): - for ie1 in range(Nel[conf_axis]): #loop on cells - for il1 in range(p_axis+1): #loops on basis function in each cell - val=0. - for q1 in range(nquads[conf_axis]): #loops on quadrature points - v0 = basis[conf_axis][ie1,il1,0,q1] - x = grid[conf_axis][ie1,q1] - val += w[conf_axis][q1]*v0*((enddom-x)/denom)**poldeg - locind=span[conf_axis][ie1]-p_axis+il1 - Mass_mat[poldeg,locind]+=val - Rhs_0 = Mass_mat[:,0] - - if reg == 0: - Mat_to_inv = Mass_mat[:,1:p_moments+2] - else: - Mat_to_inv = Mass_mat[:,2:p_moments+3] - - Correct_coef_0 = np.linalg.solve(Mat_to_inv,Rhs_0) - cc_0_ax = Correct_coef_0 - - if reg == 1: - Rhs_1 = Mass_mat[:,1] - Correct_coef_1 = np.linalg.solve(Mat_to_inv,Rhs_1) - cc_1_ax = Correct_coef_1 - - if hom_bc: - # homogeneous bc is on the point value: no constraint on the derivatives - # so only the projection of B0 (to 0) has to be corrected - Mat_to_inv_bnd = Mass_mat[:,1:p_moments+2] - Correct_coef_bnd = np.linalg.solve(Mat_to_inv_bnd,Rhs_0) - - - for p in range(0,p_moments+1): - # correction for moment preserving : - # we use the first p_moments+1 conforming ("interior") functions to preserve the p+1 moments - # modified by the C0 or C1 enforcement - if reg == 0: - a_sm[p+1] = (1-a_sm[0]) * cc_0_ax[p] - # proj constraint: - a_nb[p+1] = -a_sm[p+1] - - else: - a_sm[p+2] = (1-a_sm[0]) * cc_0_ax[p] -a_sm[1] * cc_1_ax[p] - b_sm[p+2] = -b_sm[0] * cc_0_ax[p] + (1-b_sm[1]) * cc_1_ax[p] - - # proj constraint: - b_nb[p+2] = b_sm[p+2] - a_nb[p+2] = -(a_sm[p+2] + 2*b_sm[p+2]) - return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 - -def get_vector_moment_correction(patch_space, conf_comp, conf_axis, reg=([0,0], [0,0]), p_moments=([-1,-1], [-1,-1]), nquads=None, hom_bc=([False, False],[False, False])): - """ - Calculate the coefficients for the vector-valued moment correction. + # boundary conditions + corners = get_corners(domain, True) + if hom_bc: + for (bd, co) in corners.items(): - Parameters - ---------- - patch_space : VectorFemSpace - Finite Element Space of an adjacent patch. + for patch1 in co: - conf_comp : {0, 1} - Coefficients for which vector component. + # local vertex coordinates in patch2 + coords1 = co[patch1] - conf_axis : {0, 1} - Coefficients for which axis. + # global index + ig = get_vertex_index_from_patch(patch1, coords1) - reg : tuple-like - Regularity -1 or 0. + for patch2 in co: - p_moments : tuple-like - Number of moments to be preserved. + # local vertex coordinates in patch2 + coords2 = co[patch2] + # global index + jg = get_vertex_index_from_patch(patch2, coords2) - nquads : int | None - Number of quadrature points. + # conformity constraint + Proj_vertex[jg, ig] = 0 - hom_bc : tuple-like (bool) - Homogeneous boundary conditions. + if patch1 == patch2: + continue - Returns - ------- - coeffs : list of arrays - Collection of the different coefficients. - """ - proj_op = 0 - local_shape = [[patch_space.spaces[comp].spaces[axis].nbasis - for axis in range(2)] for comp in range(2)] - Nel = patch_space.ncells # number of elements - patch_space_x, patch_space_y = [patch_space.spaces[comp] for comp in range(2)] - degree = patch_space.degree - p_comp_axis = degree[conf_comp][conf_axis] - - breaks_comp_axis = [[breakpoints(patch_space.spaces[comp].knots[axis],degree[comp][axis]) - for axis in range(2)] for comp in range(2)] - if nquads is None: - # default: Gauss-Legendre quadratures should be exact for polynomials of deg ≤ 2*degree - nquads = [ degree[0][k]+1 for k in range(2)] - #Creating vector of weights for moments preserving - uw = [gauss_legendre( k-1 ) for k in nquads] - u = [u[::-1] for u,w in uw] - w = [w[::-1] for u,w in uw] - - grid = [np.array([deepcopy((0.5*(u[axis]+1)*(breaks_comp_axis[0][axis][i+1]-breaks_comp_axis[0][axis][i])+breaks_comp_axis[0][axis][i])) - for i in range(Nel[axis])]) - for axis in range(2)] - - _, basis_x, span_x, _ = patch_space_x.preprocess_regular_tensor_grid(grid,der=0) - _, basis_y, span_y, _ = patch_space_y.preprocess_regular_tensor_grid(grid,der=0) - span_x = [deepcopy(span_x[k] + patch_space_x.vector_space.starts[k] - patch_space_x.vector_space.shifts[k] * patch_space_x.vector_space.pads[k]) for k in range(2)] - span_y = [deepcopy(span_y[k] + patch_space_y.vector_space.starts[k] - patch_space_y.vector_space.shifts[k] * patch_space_y.vector_space.pads[k]) for k in range(2)] - basis = [basis_x, basis_y] - span = [span_x, span_y] - enddom = breaks_comp_axis[0][0][-1] - begdom = breaks_comp_axis[0][0][0] - denom = enddom-begdom - - # projection coefficients - - a_sm = np.zeros(p_moments+2+reg) # coefs of P B0 on same patch - a_nb = np.zeros(p_moments+2+reg) # coefs of P B0 on neighbor patch - b_sm = np.zeros(p_moments+3) # coefs of P B1 on same patch - b_nb = np.zeros(p_moments+3) # coefs of P B1 on neighbor patch - Correct_coef_bnd = np.zeros(p_moments+1) - Correct_coef_0 = np.zeros(p_moments+2+reg) - a_sm[0] = 1/2 - a_nb[0] = a_sm[0] - - if reg == 1: - b_sm = np.zeros(p_moments+3) # coefs of P B1 on same patch - b_nb = np.zeros(p_moments+3) # coefs of P B1 on neighbor patch - if proj_op == 0: - # new slope is average of old ones - a_sm[1] = 0 - elif proj_op == 1: - # new slope is average of old ones after averaging of interface coef - a_sm[1] = 1/2 - elif proj_op == 2: - # new slope is average of reconstructed ones using local values and slopes - a_sm[1] = 1/(2*p_comp_axis) + if p_moments == -1: + continue + + # moment corrections from patch1 to patch2 + axis = 0 + d = 1 + multi_index_p = [None] * ndim + + d_moment_index = vertex_moment_indices( + d, coords2, patch2, p_moments) + axis_moment_index = vertex_moment_indices( + axis, coords2, patch2, p_moments) + + for pd in range(0, p_moments + 1): + multi_index_p[d] = d_moment_index[pd] + + for p in range(0, p_moments + 1): + multi_index_p[axis] = axis_moment_index[p] + + pg = l2g.get_index(patch2, 0, multi_index_p) + Proj_vertex[pg, ig] = 0 + + if p_moments == -1: + continue + + # moment corrections from patch1 to patch1 + axis = 0 + d = 1 + multi_index_p = [None] * ndim + + d_moment_index = vertex_moment_indices( + d, coords1, patch1, p_moments) + axis_moment_index = vertex_moment_indices( + axis, coords1, patch1, p_moments) + + for pd in range(0, p_moments + 1): + multi_index_p[d] = d_moment_index[pd] + + for p in range(0, p_moments + 1): + multi_index_p[axis] = axis_moment_index[p] + + pg = l2g.get_index(patch1, 0, multi_index_p) + Proj_vertex[pg, ig] = gamma[p] * gamma[pd] + + # P edge + # edge correction matrix + Proj_edge = sparse_eye(dim_tot, format="lil") + + Interfaces = domain.interfaces + if isinstance(Interfaces, Interface): + Interfaces = (Interfaces, ) + + def get_edge_index(j, axis, ext, space, k): + multi_index = [None] * ndim + multi_index[axis] = 0 if ext == - 1 else space.spaces[axis].nbasis - 1 + multi_index[1 - axis] = j + return l2g.get_index(k, 0, multi_index) + + def edge_moment_index(p, i, axis, ext, space, k): + multi_index = [None] * ndim + multi_index[1 - axis] = i + multi_index[axis] = p + 1 if ext == - \ + 1 else space.spaces[axis].nbasis - 1 - p - 1 + return l2g.get_index(k, 0, multi_index) + + def get_mu_plus(j, fine_space): + mu_plus = np.zeros(fine_space.nbasis) + for p in range(p_moments + 1): + if j == 0: + mu_plus[p + 1] = gamma[p] + else: + mu_plus[j - (p + 1)] = gamma[p] + return mu_plus + + def get_mu_minus(j, coarse_space, fine_space, R): + mu_plus = np.zeros(fine_space.nbasis) + mu_minus = np.zeros(coarse_space.nbasis) + + if j == 0: + mu_minus[0] = 1 + for p in range(p_moments + 1): + mu_plus[p + 1] = gamma[p] else: - # just to try something else - a_sm[1] = proj_op/2 - - a_nb[1] = 2*a_sm[0] - a_sm[1] - b_sm[0] = 0 - b_sm[1] = 1/2 - b_nb[0] = b_sm[0] - b_nb[1] = 2*b_sm[0] - b_sm[1] - - if p_moments >= 0: - # to preserve moments of degree p we need 1+p conforming basis functions in the patch (the "interior" ones) - # and for the given regularity constraint, there are local_shape[conf_comp][conf_axis]-2*(1+reg) such conforming functions - p_max = local_shape[conf_comp][conf_axis]-2*(1+reg) - 1 - if p_max < p_moments: - print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") - print( " ** WARNING -- WARNING -- WARNING ") - print(f" ** conf. projection imposing C{reg} smoothness on component {conf_comp} along axis {conf_axis}:") - print(f" ** there are not enough dofs in a patch to preserve moments of degree {p_moments} !") - print(f" ** Only able to preserve up to degree --> {p_max} <-- ") - print( " ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **") - p_moments = p_max + mu_minus[-1] = 1 + for p in range(p_moments + 1): + mu_plus[-1 - (p + 1)] = gamma[p] + + for m in range(coarse_space.nbasis): + for l in range(fine_space.nbasis): + mu_minus[m] += R[m, l] * mu_plus[l] + + if j == 0: + mu_minus[m] -= R[m, 0] + else: + mu_minus[m] -= R[m, -1] + + return mu_minus + + # loop over all interfaces + for I in Interfaces: + axis = I.axis + direction = I.ornt + # for now assume the interfaces are along the same direction + assert direction == 1 + k_minus = get_patch_index_from_face(domain, I.minus) + k_plus = get_patch_index_from_face(domain, I.plus) + + I_minus_ncells = Vh.spaces[k_minus].ncells + I_plus_ncells = Vh.spaces[k_plus].ncells + + # logical directions normal to interface + if I_minus_ncells <= I_plus_ncells: + k_fine, k_coarse = k_plus, k_minus + fine_axis, coarse_axis = I.plus.axis, I.minus.axis + fine_ext, coarse_ext = I.plus.ext, I.minus.ext - # computing the contribution to every moment of the differents basis function - # for simplicity we assemble the full matrix with all basis functions (ok if patches not too large) - Mass_mat = np.zeros((p_moments+1,local_shape[conf_comp][conf_axis])) - for poldeg in range(p_moments+1): - for ie1 in range(Nel[conf_axis]): #loop on cells - # cell_size = breaks_comp_axis[conf_comp][conf_axis][ie1+1]-breakpoints_x_y[ie1] # todo: try without (probably not needed - for il1 in range(p_comp_axis+1): #loops on basis function in each cell - val=0. - for q1 in range(nquads[conf_axis]): #loops on quadrature points - v0 = basis[conf_comp][conf_axis][ie1,il1,0,q1] - xd = grid[conf_axis][ie1,q1] - val += w[conf_axis][q1]*v0*((enddom-xd)/denom)**poldeg - locind=span[conf_comp][conf_axis][ie1]-p_comp_axis+il1 - Mass_mat[poldeg,locind]+=val - Rhs_0 = Mass_mat[:,0] - - if reg == 0: - Mat_to_inv = Mass_mat[:,1:p_moments+2] else: - Mat_to_inv = Mass_mat[:,2:p_moments+3] - Correct_coef_0 = np.linalg.solve(Mat_to_inv,Rhs_0) - cc_0_ax = Correct_coef_0 - - if reg == 1: - Rhs_1 = Mass_mat[:,1] - Correct_coef_1 = np.linalg.solve(Mat_to_inv,Rhs_1) - cc_1_ax = Correct_coef_1 - - if hom_bc: - # homogeneous bc is on the point value: no constraint on the derivatives - # so only the projection of B0 (to 0) has to be corrected - Mat_to_inv_bnd = Mass_mat[:,1:p_moments+2] - Correct_coef_bnd = np.linalg.solve(Mat_to_inv_bnd,Rhs_0) - - for p in range(0,p_moments+1): - # correction for moment preserving : - # we use the first p_moments+1 conforming ("interior") functions to preserve the p+1 moments - # modified by the C0 or C1 enforcement - if reg == 0: - a_sm[p+1] = (1-a_sm[0]) * cc_0_ax[p] - # proj constraint: - a_nb[p+1] = -a_sm[p+1] - + k_fine, k_coarse = k_minus, k_plus + fine_axis, coarse_axis = I.minus.axis, I.plus.axis + fine_ext, coarse_ext = I.minus.ext, I.plus.ext + + # logical directions along the interface + d_fine = 1 - fine_axis + d_coarse = 1 - coarse_axis + + space_fine = Vh.spaces[k_fine] + space_coarse = Vh.spaces[k_coarse] + + coarse_space_1d = space_coarse.spaces[d_coarse] + fine_space_1d = space_fine.spaces[d_fine] + E_1D, R_1D, ER_1D = get_extension_restriction( + coarse_space_1d, fine_space_1d, p_moments=p_moments) + + # Projecting coarse basis functions + for j in range(coarse_space_1d.nbasis): + jg = get_edge_index( + j, + coarse_axis, + coarse_ext, + space_coarse, + k_coarse) + + if (not corner_indices.issuperset({jg})): + + Proj_edge[jg, jg] = 1 / 2 + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, j, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[pg, jg] += 1 / 2 * gamma[p] + + for i in range(fine_space_1d.nbasis): + ig = get_edge_index( + i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[ig, jg] = 1 / 2 * E_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[pg, jg] += -1 / 2 * gamma[p] * E_1D[i, j] + else: + mu_minus = get_mu_minus( + j, coarse_space_1d, fine_space_1d, R_1D) + + for p in range(p_moments + 1): + for m in range(coarse_space_1d.nbasis): + pg = edge_moment_index( + p, m, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[pg, jg] += 1 / 2 * gamma[p] * mu_minus[m] + + for i in range(1, fine_space_1d.nbasis - 1): + ig = get_edge_index( + i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[ig, jg] = 1 / 2 * E_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, fine_axis, fine_ext, space_fine, k_fine) + for m in range(coarse_space_1d.nbasis): + Proj_edge[pg, jg] += -1 / 2 * \ + gamma[p] * E_1D[i, m] * mu_minus[m] + + # Projecting fine basis functions + for j in range(fine_space_1d.nbasis): + jg = get_edge_index(j, fine_axis, fine_ext, space_fine, k_fine) + + if (not corner_indices.issuperset({jg})): + for i in range(fine_space_1d.nbasis): + ig = get_edge_index( + i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[ig, jg] = 1 / 2 * ER_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[pg, jg] += 1 / 2 * gamma[p] * ER_1D[i, j] + + for i in range(coarse_space_1d.nbasis): + ig = get_edge_index( + i, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[ig, jg] = 1 / 2 * R_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[pg, jg] += - 1 / 2 * gamma[p] * R_1D[i, j] else: - a_sm[p+2] = (1-a_sm[0]) * cc_0_ax[p] -a_sm[1] * cc_1_ax[p] - b_sm[p+2] = -b_sm[0] * cc_0_ax[p] + (1-b_sm[1]) * cc_1_ax[p] - - # proj constraint: - b_nb[p+2] = b_sm[p+2] - a_nb[p+2] = -(a_sm[p+2] + 2*b_sm[p+2]) + mu_plus = get_mu_plus(j, fine_space_1d) + + for i in range(1, fine_space_1d.nbasis - 1): + ig = get_edge_index( + i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[ig, jg] = 1 / 2 * ER_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, fine_axis, fine_ext, space_fine, k_fine) + + for m in range(fine_space_1d.nbasis): + Proj_edge[pg, jg] += 1 / 2 * \ + gamma[p] * ER_1D[i, m] * mu_plus[m] + + for i in range(1, coarse_space_1d.nbasis - 1): + ig = get_edge_index( + i, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[ig, jg] = 1 / 2 * R_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, coarse_axis, coarse_ext, space_coarse, k_coarse) + + for m in range(fine_space_1d.nbasis): + Proj_edge[pg, jg] += - 1 / 2 * \ + gamma[p] * R_1D[i, m] * mu_plus[m] + + # boundary condition + if hom_bc: + for bn in domain.boundary: + k = get_patch_index_from_face(domain, bn) + space_k = Vh.spaces[k] + axis = bn.axis + + d = 1 - axis + ext = bn.ext + space_k_1d = space_k.spaces[d] + + for i in range(0, space_k_1d.nbasis): + ig = get_edge_index(i, axis, ext, space_k, k) + Proj_edge[ig, ig] = 0 + + if (i != 0 and i != space_k_1d.nbasis - 1): + for p in range(p_moments + 1): + + pg = edge_moment_index(p, i, axis, ext, space_k, k) + Proj_edge[pg, ig] = gamma[p] + else: + if corner_indices.issuperset({ig}): + mu_minus = get_mu_minus( + j, space_k_1d, space_k_1d, np.eye( + space_k_1d.nbasis)) + + for p in range(p_moments + 1): + for m in range(space_k_1d.nbasis): + pg = edge_moment_index( + p, m, axis, ext, space_k, k) + Proj_edge[pg, ig] = gamma[p] * mu_minus[m] + else: + multi_index = [None] * ndim + + for p in range(p_moments + 1): + multi_index[axis] = p + 1 if ext == - \ + 1 else space_k.spaces[axis].nbasis - 1 - p - 1 + for pd in range(p_moments + 1): + multi_index[1 - axis] = pd + \ + 1 if i == 0 else space_k.spaces[1 - + axis].nbasis - 1 - pd - 1 + pg = l2g.get_index(k, 0, multi_index) + Proj_edge[pg, ig] = gamma[p] * gamma[pd] - return a_sm, a_nb, b_sm, b_nb, Correct_coef_bnd, Correct_coef_0 + return Proj_edge @ Proj_vertex -def get_moment_pres_scalar_extension_restriction(matching_interfaces, coarse_space_1d, fine_space_1d, spl_type): - """ - Calculate the extension and restriction matrices for refining along an interface. + +def construct_hcurl_conforming_projection( + Vh, reg_orders=0, p_moments=-1, hom_bc=False): + """ + Construct the conforming projection for a vector Hcurl space for a given regularity (0 continuous, -1 discontinuous). Parameters ---------- - matching_interfaces : bool - Do both patches have the same number of cells? + Vh : TensorFemSpace + Finite Element Space coming from the discrete de Rham sequence. - coarse_space_1d : SplineSpace - Spline space of the coarse space. + reg_orders : (int) + Regularity in each space direction -1 or 0. - fine_space_1d : SplineSpace - Spline space of the fine space. + p_moments : (int) + Number of polynomial moments to be preserved. - spl_type : {'B', 'M'} - Spline type. + hom_bc : (bool) + Tangential homogeneous boundary conditions. Returns ------- - E_1D : numpy array - Extension matrix. + cP : scipy.sparse.csr_array + Conforming projection as a sparse matrix. + """ - R_1D : numpy array - Restriction matrix. + dim_tot = Vh.nbasis - ER_1D : numpy array - Extension-restriction matrix. - """ - grid = np.linspace(fine_space_1d.breaks[0], fine_space_1d.breaks[-1], coarse_space_1d.ncells+1) - coarse_space_1d_k_plus = SplineSpace(degree=fine_space_1d.degree, grid=grid, basis=fine_space_1d.basis) + # fully discontinuous space + if reg_orders < 0: + return sparse_eye(dim_tot, format="lil") - if not matching_interfaces: - E_1D = construct_extension_operator_1D( - domain=coarse_space_1d_k_plus, codomain=fine_space_1d) - - # Calculate the mass matrices - M_coarse = calculate_mass_matrix(coarse_space_1d, spl_type) - M_fine = calculate_mass_matrix(fine_space_1d, spl_type) + # moment corrections perpendicular to interfaces + gamma = [get_1d_moment_correction( + Vh.spaces[0].spaces[1 - d].spaces[d], p_moments=p_moments) for d in range(2)] - if spl_type == 'B': - M_coarse[:, 0] *= 1e13 - M_coarse[:, -1] *= 1e13 + domain = Vh.symbolic_space.domain + ndim = 2 + n_components = 2 + n_patches = len(domain) - M_coarse_inv = np.linalg.inv(M_coarse) - R_1D = M_coarse_inv @ E_1D.T @ M_fine - - if spl_type == 'B': - R_1D[0,0] = R_1D[-1,-1] = 1 + l2g = Local2GlobalIndexMap(ndim, len(domain), n_components) + for k in range(n_patches): + Vk = Vh.spaces[k] + # T is a TensorFemSpace and S is a 1D SplineSpace + shapes = [[S.nbasis for S in T.spaces] for T in Vk.spaces] + l2g.set_patch_shapes(k, *shapes) - ER_1D = E_1D @ R_1D - - else: - ER_1D = R_1D = E_1D = sparse_eye( - fine_space_1d.nbasis, format="lil") + # P edge + # edge correction matrix + Proj_edge = sparse_eye(dim_tot, format="lil") - return E_1D, R_1D, ER_1D + Interfaces = domain.interfaces + if isinstance(Interfaces, Interface): + Interfaces = (Interfaces, ) -# Didn't find this utility in the code base. -def calculate_mass_matrix(space_1d, spl_type): - """ - Calculate the mass-matrix of a 1d spline-space. + def get_edge_index(j, axis, ext, space, k): + multi_index = [None] * ndim + multi_index[axis] = 0 if ext == - \ + 1 else space.spaces[1 - axis].spaces[axis].nbasis - 1 + multi_index[1 - axis] = j + return l2g.get_index(k, 1 - axis, multi_index) - Parameters - ---------- + def edge_moment_index(p, i, axis, ext, space, k): + multi_index = [None] * ndim + multi_index[1 - axis] = i + multi_index[axis] = p + 1 if ext == - \ + 1 else space.spaces[1 - axis].spaces[axis].nbasis - 1 - p - 1 + return l2g.get_index(k, 1 - axis, multi_index) - space_1d : SplineSpace - Spline space of the fine space. + # loop over all interfaces + for I in Interfaces: + direction = I.ornt + # for now assume the interfaces are along the same direction + assert direction == 1 + k_minus = get_patch_index_from_face(domain, I.minus) + k_plus = get_patch_index_from_face(domain, I.plus) - spl_type : {'B', 'M'} - Spline type. + # logical directions normal to interface + minus_axis, plus_axis = I.minus.axis, I.plus.axis + # logical directions along the interface + d_minus, d_plus = 1 - minus_axis, 1 - plus_axis + I_minus_ncells = Vh.spaces[k_minus].spaces[d_minus].ncells[d_minus] + I_plus_ncells = Vh.spaces[k_plus].spaces[d_plus].ncells[d_plus] - Returns - ------- + # logical directions normal to interface + if I_minus_ncells <= I_plus_ncells: + k_fine, k_coarse = k_plus, k_minus + fine_axis, coarse_axis = I.plus.axis, I.minus.axis + fine_ext, coarse_ext = I.plus.ext, I.minus.ext - Mass_mat : numpy array - Mass matrix. - """ - Nel = space_1d.ncells - deg = space_1d.degree - knots = space_1d.knots + else: + k_fine, k_coarse = k_minus, k_plus + fine_axis, coarse_axis = I.minus.axis, I.plus.axis + fine_ext, coarse_ext = I.minus.ext, I.plus.ext - u, w = gauss_legendre(deg ) - # invert order - u = u[::-1] - w = w[::-1] + # logical directions along the interface + d_fine = 1 - fine_axis + d_coarse = 1 - coarse_axis - nquad = len(w) - quad_x, quad_w = quadrature_grid(space_1d.breaks, u, w) + space_fine = Vh.spaces[k_fine] + space_coarse = Vh.spaces[k_coarse] - coarse_basis = basis_ders_on_quad_grid(knots, deg, quad_x, 0, spl_type) - spans = elements_spans(knots, deg) + coarse_space_1d = space_coarse.spaces[d_coarse].spaces[d_coarse] + fine_space_1d = space_fine.spaces[d_fine].spaces[d_fine] + E_1D, R_1D, ER_1D = get_extension_restriction( + coarse_space_1d, fine_space_1d, p_moments=p_moments) + + # Projecting coarse basis functions + for j in range(coarse_space_1d.nbasis): + jg = get_edge_index( + j, + coarse_axis, + coarse_ext, + space_coarse, + k_coarse) + + Proj_edge[jg, jg] = 1 / 2 + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, j, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[pg, jg] += 1 / 2 * gamma[d_coarse][p] + + for i in range(fine_space_1d.nbasis): + ig = get_edge_index(i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[ig, jg] = 1 / 2 * E_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[pg, jg] += -1 / 2 * gamma[d_fine][p] * E_1D[i, j] + + # Projecting fine basis functions + for j in range(fine_space_1d.nbasis): + jg = get_edge_index(j, fine_axis, fine_ext, space_fine, k_fine) + + for i in range(fine_space_1d.nbasis): + ig = get_edge_index(i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[ig, jg] = 1 / 2 * ER_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, fine_axis, fine_ext, space_fine, k_fine) + Proj_edge[pg, jg] += 1 / 2 * gamma[d_fine][p] * ER_1D[i, j] + + for i in range(coarse_space_1d.nbasis): + ig = get_edge_index( + i, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[ig, jg] = 1 / 2 * R_1D[i, j] + + for p in range(p_moments + 1): + pg = edge_moment_index( + p, i, coarse_axis, coarse_ext, space_coarse, k_coarse) + Proj_edge[pg, jg] += - 1 / 2 * \ + gamma[d_coarse][p] * R_1D[i, j] + + # boundary condition + for bn in domain.boundary: + k = get_patch_index_from_face(domain, bn) + space_k = Vh.spaces[k] + axis = bn.axis - Mass_mat = np.zeros((space_1d.nbasis,space_1d.nbasis)) + if not hom_bc: + continue - for ie1 in range(Nel): #loop on cells - for il1 in range(deg+1): #loops on basis function in each cell - for il2 in range(deg+1): #loops on basis function in each cell - val=0. + d = 1 - axis + ext = bn.ext + space_k_1d = space_k.spaces[d].spaces[d] - for q1 in range(nquad): #loops on quadrature points - v0 = coarse_basis[ie1,il1,0,q1] - w0 = coarse_basis[ie1,il2,0,q1] - val += quad_w[ie1, q1] * v0 * w0 + for i in range(0, space_k_1d.nbasis): + ig = get_edge_index(i, axis, ext, space_k, k) + Proj_edge[ig, ig] = 0 - locind1 = il1 + spans[ie1] - deg - locind2 = il2 + spans[ie1] - deg - Mass_mat[locind1,locind2] += val + for p in range(p_moments + 1): + + pg = edge_moment_index(p, i, axis, ext, space_k, k) + Proj_edge[pg, ig] = gamma[d][p] - return Mass_mat \ No newline at end of file + return Proj_edge diff --git a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py index c10efd6b2..1aeef51ca 100644 --- a/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_conf_projectors_cart_2d.py @@ -1,71 +1,64 @@ -import numpy as np import pytest - from collections import OrderedDict -from sympde.topology import Derham, Square -from sympde.topology import IdentityMapping -from sympde.topology import Boundary, Interface, Union -from scipy.sparse.linalg import norm as sp_norm -from sympy import Tuple -from sympde.topology import Derham -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain, create_domain -from sympde.topology import IdentityMapping, PolarMapping -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection +import numpy as np +from sympy import Tuple +from scipy.sparse.linalg import norm as sp_norm + +from sympde.topology.domain import Domain +from sympde.topology import Derham, Square, IdentityMapping -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_rectangle, build_multipatch_domain -from psydac.feec.multipatch.utils_conga_2d import P_phys_l2, P_phys_hdiv, P_phys_hcurl, P_phys_h1 +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection +from psydac.feec.multipatch.utils_conga_2d import P_phys_l2, P_phys_hdiv, P_phys_hcurl, P_phys_h1 def get_polynomial_function(degree, hom_bc_axes, domain): - x, y = domain.coordinates - if hom_bc_axes[0]: + """Return a polynomial function of given degree and homogeneus boundary conditions on the domain.""" + + x, y = domain.coordinates + if hom_bc_axes[0]: assert degree[0] > 1 - g0_x = x * (x-np.pi) * (x-1.554)**(degree[0]-2) + g0_x = x * (x - 1) * (x - 1.554)**(degree[0] - 2) else: # if degree[0] > 1: # g0_x = (x-0.543)**2 * (x-1.554)**(degree[0]-2) # else: - g0_x = (x-0.25)#**degree[0] + g0_x = (x - 0.25)**degree[0] - if hom_bc_axes[1]: + if hom_bc_axes[1]: assert degree[1] > 1 - g0_y = y * (y-np.pi) * (y-0.324)**(degree[1]-2) + g0_y = y * (y - 1) * (y - 0.324)**(degree[1] - 2) else: # if degree[1] > 1: # g0_y = (y-1.675)**2 * (y-0.324)**(degree[1]-2) # else: - g0_y = (y-0.75)#**degree[1] + g0_y = (y - 0.75)**degree[1] return g0_x * g0_y -#============================================================================== + +# ============================================================================== @pytest.mark.parametrize('V1_type', ["Hcurl"]) -@pytest.mark.parametrize('degree', [[3,3]]) -@pytest.mark.parametrize('nc', [4]) -@pytest.mark.parametrize('reg', [[0,0]]) -@pytest.mark.parametrize('hom_bc', [[False, False]]) +@pytest.mark.parametrize('degree', [[3, 3]]) +@pytest.mark.parametrize('nc', [5]) +@pytest.mark.parametrize('reg', [0]) +@pytest.mark.parametrize('hom_bc', [False, True]) @pytest.mark.parametrize('domain_name', ["4patch_nc", "2patch_nc"]) -@pytest.mark.parametrize("nonconforming, full_mom_pres", [(True, False), (False, True)]) - - +@pytest.mark.parametrize("nonconforming, full_mom_pres", + [(True, True), (False, True)]) def test_conf_projectors_2d( - V1_type, - degree, - nc, - reg, - hom_bc, - full_mom_pres, - domain_name, - nonconforming - ): - - nquads=None - print(' .. multi-patch domain...') - + V1_type, + degree, + nc, + reg, + hom_bc, + full_mom_pres, + domain_name, + nonconforming +): if domain_name == '2patch_nc': @@ -76,7 +69,9 @@ def test_conf_projectors_2d( A = M1(A) B = M2(B) - domain = create_domain([A, B], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1]], name='domain') + domain = Domain.join(patches=[A, B], + connectivity=[((0, 0, 1), (1, 0, -1), 1)], + name='domain') elif domain_name == '4patch_nc': @@ -93,183 +88,222 @@ def test_conf_projectors_2d( C = M3(C) D = M4(D) - domain = create_domain([A, B, C, D], [[A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], - [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], - [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], - [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1] ], name='domain') - else: - domain = build_multipatch_domain(domain_name=domain_name) - - n_patches = len(domain) - - def levelof(k): - # some random refinement level (1 or 2 here) - return 1+((2*k) % 3) % 2 + domain = Domain.join(patches=[A, B, C, D], + connectivity=[((0, 0, 1), (1, 0, -1), 1), + ((2, 0, 1), (3, 0, -1), 1), + ((0, 1, 1), (2, 1, -1), 1), + ((1, 1, 1), (3, 1, -1), 1)], + name='domain') if nonconforming: - if len(domain) == 1: + if len(domain) == 2: ncells_h = { 'M1(A)': [nc, nc], - } - - elif len(domain) == 2: - ncells_h = { - 'M1(A)': [nc, nc], - 'M2(B)': [2*nc, 2*nc], + 'M2(B)': [2 * nc, 2 * nc], } elif len(domain) == 4: ncells_h = { 'M1(A)': [nc, nc], - 'M2(B)': [2*nc, 2*nc], - 'M3(C)': [2*nc, 2*nc], - 'M4(D)': [4*nc, 4*nc], + 'M2(B)': [2 * nc, 2 * nc], + 'M3(C)': [2 * nc, 2 * nc], + 'M4(D)': [4 * nc, 4 * nc], } - else: - ncells_h = {} - for k, D in enumerate(domain.interior): - print(k, D.name) - ncells_h[D.name] = [2**k *nc, 2**k * nc] + else: ncells_h = {} for k, D in enumerate(domain.interior): ncells_h[D.name] = [nc, nc] - print('ncells_h = ', ncells_h) - backend_language = 'python' - - print(' .. derham sequence...') derham = Derham(domain, ["H1", "Hcurl", "L2"]) - print(ncells_h) - domain_h = discretize(domain, ncells=ncells_h) # Vh space derham_h = discretize(derham, domain_h, degree=degree) V0h = derham_h.V0 V1h = derham_h.V1 V2h = derham_h.V2 - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = [m.get_callable_mapping() for m in mappings.values()] - p_derham = Derham(domain, ["H1", V1_type, "L2"]) + p_derham = Derham(domain, ["H1", V1_type, "L2"]) nquads = [(d + 1) for d in degree] - p_derham_h = discretize(p_derham, domain_h, degree=degree, nquads=nquads) + p_derham_h = discretize(p_derham, domain_h, degree=degree) p_V0h = p_derham_h.V0 p_V1h = p_derham_h.V1 p_V2h = p_derham_h.V2 - # full moment preservation only possible if enough interior functions in a patch (<=> enough cells) - if full_mom_pres and (nc >= 3 + 2*reg[0]) and (nc >= 3 + 2*reg[1]): - mom_pres = degree + # full moment preservation only possible if enough interior functions in a + # patch (<=> enough cells) + if full_mom_pres and (nc >= degree[0] + 1): + mom_pres = degree[0] else: - mom_pres = [-1,-1] - - # NOTE: if mom_pres but not full_mom_pres we could test reduced order moment preservation... + mom_pres = -1 + # NOTE: if mom_pres but not full_mom_pres we could test reduced order + # moment preservation... # geometric projections (operators) - p_geomP0, p_geomP1, p_geomP2 = p_derham_h.projectors() + p_geomP0, p_geomP1, p_geomP2 = p_derham_h.projectors(nquads=nquads) # conforming projections (scipy matrices) - cP0 = construct_scalar_conforming_projection(V0h, reg, mom_pres, nquads, hom_bc) - cP1 = construct_vector_conforming_projection(V1h, reg, mom_pres, nquads, hom_bc) - cP2 = construct_scalar_conforming_projection(V2h, [reg[0]- 1, reg[1]-1], mom_pres, nquads, hom_bc) + cP0 = construct_h1_conforming_projection(V0h, reg, mom_pres, hom_bc) + cP1 = construct_hcurl_conforming_projection(V1h, reg, mom_pres, hom_bc) + cP2 = construct_h1_conforming_projection(V2h, reg - 1, mom_pres, hom_bc) - HOp0 = HodgeOperator(p_V0h, domain_h) - M0 = HOp0.to_sparse_matrix() # mass matrix + HOp0 = HodgeOperator(p_V0h, domain_h) + M0 = HOp0.to_sparse_matrix() # mass matrix M0_inv = HOp0.get_dual_Hodge_sparse_matrix() # inverse mass matrix - HOp1 = HodgeOperator(p_V1h, domain_h) - M1 = HOp1.to_sparse_matrix() # mass matrix + HOp1 = HodgeOperator(p_V1h, domain_h) + M1 = HOp1.to_sparse_matrix() # mass matrix M1_inv = HOp1.get_dual_Hodge_sparse_matrix() # inverse mass matrix - HOp2 = HodgeOperator(p_V2h, domain_h) - M2 = HOp2.to_sparse_matrix() # mass matrix + HOp2 = HodgeOperator(p_V2h, domain_h) + M2 = HOp2.to_sparse_matrix() # mass matrix M2_inv = HOp2.get_dual_Hodge_sparse_matrix() # inverse mass matrix bD0, bD1 = p_derham_h.broken_derivatives_as_operators - - bD0 = bD0.to_sparse_matrix() # broken grad - bD1 = bD1.to_sparse_matrix() # broken curl or div - D0 = bD0 @ cP0 # Conga grad - D1 = bD1 @ cP1 # Conga curl or div - assert np.allclose(sp_norm(cP0 - cP0@cP0), 0, 1e-12, 1e-12) # cP0 is a projection - assert np.allclose(sp_norm(cP1 - cP1@cP1), 0, 1e-12, 1e-12) # cP1 is a projection - assert np.allclose(sp_norm(cP2 - cP2@cP2), 0, 1e-12, 1e-12) # cP2 is a projection + bD0 = bD0.to_sparse_matrix() # broken grad + bD1 = bD1.to_sparse_matrix() # broken curl or div + D0 = bD0 @ cP0 # Conga grad + D1 = bD1 @ cP1 # Conga curl or div + + assert np.allclose(sp_norm(cP0 - cP0 @ cP0), 0, 1e-12, + 1e-12) # cP0 is a projection + assert np.allclose(sp_norm(cP1 - cP1 @ cP1), 0, 1e-12, + 1e-12) # cP1 is a projection + assert np.allclose(sp_norm(cP2 - cP2 @ cP2), 0, 1e-12, + 1e-12) # cP2 is a projection - assert np.allclose(sp_norm( D0 - cP1@D0), 0, 1e-12, 1e-12) # D0 maps in the conforming V1 space (where cP1 coincides with Id) - assert np.allclose(sp_norm( D1 - cP2@D1), 0, 1e-12, 1e-12) # D1 maps in the conforming V2 space (where cP2 coincides with Id) + # D0 maps in the conforming V1 space (where cP1 coincides with Id) + assert np.allclose(sp_norm(D0 - cP1 @ D0), 0, 1e-12, 1e-12) + # D1 maps in the conforming V2 space (where cP2 coincides with Id) + assert np.allclose(sp_norm(D1 - cP2 @ D1), 0, 1e-12, 1e-12) # comparing projections of polynomials which should be exact - + # tests on cP0: - g0 = get_polynomial_function(degree=degree, hom_bc_axes=[hom_bc,hom_bc], domain=domain) + g0 = get_polynomial_function( + degree=degree, hom_bc_axes=[ + hom_bc, hom_bc], domain=domain) g0h = P_phys_h1(g0, p_geomP0, domain, mappings_list) - g0_c = g0h.coeffs.toarray() - - tilde_g0_c = p_derham_h.get_dual_dofs(space='V0', f=g0, return_format='numpy_array') + g0_c = g0h.coeffs.toarray() + + tilde_g0_c = p_derham_h.get_dual_dofs( + space='V0', f=g0, return_format='numpy_array') g0_L2_c = M0_inv @ tilde_g0_c - assert np.allclose(g0_c, g0_L2_c, 1e-12, 1e-12) # (P0_geom - P0_L2) polynomial = 0 - assert np.allclose(g0_c, cP0@g0_L2_c, 1e-12, 1e-12) # (P0_geom - confP0 @ P0_L2) polynomial= 0 + # (P0_geom - P0_L2) polynomial = 0 + assert np.allclose(g0_c, g0_L2_c, 1e-12, 1e-12) + # (P0_geom - confP0 @ P0_L2) polynomial= 0 + assert np.allclose(g0_c, cP0 @ g0_L2_c, 1e-12, 1e-12) if full_mom_pres: - # testing that polynomial moments are preserved: - # the following projection should be exact for polynomials of proper degree (no bc) - # conf_P0* : L2 -> V0 defined by := for all phi in V0 - g0 = get_polynomial_function(degree=degree, hom_bc_axes=[False, False], domain=domain) + # testing that polynomial moments are preserved: + # the following projection should be exact for polynomials of proper degree (no bc) + # conf_P0* : L2 -> V0 defined by := + # for all phi in V0 + g0 = get_polynomial_function(degree=degree, hom_bc_axes=[ + False, False], domain=domain) g0h = P_phys_h1(g0, p_geomP0, domain, mappings_list) - g0_c = g0h.coeffs.toarray() + g0_c = g0h.coeffs.toarray() - tilde_g0_c = p_derham_h.get_dual_dofs(space='V0', f=g0, return_format='numpy_array') + tilde_g0_c = p_derham_h.get_dual_dofs( + space='V0', f=g0, return_format='numpy_array') g0_star_c = M0_inv @ cP0.transpose() @ tilde_g0_c - assert np.allclose(g0_c, g0_star_c, 1e-12, 1e-12) # (P10_geom - P0_star) polynomial = 0 - + # (P10_geom - P0_star) polynomial = 0 + assert np.allclose(g0_c, g0_star_c, 1e-12, 1e-12) + # tests on cP1: G1 = Tuple( - get_polynomial_function(degree=[degree[0]-1,degree[1]], hom_bc_axes=[False,hom_bc], domain=domain), - get_polynomial_function(degree=[degree[0], degree[1]-1], hom_bc_axes=[hom_bc,False], domain=domain) + get_polynomial_function( + degree=[ + degree[0] - 1, + degree[1]], + hom_bc_axes=[ + False, + hom_bc], + domain=domain), + get_polynomial_function( + degree=[ + degree[0], + degree[1] - 1], + hom_bc_axes=[ + hom_bc, + False], + domain=domain) ) if V1_type == "Hcurl": G1h = P_phys_hcurl(G1, p_geomP1, domain, mappings_list) elif V1_type == "Hdiv": G1h = P_phys_hdiv(G1, p_geomP1, domain, mappings_list) - G1_c = G1h.coeffs.toarray() - tilde_G1_c = p_derham_h.get_dual_dofs(space='V1', f=G1, return_format='numpy_array') + + G1_c = G1h.coeffs.toarray() + tilde_G1_c = p_derham_h.get_dual_dofs( + space='V1', f=G1, return_format='numpy_array') G1_L2_c = M1_inv @ tilde_G1_c - assert np.allclose(G1_c, G1_L2_c, 1e-12, 1e-12) - assert np.allclose(G1_c, cP1 @ G1_L2_c, 1e-12, 1e-12) # (P1_geom - confP1 @ P1_L2) polynomial= 0 + assert np.allclose(G1_c, G1_L2_c, 1e-12, 1e-12) + # (P1_geom - confP1 @ P1_L2) polynomial= 0 + assert np.allclose(G1_c, cP1 @ G1_L2_c, 1e-12, 1e-12) if full_mom_pres: # as above G1 = Tuple( - get_polynomial_function(degree=[degree[0]-1,degree[1]], hom_bc_axes=[False,False], domain=domain), - get_polynomial_function(degree=[degree[0], degree[1]-1], hom_bc_axes=[False,False], domain=domain) - ) + get_polynomial_function( + degree=[ + degree[0] - 1, + degree[1]], + hom_bc_axes=[ + False, + False], + domain=domain), + get_polynomial_function( + degree=[ + degree[0], + degree[1] - 1], + hom_bc_axes=[ + False, + False], + domain=domain) + ) - G1h = P_phys_hcurl(G1, p_geomP1, domain, mappings_list) - G1_c = G1h.coeffs.toarray() + G1h = P_phys_hcurl(G1, p_geomP1, domain, mappings_list) + G1_c = G1h.coeffs.toarray() - tilde_G1_c = p_derham_h.get_dual_dofs(space='V1', f=G1, return_format='numpy_array') - G1_star_c = M1_inv @ cP1.transpose() @ tilde_G1_c - assert np.allclose(G1_c, G1_star_c, 1e-12, 1e-12) # (P1_geom - P1_star) polynomial = 0 + tilde_G1_c = p_derham_h.get_dual_dofs( + space='V1', f=G1, return_format='numpy_array') + G1_star_c = M1_inv @ cP1.transpose() @ tilde_G1_c + # (P1_geom - P1_star) polynomial = 0 + assert np.allclose(G1_c, G1_star_c, 1e-12, 1e-12) # tests on cP2 (non trivial for reg = 1): - g2 = get_polynomial_function(degree=[degree[0]-1,degree[1]-1], hom_bc_axes=[False,False], domain=domain) + g2 = get_polynomial_function( + degree=[ + degree[0] - 1, + degree[1] - 1], + hom_bc_axes=[ + False, + False], + domain=domain) g2h = P_phys_l2(g2, p_geomP2, domain, mappings_list) - g2_c = g2h.coeffs.toarray() + g2_c = g2h.coeffs.toarray() - tilde_g2_c = p_derham_h.get_dual_dofs(space='V2', f=g2, return_format='numpy_array') + tilde_g2_c = p_derham_h.get_dual_dofs( + space='V2', f=g2, return_format='numpy_array') g2_L2_c = M2_inv @ tilde_g2_c - assert np.allclose(g2_c, g2_L2_c, 1e-12, 1e-12) # (P2_geom - P2_L2) polynomial = 0 - assert np.allclose(g2_c, cP2 @ g2_L2_c, 1e-12, 1e-12) # (P2_geom - confP2 @ P2_L2) polynomial = 0 + # (P2_geom - P2_L2) polynomial = 0 + assert np.allclose(g2_c, g2_L2_c, 1e-12, 1e-12) + # (P2_geom - confP2 @ P2_L2) polynomial = 0 + assert np.allclose(g2_c, cP2 @ g2_L2_c, 1e-12, 1e-12) - if full_mom_pres: - # as above, here with same degree and bc as + if full_mom_pres: + # as above, here with same degree and bc as # tilde_g2_c = p_derham_h.get_dual_dofs(space='V2', f=g2, return_format='numpy_array', nquads=nquads) g2_star_c = M2_inv @ cP2.transpose() @ tilde_g2_c - assert np.allclose(g2_c, g2_star_c, 1e-12, 1e-12) # (P2_geom - P2_star) polynomial = 0 \ No newline at end of file + # (P2_geom - P2_star) polynomial = 0 + assert np.allclose(g2_c, g2_star_c, 1e-12, 1e-12) From 53c1b3c7493ef6423c6a676dec51b8d86a69288a Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Wed, 22 May 2024 16:42:44 +0200 Subject: [PATCH 41/88] adapt conforming examples to new projections --- .../examples/h1_source_pbms_conga_2d.py | 119 +++-- .../examples/hcurl_eigen_pbms_conga_2d.py | 139 +++-- .../examples/hcurl_source_pbms_conga_2d.py | 149 ++++-- .../examples/mixed_source_pbms_conga_2d.py | 181 ++++--- .../multipatch/examples/ppc_test_cases.py | 487 +++++++++--------- .../feec/multipatch/non_matching_operators.py | 4 +- 6 files changed, 625 insertions(+), 454 deletions(-) diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index 485900f14..9e792c3c3 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -11,24 +11,24 @@ from sympde.expr.expr import LinearForm from sympde.expr.expr import integral, Norm -from sympde.topology import Derham +from sympde.topology import Derham from sympde.topology import element_of - -from psydac.api.settings import PSYDAC_BACKENDS +from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.multipatch.api import discretize -from psydac.feec.pull_push import pull_2d_h1 +from psydac.feec.pull_push import pull_2d_h1 -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE -from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField +from psydac.fem.basic import FemField + def solve_h1_source_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2', source_type='manu_poisson', @@ -38,14 +38,14 @@ def solve_h1_source_pbm( """ solver for the problem: find u in H^1, such that - A u = f on \Omega - u = u_bc on \partial \Omega + A u = f on \\Omega + u = u_bc on \\partial \\Omega where the operator A u := eta * u - mu * div grad u - is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h @@ -67,7 +67,7 @@ def solve_h1_source_pbm( """ ncells = [nc, nc] - degree = [deg,deg] + degree = [deg, deg] # if backend_language is None: # backend_language='python' @@ -84,12 +84,13 @@ def solve_h1_source_pbm( print('building the multipatch domain...') domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) domain_h = discretize(domain, ncells=ncells) print('building the symbolic and discrete deRham sequences...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) derham_h = discretize(derham, domain_h, degree=degree) # multi-patch (broken) spaces @@ -108,7 +109,7 @@ def solve_h1_source_pbm( print('building the discrete operators:') print('commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) I0 = IdLinearOperator(V0h) @@ -119,29 +120,35 @@ def solve_h1_source_pbm( H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language) H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) - H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) - # cP1_m = construct_vector_conforming_projection(V1h, domain_h, hom_bc=True) + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) + # cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) if not os.path.exists(plot_dir): os.makedirs(plot_dir) def lift_u_bc(u_bc): if u_bc is not None: - print('lifting the boundary condition in V0h... [warning: Not Tested Yet!]') - # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs + print( + 'lifting the boundary condition in V0h... [warning: Not Tested Yet!]') + # note: for simplicity we apply the full P1 on u_bc, but we only + # need to set the boundary dofs u_bc = lambdify(domain.coordinates, u_bc) - u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) for m in mappings_list] - # it's a bit weird to apply P1 on the list of (pulled back) logical fields -- why not just apply it on u_bc ? + u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) + for m in mappings_list] + # it's a bit weird to apply P1 on the list of (pulled back) logical + # fields -- why not just apply it on u_bc ? uh_bc = P0(u_bc_log) ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very good approximation of uh_c ...) + # removing internal dofs (otherwise ubc_c may already be a very + # good approximation of uh_c ...) ubc_c = ubc_c - cP0_m.dot(ubc_c) else: ubc_c = None @@ -155,7 +162,8 @@ def lift_u_bc(u_bc): jump_penal_m = I0_m - cP0_m JP0_m = jump_penal_m.transpose() * H0_m * jump_penal_m - pre_A_m = cP0_m.transpose() @ ( eta * H0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) + # useful for the boundary condition (if present) + pre_A_m = cP0_m.transpose() @ (eta * H0_m - mu * pre_DG_m) A_m = pre_A_m @ cP0_m + gamma_h * JP0_m print('getting the source and ref solution...') @@ -172,18 +180,19 @@ def lift_u_bc(u_bc): if source_proj == 'P_geom': print('projecting the source with commuting projection P0...') f = lambdify(domain.coordinates, f_scal) - f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_h1(f, m.get_callable_mapping()) + for m in mappings_list] f_h = P0(f_log) f_c = f_h.coeffs.toarray() b_c = H0_m.dot(f_c) elif source_proj == 'P_L2': print('projecting the source with L2 projection...') - v = element_of(V0h.symbolic_space, name='v') + v = element_of(V0h.symbolic_space, name='v') expr = f_scal * v l = LinearForm(v, integral(domain, expr)) lh = discretize(l, domain_h, V0h) - b = lh.assemble() + b = lh.assemble() b_c = b.toarray() if plot_source: f_c = dH0_m.dot(b_c) @@ -191,7 +200,18 @@ def lift_u_bc(u_bc): raise ValueError(source_proj) if plot_source: - plot_field(numpy_coeffs=f_c, Vh=V0h, space_kind='h1', domain=domain, title='f_h with P = '+source_proj, filename=plot_dir+'fh_'+source_proj+'.png', hide_plot=hide_plots) + plot_field( + numpy_coeffs=f_c, + Vh=V0h, + space_kind='h1', + domain=domain, + title='f_h with P = ' + + source_proj, + filename=plot_dir + + 'fh_' + + source_proj + + '.png', + hide_plot=hide_plots) ubc_c = lift_u_bc(u_bc) @@ -216,17 +236,26 @@ def lift_u_bc(u_bc): print('getting and plotting the FEM solution from numpy coefs array...') title = r'solution $\phi_h$ (amplitude)' params_str = 'eta={}_mu={}_gamma_h={}'.format(eta, mu, gamma_h) - plot_field(numpy_coeffs=uh_c, Vh=V0h, space_kind='h1', domain=domain, title=title, filename=plot_dir+params_str+'_phi_h.png', hide_plot=hide_plots) - + plot_field( + numpy_coeffs=uh_c, + Vh=V0h, + space_kind='h1', + domain=domain, + title=title, + filename=plot_dir + + params_str + + '_phi_h.png', + hide_plot=hide_plots) if u_ex: - u = element_of(V0h.symbolic_space, name='u') - l2norm = Norm(u - u_ex, domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V0h) - uh_c = array_to_psydac(uh_c, V0h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) + u = element_of(V0h.symbolic_space, name='u') + l2norm = Norm(u - u_ex, domain, kind='l2') + l2norm_h = discretize(l2norm, domain_h, V0h) + uh_c = array_to_psydac(uh_c, V0h.vector_space) + l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) return l2_error + if __name__ == '__main__': t_stamp_full = time_count() @@ -234,9 +263,9 @@ def lift_u_bc(u_bc): quick_run = True # quick_run = False - omega = np.sqrt(170) # source + omega = np.sqrt(170) # source roundoff = 1e4 - eta = int(-omega**2 * roundoff)/roundoff + eta = int(-omega**2 * roundoff) / roundoff # print(eta) # source_type = 'elliptic_J' source_type = 'manu_poisson' @@ -261,13 +290,13 @@ def lift_u_bc(u_bc): solve_h1_source_pbm( nc=nc, deg=deg, eta=eta, - mu=1, #1, + mu=1, # 1, domain_name=domain_name, source_type=source_type, - source_proj = 'P_geom', + source_proj='P_geom', backend_language='pyccel-gcc', plot_source=True, - plot_dir='./plots/h1_tests_source_february/'+run_dir, + plot_dir='./plots/h1_tests_source_february/' + run_dir, hide_plots=True, ) diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index 01e74d868..b1256a356 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -7,32 +7,33 @@ from scipy.sparse.linalg import spilu, lgmres from scipy.sparse.linalg import LinearOperator, eigsh, minres -from scipy.linalg import norm +from scipy.linalg import norm -from sympde.topology import Derham +from sympde.topology import Derham -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection +from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection + def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language='python', mu=1, nu=0, gamma_h=10, sigma=None, nb_eigs=4, nb_eigs_plot=4, - plot_dir=None, hide_plots=True, m_load_dir="",skip_eigs_threshold = 1e-7,): + plot_dir=None, hide_plots=True, m_load_dir="", skip_eigs_threshold=1e-7,): """ solver for the eigenvalue problem: find lambda in R and u in H0(curl), such that - A u = lambda * u on \Omega + A u = lambda * u on \\Omega with an operator A u := mu * curl curl u - nu * grad div u - discretized as Ah: V1h -> V1h with a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + discretized as Ah: V1h -> V1h with a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h @@ -52,7 +53,7 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language """ ncells = [nc, nc] - degree = [deg,deg] + degree = [deg, deg] if sigma is None: raise ValueError('please specify a value for sigma') @@ -66,12 +67,13 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('building symbolic and discrete domain...') domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) domain_h = discretize(domain, ncells=ncells) print('building symbolic and discrete derham sequences...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) derham_h = discretize(derham, domain_h, degree=degree) V0h = derham_h.V0 @@ -83,7 +85,7 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('building the discrete operators:') print('commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) I1 = IdLinearOperator(V1h) @@ -91,21 +93,37 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('Hodge operators...') # multi-patch (broken) linear operators / matrices - H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) - H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) - - H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + H0 = HodgeOperator( + V0h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=0) + H1 = HodgeOperator( + V1h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=1) + H2 = HodgeOperator( + V2h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=2) + + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 H2_m = H2.to_sparse_matrix() # = mass matrix of V2 # dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True, True]) - cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True, True]) + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) + cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators @@ -135,22 +153,23 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('nu = {}'.format(nu)) A_m = mu * CC_m - nu * GD_m + gamma_h * JP_m - if False: #gneralized problen + if False: # gneralized problen print('adding jump stabilization to RHS of generalized eigenproblem...') B_m = cP1_m.transpose() @ H1_m @ cP1_m + JS_m else: B_m = H1_m - + print('solving matrix eigenproblem...') - all_eigenvalues, all_eigenvectors_transp = get_eigenvalues(nb_eigs, sigma, A_m, B_m) - #Eigenvalue processing - + all_eigenvalues, all_eigenvectors_transp = get_eigenvalues( + nb_eigs, sigma, A_m, B_m) + # Eigenvalue processing + zero_eigenvalues = [] if skip_eigs_threshold is not None: eigenvalues = [] eigenvectors = [] for val, vect in zip(all_eigenvalues, all_eigenvectors_transp.T): - if abs(val) < skip_eigs_threshold: + if abs(val) < skip_eigs_threshold: zero_eigenvalues.append(val) # we skip the eigenvector else: @@ -160,34 +179,35 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language eigenvalues = all_eigenvalues eigenvectors = all_eigenvectors_transp.T - - # plot first eigenvalues for i in range(min(nb_eigs_plot, len(eigenvalues))): - lambda_i = eigenvalues[i] + lambda_i = eigenvalues[i] print('looking at emode i = {}: {}... '.format(i, lambda_i)) - + emode_i = np.real(eigenvectors[i]) - norm_emode_i = np.dot(emode_i,H1_m.dot(emode_i)) + norm_emode_i = np.dot(emode_i, H1_m.dot(emode_i)) print('norm of computed eigenmode: ', norm_emode_i) - eh_c = emode_i/norm_emode_i # numpy coeffs of the normalized eigenmode - plot_field(numpy_coeffs=eh_c, Vh=V1h, space_kind='hcurl', domain=domain, title='mode e_{}, lambda_{}={}'.format(i,i,lambda_i), - filename=plot_dir+'e_{}.png'.format(i), hide_plot=hide_plots) + eh_c = emode_i / norm_emode_i # numpy coeffs of the normalized eigenmode + plot_field(numpy_coeffs=eh_c, Vh=V1h, space_kind='hcurl', domain=domain, title='mode e_{}, lambda_{}={}'.format(i, i, lambda_i), + filename=plot_dir + 'e_{}.png'.format(i), hide_plot=hide_plots) return eigenvalues, eigenvectors def get_eigenvalues(nb_eigs, sigma, A_m, M_m): print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') - print('computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format(nb_eigs, sigma) ) + print( + 'computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format( + nb_eigs, + sigma)) mode = 'normal' which = 'LM' # from eigsh docstring: # ncv = number of Lanczos vectors generated ncv must be greater than k and smaller than n; # it is recommended that ncv > 2*k. Default: min(n, max(2*k + 1, 20)) - ncv = 4*nb_eigs + ncv = 4 * nb_eigs print('A_m.shape = ', A_m.shape) try_lgmres = True max_shape_splu = 17000 @@ -197,17 +217,20 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): tol_eigsh = 0 else: - OP_m = A_m - sigma*M_m + OP_m = A_m - sigma * M_m tol_eigsh = 1e-7 if try_lgmres: - print('(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') + print( + '(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') OP_spilu = spilu(OP_m, fill_factor=15, drop_tol=5e-5) - preconditioner = LinearOperator(OP_m.shape, lambda x: OP_spilu.solve(x) ) + preconditioner = LinearOperator( + OP_m.shape, lambda x: OP_spilu.solve(x)) tol = tol_eigsh OPinv = LinearOperator( matvec=lambda v: lgmres(OP_m, v, x0=None, tol=tol, atol=tol, M=preconditioner, - callback=lambda x: print('cg -- residual = ', norm(OP_m.dot(x)-v)) - )[0], + callback=lambda x: print( + 'cg -- residual = ', norm(OP_m.dot(x) - v)) + )[0], shape=M_m.shape, dtype=M_m.dtype ) @@ -218,13 +241,21 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): # > here, minres: MINimum RESidual iteration to solve Ax=b # suggested in https://github.com/scipy/scipy/issues/4170 print('(with minres iterative solver for A_m - sigma*M1_m)') - OPinv = LinearOperator(matvec=lambda v: minres(OP_m, v, tol=1e-10)[0], shape=M_m.shape, dtype=M_m.dtype) + OPinv = LinearOperator( + matvec=lambda v: minres( + OP_m, + v, + tol=1e-10)[0], + shape=M_m.shape, + dtype=M_m.dtype) - eigenvalues, eigenvectors = eigsh(A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) + eigenvalues, eigenvectors = eigsh( + A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) print("done: eigenvalues found: " + repr(eigenvalues)) return eigenvalues, eigenvectors + if __name__ == '__main__': t_stamp_full = time_count() @@ -240,7 +271,7 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): nc = 8 deg = 4 - #domain_name = 'pretzel_f' + # domain_name = 'pretzel_f' domain_name = 'curved_L_shape' nc = 10 deg = 3 @@ -255,15 +286,15 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): hcurl_solve_eigen_pbm( nc=nc, deg=deg, nu=0, - mu=1, #1, + mu=1, # 1, domain_name=domain_name, backend_language='pyccel-gcc', - plot_dir='./plots/tests_source_february/'+run_dir, + plot_dir='./plots/tests_source_february/' + run_dir, hide_plots=True, - m_load_dir=m_load_dir, + m_load_dir=m_load_dir, gamma_h=0, - sigma=sigma, - nb_eigs=nb_eigs_solve, + sigma=sigma, + nb_eigs=nb_eigs_solve, nb_eigs_plot=nb_eigs_plot, skip_eigs_threshold=skip_eigs_threshold, ) diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index e9c6a8f7c..71bb84f4f 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -10,26 +10,27 @@ from scipy.sparse.linalg import spsolve -from sympde.calculus import dot -from sympde.topology import element_of +from sympde.calculus import dot +from sympde.topology import element_of from sympde.expr.expr import LinearForm from sympde.expr.expr import integral, Norm -from sympde.topology import Derham +from sympde.topology import Derham -from psydac.api.settings import PSYDAC_BACKENDS +from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.pull_push import pull_2d_hcurl -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE -from psydac.feec.multipatch.utilities import time_count -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.utilities import time_count +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField + +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection def solve_hcurl_source_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', @@ -40,14 +41,14 @@ def solve_hcurl_source_pbm( """ solver for the problem: find u in H(curl), such that - A u = f on \Omega - n x u = n x u_bc on \partial \Omega + A u = f on \\Omega + n x u = n x u_bc on \\partial \\Omega where the operator A u := eta * u + mu * curl curl u - nu * grad div u - is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h @@ -72,7 +73,7 @@ def solve_hcurl_source_pbm( """ ncells = [nc, nc] - degree = [deg,deg] + degree = [deg, deg] # if backend_language is None: # backend_language='python' @@ -93,12 +94,13 @@ def solve_hcurl_source_pbm( t_stamp = time_count() print('building symbolic domain sequence...') domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) t_stamp = time_count(t_stamp) print('building derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) t_stamp = time_count(t_stamp) print('building discrete domain...') @@ -110,7 +112,7 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) # multi-patch (broken) spaces @@ -132,9 +134,24 @@ def solve_hcurl_source_pbm( print('instanciating the Hodge operators...') # multi-patch (broken) linear operators / matrices # other option: define as Hodge Operators: - H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) - H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) + H0 = HodgeOperator( + V0h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=0) + H1 = HodgeOperator( + V1h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=1) + H2 = HodgeOperator( + V2h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=2) t_stamp = time_count(t_stamp) print('building the primal Hodge matrix H0_m = M0_m ...') @@ -142,7 +159,7 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building the dual Hodge matrix dH0_m = inv_M0_m ...') - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 t_stamp = time_count(t_stamp) print('building the primal Hodge matrix H1_m = M1_m ...') @@ -150,9 +167,10 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building the dual Hodge matrix dH1_m = inv_M1_m ...') - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 - # print("dH1_m @ H1_m == I1_m: {}".format(np.allclose((dH1_m @ H1_m).todense(), I1_m.todense())) ) # CHECK: OK + # print("dH1_m @ H1_m == I1_m: {}".format(np.allclose((dH1_m @ + # H1_m).todense(), I1_m.todense())) ) # CHECK: OK t_stamp = time_count(t_stamp) print('building the primal Hodge matrix H2_m = M2_m ...') @@ -160,9 +178,10 @@ def solve_hcurl_source_pbm( t_stamp = time_count(t_stamp) print('building the conforming Projection operators and matrices...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) - cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True,True]) + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) + cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) t_stamp = time_count(t_stamp) print('building the broken differential operators and matrices...') @@ -177,14 +196,18 @@ def solve_hcurl_source_pbm( def lift_u_bc(u_bc): if u_bc is not None: print('lifting the boundary condition in V1h...') - # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs + # note: for simplicity we apply the full P1 on u_bc, but we only + # need to set the boundary dofs u_bc_x = lambdify(domain.coordinates, u_bc[0]) u_bc_y = lambdify(domain.coordinates, u_bc[1]) - u_bc_log = [pull_2d_hcurl([u_bc_x, u_bc_y], m.get_callable_mapping()) for m in mappings_list] - # it's a bit weird to apply P1 on the list of (pulled back) logical fields -- why not just apply it on u_bc ? + u_bc_log = [pull_2d_hcurl( + [u_bc_x, u_bc_y], m.get_callable_mapping()) for m in mappings_list] + # it's a bit weird to apply P1 on the list of (pulled back) logical + # fields -- why not just apply it on u_bc ? uh_bc = P1(u_bc_log) ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very good approximation of uh_c ...) + # removing internal dofs (otherwise ubc_c may already be a very + # good approximation of uh_c ...) ubc_c = ubc_c - cP1_m.dot(ubc_c) else: ubc_c = None @@ -194,7 +217,7 @@ def lift_u_bc(u_bc): # curl curl: t_stamp = time_count(t_stamp) print('computing the curl-curl stiffness matrix...') - print(bD1_m.shape, H2_m.shape ) + print(bD1_m.shape, H2_m.shape) pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix @@ -215,7 +238,8 @@ def lift_u_bc(u_bc): print('eta = {}'.format(eta)) print('mu = {}'.format(mu)) print('nu = {}'.format(nu)) - pre_A_m = cP1_m.transpose() @ ( eta * H1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) + # useful for the boundary condition (if present) + pre_A_m = cP1_m.transpose() @ (eta * H1_m + mu * pre_CC_m - nu * pre_GD_m) A_m = pre_A_m @ cP1_m + gamma_h * JP_m # get exact source, bc's, ref solution... @@ -236,7 +260,8 @@ def lift_u_bc(u_bc): print('projecting the source with commuting projection...') f_x = lambdify(domain.coordinates, f_vect[0]) f_y = lambdify(domain.coordinates, f_vect[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) + for m in mappings_list] f_h = P1(f_log) f_c = f_h.coeffs.toarray() b_c = H1_m.dot(f_c) @@ -244,11 +269,11 @@ def lift_u_bc(u_bc): elif source_proj == 'P_L2': # f_h = L2 projection of f_vect print('projecting the source with L2 projection...') - v = element_of(V1h.symbolic_space, name='v') - expr = dot(f_vect,v) + v = element_of(V1h.symbolic_space, name='v') + expr = dot(f_vect, v) l = LinearForm(v, integral(domain, expr)) lh = discretize(l, domain_h, V1h) - b = lh.assemble() + b = lh.assemble() b_c = b.toarray() if plot_source: f_c = dH1_m.dot(b_c) @@ -256,7 +281,18 @@ def lift_u_bc(u_bc): raise ValueError(source_proj) if plot_source: - plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, title='f_h with P = '+source_proj, filename=plot_dir+'/fh_'+source_proj+'.png', hide_plot=hide_plots) + plot_field( + numpy_coeffs=f_c, + Vh=V1h, + space_kind='hcurl', + domain=domain, + title='f_h with P = ' + + source_proj, + filename=plot_dir + + '/fh_' + + source_proj + + '.png', + hide_plot=hide_plots) ubc_c = lift_u_bc(u_bc) @@ -284,22 +320,33 @@ def lift_u_bc(u_bc): t_stamp = time_count(t_stamp) print('getting and plotting the FEM solution from numpy coefs array...') - title = r'solution $u_h$ (amplitude) for $\eta = $'+repr(eta) + title = r'solution $u_h$ (amplitude) for $\eta = $' + repr(eta) params_str = 'eta={}_mu={}_nu={}_gamma_h={}'.format(eta, mu, nu, gamma_h) if plot_dir: - plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title, filename=plot_dir+params_str+'_uh.png', hide_plot=hide_plots) + plot_field( + numpy_coeffs=uh_c, + Vh=V1h, + space_kind='hcurl', + domain=domain, + title=title, + filename=plot_dir + + params_str + + '_uh.png', + hide_plot=hide_plots) time_count(t_stamp) if u_ex: - u = element_of(V1h.symbolic_space, name='u') - l2norm = Norm(Matrix([u[0] - u_ex[0],u[1] - u_ex[1]]), domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V1h) - uh_c = array_to_psydac(uh_c, V1h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) + u = element_of(V1h.symbolic_space, name='u') + l2norm = Norm( + Matrix([u[0] - u_ex[0], u[1] - u_ex[1]]), domain, kind='l2') + l2norm_h = discretize(l2norm, domain_h, V1h) + uh_c = array_to_psydac(uh_c, V1h.vector_space) + l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) return l2_error + if __name__ == '__main__': t_stamp_full = time_count() @@ -307,9 +354,9 @@ def lift_u_bc(u_bc): quick_run = True # quick_run = False - omega = np.sqrt(170) # source + omega = np.sqrt(170) # source roundoff = 1e4 - eta = int(-omega**2 * roundoff)/roundoff + eta = int(-omega**2 * roundoff) / roundoff source_type = 'manu_maxwell' # source_type = 'manu_J' @@ -336,12 +383,12 @@ def lift_u_bc(u_bc): nc=nc, deg=deg, eta=eta, nu=0, - mu=1, #1, + mu=1, # 1, domain_name=domain_name, source_type=source_type, backend_language='pyccel-gcc', plot_source=True, - plot_dir='./plots/tests_source_feb_13/'+run_dir, + plot_dir='./plots/tests_source_feb_13/' + run_dir, hide_plots=True, m_load_dir=m_load_dir ) diff --git a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py index ab4c17bb7..ef7abdce2 100644 --- a/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/mixed_source_pbms_conga_2d.py @@ -19,16 +19,17 @@ from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_l2 -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_sol_for_magnetostatic_pbm +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_sol_for_magnetostatic_pbm from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import get_eigenvalues -from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.utilities import time_count + +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection def solve_magnetostatic_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2_wcurl_J', @@ -47,8 +48,8 @@ def solve_magnetostatic_pbm( written in the form of a mixed problem: find p in H1, u in H(curl), such that - G^* u = f_scal on \Omega - G p + A u = f_vect on \Omega + G^* u = f_scal on \\Omega + G p + A u = f_vect on \\Omega with operators @@ -68,7 +69,7 @@ def solve_magnetostatic_pbm( Gh: V0h -> V1h and Ah: V1h -> V1h - in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h @@ -76,7 +77,7 @@ def solve_magnetostatic_pbm( Harmonic constraint: if dim_harmonic_space > 0, a constraint is added, of the form - u in H^\perp + u in H^\\perp where H = ker(L) is the kernel of the Hodge-Laplace operator L = curl curl u - grad div @@ -94,7 +95,7 @@ def solve_magnetostatic_pbm( """ ncells = [nc, nc] - degree = [deg,deg] + degree = [deg, deg] # if backend_language is None: # backend_language='python' @@ -113,12 +114,13 @@ def solve_magnetostatic_pbm( print('building symbolic and discrete domain...') domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) domain_h = discretize(domain, ncells=ncells) print('building symbolic and discrete derham sequences...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) derham_h = discretize(derham, domain_h, degree=degree) V0h = derham_h.V0 @@ -130,24 +132,28 @@ def solve_magnetostatic_pbm( print('building the discrete operators:') print('commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) - # these physical projection operators should probably be in the interface... + # these physical projection operators should probably be in the + # interface... def P0_phys(f_phys): f = lambdify(domain.coordinates, f_phys) - f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_h1(f, m.get_callable_mapping()) + for m in mappings_list] return P0(f_log) def P1_phys(f_phys): f_x = lambdify(domain.coordinates, f_phys[0]) f_y = lambdify(domain.coordinates, f_phys[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) + for m in mappings_list] return P1(f_log) def P2_phys(f_phys): f = lambdify(domain.coordinates, f_phys) - f_log = [pull_2d_l2(f, m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_l2(f, m.get_callable_mapping()) + for m in mappings_list] return P2(f_log) I0_m = IdLinearOperator(V0h).to_sparse_matrix() @@ -155,28 +161,43 @@ def P2_phys(f_phys): print('Hodge operators...') # multi-patch (broken) linear operators / matrices - H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) - H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) + H0 = HodgeOperator( + V0h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=0) + H1 = HodgeOperator( + V1h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=1) + H2 = HodgeOperator( + V2h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=2) H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 H2_m = H2.to_sparse_matrix() # = mass matrix of V2 - dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 + dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 M0_m = H0_m M1_m = H1_m # usual notation - hom_bc = (bc_type == 'pseudo-vacuum') # /!\ here u = B is in H(curl), not E /!\ + hom_bc = (bc_type == 'pseudo-vacuum') # /!\ here u = B is in H(curl), not E /!\ print('with hom_bc = {}'.format(hom_bc)) print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) - cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True,True]) - + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) + cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators @@ -216,63 +237,76 @@ def P2_phys(f_phys): GD_m = - tG_m @ dH0_m @ G_m.transpose() @ H1_m # todo: check with paper L_m = CC_m - GD_m + gamma_Lh * S1_m - eigenvalues, eigenvectors = get_eigenvalues(dim_harmonic_space+1, 1e-6, L_m, H1_m) + eigenvalues, eigenvectors = get_eigenvalues( + dim_harmonic_space + 1, 1e-6, L_m, H1_m) for i in range(dim_harmonic_space): - lambda_i = eigenvalues[i] - print(".. storing eigenmode #{}, with eigenvalue = {}".format(i, lambda_i)) + lambda_i = eigenvalues[i] + print( + ".. storing eigenmode #{}, with eigenvalue = {}".format( + i, lambda_i)) # check: if abs(lambda_i) > 1e-8: print(" ****** WARNING! this eigenvalue should be 0! ****** ") - hf_cs.append(eigenvectors[:,i]) + hf_cs.append(eigenvectors[:, i]) # matrix of the coefs of the harmonic fields (Lambda^H_i) in the basis (Lambda_i), in the form: - # hf_m = (c^H_{i,j})_{i < dim_harmonic_space, j < dim_V1} such that Lambda^H_i = sum_j c^H_{i,j} Lambda^1_j + # hf_m = (c^H_{i,j})_{i < dim_harmonic_space, j < dim_V1} such that + # Lambda^H_i = sum_j c^H_{i,j} Lambda^1_j hf_m = bmat(hf_cs).transpose() MH_m = M1_m @ hf_m # check: - lambda_i = eigenvalues[dim_harmonic_space] # should be the first positive eigenvalue of L_h + # should be the first positive eigenvalue of L_h + lambda_i = eigenvalues[dim_harmonic_space] if abs(lambda_i) < 1e-4: print(" ****** Warning -- something is probably wrong: ") - print(" ****** eigenmode #{} should have positive eigenvalue: {}".format(dim_harmonic_space, lambda_i)) + print( + " ****** eigenmode #{} should have positive eigenvalue: {}".format( + dim_harmonic_space, lambda_i)) print('computing the full operator matrix with harmonic constraint...') - A_m = bmat([[ reg_S0_m, tG_m.transpose(), None ], - [ tG_m, CC_m + gamma1_h * S1_m, MH_m ], - [ None, MH_m.transpose(), None ]]) + A_m = bmat([[reg_S0_m, tG_m.transpose(), None], + [tG_m, CC_m + gamma1_h * S1_m, MH_m], + [None, MH_m.transpose(), None]]) else: print('computing the full operator matrix without harmonic constraint...') - A_m = bmat([[ reg_S0_m, tG_m.transpose() ], - [ tG_m, CC_m + gamma1_h * S1_m ]]) + A_m = bmat([[reg_S0_m, tG_m.transpose()], + [tG_m, CC_m + gamma1_h * S1_m]]) # get exact source, bc's, ref solution... # (not all the returned functions are useful here) print('getting the source and ref solution...') N_diag = 200 method = 'conga' - f_scal, f_vect, j_scal, uh_ref = get_source_and_sol_for_magnetostatic_pbm(source_type=source_type, domain=domain, domain_name=domain_name) + f_scal, f_vect, j_scal, uh_ref = get_source_and_sol_for_magnetostatic_pbm( + source_type=source_type, domain=domain, domain_name=domain_name) # compute approximate source: # ff_h = (f0_h, f1_h) = (P0_h f_scal, P1_h f_vect) with projection operators specified by source_proj # and dual-basis coefficients in column array bb_c = (b0_c, b1_c) - # note: f1_h may also be defined through the special option 'P_L2_wcurl_J' for magnetostatic problems + # note: f1_h may also be defined through the special option 'P_L2_wcurl_J' + # for magnetostatic problems f0_c = f1_c = j2_c = None assert source_proj in ['P_geom', 'P_L2', 'P_L2_wcurl_J'] if f_scal is None: tilde_f0_c = np.zeros(V0h.nbasis) else: - print('approximating the V0 source with '+source_proj) + print('approximating the V0 source with ' + source_proj) if source_proj == 'P_geom': f0_h = P0_phys(f_scal) f0_c = f0_h.coeffs.toarray() tilde_f0_c = H0_m.dot(f0_c) else: # L2 proj - tilde_f0_c = derham_h.get_dual_dofs(space='V0', f=f_scal, backend_language=backend_language, return_format='numpy_array') + tilde_f0_c = derham_h.get_dual_dofs( + space='V0', + f=f_scal, + backend_language=backend_language, + return_format='numpy_array') if source_proj == 'P_L2_wcurl_J': if j_scal is None: @@ -280,34 +314,42 @@ def P2_phys(f_phys): tilde_f1_c = np.zeros(V1h.nbasis) else: print('approximating the V1 source as a weak curl of j_scal') - tilde_j2_c = derham_h.get_dual_dofs(space='V2', f=j_scal, backend_language=backend_language, return_format='numpy_array') + tilde_j2_c = derham_h.get_dual_dofs( + space='V2', + f=j_scal, + backend_language=backend_language, + return_format='numpy_array') tilde_f1_c = C_m.transpose().dot(tilde_j2_c) elif f_vect is None: - tilde_f1_c = np.zeros(V1h.nbasis) + tilde_f1_c = np.zeros(V1h.nbasis) else: - print('approximating the V1 source with '+source_proj) + print('approximating the V1 source with ' + source_proj) if source_proj == 'P_geom': f1_h = P1_phys(f_vect) f1_c = f1_h.coeffs.toarray() tilde_f1_c = H1_m.dot(f1_c) else: assert source_proj == 'P_L2' - tilde_f1_c = derham_h.get_dual_dofs(space='V1', f=f_vect, backend_language=backend_language, return_format='numpy_array') + tilde_f1_c = derham_h.get_dual_dofs( + space='V1', + f=f_vect, + backend_language=backend_language, + return_format='numpy_array') if plot_source: if f0_c is None: f0_c = dH0_m.dot(tilde_f0_c) - plot_field(numpy_coeffs=f0_c, Vh=V0h, space_kind='h1', domain=domain, title='f0_h with P = '+source_proj, - filename=plot_dir+'f0h_'+source_proj+'.png', hide_plot=hide_plots) + plot_field(numpy_coeffs=f0_c, Vh=V0h, space_kind='h1', domain=domain, title='f0_h with P = ' + source_proj, + filename=plot_dir + 'f0h_' + source_proj + '.png', hide_plot=hide_plots) if f1_c is None: f1_c = dH1_m.dot(tilde_f1_c) - plot_field(numpy_coeffs=f1_c, Vh=V1h, space_kind='hcurl', domain=domain, title='f1_h with P = '+source_proj, - filename=plot_dir+'f1h_'+source_proj+'.png', hide_plot=hide_plots) + plot_field(numpy_coeffs=f1_c, Vh=V1h, space_kind='hcurl', domain=domain, title='f1_h with P = ' + source_proj, + filename=plot_dir + 'f1h_' + source_proj + '.png', hide_plot=hide_plots) if source_proj == 'P_L2_wcurl_J': if j2_c is None: j2_c = dH2_m.dot(tilde_j2_c) plot_field(numpy_coeffs=j2_c, Vh=V2h, space_kind='l2', domain=domain, title='P_L2 jh in V2h', - filename=plot_dir+'j2h.png', hide_plot=hide_plots) + filename=plot_dir + 'j2h.png', hide_plot=hide_plots) print("building block RHS") if dim_harmonic_space > 0: @@ -321,15 +363,18 @@ def P2_phys(f_phys): sol_c = spsolve(A_m.asformat('csr'), b_c) # ------------------------------------------------------------ ph_c = sol_c[:V0h.nbasis] - uh_c = sol_c[V0h.nbasis:V0h.nbasis+V1h.nbasis] + uh_c = sol_c[V0h.nbasis:V0h.nbasis + V1h.nbasis] hh_c = np.zeros(V1h.nbasis) if dim_harmonic_space > 0: # compute the harmonic part (h) of the solution - hh_hbcoefs = sol_c[V0h.nbasis+V1h.nbasis:] # coefs of the harmonic part, in the basis of the harmonic fields + # coefs of the harmonic part, in the basis of the harmonic fields + hh_hbcoefs = sol_c[V0h.nbasis + V1h.nbasis:] assert len(hh_hbcoefs) == dim_harmonic_space for i in range(dim_harmonic_space): - hi_c = hf_cs[i] # coefs the of the i-th harmonic field, in the B/M spline basis of V1h - hh_c += hh_hbcoefs[i]*hi_c + # coefs the of the i-th harmonic field, in the B/M spline basis of + # V1h + hi_c = hf_cs[i] + hh_c += hh_hbcoefs[i] * hi_c if project_solution: print('projecting the homogeneous solution on the conforming problem space...') @@ -345,19 +390,20 @@ def P2_phys(f_phys): params_str = 'gamma0_h={}_gamma1_h={}'.format(gamma0_h, gamma1_h) title = r'solution {} (amplitude)'.format(p_name) plot_field(numpy_coeffs=ph_c, Vh=V0h, space_kind='h1', - domain=domain, title=title, filename=plot_dir+params_str+'_ph.png', hide_plot=hide_plots) + domain=domain, title=title, filename=plot_dir + params_str + '_ph.png', hide_plot=hide_plots) title = r'solution $h_h$ (amplitude)' plot_field(numpy_coeffs=hh_c, Vh=V1h, space_kind='hcurl', - domain=domain, title=title, filename=plot_dir+params_str+'_hh.png', hide_plot=hide_plots) + domain=domain, title=title, filename=plot_dir + params_str + '_hh.png', hide_plot=hide_plots) title = r'solution {} (amplitude)'.format(u_name) plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', - domain=domain, title=title, filename=plot_dir+params_str+'_uh.png', hide_plot=hide_plots) + domain=domain, title=title, filename=plot_dir + params_str + '_uh.png', hide_plot=hide_plots) title = r'solution {} (vector field)'.format(u_name) plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', - domain=domain, title=title, filename=plot_dir+params_str+'_uh_vf.png', hide_plot=hide_plots) + domain=domain, title=title, filename=plot_dir + params_str + '_uh_vf.png', hide_plot=hide_plots) title = r'solution {} (components)'.format(u_name) plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', - domain=domain, title=title, filename=plot_dir+params_str+'_uh_xy.png', hide_plot=hide_plots) + domain=domain, title=title, filename=plot_dir + params_str + '_uh_xy.png', hide_plot=hide_plots) + if __name__ == '__main__': @@ -383,7 +429,8 @@ def P2_phys(f_phys): # nc = 2 # deg = 2 - run_dir = '{}_{}_bc={}_nc={}_deg={}/'.format(domain_name, source_type, bc_type, nc, deg) + run_dir = '{}_{}_bc={}_nc={}_deg={}/'.format( + domain_name, source_type, bc_type, nc, deg) m_load_dir = 'matrices_{}_nc={}_deg={}/'.format(domain_name, nc, deg) solve_magnetostatic_pbm( nc=nc, deg=deg, @@ -394,7 +441,7 @@ def P2_phys(f_phys): backend_language='pyccel-gcc', dim_harmonic_space=dim_harmonic_space, plot_source=True, - plot_dir='./plots/magnetostatic_runs/'+run_dir, + plot_dir='./plots/magnetostatic_runs/' + run_dir, hide_plots=True, m_load_dir=m_load_dir ) diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index 58003f5af..c95a0d6b8 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -1,5 +1,6 @@ # coding: utf-8 +from sympy.functions.special.error_functions import erf from mpi4py import MPI import os @@ -9,199 +10,208 @@ from sympde.topology import Derham -from psydac.fem.basic import FemField -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.plotting_utilities import get_plotting_grid, my_small_plot, my_small_streamplot +from psydac.fem.basic import FemField +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.plotting_utilities import get_plotting_grid, my_small_plot, my_small_streamplot from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain comm = MPI.COMM_WORLD -# todo [MCP, 12/02/2022]: add an 'equation' argument to be able to return 'exact solution' +# todo [MCP, 12/02/2022]: add an 'equation' argument to be able to return +# 'exact solution' def get_phi_pulse(x_0, y_0, domain=None): - x,y = domain.coordinates + x, y = domain.coordinates ds2_0 = (0.02)**2 - sigma_0 = (x-x_0)**2 + (y-y_0)**2 - phi_0 = exp(-sigma_0**2/(2*ds2_0)) + sigma_0 = (x - x_0)**2 + (y - y_0)**2 + phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) return phi_0 + def get_div_free_pulse(x_0, y_0, domain=None): - x,y = domain.coordinates + x, y = domain.coordinates ds2_0 = (0.02)**2 - sigma_0 = (x-x_0)**2 + (y-y_0)**2 - phi_0 = exp(-sigma_0**2/(2*ds2_0)) - dx_sig_0 = 2*(x-x_0) - dy_sig_0 = 2*(y-y_0) + sigma_0 = (x - x_0)**2 + (y - y_0)**2 + phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) + dx_sig_0 = 2 * (x - x_0) + dy_sig_0 = 2 * (y - y_0) dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 - f_x = dy_phi_0 - f_y = - dx_phi_0 + f_x = dy_phi_0 + f_y = - dx_phi_0 f_vect = Tuple(f_x, f_y) return f_vect + def get_curl_free_pulse(x_0, y_0, domain=None, pp=False): # return -grad phi_0 - x,y = domain.coordinates + x, y = domain.coordinates if pp: # psi=phi ds2_0 = (0.02)**2 else: ds2_0 = (0.1)**2 - sigma_0 = (x-x_0)**2 + (y-y_0)**2 - phi_0 = exp(-sigma_0**2/(2*ds2_0)) - dx_sig_0 = 2*(x-x_0) - dy_sig_0 = 2*(y-y_0) + sigma_0 = (x - x_0)**2 + (y - y_0)**2 + phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) + dx_sig_0 = 2 * (x - x_0) + dy_sig_0 = 2 * (y - y_0) dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 - f_x = -dx_phi_0 - f_y = -dy_phi_0 + f_x = -dx_phi_0 + f_y = -dy_phi_0 f_vect = Tuple(f_x, f_y) return f_vect + def get_Delta_phi_pulse(x_0, y_0, domain=None, pp=False): # return -Delta phi_0, with same phi_0 as in get_curl_free_pulse() - x,y = domain.coordinates + x, y = domain.coordinates if pp: # psi=phi ds2_0 = (0.02)**2 else: ds2_0 = (0.1)**2 - sigma_0 = (x-x_0)**2 + (y-y_0)**2 - phi_0 = exp(-sigma_0**2/(2*ds2_0)) - dx_sig_0 = 2*(x-x_0) - dy_sig_0 = 2*(y-y_0) + sigma_0 = (x - x_0)**2 + (y - y_0)**2 + phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) + dx_sig_0 = 2 * (x - x_0) + dy_sig_0 = 2 * (y - y_0) dxx_sig_0 = 2 dyy_sig_0 = 2 - dxx_phi_0 = ((dx_sig_0 * sigma_0 / ds2_0)**2 - ((dx_sig_0)**2 + dxx_sig_0 * sigma_0)/ds2_0 ) * phi_0 - dyy_phi_0 = ((dy_sig_0 * sigma_0 / ds2_0)**2 - ((dy_sig_0)**2 + dyy_sig_0 * sigma_0)/ds2_0 ) * phi_0 - f = - dxx_phi_0 - dyy_phi_0 + dxx_phi_0 = ((dx_sig_0 * sigma_0 / ds2_0)**2 - + ((dx_sig_0)**2 + dxx_sig_0 * sigma_0) / ds2_0) * phi_0 + dyy_phi_0 = ((dy_sig_0 * sigma_0 / ds2_0)**2 - + ((dy_sig_0)**2 + dyy_sig_0 * sigma_0) / ds2_0) * phi_0 + f = - dxx_phi_0 - dyy_phi_0 return f + def get_Gaussian_beam_old(x_0, y_0, domain=None): # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v - x,y = domain.coordinates + x, y = domain.coordinates x = x - x_0 y = y - y_0 - + k = (10, 0) nk = np.sqrt(k[0]**2 + k[1]**2) - v = (k[0]/nk, k[1]/nk) - + v = (k[0] / nk, k[1] / nk) + sigma = 0.05 xy = x**2 + y**2 - ef = exp( - xy/(2*sigma**2) ) + ef = exp(- xy / (2 * sigma**2)) E = cos(k[1] * x + k[0] * y) * ef - B = (-v[1]*x + v[0]*y)/(sigma**2) * E - - return Tuple(v[0]*E, v[1]*E), B + B = (-v[1] * x + v[0] * y) / (sigma**2) * E + + return Tuple(v[0] * E, v[1] * E), B + -from sympy.functions.special.error_functions import erf def get_Gaussian_beam(x_0, y_0, domain=None): # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v - x,y = domain.coordinates - + x, y = domain.coordinates + x = x - x_0 y = y - y_0 - + sigma = 0.1 xy = x**2 + y**2 - ef = 1/(sigma**2) * exp( - xy/(2*sigma**2) ) + ef = 1 / (sigma**2) * exp(- xy / (2 * sigma**2)) # E = curl exp - E = Tuple( y * ef, -x * ef) + E = Tuple(y * ef, -x * ef) + + # B = curl E + B = (xy / (sigma**2) - 2) * ef - # B = curl E - B = (xy/(sigma**2) - 2) * ef - return E, B + def get_diag_Gaussian_beam(x_0, y_0, domain=None): # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v - x,y = domain.coordinates + x, y = domain.coordinates x = x - x_0 y = y - y_0 - + k = (np.pi, np.pi) nk = np.sqrt(k[0]**2 + k[1]**2) - v = (k[0]/nk, k[1]/nk) - + v = (k[0] / nk, k[1] / nk) + sigma = 0.25 xy = x**2 + y**2 - ef = exp( - xy/(2*sigma**2) ) + ef = exp(- xy / (2 * sigma**2)) E = cos(k[1] * x + k[0] * y) * ef - B = (-v[1]*x + v[0]*y)/(sigma**2) * E - - return Tuple(v[0]*E, v[1]*E), B + B = (-v[1] * x + v[0] * y) / (sigma**2) * E + + return Tuple(v[0] * E, v[1] * E), B + def get_easy_Gaussian_beam(x_0, y_0, domain=None): # return E = cos(k*x) exp( - x^2 + y^2 / 2 sigma^2) v - x,y = domain.coordinates + x, y = domain.coordinates x = x - x_0 y = y - y_0 - - k = pi + + k = pi sigma = 0.5 xy = x**2 + y**2 - ef = exp( - xy/(2*sigma**2) ) + ef = exp(- xy / (2 * sigma**2)) E = cos(k * y) * ef - B = -y/(sigma**2) * E - + B = -y / (sigma**2) * E + return Tuple(E, 0), B + def get_Gaussian_beam2(x_0, y_0, domain=None): - """ + """ Gaussian beam Beam inciding from the left, centered and normal to wall: x: axial normalized distance to the beam's focus y: radial normalized distance to the center axis of the beam """ - x,y = domain.coordinates - + x, y = domain.coordinates x0 = x_0 y0 = y_0 - theta = pi/2 + theta = pi / 2 w0 = 1 - t = [(x-x0)*cos(theta) - (y - y0) * sin(theta), (x-x0)*sin(theta) + (y-y0) * cos(theta)] + t = [(x - x0) * cos(theta) - (y - y0) * sin(theta), + (x - x0) * sin(theta) + (y - y0) * cos(theta)] - - EW0 = 1.0 # amplitude at the waist - k0 = 2 * pi # free-space wavenumber + k0 = 2 * pi # free-space wavenumber - x_ray = pi * w0 ** 2 # Rayleigh range + x_ray = pi * w0 ** 2 # Rayleigh range - w = w0 * ( 1 + t[0]**2/x_ray**2 )**0.5 #width - curv = t[0] / ( t[0]**2 + x_ray**2 ) #curvature + w = w0 * (1 + t[0]**2 / x_ray**2)**0.5 # width + curv = t[0] / (t[0]**2 + x_ray**2) # curvature - gouy_psi = -0.5 * atan2(t[0] / x_ray, 1.) # corresponds to atan(x / x_ray), which is the Gouy phase + # corresponds to atan(x / x_ray), which is the Gouy phase + gouy_psi = -0.5 * atan2(t[0] / x_ray, 1.) - EW_mod = EW0 * (w0 / w)**0.5 * exp(-(t[1] ** 2) / (w ** 2)) # Amplitude - phase = k0 * t[0] + 0.5 * k0 * curv * t[1] ** 2 + gouy_psi # Phase + EW_mod = EW0 * (w0 / w)**0.5 * exp(-(t[1] ** 2) / (w ** 2)) # Amplitude + phase = k0 * t[0] + 0.5 * k0 * curv * t[1] ** 2 + gouy_psi # Phase - EW_r = EW_mod * cos(phase) # Real part - EW_i = EW_mod * sin(phase) # Imaginary part + EW_r = EW_mod * cos(phase) # Real part + EW_i = EW_mod * sin(phase) # Imaginary part - B = 0#t[1]/(w**2) * EW_r + B = 0 # t[1]/(w**2) * EW_r - return Tuple(0,EW_r), B + return Tuple(0, EW_r), B def get_source_and_sol_for_magnetostatic_pbm( @@ -211,16 +221,16 @@ def get_source_and_sol_for_magnetostatic_pbm( ): """ provide source, and exact solutions when available, for: - + Find u=B in H(curl) such that - + div B = 0 curl B = j written as a mixed problem, see solve_magnetostatic_pbm() """ u_ex = None # exact solution - x,y = domain.coordinates + x, y = domain.coordinates if source_type == 'dipole_J': # we compute two possible source terms: # . a dipole current j_scal = phi_0 - phi_1 (two blobs) @@ -228,27 +238,27 @@ def get_source_and_sol_for_magnetostatic_pbm( x_0 = 1.0 y_0 = 1.0 ds2_0 = (0.02)**2 - sigma_0 = (x-x_0)**2 + (y-y_0)**2 - phi_0 = exp(-sigma_0**2/(2*ds2_0)) - dx_sig_0 = 2*(x-x_0) - dy_sig_0 = 2*(y-y_0) + sigma_0 = (x - x_0)**2 + (y - y_0)**2 + phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) + dx_sig_0 = 2 * (x - x_0) + dy_sig_0 = 2 * (y - y_0) dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 x_1 = 2.0 y_1 = 2.0 ds2_1 = (0.02)**2 - sigma_1 = (x-x_1)**2 + (y-y_1)**2 - phi_1 = exp(-sigma_1**2/(2*ds2_1)) - dx_sig_1 = 2*(x-x_1) - dy_sig_1 = 2*(y-y_1) + sigma_1 = (x - x_1)**2 + (y - y_1)**2 + phi_1 = exp(-sigma_1**2 / (2 * ds2_1)) + dx_sig_1 = 2 * (x - x_1) + dy_sig_1 = 2 * (y - y_1) dx_phi_1 = - dx_sig_1 * sigma_1 / ds2_1 * phi_1 dy_phi_1 = - dy_sig_1 * sigma_1 / ds2_1 * phi_1 - f_scal = None # + f_scal = None j_scal = phi_0 - phi_1 - f_x = dy_phi_0 - dy_phi_1 - f_y = - dx_phi_0 + dx_phi_1 + f_x = dy_phi_0 - dy_phi_1 + f_y = - dx_phi_0 + dx_phi_1 f_vect = Tuple(f_x, f_y) else: @@ -256,22 +266,22 @@ def get_source_and_sol_for_magnetostatic_pbm( return f_scal, f_vect, j_scal, u_ex - + def get_source_and_solution_hcurl( - source_type=None, eta=0, mu=0, nu=0, - domain=None, domain_name=None): + source_type=None, eta=0, mu=0, nu=0, + domain=None, domain_name=None): """ provide source, and exact solutions when available, for: - + Find u in H(curl) such that - - A u = f on \Omega - n x u = n x u_bc on \partial \Omega + + A u = f on \\Omega + n x u = n x u_bc on \\partial \\Omega with A u := eta * u + mu * curl curl u - nu * grad div u - + see solve_hcurl_source_pbm() """ @@ -280,26 +290,27 @@ def get_source_and_solution_hcurl( curl_u_ex = None div_u_ex = None - # bc solution: describe the bc on boundary. Inside domain, values should not matter. Homogeneous bc will be used if None + # bc solution: describe the bc on boundary. Inside domain, values should + # not matter. Homogeneous bc will be used if None u_bc = None # source terms f_vect = None - + # auxiliary term (for more diagnostics) grad_phi = None phi = None - x,y = domain.coordinates + x, y = domain.coordinates if source_type == 'manu_maxwell_inhom': # used for Maxwell equation with manufactured solution - f_vect = Tuple(eta*sin(pi*y) - pi**2*sin(pi*y)*cos(pi*x) + pi**2*sin(pi*y), - eta*sin(pi*x)*cos(pi*y) + pi**2*sin(pi*x)*cos(pi*y)) + f_vect = Tuple(eta * sin(pi * y) - pi**2 * sin(pi * y) * cos(pi * x) + pi**2 * sin(pi * y), + eta * sin(pi * x) * cos(pi * y) + pi**2 * sin(pi * x) * cos(pi * y)) if nu == 0: - u_ex = Tuple(sin(pi*y), sin(pi*x)*cos(pi*y)) - curl_u_ex = pi*(cos(pi*x)*cos(pi*y) - cos(pi*y)) - div_u_ex = -pi*sin(pi*x)*sin(pi*y) + u_ex = Tuple(sin(pi * y), sin(pi * x) * cos(pi * y)) + curl_u_ex = pi * (cos(pi * x) * cos(pi * y) - cos(pi * y)) + div_u_ex = -pi * sin(pi * x) * sin(pi * y) else: raise NotImplementedError u_bc = u_ex @@ -308,17 +319,17 @@ def get_source_and_solution_hcurl( # no manufactured solution for Maxwell pbm x0 = 1.5 y0 = 1.5 - s = (x-x0) - (y-y0) - t = (x-x0) + (y-y0) - a = (1/1.9)**2 - b = (1/1.2)**2 + s = (x - x0) - (y - y0) + t = (x - x0) + (y - y0) + a = (1 / 1.9)**2 + b = (1 / 1.2)**2 sigma2 = 0.0121 - tau = a*s**2 + b*t**2 - 1 - phi = exp(-tau**2/(2*sigma2)) - dx_tau = 2*( a*s + b*t) - dy_tau = 2*(-a*s + b*t) + tau = a * s**2 + b * t**2 - 1 + phi = exp(-tau**2 / (2 * sigma2)) + dx_tau = 2 * (a * s + b * t) + dy_tau = 2 * (-a * s + b * t) - f_x = dy_tau * phi + f_x = dy_tau * phi f_y = - dx_tau * phi f_vect = Tuple(f_x, f_y) @@ -326,29 +337,31 @@ def get_source_and_solution_hcurl( raise ValueError(source_type) # u_ex = Tuple(0, 1) # DEBUG - return f_vect, u_bc, u_ex, curl_u_ex, div_u_ex #, phi, grad_phi + return f_vect, u_bc, u_ex, curl_u_ex, div_u_ex # , phi, grad_phi + def get_source_and_solution_h1(source_type=None, eta=0, mu=0, - domain=None, domain_name=None): + domain=None, domain_name=None): """ provide source, and exact solutions when available, for: - + Find u in H^1, such that - A u = f on \Omega - u = u_bc on \partial \Omega + A u = f on \\Omega + u = u_bc on \\partial \\Omega with A u := eta * u - mu * div grad u - + see solve_h1_source_pbm() """ # exact solutions (if available) u_ex = None - # bc solution: describe the bc on boundary. Inside domain, values should not matter. Homogeneous bc will be used if None + # bc solution: describe the bc on boundary. Inside domain, values should + # not matter. Homogeneous bc will be used if None u_bc = None # source terms @@ -358,51 +371,51 @@ def get_source_and_solution_h1(source_type=None, eta=0, mu=0, grad_phi = None phi = None - x,y = domain.coordinates + x, y = domain.coordinates if source_type in ['manu_poisson_elliptic']: x0 = 1.5 y0 = 1.5 - s = (x-x0) - (y-y0) - t = (x-x0) + (y-y0) - a = (1/1.9)**2 - b = (1/1.2)**2 + s = (x - x0) - (y - y0) + t = (x - x0) + (y - y0) + a = (1 / 1.9)**2 + b = (1 / 1.2)**2 sigma2 = 0.0121 - tau = a*s**2 + b*t**2 - 1 - phi = exp(-tau**2/(2*sigma2)) - dx_tau = 2*( a*s + b*t) - dy_tau = 2*(-a*s + b*t) - dxx_tau = 2*(a + b) - dyy_tau = 2*(a + b) - - dx_phi = (-tau*dx_tau/sigma2)*phi - dy_phi = (-tau*dy_tau/sigma2)*phi + tau = a * s**2 + b * t**2 - 1 + phi = exp(-tau**2 / (2 * sigma2)) + dx_tau = 2 * (a * s + b * t) + dy_tau = 2 * (-a * s + b * t) + dxx_tau = 2 * (a + b) + dyy_tau = 2 * (a + b) + + dx_phi = (-tau * dx_tau / sigma2) * phi + dy_phi = (-tau * dy_tau / sigma2) * phi grad_phi = Tuple(dx_phi, dy_phi) - f_scal = -( (tau*dx_tau/sigma2)**2 - (tau*dxx_tau + dx_tau**2)/sigma2 - +(tau*dy_tau/sigma2)**2 - (tau*dyy_tau + dy_tau**2)/sigma2 )*phi + f_scal = -((tau * dx_tau / sigma2)**2 - (tau * dxx_tau + dx_tau**2) / sigma2 + + (tau * dy_tau / sigma2)**2 - (tau * dyy_tau + dy_tau**2) / sigma2) * phi # exact solution of -p'' = f with hom. bc's on pretzel domain if mu == 1 and eta == 0: u_ex = phi else: - print('WARNING (54375385643): exact solution not available in this case!') + print('WARNING (54375385643): exact solution not available in this case!') if not domain_name in ['pretzel', 'pretzel_f']: - # we may have non-hom bc's + # we may have non-hom bc's u_bc = u_ex elif source_type == 'manu_poisson_2': f_scal = -4 if mu == 1 and eta == 0: - u_ex = x**2+y**2 + u_ex = x**2 + y**2 else: raise NotImplementedError - u_bc = u_ex + u_bc = u_ex elif source_type == 'manu_poisson_sincos': - u_ex = sin(pi*x)*cos(pi*y) - f_scal = (eta + 2*mu*pi**2) * u_ex + u_ex = sin(pi * x) * cos(pi * y) + f_scal = (eta + 2 * mu * pi**2) * u_ex u_bc = u_ex else: @@ -411,10 +424,9 @@ def get_source_and_solution_h1(source_type=None, eta=0, mu=0, return f_scal, u_bc, u_ex - def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, - domain=None, domain_name=None, - refsol_params=None): + domain=None, domain_name=None, + refsol_params=None): """ OBSOLETE: kept for some test-cases """ @@ -423,7 +435,8 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, u_ex = None p_ex = None - # bc solution: describe the bc on boundary. Inside domain, values should not matter. Homogeneous bc will be used if None + # bc solution: describe the bc on boundary. Inside domain, values should + # not matter. Homogeneous bc will be used if None u_bc = None # only hom bc on p (for now...) @@ -435,23 +448,25 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, grad_phi = None phi = None - x,y = domain.coordinates + x, y = domain.coordinates if source_type == 'manu_J': # todo: remove if not used ? - # use a manufactured solution, with ad-hoc (homogeneous or inhomogeneous) bc + # use a manufactured solution, with ad-hoc (homogeneous or + # inhomogeneous) bc if domain_name in ['square_2', 'square_6', 'square_8', 'square_9']: t = 1 else: t = pi - u_ex = Tuple(sin(t*y), sin(t*x)*cos(t*y)) + u_ex = Tuple(sin(t * y), sin(t * x) * cos(t * y)) f_vect = Tuple( - sin(t*y) * (eta + t**2 *(mu - cos(t*x)*(mu-nu))), - sin(t*x) * cos(t*y) * (eta + t**2 *(mu+nu) ) + sin(t * y) * (eta + t**2 * (mu - cos(t * x) * (mu - nu))), + sin(t * x) * cos(t * y) * (eta + t**2 * (mu + nu)) ) - # boundary condition: (here we only need to coincide with u_ex on the boundary !) + # boundary condition: (here we only need to coincide with u_ex on the + # boundary !) if domain_name in ['square_2', 'square_6', 'square_9']: u_bc = None else: @@ -462,28 +477,28 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, # same as manu_poisson_ellip, with arbitrary value for tor x0 = 1.5 y0 = 1.5 - s = (x-x0) - (y-y0) - t = (x-x0) + (y-y0) - a = (1/1.9)**2 - b = (1/1.2)**2 + s = (x - x0) - (y - y0) + t = (x - x0) + (y - y0) + a = (1 / 1.9)**2 + b = (1 / 1.2)**2 sigma2 = 0.0121 tor = 2 - tau = a*s**2 + b*t**2 - 1 - phi = exp(-tau**tor/(2*sigma2)) - dx_tau = 2*( a*s + b*t) - dy_tau = 2*(-a*s + b*t) - dxx_tau = 2*(a + b) - dyy_tau = 2*(a + b) - f_scal = -((tor*tau**(tor-1)*dx_tau/(2*sigma2))**2 - (tau**(tor-1)*dxx_tau + (tor-1)*tau**(tor-2)*dx_tau**2)*tor/(2*sigma2) - +(tor*tau**(tor-1)*dy_tau/(2*sigma2))**2 - (tau**(tor-1)*dyy_tau + (tor-1)*tau**(tor-2)*dy_tau**2)*tor/(2*sigma2))*phi + tau = a * s**2 + b * t**2 - 1 + phi = exp(-tau**tor / (2 * sigma2)) + dx_tau = 2 * (a * s + b * t) + dy_tau = 2 * (-a * s + b * t) + dxx_tau = 2 * (a + b) + dyy_tau = 2 * (a + b) + f_scal = -((tor * tau**(tor - 1) * dx_tau / (2 * sigma2))**2 - (tau**(tor - 1) * dxx_tau + (tor - 1) * tau**(tor - 2) * dx_tau**2) * tor / (2 * sigma2) + + (tor * tau**(tor - 1) * dy_tau / (2 * sigma2))**2 - (tau**(tor - 1) * dyy_tau + (tor - 1) * tau**(tor - 2) * dy_tau**2) * tor / (2 * sigma2)) * phi p_ex = phi elif source_type == 'manu_maxwell': # used for Maxwell equation with manufactured solution - alpha = eta - u_ex = Tuple(sin(pi*y), sin(pi*x)*cos(pi*y)) - f_vect = Tuple(alpha*sin(pi*y) - pi**2*sin(pi*y)*cos(pi*x) + pi**2*sin(pi*y), - alpha*sin(pi*x)*cos(pi*y) + pi**2*sin(pi*x)*cos(pi*y)) + alpha = eta + u_ex = Tuple(sin(pi * y), sin(pi * x) * cos(pi * y)) + f_vect = Tuple(alpha * sin(pi * y) - pi**2 * sin(pi * y) * cos(pi * x) + pi**2 * sin(pi * y), + alpha * sin(pi * x) * cos(pi * y) + pi**2 * sin(pi * x) * cos(pi * y)) u_bc = u_ex elif source_type in ['manu_poisson', 'elliptic_J']: @@ -491,25 +506,24 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, # 'elliptic_J': used for Maxwell pbm (no manufactured solution) -- (was 'ellnew_J' in previous version) x0 = 1.5 y0 = 1.5 - s = (x-x0) - (y-y0) - t = (x-x0) + (y-y0) - a = (1/1.9)**2 - b = (1/1.2)**2 + s = (x - x0) - (y - y0) + t = (x - x0) + (y - y0) + a = (1 / 1.9)**2 + b = (1 / 1.2)**2 sigma2 = 0.0121 - tau = a*s**2 + b*t**2 - 1 - phi = exp(-tau**2/(2*sigma2)) - dx_tau = 2*( a*s + b*t) - dy_tau = 2*(-a*s + b*t) - dxx_tau = 2*(a + b) - dyy_tau = 2*(a + b) - - dx_phi = (-tau*dx_tau/sigma2)*phi - dy_phi = (-tau*dy_tau/sigma2)*phi + tau = a * s**2 + b * t**2 - 1 + phi = exp(-tau**2 / (2 * sigma2)) + dx_tau = 2 * (a * s + b * t) + dy_tau = 2 * (-a * s + b * t) + dxx_tau = 2 * (a + b) + dyy_tau = 2 * (a + b) + + dx_phi = (-tau * dx_tau / sigma2) * phi + dy_phi = (-tau * dy_tau / sigma2) * phi grad_phi = Tuple(dx_phi, dy_phi) - - f_scal = -( (tau*dx_tau/sigma2)**2 - (tau*dxx_tau + dx_tau**2)/sigma2 - +(tau*dy_tau/sigma2)**2 - (tau*dyy_tau + dy_tau**2)/sigma2 )*phi + f_scal = -((tau * dx_tau / sigma2)**2 - (tau * dxx_tau + dx_tau**2) / sigma2 + + (tau * dy_tau / sigma2)**2 - (tau * dyy_tau + dy_tau**2) / sigma2) * phi # exact solution of -p'' = f with hom. bc's on pretzel domain p_ex = phi @@ -518,19 +532,21 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, u_ex = phi if not domain_name in ['pretzel', 'pretzel_f']: - print("WARNING (87656547) -- I'm not sure we have an exact solution -- check the bc's on the domain "+domain_name) + print( + "WARNING (87656547) -- I'm not sure we have an exact solution -- check the bc's on the domain " + + domain_name) # raise NotImplementedError(domain_name) - f_x = dy_tau * phi + f_x = dy_tau * phi f_y = - dx_tau * phi f_vect = Tuple(f_x, f_y) elif source_type == 'manu_poisson_2': f_scal = -4 - p_ex = x**2+y**2 - phi = p_ex - u_bc = p_ex - u_ex = p_ex + p_ex = x**2 + y**2 + phi = p_ex + u_bc = p_ex + u_ex = p_ex elif source_type == 'curl_dipole_J': # used for the magnetostatic problem @@ -542,31 +558,32 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, # curl curl u = curl j # # then corresponds to a magnetic density, - # see Beirão da Veiga, Brezzi, Dassi, Marini and Russo, Virtual Element approx of 2D magnetostatic pbms, CMAME 327 (2017) + # see Beirão da Veiga, Brezzi, Dassi, Marini and Russo, Virtual Element + # approx of 2D magnetostatic pbms, CMAME 327 (2017) x_0 = 1.0 y_0 = 1.0 ds2_0 = (0.02)**2 - sigma_0 = (x-x_0)**2 + (y-y_0)**2 - phi_0 = exp(-sigma_0**2/(2*ds2_0)) - dx_sig_0 = 2*(x-x_0) - dy_sig_0 = 2*(y-y_0) + sigma_0 = (x - x_0)**2 + (y - y_0)**2 + phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) + dx_sig_0 = 2 * (x - x_0) + dy_sig_0 = 2 * (y - y_0) dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 x_1 = 2.0 y_1 = 2.0 ds2_1 = (0.02)**2 - sigma_1 = (x-x_1)**2 + (y-y_1)**2 - phi_1 = exp(-sigma_1**2/(2*ds2_1)) - dx_sig_1 = 2*(x-x_1) - dy_sig_1 = 2*(y-y_1) + sigma_1 = (x - x_1)**2 + (y - y_1)**2 + phi_1 = exp(-sigma_1**2 / (2 * ds2_1)) + dx_sig_1 = 2 * (x - x_1) + dy_sig_1 = 2 * (y - y_1) dx_phi_1 = - dx_sig_1 * sigma_1 / ds2_1 * phi_1 dy_phi_1 = - dy_sig_1 * sigma_1 / ds2_1 * phi_1 - f_x = dy_phi_0 - dy_phi_1 + f_x = dy_phi_0 - dy_phi_1 f_y = - dx_phi_0 + dx_phi_1 - f_scal = 0 # phi_0 - phi_1 + f_scal = 0 # phi_0 - phi_1 f_vect = Tuple(f_x, f_y) elif source_type == 'old_ellip_J': @@ -579,23 +596,24 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, y0 = 1.5 # s0 = x0-y0 # t0 = x0+y0 - s = (x-x0) - (y-y0) - t = (x-x0) + (y-y0) - aa = (1/1.7)**2 - bb = (1/1.1)**2 + s = (x - x0) - (y - y0) + t = (x - x0) + (y - y0) + aa = (1 / 1.7)**2 + bb = (1 / 1.1)**2 dsigpsi2 = 0.01 - sigma = aa*s**2 + bb*t**2 - 1 - psi = exp(-sigma**2/(2*dsigpsi2)) - dx_sig = 2*( aa*s + bb*t) - dy_sig = 2*(-aa*s + bb*t) - f_x = dy_sig * psi + sigma = aa * s**2 + bb * t**2 - 1 + psi = exp(-sigma**2 / (2 * dsigpsi2)) + dx_sig = 2 * (aa * s + bb * t) + dy_sig = 2 * (-aa * s + bb * t) + f_x = dy_sig * psi f_y = - dx_sig * psi dsigphi2 = 0.01 # this one gives approx 1e-10 at boundary for phi - # dsigphi2 = 0.005 # if needed: smaller support for phi, to have a smaller value at boundary - phi = exp(-sigma**2/(2*dsigphi2)) - dx_phi = phi*(-dx_sig*sigma/dsigphi2) - dy_phi = phi*(-dy_sig*sigma/dsigphi2) + # dsigphi2 = 0.005 # if needed: smaller support for phi, to have + # a smaller value at boundary + phi = exp(-sigma**2 / (2 * dsigphi2)) + dx_phi = phi * (-dx_sig * sigma / dsigphi2) + dy_phi = phi * (-dy_sig * sigma / dsigphi2) grad_phi = Tuple(dx_phi, dy_phi) f_vect = Tuple(f_x, f_y) @@ -608,20 +626,20 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, # 'rotating' (divergence-free) f field: if domain_name in ['square_2', 'square_6', 'square_8', 'square_9']: - r0 = np.pi/4 + r0 = np.pi / 4 dr = 0.1 - x0 = np.pi/2 - y0 = np.pi/2 - omega = 43/2 + x0 = np.pi / 2 + y0 = np.pi / 2 + omega = 43 / 2 # alpha = -omega**2 # not a square eigenvalue f_factor = 100 elif domain_name in ['curved_L_shape']: - r0 = np.pi/4 + r0 = np.pi / 4 dr = 0.1 - x0 = np.pi/2 - y0 = np.pi/2 - omega = 43/2 + x0 = np.pi / 2 + y0 = np.pi / 2 + omega = 43 / 2 # alpha = -omega**2 # not a square eigenvalue f_factor = 100 @@ -633,7 +651,7 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, source_option = 2 - if source_option==1: + if source_option == 1: # big circle: r0 = 2.4 dr = 0.05 @@ -641,7 +659,7 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, y0 = 0.5 f_factor = 10 - elif source_option==2: + elif source_option == 2: # small circle in corner: if source_type == 'ring_J': dr = 0.2 @@ -658,10 +676,11 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, raise NotImplementedError # note: some other currents give sympde error, see below [1] - phi = f_factor * exp( - .5*(( (x-x0)**2 + (y-y0)**2 - r0**2 )/dr)**2 ) + phi = f_factor * \ + exp(- .5 * (((x - x0)**2 + (y - y0)**2 - r0**2) / dr)**2) - f_x = - (y-y0) * phi - f_y = (x-x0) * phi + f_x = - (y - y0) * phi + f_y = (x - x0) * phi f_vect = Tuple(f_x, f_y) @@ -669,5 +688,3 @@ def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, raise ValueError(source_type) return f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi - - diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index f98ca00dd..8d5d2a94d 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -282,8 +282,8 @@ def get_extension_restriction(coarse_space_1d, fine_space_1d, p_moments=-1): Extension-restriction matrix. """ matching_interfaces = (coarse_space_1d.ncells == fine_space_1d.ncells) - assert (coarse_space_1d.breaks[0] == fine_space_1d.breaks[0]) and ( - coarse_space_1d.breaks[-1] == fine_space_1d.breaks[-1]) + # assert (coarse_space_1d.breaks[0] == fine_space_1d.breaks[0]) and ( + # coarse_space_1d.breaks[-1] == fine_space_1d.breaks[-1]) assert (coarse_space_1d.basis == fine_space_1d.basis) spl_type = coarse_space_1d.basis From dcc86b1ee5444f9a9cf3b5d8a4b5219bf54fda5e Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Thu, 23 May 2024 13:15:10 +0200 Subject: [PATCH 42/88] update non-matching examples --- .../examples_nc/h1_source_pbms_nc.py | 137 +-- .../examples_nc/hcurl_eigen_pbms_dg.py | 258 ++++-- .../examples_nc/hcurl_eigen_pbms_nc.py | 253 ++++-- .../examples_nc/hcurl_eigen_testcase.py | 188 +++-- .../examples_nc/hcurl_source_pbms_nc.py | 226 +++-- .../examples_nc/hcurl_source_testcase.py | 97 ++- .../examples_nc/timedomain_maxwell_nc.py | 792 ++++++++++-------- .../timedomain_maxwell_testcase.py | 275 ++++++ .../timedomain_maxwells_testcase.py | 251 ------ .../feec/multipatch/non_matching_operators.py | 5 +- 10 files changed, 1460 insertions(+), 1022 deletions(-) create mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwell_testcase.py delete mode 100644 psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py diff --git a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py index b646e84d8..9f12c6f58 100644 --- a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py @@ -1,4 +1,17 @@ -# coding: utf-8 +""" + solver for the problem: find u in H^1, such that + + A u = f on \\Omega + u = u_bc on \\partial \\Omega + + where the operator + + A u := eta * u - mu * div grad u + + is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, + + V0h --grad-> V1h -—curl-> V2h +""" from mpi4py import MPI @@ -11,25 +24,25 @@ from sympde.expr.expr import LinearForm from sympde.expr.expr import integral, Norm -from sympde.topology import Derham +from sympde.topology import Derham from sympde.topology import element_of - -from psydac.api.settings import PSYDAC_BACKENDS +from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.multipatch.api import discretize -from psydac.feec.pull_push import pull_2d_h1 +from psydac.feec.pull_push import pull_2d_h1 -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE -from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.utilities import time_count +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField +from psydac.fem.basic import FemField + def solve_h1_source_pbm_nc( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2', source_type='manu_poisson', @@ -39,14 +52,14 @@ def solve_h1_source_pbm_nc( """ solver for the problem: find u in H^1, such that - A u = f on \Omega - u = u_bc on \partial \Omega + A u = f on \\Omega + u = u_bc on \\partial \\Omega where the operator A u := eta * u - mu * div grad u - is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h @@ -68,7 +81,7 @@ def solve_h1_source_pbm_nc( """ ncells = nc - degree = [deg,deg] + degree = [deg, deg] # if backend_language is None: # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: @@ -88,13 +101,15 @@ def solve_h1_source_pbm_nc( print('building the multipatch domain...') domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) - ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + ncells_h = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} domain_h = discretize(domain, ncells=ncells_h) print('building the symbolic and discrete deRham sequences...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) derham_h = discretize(derham, domain_h, degree=degree) # multi-patch (broken) spaces @@ -113,7 +128,7 @@ def solve_h1_source_pbm_nc( print('building the discrete operators:') print('commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) I0 = IdLinearOperator(V0h) @@ -125,27 +140,33 @@ def solve_h1_source_pbm_nc( H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 H1_m = H1.to_sparse_matrix() # = mass matrix of V1 print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True,True]) - # cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True, True]) + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) + # cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) if not os.path.exists(plot_dir): os.makedirs(plot_dir) def lift_u_bc(u_bc): if u_bc is not None: - print('lifting the boundary condition in V0h... [warning: Not Tested Yet!]') - # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs + print( + 'lifting the boundary condition in V0h... [warning: Not Tested Yet!]') + # note: for simplicity we apply the full P1 on u_bc, but we only + # need to set the boundary dofs u_bc = lambdify(domain.coordinates, u_bc) - u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) for m in mappings_list] - # it's a bit weird to apply P1 on the list of (pulled back) logical fields -- why not just apply it on u_bc ? + u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) + for m in mappings_list] + # it's a bit weird to apply P1 on the list of (pulled back) logical + # fields -- why not just apply it on u_bc ? uh_bc = P0(u_bc_log) ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very good approximation of uh_c ...) + # removing internal dofs (otherwise ubc_c may already be a very + # good approximation of uh_c ...) ubc_c = ubc_c - cP0_m.dot(ubc_c) else: ubc_c = None @@ -159,7 +180,8 @@ def lift_u_bc(u_bc): jump_penal_m = I0_m - cP0_m JP0_m = jump_penal_m.transpose() * H0_m * jump_penal_m - pre_A_m = cP0_m.transpose() @ ( eta * H0_m - mu * pre_DG_m ) # useful for the boundary condition (if present) + # useful for the boundary condition (if present) + pre_A_m = cP0_m.transpose() @ (eta * H0_m - mu * pre_DG_m) A_m = pre_A_m @ cP0_m + gamma_h * JP0_m print('getting the source and ref solution...') @@ -176,18 +198,19 @@ def lift_u_bc(u_bc): if source_proj == 'P_geom': print('projecting the source with commuting projection P0...') f = lambdify(domain.coordinates, f_scal) - f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_h1(f, m.get_callable_mapping()) + for m in mappings_list] f_h = P0(f_log) f_c = f_h.coeffs.toarray() b_c = H0_m.dot(f_c) elif source_proj == 'P_L2': print('projecting the source with L2 projection...') - v = element_of(V0h.symbolic_space, name='v') + v = element_of(V0h.symbolic_space, name='v') expr = f_scal * v l = LinearForm(v, integral(domain, expr)) lh = discretize(l, domain_h, V0h) - b = lh.assemble() + b = lh.assemble() b_c = b.toarray() if plot_source: f_c = dH0_m.dot(b_c) @@ -195,7 +218,18 @@ def lift_u_bc(u_bc): raise ValueError(source_proj) if plot_source: - plot_field(numpy_coeffs=f_c, Vh=V0h, space_kind='h1', domain=domain, title='f_h with P = '+source_proj, filename=plot_dir+'/fh_'+source_proj+'.png', hide_plot=hide_plots) + plot_field( + numpy_coeffs=f_c, + Vh=V0h, + space_kind='h1', + domain=domain, + title='f_h with P = ' + + source_proj, + filename=plot_dir + + '/fh_' + + source_proj + + '.png', + hide_plot=hide_plots) ubc_c = lift_u_bc(u_bc) @@ -220,17 +254,26 @@ def lift_u_bc(u_bc): print('getting and plotting the FEM solution from numpy coefs array...') title = r'solution $\phi_h$ (amplitude)' params_str = 'eta={}_mu={}_gamma_h={}'.format(eta, mu, gamma_h) - plot_field(numpy_coeffs=uh_c, Vh=V0h, space_kind='h1', domain=domain, title=title, filename=plot_dir+params_str+'_phi_h.png', hide_plot=hide_plots) - + plot_field( + numpy_coeffs=uh_c, + Vh=V0h, + space_kind='h1', + domain=domain, + title=title, + filename=plot_dir + + params_str + + '_phi_h.png', + hide_plot=hide_plots) if u_ex: - u = element_of(V0h.symbolic_space, name='u') - l2norm = Norm(u - u_ex, domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V0h) - uh_c = array_to_psydac(uh_c, V0h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) + u = element_of(V0h.symbolic_space, name='u') + l2norm = Norm(u - u_ex, domain, kind='l2') + l2norm_h = discretize(l2norm, domain_h, V0h) + uh_c = array_to_psydac(uh_c, V0h.vector_space) + l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) return l2_error + if __name__ == '__main__': t_stamp_full = time_count() @@ -238,9 +281,9 @@ def lift_u_bc(u_bc): quick_run = True # quick_run = False - omega = np.sqrt(170) # source + omega = np.sqrt(170) # source roundoff = 1e4 - eta = int(-omega**2 * roundoff)/roundoff + eta = int(-omega**2 * roundoff) / roundoff # print(eta) # source_type = 'elliptic_J' source_type = 'manu_poisson' @@ -255,8 +298,8 @@ def lift_u_bc(u_bc): domain_name = 'pretzel_f' # domain_name = 'curved_L_shape' - nc = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) - + nc = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, + 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) deg = 2 @@ -267,12 +310,12 @@ def lift_u_bc(u_bc): solve_h1_source_pbm_nc( nc=nc, deg=deg, eta=eta, - mu=1, #1, + mu=1, # 1, domain_name=domain_name, source_type=source_type, backend_language='pyccel-gcc', plot_source=True, - plot_dir='./plots/h1_tests_source_february/'+run_dir, + plot_dir='./plots/h1_tests_source_february/' + run_dir, hide_plots=True, ) diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py index 0fba6297d..c37b73238 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py @@ -1,3 +1,7 @@ +""" + Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization +""" + import os from mpi4py import MPI from collections import OrderedDict @@ -5,37 +9,78 @@ import numpy as np import matplotlib.pyplot from scipy.sparse.linalg import spsolve, inv -from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS -from sympde.calculus import grad, dot, curl, cross -from sympde.calculus import minus, plus -from sympde.topology import VectorFunctionSpace -from sympde.topology import elements_of -from sympde.topology import NormalVector -from sympde.topology import Square -from sympde.topology import IdentityMapping, PolarMapping -from sympde.expr.expr import LinearForm, BilinearForm -from sympde.expr.expr import integral -from sympde.expr.expr import Norm -from sympde.expr.equation import find, EssentialBC from scipy.sparse.linalg import LinearOperator, eigsh, minres -from psydac.linalg.utilities import array_to_psydac -from psydac.api.tests.build_domain import build_pretzel -from psydac.fem.basic import FemField -from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL -from psydac.feec.pull_push import pull_2d_hcurl +from sympde.calculus import grad, dot, curl, cross +from sympde.calculus import minus, plus +from sympde.topology import VectorFunctionSpace +from sympde.topology import elements_of +from sympde.topology import NormalVector +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping +from sympde.expr.expr import LinearForm, BilinearForm +from sympde.expr.expr import integral +from sympde.expr.expr import Norm +from sympde.expr.equation import find, EssentialBC +from psydac.linalg.utilities import array_to_psydac +from psydac.api.tests.build_domain import build_pretzel +from psydac.fem.basic import FemField +from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL +from psydac.feec.pull_push import pull_2d_hcurl + +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.api import discretize from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), domain=([0, np.pi],[0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, - generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, - plot_dir=None, hide_plots=True, m_load_dir="",): + +def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), domain=([0, np.pi], [0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, + plot_dir=None, hide_plots=True, m_load_dir="",): + """ + Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization + + Parameters + ---------- + ncells : array + Number of cells in each direction + degree : tuple + Degree of the basis functions + domain : list + Interval in x- and y-direction + domain_name : str + Name of the domain + backend_language : str + Language used for the backend + mu : float + Coefficient in the curl-curl operator + nu : float + Coefficient in the curl-curl operator + gamma_h : float + Coefficient in the curl-curl operator + generalized_pbm : bool + If True, solve the generalized eigenvalue problem + sigma : float + Calculate eigenvalues close to sigma + ref_sigmas : list + List of reference eigenvalues + nb_eigs_solve : int + Number of eigenvalues to solve + nb_eigs_plot : int + Number of eigenvalues to plot + skip_eigs_threshold : float + Threshold for the eigenvalues to skip + plot_dir : str + Directory for the plots + hide_plots : bool + If True, hide the plots + m_load_dir : str + Directory to save and load the matrices + """ diags = {} - + if sigma is None: raise ValueError('please specify a value for sigma') @@ -50,69 +95,75 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do print('building symbolic and discrete domain...') int_x, int_y = domain - - if domain_name == 'refined_square' or domain_name =='square_L_shape': + + if domain_name == 'refined_square' or domain_name == 'square_L_shape': domain = create_square_domain(ncells, int_x, int_y, mapping='identity') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( + patch.name[2])][int(patch.name[4])]] for patch in domain.interior} elif domain_name == 'curved_L_shape': domain = create_square_domain(ncells, int_x, int_y, mapping='polar') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( + patch.name[2])][int(patch.name[4])]] for patch in domain.interior} elif domain_name == 'pretzel_f': - domain = build_multipatch_domain(domain_name=domain_name) - ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + domain = build_multipatch_domain(domain_name=domain_name) + ncells_h = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} else: ValueError("Domain not defined.") # domain = build_multipatch_domain(domain_name = 'curved_L_shape') - # + # # ncells = np.array([4,8,4]) # ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) - t_stamp = time_count(t_stamp) print(' .. discrete domain...') - V = VectorFunctionSpace('V', domain, kind='hcurl') + V = VectorFunctionSpace('V', domain, kind='hcurl') - u, v, F = elements_of(V, names='u, v, F') - nn = NormalVector('nn') + u, v, F = elements_of(V, names='u, v, F') + nn = NormalVector('nn') - I = domain.interfaces + I = domain.interfaces boundary = domain.boundary - kappa = 10 - k = 1 + kappa = 10 + k = 1 - jump = lambda w:plus(w)-minus(w) - avr = lambda w:0.5*plus(w) + 0.5*minus(w) + def jump(w): return plus(w) - minus(w) + def avr(w): return 0.5 * plus(w) + 0.5 * minus(w) - expr1_I = cross(nn, jump(v))*curl(avr(u))\ - +k*cross(nn, jump(u))*curl(avr(v))\ - +kappa*cross(nn, jump(u))*cross(nn, jump(v)) + expr1_I = cross(nn, jump(v)) * curl(avr(u))\ + + k * cross(nn, jump(u)) * curl(avr(v))\ + + kappa * cross(nn, jump(u)) * cross(nn, jump(v)) - expr1 = curl(u)*curl(v) - expr1_b = -cross(nn, v) * curl(u) -k*cross(nn, u)*curl(v) + kappa*cross(nn, u)*cross(nn, v) - ## curl curl u = - omega**2 u + expr1 = curl(u) * curl(v) + expr1_b = -cross(nn, v) * curl(u) - k * cross(nn, u) * \ + curl(v) + kappa * cross(nn, u) * cross(nn, v) + # curl curl u = - omega**2 u - expr2 = dot(u,v) - #expr2_I = kappa*cross(nn, jump(u))*cross(nn, jump(v)) - #expr2_b = -k*cross(nn, u)*curl(v) + kappa * cross(nn, u) * cross(nn, v) + expr2 = dot(u, v) + # expr2_I = kappa*cross(nn, jump(u))*cross(nn, jump(v)) + # expr2_b = -k*cross(nn, u)*curl(v) + kappa * cross(nn, u) * cross(nn, v) # Bilinear form a: V x V --> R - a = BilinearForm((u,v), integral(domain, expr1) + integral(I, expr1_I) + integral(boundary, expr1_b)) - + a = BilinearForm((u, v), integral(domain, expr1) + + integral(I, expr1_I) + integral(boundary, expr1_b)) + # Linear form l: V --> R - b = BilinearForm((u,v), integral(domain, expr2))# + integral(I, expr2_I) + integral(boundary, expr2_b)) + # + integral(I, expr2_I) + integral(boundary, expr2_b)) + b = BilinearForm((u, v), integral(domain, expr2)) - #+++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++ # 2. Discretization - #+++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++ domain_h = discretize(domain, ncells=ncells_h) - Vh = discretize(V, domain_h, degree=degree) + Vh = discretize(V, domain_h, degree=degree) ah = discretize(a, domain_h, [Vh, Vh]) Ah_m = ah.assemble().tosparse() @@ -120,9 +171,10 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do bh = discretize(b, domain_h, [Vh, Vh]) Bh_m = bh.assemble().tosparse() - all_eigenvalues_2, all_eigenvectors_transp_2 = get_eigenvalues(nb_eigs_solve, sigma, Ah_m, Bh_m) + all_eigenvalues_2, all_eigenvectors_transp_2 = get_eigenvalues( + nb_eigs_solve, sigma, Ah_m, Bh_m) - #Eigenvalue processing + # Eigenvalue processing t_stamp = time_count(t_stamp) print('sorting out eigenvalues...') zero_eigenvalues = [] @@ -130,7 +182,7 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do eigenvalues = [] eigenvectors2 = [] for val, vect in zip(all_eigenvalues_2, all_eigenvectors_transp_2.T): - if abs(val) < skip_eigs_threshold: + if abs(val) < skip_eigs_threshold: zero_eigenvalues.append(val) # we skip the eigenvector else: @@ -139,44 +191,56 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do else: eigenvalues = all_eigenvalues_2 eigenvectors2 = all_eigenvectors_transp_2.T - diags['DG'] = True + diags['DG'] = True for k, val in enumerate(eigenvalues): - diags['eigenvalue2_{}'.format(k)] = val #eigenvalues[k] - + diags['eigenvalue2_{}'.format(k)] = val # eigenvalues[k] + for k, val in enumerate(zero_eigenvalues): diags['skipped eigenvalue2_{}'.format(k)] = val t_stamp = time_count(t_stamp) - print('plotting the eigenmodes...') - + print('plotting the eigenmodes...') + if not os.path.exists(plot_dir): os.makedirs(plot_dir) - + # OM = OutputManager('spaces.yml', 'fields.h5') # OM.add_spaces(V1h=V1h) nb_eigs = len(eigenvalues) for i in range(min(nb_eigs_plot, nb_eigs)): - OM = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + OM = OutputManager(plot_dir + '/spaces2.yml', plot_dir + '/fields2.h5') OM.add_spaces(V1h=Vh) print('looking at emode i = {}... '.format(i)) - lambda_i = eigenvalues[i] + lambda_i = eigenvalues[i] emode_i = np.real(eigenvectors2[i]) - norm_emode_i = np.dot(emode_i,Bh_m.dot(emode_i)) - eh_c = emode_i/norm_emode_i + norm_emode_i = np.dot(emode_i, Bh_m.dot(emode_i)) + eh_c = emode_i / norm_emode_i stencil_coeffs = array_to_psydac(eh_c, Vh.vector_space) vh = FemField(Vh, coeffs=stencil_coeffs) OM.set_static() - #OM.add_snapshot(t=i , ts=0) - OM.export_fields(vh = vh) + # OM.add_snapshot(t=i , ts=0) + OM.export_fields(vh=vh) - #print('norm of computed eigenmode: ', norm_emode_i) + # print('norm of computed eigenmode: ', norm_emode_i) # plot the broken eigenmode: OM.export_space_info() OM.close() - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/eigen2_{}".format(i),grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces2.yml', + fields_file=plot_dir + + '/fields2.h5') + PM.export_to_vtk( + plot_dir + + "/eigen2_{}".format(i), + grid=None, + npts_per_cell=[6] * + 2, + snapshots='all', + fields='vh') PM.close() t_stamp = time_count(t_stamp) @@ -185,14 +249,32 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do def get_eigenvalues(nb_eigs, sigma, A_m, M_m): + """ + Compute the eigenvalues of the matrix A close to sigma and right-hand-side M + + Parameters + ---------- + nb_eigs : int + Number of eigenvalues to compute + sigma : float + Value close to which the eigenvalues are computed + A_m : sparse matrix + Matrix A + M_m : sparse matrix + Matrix M + """ + print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') - print('computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format(nb_eigs, sigma) ) + print( + 'computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format( + nb_eigs, + sigma)) mode = 'normal' which = 'LM' # from eigsh docstring: # ncv = number of Lanczos vectors generated ncv must be greater than k and smaller than n; # it is recommended that ncv > 2*k. Default: min(n, max(2*k + 1, 20)) - ncv = 4*nb_eigs + ncv = 4 * nb_eigs print('A_m.shape = ', A_m.shape) try_lgmres = True max_shape_splu = 24000 # OK for nc=20, deg=6 on pretzel_f @@ -202,17 +284,20 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): tol_eigsh = 0 else: - OP_m = A_m - sigma*M_m + OP_m = A_m - sigma * M_m tol_eigsh = 1e-7 if try_lgmres: - print('(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') + print( + '(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') OP_spilu = spilu(OP_m, fill_factor=15, drop_tol=5e-5) - preconditioner = LinearOperator(OP_m.shape, lambda x: OP_spilu.solve(x) ) + preconditioner = LinearOperator( + OP_m.shape, lambda x: OP_spilu.solve(x)) tol = tol_eigsh OPinv = LinearOperator( matvec=lambda v: lgmres(OP_m, v, x0=None, tol=tol, atol=tol, M=preconditioner, - callback=lambda x: print('cg -- residual = ', norm(OP_m.dot(x)-v)) - )[0], + callback=lambda x: print( + 'cg -- residual = ', norm(OP_m.dot(x) - v)) + )[0], shape=M_m.shape, dtype=M_m.dtype ) @@ -223,9 +308,16 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): # > here, minres: MINimum RESidual iteration to solve Ax=b # suggested in https://github.com/scipy/scipy/issues/4170 print('(with minres iterative solver for A_m - sigma*M1_m)') - OPinv = LinearOperator(matvec=lambda v: minres(OP_m, v, tol=1e-10)[0], shape=M_m.shape, dtype=M_m.dtype) + OPinv = LinearOperator( + matvec=lambda v: minres( + OP_m, + v, + tol=1e-10)[0], + shape=M_m.shape, + dtype=M_m.dtype) - eigenvalues, eigenvectors = eigsh(A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) + eigenvalues, eigenvectors = eigsh( + A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) print("done: eigenvalues found: " + repr(eigenvalues)) - return eigenvalues, eigenvectors \ No newline at end of file + return eigenvalues, eigenvectors diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py index a35b71dfb..ea64a6759 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py @@ -1,44 +1,87 @@ +""" + Solve the eigenvalue problem for the curl-curl operator in 2D with non-matching FEEC discretization +""" import os from mpi4py import MPI import numpy as np import matplotlib.pyplot as plt from collections import OrderedDict -from sympde.topology import Derham +from sympde.topology import Derham -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn -from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file +from psydac.feec.multipatch.plotting_utilities import plot_field +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file -from sympde.topology import Square -from sympde.topology import IdentityMapping, PolarMapping +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping from psydac.fem.vector import ProductFemSpace from scipy.sparse.linalg import spilu, lgmres from scipy.sparse.linalg import LinearOperator, eigsh, minres -from scipy.sparse import csr_matrix -from scipy.linalg import norm +from scipy.sparse import csr_matrix +from scipy.linalg import norm from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField +from psydac.fem.basic import FemField from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain -from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection +from psydac.feec.multipatch.non_matching_operators import construct_hcurl_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), domain=([0, np.pi],[0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, - generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, - plot_dir=None, hide_plots=True, m_load_dir=None,): +def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), domain=([0, np.pi], [0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, + plot_dir=None, hide_plots=True, m_load_dir=None,): + """ + Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization + + Parameters + ---------- + ncells : array + Number of cells in each direction + degree : tuple + Degree of the basis functions + domain : list + Interval in x- and y-direction + domain_name : str + Name of the domain + backend_language : str + Language used for the backend + mu : float + Coefficient in the curl-curl operator + nu : float + Coefficient in the curl-curl operator + gamma_h : float + Coefficient in the curl-curl operator + generalized_pbm : bool + If True, solve the generalized eigenvalue problem + sigma : float + Calculate eigenvalues close to sigma + ref_sigmas : list + List of reference eigenvalues + nb_eigs_solve : int + Number of eigenvalues to solve + nb_eigs_plot : int + Number of eigenvalues to plot + skip_eigs_threshold : float + Threshold for the eigenvalues to skip + plot_dir : str + Directory for the plots + hide_plots : bool + If True, hide the plots + m_load_dir : str + Directory to save and load the matrices + """ diags = {} - + if sigma is None: raise ValueError('please specify a value for sigma') @@ -53,28 +96,31 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do print('building symbolic and discrete domain...') int_x, int_y = domain - - if domain_name == 'refined_square' or domain_name =='square_L_shape': + + if domain_name == 'refined_square' or domain_name == 'square_L_shape': domain = create_square_domain(ncells, int_x, int_y, mapping='identity') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( + patch.name[2])][int(patch.name[4])]] for patch in domain.interior} elif domain_name == 'curved_L_shape': domain = create_square_domain(ncells, int_x, int_y, mapping='polar') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( + patch.name[2])][int(patch.name[4])]] for patch in domain.interior} elif domain_name == 'pretzel_f': - domain = build_multipatch_domain(domain_name=domain_name) - ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + domain = build_multipatch_domain(domain_name=domain_name) + ncells_h = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} else: ValueError("Domain not defined.") # domain = build_multipatch_domain(domain_name = 'curved_L_shape') - # + # # ncells = np.array([4,8,4]) # ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) - t_stamp = time_count(t_stamp) print(' .. discrete domain...') domain_h = discretize(domain, ncells=ncells_h) # Vh space @@ -82,16 +128,15 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do print('building symbolic and discrete derham sequences...') t_stamp = time_count() print(' .. derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) t_stamp = time_count(t_stamp) print(' .. discrete derham sequence...') derham_h = discretize(derham, domain_h, degree=degree) - V0h = derham_h.V0 V1h = derham_h.V1 - V2h = derham_h.V2 + V2h = derham_h.V2 print('dim(V0h) = {}'.format(V0h.nbasis)) print('dim(V1h) = {}'.format(V1h.nbasis)) print('dim(V2h) = {}'.format(V2h.nbasis)) @@ -99,12 +144,11 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do diags['ndofs_V1'] = V1h.nbasis diags['ndofs_V2'] = V2h.nbasis - t_stamp = time_count(t_stamp) print('building the discrete operators:') - #print('commuting projection operators...') - #nquads = [4*(d + 1) for d in degree] - #P0, P1, P2 = derham_h.projectors(nquads=nquads) + # print('commuting projection operators...') + # nquads = [4*(d + 1) for d in degree] + # P0, P1, P2 = derham_h.projectors(nquads=nquads) I1 = IdLinearOperator(V1h) I1_m = I1.to_sparse_matrix() @@ -112,27 +156,38 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do t_stamp = time_count(t_stamp) print('Hodge operators...') # multi-patch (broken) linear operators / matrices - #H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) - H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) - - #H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - #dH0_m = H0.get_dual_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - dH1_m = H1.get_dual_Hodge_sparse_matrix()# = inverse mass matrix of V1 - H2_m = H2.to_sparse_matrix() # = mass matrix of V2 - dH2_m = H2.get_dual_Hodge_sparse_matrix()# = inverse mass matrix of V2 - + # H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) + H1 = HodgeOperator( + V1h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=1) + H2 = HodgeOperator( + V2h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=2) + + # H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + # dH0_m = H0.get_dual_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 + t_stamp = time_count(t_stamp) print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) cP0_m = None - cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True,True]) + cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) t_stamp = time_count(t_stamp) print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators - #bD0_m = bD0.to_sparse_matrix() + # bD0_m = bD0.to_sparse_matrix() bD1_m = bD1.to_sparse_matrix() t_stamp = time_count(t_stamp) @@ -142,13 +197,13 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do dH1_m = dH1_m.tocsr() H2_m = H2_m.tocsr() cP1_m = cP1_m.tocsr() - bD1_m = bD1_m.tocsr() + bD1_m = bD1_m.tocsr() if not os.path.exists(plot_dir): os.makedirs(plot_dir) print('computing the full operator matrix...') - A_m = np.zeros_like(H1_m) + A_m = np.zeros_like(H1_m) # Conga (projection-based) stiffness matrices if mu != 0: @@ -156,10 +211,10 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do t_stamp = time_count(t_stamp) print('mu = {}'.format(mu)) print('curl-curl stiffness matrix...') - + pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix - A_m += mu * CC_m + A_m += mu * CC_m # jump stabilization in V1h: if gamma_h != 0 or generalized_pbm: @@ -167,18 +222,18 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do print('jump stabilization matrix...') jump_stab_m = I1_m - cP1_m JS_m = jump_stab_m.transpose() @ H1_m @ jump_stab_m - + if generalized_pbm: print('adding jump stabilization to RHS of generalized eigenproblem...') B_m = cP1_m.transpose() @ H1_m @ cP1_m + JS_m else: B_m = H1_m - t_stamp = time_count(t_stamp) print('solving matrix eigenproblem...') - all_eigenvalues, all_eigenvectors_transp = get_eigenvalues(nb_eigs_solve, sigma, A_m, B_m) - #Eigenvalue processing + all_eigenvalues, all_eigenvectors_transp = get_eigenvalues( + nb_eigs_solve, sigma, A_m, B_m) + # Eigenvalue processing t_stamp = time_count(t_stamp) print('sorting out eigenvalues...') zero_eigenvalues = [] @@ -186,7 +241,7 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do eigenvalues = [] eigenvectors = [] for val, vect in zip(all_eigenvalues, all_eigenvectors_transp.T): - if abs(val) < skip_eigs_threshold: + if abs(val) < skip_eigs_threshold: zero_eigenvalues.append(val) # we skip the eigenvector else: @@ -197,39 +252,51 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do eigenvectors = all_eigenvectors_transp.T for k, val in enumerate(eigenvalues): - diags['eigenvalue_{}'.format(k)] = val #eigenvalues[k] - + diags['eigenvalue_{}'.format(k)] = val # eigenvalues[k] + for k, val in enumerate(zero_eigenvalues): diags['skipped eigenvalue_{}'.format(k)] = val t_stamp = time_count(t_stamp) - print('plotting the eigenmodes...') + print('plotting the eigenmodes...') # OM = OutputManager('spaces.yml', 'fields.h5') # OM.add_spaces(V1h=V1h) nb_eigs = len(eigenvalues) for i in range(min(nb_eigs_plot, nb_eigs)): - OM = OutputManager(plot_dir+'/spaces.yml', plot_dir+'/fields.h5') + OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') OM.add_spaces(V1h=V1h) print('looking at emode i = {}... '.format(i)) - lambda_i = eigenvalues[i] + lambda_i = eigenvalues[i] emode_i = np.real(eigenvectors[i]) - norm_emode_i = np.dot(emode_i,H1_m.dot(emode_i)) - eh_c = emode_i/norm_emode_i + norm_emode_i = np.dot(emode_i, H1_m.dot(emode_i)) + eh_c = emode_i / norm_emode_i stencil_coeffs = array_to_psydac(cP1_m @ eh_c, V1h.vector_space) vh = FemField(V1h, coeffs=stencil_coeffs) OM.set_static() - #OM.add_snapshot(t=i , ts=0) - OM.export_fields(vh = vh) + # OM.add_snapshot(t=i , ts=0) + OM.export_fields(vh=vh) - #print('norm of computed eigenmode: ', norm_emode_i) + # print('norm of computed eigenmode: ', norm_emode_i) # plot the broken eigenmode: OM.export_space_info() OM.close() - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces.yml', fields_file=plot_dir+'/fields.h5' ) - PM.export_to_vtk(plot_dir+"/eigen_{}".format(i),grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces.yml', + fields_file=plot_dir + + '/fields.h5') + PM.export_to_vtk( + plot_dir + + "/eigen_{}".format(i), + grid=None, + npts_per_cell=[6] * + 2, + snapshots='all', + fields='vh') PM.close() t_stamp = time_count(t_stamp) @@ -238,14 +305,32 @@ def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3,3), do def get_eigenvalues(nb_eigs, sigma, A_m, M_m): + """ + Compute the eigenvalues of the matrix A close to sigma and right-hand-side M + + Parameters + ---------- + nb_eigs : int + Number of eigenvalues to compute + sigma : float + Value close to which the eigenvalues are computed + A_m : sparse matrix + Matrix A + M_m : sparse matrix + Matrix M + """ + print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') - print('computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format(nb_eigs, sigma) ) + print( + 'computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format( + nb_eigs, + sigma)) mode = 'normal' which = 'LM' # from eigsh docstring: # ncv = number of Lanczos vectors generated ncv must be greater than k and smaller than n; # it is recommended that ncv > 2*k. Default: min(n, max(2*k + 1, 20)) - ncv = 4*nb_eigs + ncv = 4 * nb_eigs print('A_m.shape = ', A_m.shape) try_lgmres = True max_shape_splu = 24000 # OK for nc=20, deg=6 on pretzel_f @@ -255,17 +340,20 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): tol_eigsh = 0 else: - OP_m = A_m - sigma*M_m + OP_m = A_m - sigma * M_m tol_eigsh = 1e-7 if try_lgmres: - print('(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') + print( + '(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') OP_spilu = spilu(OP_m, fill_factor=15, drop_tol=5e-5) - preconditioner = LinearOperator(OP_m.shape, lambda x: OP_spilu.solve(x) ) + preconditioner = LinearOperator( + OP_m.shape, lambda x: OP_spilu.solve(x)) tol = tol_eigsh OPinv = LinearOperator( matvec=lambda v: lgmres(OP_m, v, x0=None, tol=tol, atol=tol, M=preconditioner, - callback=lambda x: print('cg -- residual = ', norm(OP_m.dot(x)-v)) - )[0], + callback=lambda x: print( + 'cg -- residual = ', norm(OP_m.dot(x) - v)) + )[0], shape=M_m.shape, dtype=M_m.dtype ) @@ -276,9 +364,16 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): # > here, minres: MINimum RESidual iteration to solve Ax=b # suggested in https://github.com/scipy/scipy/issues/4170 print('(with minres iterative solver for A_m - sigma*M1_m)') - OPinv = LinearOperator(matvec=lambda v: minres(OP_m, v, tol=1e-10)[0], shape=M_m.shape, dtype=M_m.dtype) + OPinv = LinearOperator( + matvec=lambda v: minres( + OP_m, + v, + tol=1e-10)[0], + shape=M_m.shape, + dtype=M_m.dtype) - eigenvalues, eigenvectors = eigsh(A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) + eigenvalues, eigenvectors = eigsh( + A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) print("done: eigenvalues found: " + repr(eigenvalues)) - return eigenvalues, eigenvectors \ No newline at end of file + return eigenvalues, eigenvectors diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py index 6e4defabf..513260779 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py @@ -1,63 +1,62 @@ +""" + Runner script for solving the eigenvalue problem for the H(curl) operator for different discretizations. +""" + import os import numpy as np - from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_nc from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_dg - -from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn -from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file - +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file from psydac.api.postprocessing import OutputManager, PostProcessManager t_stamp_full = time_count() -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # # test-case and numerical parameters: -# method = 'feec' -method = 'dg' +method = 'feec' +# method = 'dg' -operator = 'curl-curl' -degree = [3,3] # shared across all patches +operator = 'curl-curl' +degree = [3, 3] # shared across all patches +# pretzel_f (18 patches) +# domain_name = 'pretzel_f' +# ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) +# ncells = np.array([4 for _ in range(18)]) - -#pretzel_f (18 patches) -#domain_name = 'pretzel_f' -#ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) -#ncells = np.array([4 for _ in range(18)]) - -#domain onlyneeded for square like domains -domain=[[0, np.pi],[0, np.pi]] # interval in x- and y-direction +# domain onlyneeded for square like domains +domain = [[0, np.pi], [0, np.pi]] # interval in x- and y-direction # refined square domain -# domain_name = 'refined_square' +# domain_name = 'refined_square' # the shape of ncells gives the shape of the domain, -# while the entries describe the isometric number of cells in each patch +# while the entries describe the isometric number of cells in each patch # 2x2 = 4 patches # ncells = np.array([[8, 4], # [4, 4]]) # 3x3= 9 patches -#ncells = np.array([[4, 2, 4], +# ncells = np.array([[4, 2, 4], # [2, 4, 2], # [4, 2, 4]]) # L-shaped domain -#domain_name = 'square_L_shape' -#domain=[[-1, 1],[-1, 1]] # interval in x- and y-direction +# domain_name = 'square_L_shape' +# domain=[[-1, 1],[-1, 1]] # interval in x- and y-direction # The None indicates the patches to leave out # 2x2 = 4 patches -#ncells = np.array([[None, 2], +# ncells = np.array([[None, 2], # [2, 2]]) # 4x4 = 16 patches -#ncells = np.array([[None, None, 4, 2], +# ncells = np.array([[None, None, 4, 2], # [None, None, 8, 4], # [4, 8, 8, 4], # [2, 4, 4, 2]]) # 8x8 = 64 patches -#ncells = np.array([[None, None, None, None, 2, 2, 2,1 2], +# ncells = np.array([[None, None, None, None, 2, 2, 2,1 2], # [None, None, None, None, 2, 2, 2, 2], # [None, None, None, None, 2, 2, 2, 2], # [None, None, None, None, 4, 4, 2, 2], @@ -67,13 +66,12 @@ # [2, 2, 2, 2, 2, 2, 2, 2]]) # Curved L-shape domain -domain_name = 'curved_L_shape' -domain=[[1, 3],[0, np.pi/4]] # interval in x- and y-direction +domain_name = 'curved_L_shape' +domain = [[1, 3], [0, np.pi / 4]] # interval in x- and y-direction ncells = np.array([[None, 5], - [5, 10]]) - + [5, 10]]) # ncells = np.array([[None, None, 2, 2], @@ -99,37 +97,39 @@ # all kinds of different square refinements and constructions are possible, eg # doubly connected domains -#ncells = np.array([[4, 2, 2, 4], +# ncells = np.array([[4, 2, 2, 4], # [2, None, None, 2], # [2, None, None, 2], # [4, 2, 2, 4]]) gamma_h = 0 -generalized_pbm = True # solves generalized eigenvalue problem with: B(v,w) = + <(I-P)v,(I-P)w> in rhs +# solves generalized eigenvalue problem with: B(v,w) = + +# <(I-P)v,(I-P)w> in rhs +generalized_pbm = True if operator == 'curl-curl': - nu=0 - mu=1 + nu = 0 + mu = 1 else: raise ValueError(operator) -case_dir = 'eigenpbm_'+operator+'_'+method +case_dir = 'eigenpbm_' + operator + '_' + method ref_case_dir = case_dir ref_sigmas = None sigma = None -nb_eigs_solve = None +nb_eigs_solve = None nb_eigs_plot = None skip_eigs_threshold = None diags = None eigenvalues = None if domain_name == 'refined_square': - assert domain == [[0, np.pi],[0, np.pi]] + assert domain == [[0, np.pi], [0, np.pi]] ref_sigmas = [ 1, 1, - 2, - 4, 4, + 2, + 4, 4, 5, 5, 8, 9, 9, @@ -140,29 +140,29 @@ skip_eigs_threshold = 1e-7 elif domain_name == 'square_L_shape': - assert domain == [[-1, 1],[-1, 1]] + assert domain == [[-1, 1], [-1, 1]] ref_sigmas = [ 1.47562182408, 3.53403136678, 9.86960440109, 9.86960440109, - 11.3894793979, + 11.3894793979, ] sigma = 6 - nb_eigs_solve = 5 + nb_eigs_solve = 5 nb_eigs_plot = 5 skip_eigs_threshold = 1e-7 -elif domain_name == 'curved_L_shape': +elif domain_name == 'curved_L_shape': # ref eigenvalues from Monique Dauge benchmark page - assert domain==[[1, 3],[0, np.pi/4]] + assert domain == [[1, 3], [0, np.pi / 4]] ref_sigmas = [ 0.181857115231E+01, 0.349057623279E+01, 0.100656015004E+02, 0.101118862307E+02, 0.124355372484E+02, - ] + ] sigma = 7 nb_eigs_solve = 7 nb_eigs_plot = 7 @@ -170,25 +170,26 @@ elif domain_name in ['pretzel_f']: if operator == 'curl-curl': - # ref sigmas computed with nc=20 and deg=6 and gamma = 0 (and generalized ev-pbm) + # ref sigmas computed with nc=20 and deg=6 and gamma = 0 (and + # generalized ev-pbm) ref_sigmas = [ 0.1795339843, 0.1992261261, - 0.6992717244, - 0.8709410438, - 1.1945106937, + 0.6992717244, + 0.8709410438, + 1.1945106937, 1.2546992683, ] sigma = .8 - nb_eigs_solve = 10 - nb_eigs_plot = 5 + nb_eigs_solve = 10 + nb_eigs_plot = 5 skip_eigs_threshold = 1e-7 # -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -common_diag_filename = './'+case_dir+'_diags.txt' +common_diag_filename = './' + case_dir + '_diags.txt' params = { @@ -198,7 +199,7 @@ 'mu': mu, 'nu': nu, 'ncells': ncells, - 'degree': degree, + 'degree': degree, 'gamma_h': gamma_h, 'generalized_pbm': generalized_pbm, 'nb_eigs_solve': nb_eigs_solve, @@ -208,27 +209,28 @@ print(params) # backend_language = 'numba' -backend_language='pyccel-gcc' +backend_language = 'pyccel-gcc' dims = ncells.shape -sz = ncells[ncells != None].sum() +sz = ncells[ncells is not None].sum() print(dims) -run_dir = domain_name+str(dims)+'patches_'+'size_{}'.format(sz) #get_run_dir(domain_name, nc, deg) +# get_run_dir(domain_name, nc, deg) +run_dir = domain_name + str(dims) + 'patches_' + 'size_{}'.format(sz) plot_dir = get_plot_dir(case_dir, run_dir) -diag_filename = plot_dir+'/'+diag_fn() -common_diag_filename = './'+case_dir+'_diags.txt' +diag_filename = plot_dir + '/' + diag_fn() +common_diag_filename = './' + case_dir + '_diags.txt' # to save and load matrices -#m_load_dir = get_mat_dir(domain_name, nc, deg) +# m_load_dir = get_mat_dir(domain_name, nc, deg) m_load_dir = None print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') print(' Calling hcurl_solve_eigen_pbm() with params = {}'.format(params)) print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # calling eigenpbm solver for: -# +# # find lambda in R and u in H0(curl), such that # A u = lambda * u on \Omega # with @@ -256,36 +258,44 @@ hide_plots=True, m_load_dir=m_load_dir, ) -elif method == 'dg': +elif method == 'dg': diags, eigenvalues = hcurl_solve_eigen_pbm_dg( - ncells=ncells, degree=degree, - gamma_h=gamma_h, - generalized_pbm=generalized_pbm, - nu=nu, - mu=mu, - sigma=sigma, - ref_sigmas=ref_sigmas, - skip_eigs_threshold=skip_eigs_threshold, - nb_eigs_solve=nb_eigs_solve, - nb_eigs_plot=nb_eigs_plot, - domain_name=domain_name, domain=domain, - backend_language=backend_language, - plot_dir=plot_dir, - hide_plots=True, - m_load_dir=m_load_dir, + ncells=ncells, degree=degree, + gamma_h=gamma_h, + generalized_pbm=generalized_pbm, + nu=nu, + mu=mu, + sigma=sigma, + ref_sigmas=ref_sigmas, + skip_eigs_threshold=skip_eigs_threshold, + nb_eigs_solve=nb_eigs_solve, + nb_eigs_plot=nb_eigs_plot, + domain_name=domain_name, domain=domain, + backend_language=backend_language, + plot_dir=plot_dir, + hide_plots=True, + m_load_dir=m_load_dir, ) if ref_sigmas is not None: - errors = [] - n_errs = min(len(ref_sigmas), len(eigenvalues)) - for k in range(n_errs): - diags['error_{}'.format(k)] = abs(eigenvalues[k]-ref_sigmas[k]) + errors = [] + n_errs = min(len(ref_sigmas), len(eigenvalues)) + for k in range(n_errs): + diags['error_{}'.format(k)] = abs(eigenvalues[k] - ref_sigmas[k]) # -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- - -write_diags_to_file(diags, script_filename=__file__, diag_filename=diag_filename, params=params) -write_diags_to_file(diags, script_filename=__file__, diag_filename=common_diag_filename, params=params) - -#PM = PostProcessManager(geometry_file=, ) -time_count(t_stamp_full, msg='full program') \ No newline at end of file +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +write_diags_to_file( + diags, + script_filename=__file__, + diag_filename=diag_filename, + params=params) +write_diags_to_file( + diags, + script_filename=__file__, + diag_filename=common_diag_filename, + params=params) + +# PM = PostProcessManager(geometry_file=, ) +time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py index 7e25d6cf1..75782b5bc 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py @@ -1,8 +1,20 @@ -# coding: utf-8 +""" + solver for the problem: find u in H(curl), such that -from mpi4py import MPI + A u = f on \\Omega + n x u = n x u_bc on \\partial \\Omega + + where the operator + + A u := eta * u + mu * curl curl u - nu * grad div u + + is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, + + V0h --grad-> V1h -—curl-> V2h +""" import os +from mpi4py import MPI import numpy as np from collections import OrderedDict @@ -10,33 +22,33 @@ from scipy.sparse.linalg import spsolve -from sympde.calculus import dot -from sympde.topology import element_of +from sympde.calculus import dot +from sympde.topology import element_of from sympde.expr.expr import LinearForm from sympde.expr.expr import integral, Norm -from sympde.topology import Derham +from sympde.topology import Derham -from psydac.api.settings import PSYDAC_BACKENDS +from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.pull_push import pull_2d_hcurl -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator +from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl -from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for -from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE - -from psydac.feec.multipatch.non_matching_operators import construct_scalar_conforming_projection, construct_vector_conforming_projection +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager + def solve_hcurl_source_pbm_nc( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', - eta=-10., mu=1., nu=1., gamma_h=10., + eta=-10., mu=1., nu=1., gamma_h=10., project_sol=False, plot_source=False, plot_dir=None, hide_plots=True, skip_plot_titles=False, cb_min_sol=None, cb_max_sol=None, @@ -46,14 +58,14 @@ def solve_hcurl_source_pbm_nc( """ solver for the problem: find u in H(curl), such that - A u = f on \Omega - n x u = n x u_bc on \partial \Omega + A u = f on \\Omega + n x u = n x u_bc on \\partial \\Omega where the operator A u := eta * u + mu * curl curl u - nu * grad div u - is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \Omega, + is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h @@ -82,7 +94,7 @@ def solve_hcurl_source_pbm_nc( diags = {} ncells = nc - degree = [deg,deg] + degree = [deg, deg] # if backend_language is None: # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: @@ -109,20 +121,22 @@ def solve_hcurl_source_pbm_nc( t_stamp = time_count() print(' .. multi-patch domain...') domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) - ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} + ncells_h = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} - #corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0, 0 ] - #ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) - #ncells = np.array([4 for _ in range(18)]) + # corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0, 0 ] + # ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) + # ncells = np.array([4 for _ in range(18)]) # for diagnosttics diag_grid = DiagGrid(mappings=mappings, N_diag=100) t_stamp = time_count(t_stamp) print(' .. derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) t_stamp = time_count(t_stamp) print(' .. discrete domain...') @@ -134,7 +148,7 @@ def solve_hcurl_source_pbm_nc( t_stamp = time_count(t_stamp) print(' .. commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) t_stamp = time_count(t_stamp) @@ -158,40 +172,50 @@ def solve_hcurl_source_pbm_nc( print(' .. Hodge operators...') # multi-patch (broken) linear operators / matrices # other option: define as Hodge Operators: - H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=1) - H2 = HodgeOperator(V2h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=2) + H0 = HodgeOperator( + V0h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=0) + H1 = HodgeOperator( + V1h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=1) + H2 = HodgeOperator( + V2h, + domain_h, + backend_language=backend_language, + load_dir=m_load_dir, + load_space_index=2) t_stamp = time_count(t_stamp) print(' .. Hodge matrix H0_m = M0_m ...') - H0_m = H0.to_sparse_matrix() + H0_m = H0.to_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') - dH0_m = H0.get_dual_Hodge_sparse_matrix() + dH0_m = H0.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. Hodge matrix H1_m = M1_m ...') - H1_m = H1.to_sparse_matrix() + H1_m = H1.to_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') - dH1_m = H1.get_dual_Hodge_sparse_matrix() + dH1_m = H1.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. Hodge matrix H2_m = M2_m ...') - H2_m = H2.to_sparse_matrix() - dH2_m = H2.get_dual_Hodge_sparse_matrix() + H2_m = H2.to_sparse_matrix() + dH2_m = H2.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. conforming Projection operators...') - # conforming Projections (should take into account the boundary conditions of the continuous deRham sequence) - #cP0 = derham_h.conforming_projection(space='V0', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) - #cP1 = derham_h.conforming_projection(space='V1', hom_bc=True, backend_language=backend_language, load_dir=m_load_dir) - #cP0_m = cP0.to_sparse_matrix() - #cP1_m = cP1.to_sparse_matrix() - - # Try the NC one - cP1_m = construct_vector_conforming_projection(V1h, hom_bc=[True, True]) - cP0_m = construct_scalar_conforming_projection(V0h, hom_bc=[True, True]) + # conforming Projections (should take into account the boundary conditions + # of the continuous deRham sequence) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) + cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) t_stamp = time_count(t_stamp) print(' .. broken differential operators...') @@ -206,10 +230,12 @@ def solve_hcurl_source_pbm_nc( def lift_u_bc(u_bc): if u_bc is not None: print('lifting the boundary condition in V1h...') - # note: for simplicity we apply the full P1 on u_bc, but we only need to set the boundary dofs + # note: for simplicity we apply the full P1 on u_bc, but we only + # need to set the boundary dofs uh_bc = P1_phys(u_bc, P1, domain, mappings_list) ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very good approximation of uh_c ...) + # removing internal dofs (otherwise ubc_c may already be a very + # good approximation of uh_c ...) ubc_c = ubc_c - cP1_m.dot(ubc_c) else: ubc_c = None @@ -219,7 +245,7 @@ def lift_u_bc(u_bc): # curl curl: t_stamp = time_count(t_stamp) print(' .. curl-curl stiffness matrix...') - print(bD1_m.shape, H2_m.shape ) + print(bD1_m.shape, H2_m.shape) pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix @@ -241,7 +267,8 @@ def lift_u_bc(u_bc): print('mu = {}'.format(mu)) print('nu = {}'.format(nu)) print('STABILIZATION: gamma_h = {}'.format(gamma_h)) - pre_A_m = cP1_m.transpose() @ ( eta * H1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) + # useful for the boundary condition (if present) + pre_A_m = cP1_m.transpose() @ (eta * H1_m + mu * pre_CC_m - nu * pre_GD_m) A_m = pre_A_m @ cP1_m + gamma_h * JP_m t_stamp = time_count(t_stamp) @@ -249,11 +276,11 @@ def lift_u_bc(u_bc): print(' -- getting source --') if source_type == 'manu_maxwell': f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( - source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, + source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, ) else: f_vect, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( - source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, + source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, ) # compute approximate source f_h t_stamp = time_count(t_stamp) @@ -267,29 +294,34 @@ def lift_u_bc(u_bc): elif source_proj in ['P_L2', 'tilde_Pi']: # f_h = L2 projection of f_vect, with filtering if tilde_Pi - print(' .. projecting the source with '+source_proj+' projection...') - tilde_f_c = derham_h.get_dual_dofs(space='V1', f=f_vect, backend_language=backend_language, return_format='numpy_array') + print( + ' .. projecting the source with ' + + source_proj + + ' projection...') + tilde_f_c = derham_h.get_dual_dofs( + space='V1', + f=f_vect, + backend_language=backend_language, + return_format='numpy_array') if source_proj == 'tilde_Pi': print(' .. filtering the discrete source with P0.T ...') tilde_f_c = cP1_m.transpose() @ tilde_f_c else: raise ValueError(source_proj) - - if plot_source: if True: title = '' title_vf = '' else: - title = 'f_h with P = '+source_proj - title_vf = 'f_h with P = '+source_proj + title = 'f_h with P = ' + source_proj + title_vf = 'f_h with P = ' + source_proj if f_c is None: f_c = dH1_m.dot(tilde_f_c) - plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, - title=title, filename=plot_dir+'/fh_'+source_proj+'.pdf', hide_plot=hide_plots) - plot_field(numpy_coeffs=f_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, - title=title_vf, filename=plot_dir+'/fh_'+source_proj+'_vf.pdf', hide_plot=hide_plots) + plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, + title=title, filename=plot_dir + '/fh_' + source_proj + '.pdf', hide_plot=hide_plots) + plot_field(numpy_coeffs=f_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, + title=title_vf, filename=plot_dir + '/fh_' + source_proj + '_vf.pdf', hide_plot=hide_plots) ubc_c = lift_u_bc(u_bc) if ubc_c is not None: @@ -297,7 +329,7 @@ def lift_u_bc(u_bc): t_stamp = time_count(t_stamp) print(' .. modifying the source with lifted bc solution...') tilde_f_c = tilde_f_c - pre_A_m.dot(ubc_c) - + # direct solve with scipy spsolve t_stamp = time_count(t_stamp) print() @@ -317,7 +349,7 @@ def lift_u_bc(u_bc): t_stamp = time_count(t_stamp) print(' .. adding the lifted boundary condition...') uh_c += ubc_c - + uh = FemField(V1h, coeffs=array_to_psydac(uh_c, V1h.vector_space)) f_c = dH1_m.dot(tilde_f_c) jh = FemField(V1h, coeffs=array_to_psydac(f_c, V1h.vector_space)) @@ -332,40 +364,56 @@ def lift_u_bc(u_bc): title = '' title_vf = '' else: - title = r'solution $u_h$ (amplitude) for $\eta = $'+repr(eta) - title_vf = r'solution $u_h$ for $\eta = $'+repr(eta) - params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format(eta, mu, nu, gamma_h, source_proj) - plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_uh.pdf', - plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) - plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title_vf, - filename=plot_dir+'/'+params_str+'_uh_vf.pdf', - plot_type='vector_field', hide_plot=hide_plots) - - OM = OutputManager(plot_dir+'/spaces.yml', plot_dir+'/fields.h5') + title = r'solution $u_h$ (amplitude) for $\eta = $' + repr(eta) + title_vf = r'solution $u_h$ for $\eta = $' + repr(eta) + params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format( + eta, mu, nu, gamma_h, source_proj) + plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_uh.pdf', + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title_vf, + filename=plot_dir + '/' + params_str + '_uh_vf.pdf', + plot_type='vector_field', hide_plot=hide_plots) + + OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') OM.add_spaces(V1h=V1h) OM.set_static() - OM.export_fields(vh = uh) - OM.export_fields(jh = jh) + OM.export_fields(vh=uh) + OM.export_fields(jh=jh) OM.export_space_info() OM.close() - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces.yml', fields_file=plot_dir+'/fields.h5' ) - PM.export_to_vtk(plot_dir+"/sol",grid=None, npts_per_cell=[6]*2,snapshots='all', fields='vh' ) - PM.export_to_vtk(plot_dir+"/source",grid=None, npts_per_cell=[6]*2,snapshots='all', fields='jh' ) + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces.yml', + fields_file=plot_dir + + '/fields.h5') + PM.export_to_vtk( + plot_dir + "/sol", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='vh') + PM.export_to_vtk( + plot_dir + "/source", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='jh') PM.close() time_count(t_stamp) if test: - u = element_of(V1h.symbolic_space, name='u') - l2norm = Norm(Matrix([u[0] - u_ex[0],u[1] - u_ex[1]]), domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V1h) - uh_c = array_to_psydac(uh_c, V1h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) + u = element_of(V1h.symbolic_space, name='u') + l2norm = Norm( + Matrix([u[0] - u_ex[0], u[1] - u_ex[1]]), domain, kind='l2') + l2norm_h = discretize(l2norm, domain_h, V1h) + uh_c = array_to_psydac(uh_c, V1h.vector_space) + l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) print(l2_error) return l2_error - - return diags \ No newline at end of file + return diags diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py b/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py index 23ef31fb9..066a287bd 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py @@ -1,53 +1,54 @@ +""" + Runner script for solving the H(curl) source problem. +""" + import os import numpy as np from psydac.feec.multipatch.examples_nc.hcurl_source_pbms_nc import solve_hcurl_source_pbm_nc - -from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn -from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file +from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file t_stamp_full = time_count() -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # # main test-cases used for the ppc paper: -#test_case = 'maxwell_hom_eta=50' # used in paper +# test_case = 'maxwell_hom_eta=50' # used in paper test_case = 'maxwell_hom_eta=170' # used in paper # test_case = 'maxwell_inhom' # used in paper - -# -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- - +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # numerical parameters: domain_name = 'pretzel_f' -#domain_name = 'curved_L_shape' +# domain_name = 'curved_L_shape' source_proj = 'tilde_Pi' -# other values are: +# other values are: -#source_proj = 'P_L2' # L2 projection in broken space +# source_proj = 'P_L2' # L2 projection in broken space # source_proj = 'P_geom' # geometric projection (primal commuting proj) -#nc_s = [np.array([16 for _ in range(18)])] +# nc_s = [np.array([16 for _ in range(18)])] -#corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0 ] -nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16, 8, 8])] +# corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0 ] +nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, + 8, 8, 8, 8, 16, 16, 16, 8, 8])] -#refine handles only -#nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 2, 2, 4, 16, 16, 16, 2, 2])] +# refine handles only +# nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 2, 2, 4, 16, 16, 16, 2, 2])] -#refine source -#nc_s = [np.array([32, 8, 8, 32, 32, 32, 32, 8, 8, 8, 8, 8, 8, 32, 8, 8, 8, 8])] +# refine source +# nc_s = [np.array([32, 8, 8, 32, 32, 32, 32, 8, 8, 8, 8, 8, 8, 32, 8, 8, 8, 8])] deg_s = [3] if test_case == 'maxwell_hom_eta=50': homogeneous = True source_type = 'elliptic_J' - omega = np.sqrt(50) # source time pulsation + omega = np.sqrt(50) # source time pulsation cb_min_sol = 0 cb_max_sol = 1 @@ -59,7 +60,7 @@ elif test_case == 'maxwell_hom_eta=170': homogeneous = True source_type = 'elliptic_J' - omega = np.sqrt(170) # source time pulsation + omega = np.sqrt(170) # source time pulsation cb_min_sol = 0 cb_max_sol = 1 @@ -68,12 +69,12 @@ ref_nc = 10 ref_deg = 6 - + elif test_case == 'maxwell_inhom': - homogeneous = False # + homogeneous = False source_type = 'manu_maxwell_inhom' - omega = np.pi + omega = np.pi cb_min_sol = 0 cb_max_sol = 1 @@ -89,15 +90,15 @@ ref_case_dir = case_dir roundoff = 1e4 -eta = int(-omega**2 * roundoff)/roundoff +eta = int(-omega**2 * roundoff) / roundoff -project_sol = True # True # (use conf proj of solution for visualization) +project_sol = True # True # (use conf proj of solution for visualization) gamma_h = 10 # -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -common_diag_filename = './'+case_dir+'_diags.txt' +common_diag_filename = './' + case_dir + '_diags.txt' for nc in nc_s: for deg in deg_s: @@ -116,27 +117,29 @@ 'ref_deg': ref_deg, } # backend_language = 'numba' - backend_language='pyccel-gcc' + backend_language = 'pyccel-gcc' run_dir = get_run_dir(domain_name, nc, deg, source_type=source_type) plot_dir = get_plot_dir(case_dir, run_dir) - diag_filename = plot_dir+'/'+diag_fn(source_type=source_type, source_proj=source_proj) + diag_filename = plot_dir + '/' + \ + diag_fn(source_type=source_type, source_proj=source_proj) # to save and load matrices m_load_dir = get_mat_dir(domain_name, nc, deg) # to save the FEM sol - + # to load the ref FEM sol sol_ref_dir = get_sol_dir(ref_case_dir, domain_name, ref_nc, ref_deg) - sol_ref_filename = sol_ref_dir+'/'+FEM_sol_fn(source_type=source_type, source_proj=source_proj) + sol_ref_filename = sol_ref_dir + '/' + \ + FEM_sol_fn(source_type=source_type, source_proj=source_proj) print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') print(' Calling solve_hcurl_source_pbm() with params = {}'.format(params)) print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') - - # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + + # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- # calling solver for: - # + # # find u in H(curl), s.t. # A u = f on \Omega # n x u = n x u_bc on \partial \Omega @@ -158,19 +161,27 @@ plot_dir=plot_dir, hide_plots=True, skip_plot_titles=False, - cb_min_sol=cb_min_sol, + cb_min_sol=cb_min_sol, cb_max_sol=cb_max_sol, m_load_dir=m_load_dir, sol_filename=None, sol_ref_filename=sol_ref_filename, ref_nc=ref_nc, - ref_deg=ref_deg, + ref_deg=ref_deg, ) # - # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- - - write_diags_to_file(diags, script_filename=__file__, diag_filename=diag_filename, params=params) - write_diags_to_file(diags, script_filename=__file__, diag_filename=common_diag_filename, params=params) - -time_count(t_stamp_full, msg='full program') \ No newline at end of file + # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + + write_diags_to_file( + diags, + script_filename=__file__, + diag_filename=diag_filename, + params=params) + write_diags_to_file( + diags, + script_filename=__file__, + diag_filename=common_diag_filename, + params=params) + +time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py index 1a80c13e4..456b7d121 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py @@ -1,3 +1,16 @@ +""" + solver for the TD Maxwell problem: find E(t) in H(curl), B in L2, such that + + dt E - curl B = -J on \\Omega + dt B + curl E = 0 on \\Omega + n x E = n x E_bc on \\partial \\Omega + + with Ampere discretized weakly and Faraday discretized strongly, in a broken-FEEC approach on a 2D multipatch domain \\Omega, + + V0h --grad-> V1h -—curl-> V2h + (Eh) (Bh) +""" + from pytest import param from mpi4py import MPI @@ -12,70 +25,72 @@ from scipy.sparse.linalg import spsolve from scipy import special -from sympde.calculus import dot -from sympde.topology import element_of +from sympde.calculus import dot +from sympde.topology import element_of from sympde.expr.expr import LinearForm from sympde.expr.expr import integral, Norm -from sympde.topology import Derham +from sympde.topology import Derham -from psydac.api.settings import PSYDAC_BACKENDS +from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.pull_push import pull_2d_hcurl - -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv -from psydac.feec.multipatch.plotting_utilities import plot_field #, write_field_to_diag_grid, +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator +from psydac.feec.multipatch.operators import HodgeOperator, get_K0_and_K0_inv, get_K1_and_K1_inv +# , write_field_to_diag_grid, +from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam#, get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 -from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for -from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_operators import construct_vector_conforming_projection, construct_scalar_conforming_projection +# , get_praxial_Gaussian_beam_E, get_easy_Gaussian_beam_E, get_easy_Gaussian_beam_B,get_easy_Gaussian_beam_E_2, get_easy_Gaussian_beam_B_2 +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl, get_div_free_pulse, get_curl_free_pulse, get_Delta_phi_pulse, get_Gaussian_beam +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for +from psydac.feec.multipatch.utilities import time_count # , export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField +from psydac.feec.multipatch.non_matching_operators import construct_hcurl_conforming_projection, construct_h1_conforming_projection from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from psydac.api.postprocessing import OutputManager, PostProcessManager + def solve_td_maxwell_pbm(*, - nc = 4, - deg = 4, - final_time = 20, - cfl_max = 0.8, - dt_max = None, - domain_name = 'pretzel_f', - backend = None, - source_type = 'zero', - source_omega = None, - source_proj = 'P_geom', - conf_proj = 'BSP', - gamma_h = 10., - project_sol = False, - filter_source = True, - quad_param = 1, - E0_type = 'zero', - E0_proj = 'P_L2', - hide_plots = True, - plot_dir = None, - plot_time_ranges = None, - plot_source = False, - plot_divE = False, - diag_dt = None, -# diag_dtau = None, - cb_min_sol = None, - cb_max_sol = None, - m_load_dir = "", - th_sol_filename = "", - source_is_harmonic=False, - domain_lims=None -): + nc=4, + deg=4, + final_time=20, + cfl_max=0.8, + dt_max=None, + domain_name='pretzel_f', + backend=None, + source_type='zero', + source_omega=None, + source_proj='P_geom', + conf_proj='BSP', + gamma_h=10., + project_sol=False, + filter_source=True, + quad_param=1, + E0_type='zero', + E0_proj='P_L2', + hide_plots=True, + plot_dir=None, + plot_time_ranges=None, + plot_source=False, + plot_divE=False, + diag_dt=None, + # diag_dtau = None, + cb_min_sol=None, + cb_max_sol=None, + m_load_dir="", + th_sol_filename="", + source_is_harmonic=False, + domain_lims=None + ): """ solver for the TD Maxwell problem: find E(t) in H(curl), B in L2, such that - dt E - curl B = -J on \Omega - dt B + curl E = 0 on \Omega - n x E = n x E_bc on \partial \Omega + dt E - curl B = -J on \\Omega + dt B + curl E = 0 on \\Omega + n x E = n x E_bc on \\partial \\Omega - with Ampere discretized weakly and Faraday discretized strongly, in a broken-FEEC approach on a 2D multipatch domain \Omega, + with Ampere discretized weakly and Faraday discretized strongly, in a broken-FEEC approach on a 2D multipatch domain \\Omega, V0h --grad-> V1h -—curl-> V2h (Eh) (Bh) @@ -198,12 +213,12 @@ def solve_td_maxwell_pbm(*, """ diags = {} - #ncells = [nc, nc] + # ncells = [nc, nc] degree = [deg, deg] if source_omega is not None: - period_time = 2*np.pi / source_omega - Nt_pp = period_time // dt_max + period_time = 2 * np.pi / source_omega + Nt_pp = period_time // dt_max if plot_time_ranges is None: plot_time_ranges = [ @@ -222,7 +237,7 @@ def solve_td_maxwell_pbm(*, if m_load_dir is not None: if not os.path.exists(m_load_dir): os.makedirs(m_load_dir) - + print('---------------------------------------------------------------------------------------------------------') print('Starting solve_td_maxwell_pbm function with: ') print(' ncells = {}'.format(nc)) @@ -243,24 +258,26 @@ def solve_td_maxwell_pbm(*, t_stamp = time_count() print(' .. multi-patch domain...') - if domain_name == 'refined_square' or domain_name =='square_L_shape': + if domain_name == 'refined_square' or domain_name == 'square_L_shape': int_x, int_y = domain_lims domain = create_square_domain(nc, int_x, int_y, mapping='identity') - ncells_h = {patch.name: [nc[int(patch.name[2])][int(patch.name[4])], nc[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + ncells_h = {patch.name: [nc[int(patch.name[2])][int(patch.name[4])], nc[int( + patch.name[2])][int(patch.name[4])]] for patch in domain.interior} else: domain = build_multipatch_domain(domain_name=domain_name) - ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} - - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + ncells_h = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} + + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) - - + # for diagnosttics diag_grid = DiagGrid(mappings=mappings, N_diag=100) t_stamp = time_count(t_stamp) print(' .. derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) t_stamp = time_count(t_stamp) print(' .. discrete domain...') @@ -273,7 +290,7 @@ def solve_td_maxwell_pbm(*, t_stamp = time_count(t_stamp) print(' .. commuting projection operators...') - nquads = [4*(d + 1) for d in degree] + nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) t_stamp = time_count(t_stamp) @@ -297,35 +314,49 @@ def solve_td_maxwell_pbm(*, print(' .. Hodge operators...') # multi-patch (broken) linear operators / matrices # other option: define as Hodge Operators: - H0 = HodgeOperator(V0h, domain_h, backend_language=backend, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend, load_dir=m_load_dir, load_space_index=1) - H2 = HodgeOperator(V2h, domain_h, backend_language=backend, load_dir=m_load_dir, load_space_index=2) + H0 = HodgeOperator( + V0h, + domain_h, + backend_language=backend, + load_dir=m_load_dir, + load_space_index=0) + H1 = HodgeOperator( + V1h, + domain_h, + backend_language=backend, + load_dir=m_load_dir, + load_space_index=1) + H2 = HodgeOperator( + V2h, + domain_h, + backend_language=backend, + load_dir=m_load_dir, + load_space_index=2) t_stamp = time_count(t_stamp) print(' .. Hodge matrix H0_m = M0_m ...') - H0_m = H0.to_sparse_matrix() + H0_m = H0.to_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') - dH0_m = H0.get_dual_Hodge_sparse_matrix() + dH0_m = H0.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. Hodge matrix H1_m = M1_m ...') - H1_m = H1.to_sparse_matrix() + H1_m = H1.to_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') - dH1_m = H1.get_dual_Hodge_sparse_matrix() + dH1_m = H1.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. Hodge matrix dH2_m = M2_m ...') - H2_m = H2.to_sparse_matrix() - print(' .. dual Hodge matrix dH2_m = inv_M2_m ...') - dH2_m = H2.get_dual_Hodge_sparse_matrix() + H2_m = H2.to_sparse_matrix() + print(' .. dual Hodge matrix dH2_m = inv_M2_m ...') + dH2_m = H2.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) print(' .. conforming Projection operators...') - #(Vh, reg_orders=[0,0], p_moments=[-1,-1], nquads=None, hom_bc=[False, False]) - cP0_m = construct_scalar_conforming_projection(V0h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) - cP1_m = construct_vector_conforming_projection(V1h, [0,0], [-1,-1], nquads=None, hom_bc=[False,False]) + cP0_m = construct_h1_conforming_projection(V0h, hom_bc=False) + cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=False) if conf_proj == 'GSP': print(' [* GSP-conga: using Geometric Spline conf Projections ]') @@ -348,41 +379,39 @@ def solve_td_maxwell_pbm(*, if plot_dir is not None and not os.path.exists(plot_dir): os.makedirs(plot_dir) - # Conga (projection-based) matrices - t_stamp = time_count(t_stamp) + t_stamp = time_count(t_stamp) dH1_m = dH1_m.tocsr() H2_m = H2_m.tocsr() cP1_m = cP1_m.tocsr() - bD1_m = bD1_m.tocsr() + bD1_m = bD1_m.tocsr() print(' .. matrix of the primal curl (in primal bases)...') C_m = bD1_m @ cP1_m print(' .. matrix of the dual curl (also in primal bases)...') - from sympde.calculus import grad, dot, curl, cross - from sympde.topology import NormalVector - from sympde.expr.expr import BilinearForm - from sympde.topology import elements_of + from sympde.calculus import grad, dot, curl, cross + from sympde.topology import NormalVector + from sympde.expr.expr import BilinearForm + from sympde.topology import elements_of - u, v = elements_of(derham.V1, names='u, v') - nn = NormalVector('nn') + u, v = elements_of(derham.V1, names='u, v') + nn = NormalVector('nn') boundary = domain.boundary - expr_b = cross(nn, u)*cross(nn, v) + expr_b = cross(nn, u) * cross(nn, v) - a = BilinearForm((u,v), integral(boundary, expr_b)) + a = BilinearForm((u, v), integral(boundary, expr_b)) ah = discretize(a, domain_h, [V1h, V1h], backend=PSYDAC_BACKENDS[backend],) A_eps = ah.assemble().tosparse() - dC_m = dH1_m @ C_m.transpose() @ H2_m - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Compute stable time step size based on max CFL and max dt dt = compute_stable_dt(C_m=C_m, dC_m=dC_m, cfl_max=cfl_max, dt_max=dt_max) - #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - #Absorbing dC_m + # Absorbing dC_m CH2 = C_m.transpose() @ H2_m H1A = H1_m + dt * A_eps dC_m = sp.sparse.linalg.spsolve(H1A, CH2) @@ -404,40 +433,44 @@ def solve_td_maxwell_pbm(*, # pre_A_m = cP1_m.transpose() @ ( eta * H1_m + mu * pre_CC_m - nu * pre_GD_m ) # useful for the boundary condition (if present) # A_m = pre_A_m @ cP1_m + gamma_h * JP_m - - print(" Reduce time step to match the simulation final time:") - Nt = int(np.ceil(final_time/dt)) + Nt = int(np.ceil(final_time / dt)) dt = final_time / Nt print(f" . Time step size : dt = {dt}") print(f" . Nb of time steps: Nt = {Nt}") # ... - def is_plotting_time(nt, *, dt=dt, Nt=Nt, plot_time_ranges=plot_time_ranges): + def is_plotting_time(nt, *, dt=dt, Nt=Nt, + plot_time_ranges=plot_time_ranges): if nt in [0, Nt]: return True for [start, end], dt_plots in plot_time_ranges: - ds = max(dt_plots // dt, 1) # number of time steps between two successive plots + # number of time steps between two successive plots + ds = max(dt_plots // dt, 1) if (start <= nt * dt <= end) and (nt % ds == 0): return True return False # ... - # Number of time step between two successive calculations of the scalar diagnostics + # Number of time step between two successive calculations of the scalar + # diagnostics diag_nt = max(int(diag_dt // dt), 1) print(' ------ ------ ------ ------ ------ ------ ------ ------ ') print(' ------ ------ ------ ------ ------ ------ ------ ------ ') - print(' total nb of time steps: Nt = {}, final time: T = {:5.4f}'.format(Nt, final_time)) + print( + ' total nb of time steps: Nt = {}, final time: T = {:5.4f}'.format( + Nt, + final_time)) print(' ------ ------ ------ ------ ------ ------ ------ ------ ') print(' plotting times: the solution will be plotted for...') - for nt in range(Nt+1): + for nt in range(Nt + 1): if is_plotting_time(nt): - print(' * nt = {}, t = {:5.4f}'.format(nt, dt*nt)) + print(' * nt = {}, t = {:5.4f}'.format(nt, dt * nt)) print(' ------ ------ ------ ------ ------ ------ ------ ------ ') print(' ------ ------ ------ ------ ------ ------ ------ ------ ') - # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # source t_stamp = time_count(t_stamp) @@ -458,21 +491,28 @@ def is_plotting_time(nt, *, dt=dt, Nt=Nt, plot_time_ranges=plot_time_ranges): f0 = get_curl_free_pulse(x_0=1.0, y_0=1.0, domain=domain) - elif source_type == 'Il_pulse': #Issautier-like pulse - # source will be + elif source_type == 'Il_pulse': # Issautier-like pulse + # source will be # J = curl A + cos(om*t) * grad phi - # so that + # so that # dt rho = - div J = - cos(om*t) Delta phi # for instance, with rho(t=0) = 0 this gives # rho = - sin(om*t)/om * Delta phi # and Gauss' law reads # div E = rho = - sin(om*t)/om * Delta phi - f0 = get_div_free_pulse(x_0=1.0, y_0=1.0, domain=domain) # this is curl A - f0_harmonic = get_curl_free_pulse(x_0=1.0, y_0=1.0, domain=domain) # this is grad phi + f0 = get_div_free_pulse( + x_0=1.0, y_0=1.0, domain=domain) # this is curl A + f0_harmonic = get_curl_free_pulse( + x_0=1.0, y_0=1.0, domain=domain) # this is grad phi assert not source_is_harmonic - rho0 = get_Delta_phi_pulse(x_0=1.0, y_0=1.0, domain=domain) # this is Delta phi - tilde_rho0_c = derham_h.get_dual_dofs(space='V0', f=rho0, backend_language=backend, return_format='numpy_array') + rho0 = get_Delta_phi_pulse( + x_0=1.0, y_0=1.0, domain=domain) # this is Delta phi + tilde_rho0_c = derham_h.get_dual_dofs( + space='V0', + f=rho0, + backend_language=backend, + return_format='numpy_array') tilde_rho0_c = cP0_m.transpose() @ tilde_rho0_c rho0_c = dH0_m.dot(tilde_rho0_c) else: @@ -489,10 +529,10 @@ def is_plotting_time(nt, *, dt=dt, Nt=Nt, plot_time_ranges=plot_time_ranges): f0 = None if E0_type == 'th_sol': # use source enveloppe for smooth transition from 0 to 1 - def source_enveloppe(tau): - return (special.erf((tau/25)-2)-special.erf(-2))/2 + def source_enveloppe(tau): + return (special.erf((tau / 25) - 2) - special.erf(-2)) / 2 else: - def source_enveloppe(tau): + def source_enveloppe(tau): return 1 t_stamp = time_count(t_stamp) @@ -503,11 +543,11 @@ def source_enveloppe(tau): if f0 is not None: f0_h = P1_phys(f0, P1, domain, mappings_list) f0_c = f0_h.coeffs.toarray() - tilde_f0_c = H1_m.dot(f0_c) + tilde_f0_c = H1_m.dot(f0_c) if f0_harmonic is not None: f0_harmonic_h = P1_phys(f0_harmonic, P1, domain, mappings_list) f0_harmonic_c = f0_harmonic_h.coeffs.toarray() - tilde_f0_harmonic_c = H1_m.dot(f0_harmonic_c) + tilde_f0_harmonic_c = H1_m.dot(f0_harmonic_c) elif source_proj == 'P_L2': # helper: save/load coefs @@ -516,13 +556,16 @@ def source_enveloppe(tau): source_name = 'Il_pulse_f0' else: source_name = source_type - sdd_filename = m_load_dir+'/'+source_name+'_dual_dofs_qp{}.npy'.format(quad_param) + sdd_filename = m_load_dir + '/' + source_name + \ + '_dual_dofs_qp{}.npy'.format(quad_param) if os.path.exists(sdd_filename): - print(' .. loading source dual dofs from file {}'.format(sdd_filename)) + print( + ' .. loading source dual dofs from file {}'.format(sdd_filename)) tilde_f0_c = np.load(sdd_filename) else: print(' .. projecting the source f0 with L2 projection...') - tilde_f0_c = derham_h.get_dual_dofs(space='V1', f=f0, backend_language=backend, return_format='numpy_array') + tilde_f0_c = derham_h.get_dual_dofs( + space='V1', f=f0, backend_language=backend, return_format='numpy_array') print(' .. saving source dual dofs to file {}'.format(sdd_filename)) np.save(sdd_filename, tilde_f0_c) if f0_harmonic is not None: @@ -530,13 +573,16 @@ def source_enveloppe(tau): source_name = 'Il_pulse_f0_harmonic' else: source_name = source_type - sdd_filename = m_load_dir+'/'+source_name+'_dual_dofs_qp{}.npy'.format(quad_param) + sdd_filename = m_load_dir + '/' + source_name + \ + '_dual_dofs_qp{}.npy'.format(quad_param) if os.path.exists(sdd_filename): - print(' .. loading source dual dofs from file {}'.format(sdd_filename)) + print( + ' .. loading source dual dofs from file {}'.format(sdd_filename)) tilde_f0_harmonic_c = np.load(sdd_filename) else: print(' .. projecting the source f0_harmonic with L2 projection...') - tilde_f0_harmonic_c = derham_h.get_dual_dofs(space='V1', f=f0_harmonic, backend_language=backend, return_format='numpy_array') + tilde_f0_harmonic_c = derham_h.get_dual_dofs( + space='V1', f=f0_harmonic, backend_language=backend, return_format='numpy_array') print(' .. saving source dual dofs to file {}'.format(sdd_filename)) np.save(sdd_filename, tilde_f0_harmonic_c) @@ -547,7 +593,7 @@ def source_enveloppe(tau): if filter_source: print(' .. filtering the source...') if tilde_f0_c is not None: - tilde_f0_c = cP1_m.transpose() @ tilde_f0_c + tilde_f0_c = cP1_m.transpose() @ tilde_f0_c if tilde_f0_harmonic_c is not None: tilde_f0_harmonic_c = cP1_m.transpose() @ tilde_f0_harmonic_c @@ -556,37 +602,38 @@ def source_enveloppe(tau): if debug: title = 'f0 part of source' - params_str = 'omega={}_gamma_h={}_Pf={}'.format(source_omega, gamma_h, source_proj) - plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_f0.pdf', - plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) - plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_f0_vf.pdf', - plot_type='vector_field', cb_min=None, cb_max=None, hide_plot=hide_plots) + params_str = 'omega={}_gamma_h={}_Pf={}'.format( + source_omega, gamma_h, source_proj) + plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_f0.pdf', + plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_f0_vf.pdf', + plot_type='vector_field', cb_min=None, cb_max=None, hide_plot=hide_plots) divf0_c = div_m @ f0_c title = 'div f0' - plot_field(numpy_coeffs=divf0_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_divf0.pdf', - plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) - + plot_field(numpy_coeffs=divf0_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_divf0.pdf', + plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) if tilde_f0_harmonic_c is not None: - f0_harmonic_c = dH1_m.dot(tilde_f0_harmonic_c) - + f0_harmonic_c = dH1_m.dot(tilde_f0_harmonic_c) + if debug: title = 'f0_harmonic part of source' - params_str = 'omega={}_gamma_h={}_Pf={}'.format(source_omega, gamma_h, source_proj) - plot_field(numpy_coeffs=f0_harmonic_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_f0_harmonic.pdf', - plot_type='components', cb_min=None, cb_max=None, hide_plot=hide_plots) - plot_field(numpy_coeffs=f0_harmonic_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_f0_harmonic_vf.pdf', - plot_type='vector_field', cb_min=None, cb_max=None, hide_plot=hide_plots) + params_str = 'omega={}_gamma_h={}_Pf={}'.format( + source_omega, gamma_h, source_proj) + plot_field(numpy_coeffs=f0_harmonic_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_f0_harmonic.pdf', + plot_type='components', cb_min=None, cb_max=None, hide_plot=hide_plots) + plot_field(numpy_coeffs=f0_harmonic_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_f0_harmonic_vf.pdf', + plot_type='vector_field', cb_min=None, cb_max=None, hide_plot=hide_plots) divf0_c = div_m @ f0_harmonic_c title = 'div f0_harmonic' - plot_field(numpy_coeffs=divf0_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_divf0_harmonic.pdf', - plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + plot_field(numpy_coeffs=divf0_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + '_divf0_harmonic.pdf', + plot_type='components', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) # else: # raise NotImplementedError @@ -594,24 +641,28 @@ def source_enveloppe(tau): if f0_c is None: f0_c = np.zeros(V1h.nbasis) - # if plot_source and plot_dir: # plot_field(numpy_coeffs=f0_c, Vh=V1h, space_kind='hcurl', domain=domain, title='f0_h with P = '+source_proj, filename=plot_dir+'/f0h_'+source_proj+'.png', hide_plot=hide_plots) # plot_field(numpy_coeffs=f0_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, title='f0_h with P = '+source_proj, filename=plot_dir+'/f0h_'+source_proj+'_vf.png', hide_plot=hide_plots) - + t_stamp = time_count(t_stamp) - + def plot_J_source_nPlusHalf(f_c, nt): - print(' .. plotting the source...') - title = r'source $J^{n+1/2}_h$ (amplitude)'+' for $\omega = {}$, $n = {}$'.format(source_omega, nt) - params_str = 'omega={}_gamma_h={}_Pf={}'.format(source_omega, gamma_h, source_proj) - plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_Jh_nt={}.pdf'.format(nt), - plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) - title = r'source $J^{n+1/2}_h$'+' for $\omega = {}$, $n = {}$'.format(source_omega, nt) - plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title, - filename=plot_dir+'/'+params_str+'_Jh_vf_nt={}.pdf'.format(nt), - plot_type='vector_field', vf_skip=1, hide_plot=hide_plots) + print(' .. plotting the source...') + title = r'source $J^{n+1/2}_h$ (amplitude)' + \ + ' for $\\omega = {}$, $n = {}$'.format(source_omega, nt) + params_str = 'omega={}_gamma_h={}_Pf={}'.format( + source_omega, gamma_h, source_proj) + plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + + '_Jh_nt={}.pdf'.format(nt), + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + title = r'source $J^{n+1/2}_h$' + \ + ' for $\\omega = {}$, $n = {}$'.format(source_omega, nt) + plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title, + filename=plot_dir + '/' + params_str + + '_Jh_vf_nt={}.pdf'.format(nt), + plot_type='vector_field', vf_skip=1, hide_plot=hide_plots) def plot_E_field(E_c, nt, project_sol=False, plot_divE=False): @@ -622,51 +673,63 @@ def plot_E_field(E_c, nt, project_sol=False, plot_divE=False): # project the homogeneous solution on the conforming problem space if project_sol: # t_stamp = time_count(t_stamp) - print(' .. projecting the homogeneous solution on the conforming problem space...') + print( + ' .. projecting the homogeneous solution on the conforming problem space...') Ep_c = cP1_m.dot(E_c) else: Ep_c = E_c - print(' .. NOT projecting the homogeneous solution on the conforming problem space') + print( + ' .. NOT projecting the homogeneous solution on the conforming problem space') if plot_omega_normalized_sol: print(' .. plotting the E/omega field...') - u_c = (1/source_omega)*Ep_c - title = r'$u_h = E_h/\omega$ (amplitude) for $\omega = {:5.4f}$, $t = {:5.4f}$'.format(source_omega, dt*nt) - params_str = 'omega={:5.4f}_gamma_h={}_Pf={}_Nt_pp={}'.format(source_omega, gamma_h, source_proj, Nt_pp) + u_c = (1 / source_omega) * Ep_c + title = r'$u_h = E_h/\omega$ (amplitude) for $\omega = {:5.4f}$, $t = {:5.4f}$'.format( + source_omega, dt * nt) + params_str = 'omega={:5.4f}_gamma_h={}_Pf={}_Nt_pp={}'.format( + source_omega, gamma_h, source_proj, Nt_pp) else: - print(' .. plotting the E field...') + print(' .. plotting the E field...') if E0_type == 'pulse': - title = r'$t = {:5.4f}$'.format(dt*nt) + title = r'$t = {:5.4f}$'.format(dt * nt) else: - title = r'$E_h$ (amplitude) at $t = {:5.4f}$'.format(dt*nt) + title = r'$E_h$ (amplitude) at $t = {:5.4f}$'.format( + dt * nt) u_c = Ep_c params_str = f'gamma_h={gamma_h}_dt={dt}' - - plot_field(numpy_coeffs=u_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_Eh_nt={}.pdf'.format(nt), - plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + + plot_field(numpy_coeffs=u_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + + '_Eh_nt={}.pdf'.format(nt), + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) if plot_divE: params_str = f'gamma_h={gamma_h}_dt={dt}' if source_type == 'Il_pulse': plot_type = 'components' - rho_c = rho0_c * np.sin(source_omega*dt*nt) / source_omega + rho_c = rho0_c * \ + np.sin(source_omega * dt * nt) / source_omega rho_norm2 = np.dot(rho_c, H0_m.dot(rho_c)) - title = r'$\rho_h$ at $t = {:5.4f}, norm = {}$'.format(dt*nt, np.sqrt(rho_norm2)) - plot_field(numpy_coeffs=rho_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_rho_nt={}.pdf'.format(nt), - plot_type=plot_type, cb_min=None, cb_max=None, hide_plot=hide_plots) + title = r'$\rho_h$ at $t = {:5.4f}, norm = {}$'.format( + dt * nt, np.sqrt(rho_norm2)) + plot_field(numpy_coeffs=rho_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + + '_rho_nt={}.pdf'.format(nt), + plot_type=plot_type, cb_min=None, cb_max=None, hide_plot=hide_plots) else: plot_type = 'amplitude' divE_c = div_m @ Ep_c divE_norm2 = np.dot(divE_c, H0_m.dot(divE_c)) if project_sol: - title = r'div $P^1_h E_h$ at $t = {:5.4f}, norm = {}$'.format(dt*nt, np.sqrt(divE_norm2)) + title = r'div $P^1_h E_h$ at $t = {:5.4f}, norm = {}$'.format( + dt * nt, np.sqrt(divE_norm2)) else: - title = r'div $E_h$ at $t = {:5.4f}, norm = {}$'.format(dt*nt, np.sqrt(divE_norm2)) - plot_field(numpy_coeffs=divE_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_divEh_nt={}.pdf'.format(nt), - plot_type=plot_type, cb_min=None, cb_max=None, hide_plot=hide_plots) + title = r'div $E_h$ at $t = {:5.4f}, norm = {}$'.format( + dt * nt, np.sqrt(divE_norm2)) + plot_field(numpy_coeffs=divE_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + + '_divEh_nt={}.pdf'.format(nt), + plot_type=plot_type, cb_min=None, cb_max=None, hide_plot=hide_plots) else: print(' -- WARNING: unknown plot_dir !!') @@ -678,251 +741,279 @@ def plot_B_field(B_c, nt): print(' .. plotting B field...') params_str = f'gamma_h={gamma_h}_dt={dt}' - title = r'$B_h$ (amplitude) for $t = {:5.4f}$'.format(dt*nt) - plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, surface_plot=False, title=title, - filename=plot_dir+'/'+params_str+'_Bh_nt={}.pdf'.format(nt), - plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) + title = r'$B_h$ (amplitude) for $t = {:5.4f}$'.format(dt * nt) + plot_field(numpy_coeffs=B_c, Vh=V2h, space_kind='l2', domain=domain, surface_plot=False, title=title, + filename=plot_dir + '/' + params_str + + '_Bh_nt={}.pdf'.format(nt), + plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) else: print(' -- WARNING: unknown plot_dir !!') - def plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start, nt_end, - GaussErr_norm2_diag=None, GaussErrP_norm2_diag=None, - PE_norm2_diag=None, I_PE_norm2_diag=None, J_norm2_diag=None, skip_titles=True): + def plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start, nt_end, + GaussErr_norm2_diag=None, GaussErrP_norm2_diag=None, + PE_norm2_diag=None, I_PE_norm2_diag=None, J_norm2_diag=None, skip_titles=True): nt_start = max(nt_start, 0) nt_end = min(nt_end, Nt) - td = time_diag[nt_start:nt_end+1] + td = time_diag[nt_start:nt_end + 1] t_label = r'$t$' # norm || E || fig, ax = plt.subplots() - ax.plot(td, np.sqrt(E_norm2_diag[nt_start:nt_end+1]), '-', ms=7, mfc='None', mec='k') #, label='||E||', zorder=10) + ax.plot(td, + np.sqrt(E_norm2_diag[nt_start:nt_end + 1]), + '-', + ms=7, + mfc='None', + mec='k') # , label='||E||', zorder=10) if skip_titles: title = '' else: - title = r'$||E_h(t)||$ vs '+t_label + title = r'$||E_h(t)||$ vs ' + t_label ax.set_xlabel(t_label, fontsize=16) ax.set_title(title, fontsize=18) fig.tight_layout() - diag_fn = plot_dir + f'/diag_E_norm_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start}, {dt*nt_end}].pdf' + diag_fn = plot_dir + \ + f'/diag_E_norm_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start}, {dt*nt_end}].pdf' print(f"saving plot for '{title}' in figure '{diag_fn}") fig.savefig(diag_fn) # energy fig, ax = plt.subplots() - E_energ = .5*E_norm2_diag[nt_start:nt_end+1] - B_energ = .5*B_norm2_diag[nt_start:nt_end+1] - ax.plot(td, E_energ, '-', ms=7, mfc='None', c='k', label=r'$\frac{1}{2}||E||^2$') #, zorder=10) - ax.plot(td, B_energ, '-', ms=7, mfc='None', c='g', label=r'$\frac{1}{2}||B||^2$') #, zorder=10) - ax.plot(td, E_energ+B_energ, '-', ms=7, mfc='None', c='b', label=r'$\frac{1}{2}(||E||^2+||B||^2)$') #, zorder=10) + E_energ = .5 * E_norm2_diag[nt_start:nt_end + 1] + B_energ = .5 * B_norm2_diag[nt_start:nt_end + 1] + ax.plot(td, E_energ, '-', ms=7, mfc='None', c='k', + label=r'$\frac{1}{2}||E||^2$') # , zorder=10) + ax.plot(td, B_energ, '-', ms=7, mfc='None', c='g', + label=r'$\frac{1}{2}||B||^2$') # , zorder=10) + ax.plot(td, E_energ + B_energ, '-', ms=7, mfc='None', c='b', + label=r'$\frac{1}{2}(||E||^2+||B||^2)$') # , zorder=10) ax.legend(loc='best') - if skip_titles: + if skip_titles: title = '' else: - title = r'energy vs '+t_label + title = r'energy vs ' + t_label if E0_type == 'pulse': ax.set_ylim([0, 5]) - ax.set_xlabel(t_label, fontsize=16) + ax.set_xlabel(t_label, fontsize=16) ax.set_title(title, fontsize=18) fig.tight_layout() - diag_fn = plot_dir + f'/diag_energy_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start},{dt*nt_end}].pdf' + diag_fn = plot_dir + \ + f'/diag_energy_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start},{dt*nt_end}].pdf' print(f"saving plot for '{title}' in figure '{diag_fn}") fig.savefig(diag_fn) # One curve per plot from now on. - # Collect information in a list where each item is of the form [tag, data, title] + # Collect information in a list where each item is of the form [tag, + # data, title] time_diagnostics = [] if project_sol: - time_diagnostics += [['divPE', divE_norm2_diag, r'$||div_h P^1_h E_h(t)||$ vs '+t_label]] + time_diagnostics += [['divPE', divE_norm2_diag, + r'$||div_h P^1_h E_h(t)||$ vs ' + t_label]] else: - time_diagnostics += [['divE', divE_norm2_diag, r'$||div_h E_h(t)||$ vs '+t_label]] + time_diagnostics += [['divE', divE_norm2_diag, + r'$||div_h E_h(t)||$ vs ' + t_label]] time_diagnostics += [ - ['I_PE' , I_PE_norm2_diag, r'$||(I-P^1)E_h(t)||$ vs '+t_label], - ['PE' , PE_norm2_diag, r'$||(I-P^1)E_h(t)||$ vs '+t_label], - ['GaussErr' , GaussErr_norm2_diag, r'$||(\rho_h - div_h E_h)(t)||$ vs '+t_label], - ['GaussErrP', GaussErrP_norm2_diag, r'$||(\rho_h - div_h E_h)(t)||$ vs '+t_label], - ['J_norm' , J_norm2_diag, r'$||J_h(t)||$ vs '+t_label], + ['I_PE', I_PE_norm2_diag, r'$||(I-P^1)E_h(t)||$ vs ' + t_label], + ['PE', PE_norm2_diag, r'$||(I-P^1)E_h(t)||$ vs ' + t_label], + ['GaussErr', GaussErr_norm2_diag, + r'$||(\rho_h - div_h E_h)(t)||$ vs ' + t_label], + ['GaussErrP', GaussErrP_norm2_diag, + r'$||(\rho_h - div_h E_h)(t)||$ vs ' + t_label], + ['J_norm', J_norm2_diag, r'$||J_h(t)||$ vs ' + t_label], ] for tag, data, title in time_diagnostics: if data is None: continue - fig, ax = plt.subplots() - ax.plot(td, np.sqrt(I_PE_norm2_diag[nt_start:nt_end+1]), '-', ms=7, mfc='None', mec='k') #, label='||E||', zorder=10) - diag_fn = plot_dir + f'/diag_{tag}_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start},{dt*nt_end}].pdf' + fig, ax = plt.subplots() + ax.plot(td, + np.sqrt(I_PE_norm2_diag[nt_start:nt_end + 1]), + '-', + ms=7, + mfc='None', + mec='k') # , label='||E||', zorder=10) + diag_fn = plot_dir + \ + f'/diag_{tag}_gamma={gamma_h}_dt={dt}_trange=[{dt*nt_start},{dt*nt_end}].pdf' ax.set_xlabel(t_label, fontsize=16) if not skip_titles: ax.set_title(title, fontsize=18) fig.tight_layout() print(f"saving plot for '{title}' in figure '{diag_fn}") - fig.savefig(diag_fn) + fig.savefig(diag_fn) # diags arrays - E_norm2_diag = np.zeros(Nt+1) - B_norm2_diag = np.zeros(Nt+1) - divE_norm2_diag = np.zeros(Nt+1) - time_diag = np.zeros(Nt+1) - PE_norm2_diag = np.zeros(Nt+1) - I_PE_norm2_diag = np.zeros(Nt+1) - J_norm2_diag = np.zeros(Nt+1) + E_norm2_diag = np.zeros(Nt + 1) + B_norm2_diag = np.zeros(Nt + 1) + divE_norm2_diag = np.zeros(Nt + 1) + time_diag = np.zeros(Nt + 1) + PE_norm2_diag = np.zeros(Nt + 1) + I_PE_norm2_diag = np.zeros(Nt + 1) + J_norm2_diag = np.zeros(Nt + 1) if source_type == 'Il_pulse': - GaussErr_norm2_diag = np.zeros(Nt+1) - GaussErrP_norm2_diag = np.zeros(Nt+1) + GaussErr_norm2_diag = np.zeros(Nt + 1) + GaussErrP_norm2_diag = np.zeros(Nt + 1) else: GaussErr_norm2_diag = None GaussErrP_norm2_diag = None - # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # initial solution print(' .. initial solution ..') # initial B sol B_c = np.zeros(V2h.nbasis) - + # initial E sol if E0_type == 'th_sol': if os.path.exists(th_sol_filename): - print(' .. loading time-harmonic solution from file {}'.format(th_sol_filename)) + print( + ' .. loading time-harmonic solution from file {}'.format(th_sol_filename)) E_c = source_omega * np.load(th_sol_filename) assert len(E_c) == V1h.nbasis else: - print(' .. Error: time-harmonic solution file given {}, but not found'.format(th_sol_filename)) + print( + ' .. Error: time-harmonic solution file given {}, but not found'.format(th_sol_filename)) raise ValueError(th_sol_filename) - + elif E0_type == 'zero': E_c = np.zeros(V1h.nbasis) - elif E0_type == 'pulse': + elif E0_type == 'pulse': E0 = get_div_free_pulse(x_0=1.0, y_0=1.0, domain=domain) - + if E0_proj == 'P_geom': print(' .. projecting E0 with commuting projection...') E0_h = P1_phys(E0, P1, domain, mappings_list) E_c = E0_h.coeffs.toarray() - + elif E0_proj == 'P_L2': # helper: save/load coefs - E0dd_filename = m_load_dir+'/E0_pulse_dual_dofs_qp{}.npy'.format(quad_param) + E0dd_filename = m_load_dir + \ + '/E0_pulse_dual_dofs_qp{}.npy'.format(quad_param) if os.path.exists(E0dd_filename): print(' .. loading E0 dual dofs from file {}'.format(E0dd_filename)) tilde_E0_c = np.load(E0dd_filename) else: print(' .. projecting E0 with L2 projection...') - tilde_E0_c = derham_h.get_dual_dofs(space='V1', f=E0, backend_language=backend, return_format='numpy_array') + tilde_E0_c = derham_h.get_dual_dofs( + space='V1', f=E0, backend_language=backend, return_format='numpy_array') print(' .. saving E0 dual dofs to file {}'.format(E0dd_filename)) np.save(E0dd_filename, tilde_E0_c) E_c = dH1_m.dot(tilde_E0_c) - elif E0_type == 'pulse_2': - #E0 = get_praxial_Gaussian_beam_E(x_0=3.14, y_0=3.14, domain=domain) - - #E0 = get_easy_Gaussian_beam_E_2(x_0=0.05, y_0=0.05, domain=domain) - #B0 = get_easy_Gaussian_beam_B_2(x_0=0.05, y_0=0.05, domain=domain) + elif E0_type == 'pulse_2': + # E0 = get_praxial_Gaussian_beam_E(x_0=3.14, y_0=3.14, domain=domain) + + # E0 = get_easy_Gaussian_beam_E_2(x_0=0.05, y_0=0.05, domain=domain) + # B0 = get_easy_Gaussian_beam_B_2(x_0=0.05, y_0=0.05, domain=domain) - E0, B0 = get_Gaussian_beam(y_0=3.14, x_0=3.14 , domain=domain) - #B0 = get_easy_Gaussian_beam_B(x_0=3.14, y_0=0.05, domain=domain) + E0, B0 = get_Gaussian_beam(y_0=3.14, x_0=3.14, domain=domain) + # B0 = get_easy_Gaussian_beam_B(x_0=3.14, y_0=0.05, domain=domain) if E0_proj == 'P_geom': print(' .. projecting E0 with commuting projection...') - + E0_h = P1_phys(E0, P1, domain, mappings_list) E_c = E0_h.coeffs.toarray() - - #B_c = np.real( - 1j * C_m @ E_c) - #E_c = np.real(E_c) + + # B_c = np.real( - 1j * C_m @ E_c) + # E_c = np.real(E_c) B0_h = P2_phys(B0, P2, domain, mappings_list) B_c = B0_h.coeffs.toarray() - + elif E0_proj == 'P_L2': # helper: save/load coefs - E0dd_filename = m_load_dir+'/E0_pulse_dual_dofs_qp{}.npy'.format(quad_param) - if False:#os.path.exists(E0dd_filename): + E0dd_filename = m_load_dir + \ + '/E0_pulse_dual_dofs_qp{}.npy'.format(quad_param) + if False: # os.path.exists(E0dd_filename): print(' .. loading E0 dual dofs from file {}'.format(E0dd_filename)) tilde_E0_c = np.load(E0dd_filename) else: print(' .. projecting E0 with L2 projection...') - tilde_E0_c = derham_h.get_dual_dofs(space='V1', f=E0, backend_language=backend, return_format='numpy_array') + tilde_E0_c = derham_h.get_dual_dofs( + space='V1', f=E0, backend_language=backend, return_format='numpy_array') print(' .. saving E0 dual dofs to file {}'.format(E0dd_filename)) - #np.save(E0dd_filename, tilde_E0_c) - + # np.save(E0dd_filename, tilde_E0_c) E_c = dH1_m.dot(tilde_E0_c) - dH2_m = H2.get_dual_sparse_matrix() - tilde_B0_c = derham_h.get_dual_dofs(space='V2', f=B0, backend_language=backend, return_format='numpy_array') + dH2_m = H2.get_dual_sparse_matrix() + tilde_B0_c = derham_h.get_dual_dofs( + space='V2', f=B0, backend_language=backend, return_format='numpy_array') B_c = dH2_m.dot(tilde_B0_c) - - #B_c = np.real( - C_m @ E_c) - #E_c = np.real(E_c) - else: - raise ValueError(E0_type) + # B_c = np.real( - C_m @ E_c) + # E_c = np.real(E_c) + else: + raise ValueError(E0_type) - # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- + # ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- # time loop + def compute_diags(E_c, B_c, J_c, nt): - time_diag[nt] = (nt)*dt + time_diag[nt] = (nt) * dt PE_c = cP1_m.dot(E_c) - I_PE_c = E_c-PE_c - E_norm2_diag[nt] = np.dot(E_c,H1_m.dot(E_c)) - PE_norm2_diag[nt] = np.dot(PE_c,H1_m.dot(PE_c)) - I_PE_norm2_diag[nt] = np.dot(I_PE_c,H1_m.dot(I_PE_c)) - J_norm2_diag[nt] = np.dot(J_c,H1_m.dot(J_c)) - B_norm2_diag[nt] = np.dot(B_c,H2_m.dot(B_c)) + I_PE_c = E_c - PE_c + E_norm2_diag[nt] = np.dot(E_c, H1_m.dot(E_c)) + PE_norm2_diag[nt] = np.dot(PE_c, H1_m.dot(PE_c)) + I_PE_norm2_diag[nt] = np.dot(I_PE_c, H1_m.dot(I_PE_c)) + J_norm2_diag[nt] = np.dot(J_c, H1_m.dot(J_c)) + B_norm2_diag[nt] = np.dot(B_c, H2_m.dot(B_c)) divE_c = div_m @ E_c divE_norm2_diag[nt] = np.dot(divE_c, H0_m.dot(divE_c)) if source_type == 'Il_pulse': - rho_c = rho0_c * np.sin(source_omega*nt*dt)/omega + rho_c = rho0_c * np.sin(source_omega * nt * dt) / omega GaussErr = rho_c - divE_c GaussErrP = rho_c - div_m @ PE_c GaussErr_norm2_diag[nt] = np.dot(GaussErr, H0_m.dot(GaussErr)) GaussErrP_norm2_diag[nt] = np.dot(GaussErrP, H0_m.dot(GaussErrP)) - OM1 = OutputManager(plot_dir+'/spaces1.yml', plot_dir+'/fields1.h5') + OM1 = OutputManager(plot_dir + '/spaces1.yml', plot_dir + '/fields1.h5') OM1.add_spaces(V1h=V1h) OM1.export_space_info() - - OM2 = OutputManager(plot_dir+'/spaces2.yml', plot_dir+'/fields2.h5') + + OM2 = OutputManager(plot_dir + '/spaces2.yml', plot_dir + '/fields2.h5') OM2.add_spaces(V2h=V2h) OM2.export_space_info() stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=0 , ts=0) + OM1.add_snapshot(t=0, ts=0) OM1.export_fields(Eh=Eh) stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=0 , ts=0) + OM2.add_snapshot(t=0, ts=0) OM2.export_fields(Bh=Bh) - - #PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) - #PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=[6]*2, snapshots='all', fields='vh' ) + # PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) + # PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=[6]*2, snapshots='all', fields='vh' ) + + # OM1.close() + # PM.close() - #OM1.close() - #PM.close() + # plot_E_field(E_c, nt=0, project_sol=project_sol, plot_divE=plot_divE) + # plot_B_field(B_c, nt=0) - #plot_E_field(E_c, nt=0, project_sol=project_sol, plot_divE=plot_divE) - #plot_B_field(B_c, nt=0) - f_c = np.copy(f0_c) for nt in range(Nt): - print(' .. nt+1 = {}/{}'.format(nt+1, Nt)) + print(' .. nt+1 = {}/{}'.format(nt + 1, Nt)) # 1/2 faraday: Bn -> Bn+1/2 - B_c[:] -= (dt/2) * C_m @ E_c + B_c[:] -= (dt / 2) * C_m @ E_c # ampere: En -> En+1 if f0_harmonic_c is not None: - f_harmonic_c = f0_harmonic_c * (np.sin(source_omega*(nt+1)*dt)-np.sin(source_omega*(nt)*dt))/(dt*source_omega) # * source_enveloppe(omega*(nt+1/2)*dt) + f_harmonic_c = f0_harmonic_c * (np.sin(source_omega * (nt + 1) * dt) - np.sin( + source_omega * (nt) * dt)) / (dt * source_omega) # * source_enveloppe(omega*(nt+1/2)*dt) f_c[:] = f0_c + f_harmonic_c if nt == 0: @@ -930,16 +1021,16 @@ def compute_diags(E_c, B_c, J_c, nt): compute_diags(E_c, B_c, f_c, nt=0) E_c[:] = dCH1_m @ E_c + dt * (dC_m @ B_c - f_c) - - #if abs(gamma_h) > 1e-10: + + # if abs(gamma_h) > 1e-10: # E_c[:] -= dt * gamma_h * JP_m @ E_c # 1/2 faraday: Bn+1/2 -> Bn+1 - B_c[:] -= (dt/2) * C_m @ E_c + B_c[:] -= (dt / 2) * C_m @ E_c + + # diags: + compute_diags(E_c, B_c, f_c, nt=nt + 1) - # diags: - compute_diags(E_c, B_c, f_c, nt=nt+1) - # PE_c = cP1_m.dot(E_c) # I_PE_c = E_c-PE_c # E_norm2_diag[nt+1] = np.dot(E_c,H1_m.dot(E_c)) @@ -948,7 +1039,7 @@ def compute_diags(E_c, B_c, J_c, nt): # B_norm2_diag[nt+1] = np.dot(B_c,H2_m.dot(B_c)) # time_diag[nt+1] = (nt+1)*dt - # diags: div + # diags: div # if project_sol: # Ep_c = PE_c # = cP1_m.dot(E_c) # else: @@ -964,70 +1055,93 @@ def compute_diags(E_c, B_c, J_c, nt): # GaussErrP = rho_c - div_m @ (cP1_m.dot(E_c)) # GaussErr_norm2_diag[nt+1] = np.dot(GaussErr, H0_m.dot(GaussErr)) # GaussErrP_norm2_diag[nt+1] = np.dot(GaussErrP, H0_m.dot(GaussErrP)) - + if debug: divCB_c = div_m @ dC_m @ B_c divCB_norm2 = np.dot(divCB_c, H0_m.dot(divCB_c)) - print('-- [{}]: dt*|| div CB || = {}'.format(nt+1, dt*np.sqrt(divCB_norm2))) + print('-- [{}]: dt*|| div CB || = {}'.format(nt + + 1, dt * np.sqrt(divCB_norm2))) divf_c = div_m @ f_c divf_norm2 = np.dot(divf_c, H0_m.dot(divf_c)) - print('-- [{}]: dt*|| div f || = {}'.format(nt+1, dt*np.sqrt(divf_norm2))) + print('-- [{}]: dt*|| div f || = {}'.format(nt + + 1, dt * np.sqrt(divf_norm2))) divE_c = div_m @ E_c divE_norm2 = np.dot(divE_c, H0_m.dot(divE_c)) - print('-- [{}]: || div E || = {}'.format(nt+1, np.sqrt(divE_norm2))) + print('-- [{}]: || div E || = {}'.format(nt + 1, np.sqrt(divE_norm2))) - if is_plotting_time(nt+1): + if is_plotting_time(nt + 1): print("Plot Stuff") - #plot_E_field(E_c, nt=nt+1, project_sol=True, plot_divE=False) - #plot_B_field(B_c, nt=nt+1) - #plot_J_source_nPlusHalf(f_c, nt=nt) + # plot_E_field(E_c, nt=nt+1, project_sol=True, plot_divE=False) + # plot_B_field(B_c, nt=nt+1) + # plot_J_source_nPlusHalf(f_c, nt=nt) - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=nt*dt, ts=nt) - OM1.export_fields(Eh = Eh) + OM1.add_snapshot(t=nt * dt, ts=nt) + OM1.export_fields(Eh=Eh) stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=nt*dt, ts=nt) + OM2.add_snapshot(t=nt * dt, ts=nt) OM2.export_fields(Bh=Bh) - - #if (nt+1) % diag_nt == 0: - #plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=(nt+1)-diag_nt, nt_end=(nt+1), - #PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, - #GaussErr_norm2_diag=GaussErr_norm2_diag, GaussErrP_norm2_diag=GaussErrP_norm2_diag) + + # if (nt+1) % diag_nt == 0: + # plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=(nt+1)-diag_nt, nt_end=(nt+1), + # PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, + # GaussErr_norm2_diag=GaussErr_norm2_diag, + # GaussErrP_norm2_diag=GaussErrP_norm2_diag) OM1.close() print("Do some PP") - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) - PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=2,snapshots='all', fields = 'Eh' ) + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces1.yml', + fields_file=plot_dir + + '/fields1.h5') + PM.export_to_vtk( + plot_dir + "/Eh", + grid=None, + npts_per_cell=2, + snapshots='all', + fields='Eh') PM.close() - PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces2.yml', fields_file=plot_dir+'/fields2.h5' ) - PM.export_to_vtk(plot_dir+"/Bh",grid=None, npts_per_cell=2,snapshots='all', fields = 'Bh' ) + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces2.yml', + fields_file=plot_dir + + '/fields2.h5') + PM.export_to_vtk( + plot_dir + "/Bh", + grid=None, + npts_per_cell=2, + snapshots='all', + fields='Bh') PM.close() - # plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=0, nt_end=Nt, + # plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=0, nt_end=Nt, # PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, - # GaussErr_norm2_diag=GaussErr_norm2_diag, GaussErrP_norm2_diag=GaussErrP_norm2_diag) + # GaussErr_norm2_diag=GaussErr_norm2_diag, + # GaussErrP_norm2_diag=GaussErrP_norm2_diag) # Eh = FemField(V1h, coeffs=array_to_stencil(E_c, V1h.vector_space)) # t_stamp = time_count(t_stamp) # if sol_filename: # raise NotImplementedError - # print(' .. saving final solution coeffs to file {}'.format(sol_filename)) - # np.save(sol_filename, E_c) - + # print(' .. saving final solution coeffs to file {}'.format(sol_filename)) + # np.save(sol_filename, E_c) + # time_count(t_stamp) # print() # print(' -- plots and diagnostics --') - + # # diagnostics: errors # err_diags = diag_grid.get_diags_for(v=uh, space='V1') # for key, value in err_diags.items(): @@ -1043,18 +1157,17 @@ def compute_diags(E_c, B_c, J_c, nt): # curl_uh_c = bD1_m @ cP1_m @ uh_c # title = r'curl $u_h$ (amplitude) for $\eta = $'+repr(eta) # params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format(eta, mu, nu, gamma_h, source_proj) - # plot_field(numpy_coeffs=curl_uh_c, Vh=V2h, space_kind='l2', domain=domain, surface_plot=False, title=title, filename=plot_dir+'/'+params_str+'_curl_uh.png', - # plot_type='amplitude', cb_min=None, cb_max=None, hide_plot=hide_plots) + # plot_field(numpy_coeffs=curl_uh_c, Vh=V2h, space_kind='l2', domain=domain, surface_plot=False, title=title, filename=plot_dir+'/'+params_str+'_curl_uh.png', + # plot_type='amplitude', cb_min=None, cb_max=None, hide_plot=hide_plots) # curl_uh = FemField(V2h, coeffs=array_to_stencil(curl_uh_c, V2h.vector_space)) # curl_diags = diag_grid.get_diags_for(v=curl_uh, space='V2') # diags['curl_error (to be checked)'] = curl_diags['rel_l2_error'] - # title = r'div_h $u_h$ (amplitude) for $\eta = $'+repr(eta) # params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format(eta, mu, nu, gamma_h, source_proj) - # plot_field(numpy_coeffs=div_uh_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, filename=plot_dir+'/'+params_str+'_div_uh.png', - # plot_type='amplitude', cb_min=None, cb_max=None, hide_plot=hide_plots) + # plot_field(numpy_coeffs=div_uh_c, Vh=V0h, space_kind='h1', domain=domain, surface_plot=False, title=title, filename=plot_dir+'/'+params_str+'_div_uh.png', + # plot_type='amplitude', cb_min=None, cb_max=None, hide_plot=hide_plots) # div_uh = FemField(V0h, coeffs=array_to_stencil(div_uh_c, V0h.vector_space)) # div_diags = diag_grid.get_diags_for(v=div_uh, space='V0') @@ -1063,7 +1176,7 @@ def compute_diags(E_c, B_c, J_c, nt): return diags -#def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): +# def compute_stable_dt(cfl_max, dt_max, C_m, dC_m, V1_dim): def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): """ Compute a stable time step size based on the maximum CFL parameter in the @@ -1103,52 +1216,55 @@ def compute_stable_dt(*, C_m, dC_m, cfl_max, dt_max=None): """ - print (" .. compute_stable_dt by estimating the operator norm of ") - print (" .. dC_m @ C_m: V1h -> V1h ") - print (" .. with dim(V1h) = {} ...".format(C_m.shape[1])) + print(" .. compute_stable_dt by estimating the operator norm of ") + print(" .. dC_m @ C_m: V1h -> V1h ") + print(" .. with dim(V1h) = {} ...".format(C_m.shape[1])) if not (0 < cfl_max < 1): print(' ****** ****** ****** ****** ****** ****** ') print(' WARNING !!! cfl = {} '.format(cfl)) print(' ****** ****** ****** ****** ****** ****** ') - def vect_norm_2 (vv): - return np.sqrt(np.dot(vv,vv)) + def vect_norm_2(vv): + return np.sqrt(np.dot(vv, vv)) t_stamp = time_count() vv = np.random.random(C_m.shape[1]) - norm_vv = vect_norm_2(vv) + norm_vv = vect_norm_2(vv) max_ncfl = 500 ncfl = 0 spectral_rho = 1 conv = False CC_m = dC_m @ C_m - while not( conv or ncfl > max_ncfl ): + while not (conv or ncfl > max_ncfl): - vv[:] = (1./norm_vv)*vv + vv[:] = (1. / norm_vv) * vv ncfl += 1 vv[:] = CC_m.dot(vv) - + norm_vv = vect_norm_2(vv) old_spectral_rho = spectral_rho - spectral_rho = vect_norm_2(vv) # approximation - conv = abs((spectral_rho - old_spectral_rho)/spectral_rho) < 0.001 - print (" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) + spectral_rho = vect_norm_2(vv) # approximation + conv = abs((spectral_rho - old_spectral_rho) / spectral_rho) < 0.001 + print(" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {}".format(spectral_rho)) t_stamp = time_count(t_stamp) - + norm_op = np.sqrt(spectral_rho) - c_dt_max = 2./norm_op - + c_dt_max = 2. / norm_op + light_c = 1 dt = cfl_max * c_dt_max / light_c if dt_max is not None: dt = min(dt, dt_max) - print( " Time step dt computed for Maxwell solver:") - print(f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") - print(f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") - print(f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") + print(" Time step dt computed for Maxwell solver:") + print( + f" Based on cfl_max = {cfl_max} and dt_max = {dt_max}, we set dt = {dt}") + print( + f" -- note that c*Dt = {light_c*dt} and c_dt_max = {c_dt_max}, thus c * dt / c_dt_max = {light_c*dt/c_dt_max}") + print( + f" -- and spectral_radius((c*dt)**2* dC_m @ C_m ) = {(light_c * dt * norm_op)**2} (should be < 4).") - return dt \ No newline at end of file + return dt diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_testcase.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_testcase.py new file mode 100644 index 000000000..81ca2be3c --- /dev/null +++ b/psydac/feec/multipatch/examples_nc/timedomain_maxwell_testcase.py @@ -0,0 +1,275 @@ +""" + Runner script for solving the time-domain Maxwell problem. +""" + +import numpy as np + +from psydac.feec.multipatch.examples_nc.timedomain_maxwell_nc import solve_td_maxwell_pbm +from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file + +t_stamp_full = time_count() + +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- +# +# main test-cases and parameters used for the ppc paper: + +test_case = 'E0_pulse_no_source' # used in paper +# test_case = 'Issautier_like_source' # used in paper +# test_case = 'transient_to_harmonic' # actually, not used in paper + +# J_proj_case = 'P_geom' +J_proj_case = 'P_L2' +# J_proj_case = 'tilde Pi_1' + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +# Parameters to be changed in the batch run +deg = 3 + +# Common simulation parameters +# domain_name = 'square_6' +# ncells = [4,4,4,4,4,4] +# domain_name = 'pretzel_f' + +# non-conf domains +domain = [[0, 2 * np.pi], [0, 2 * np.pi]] # interval in x- and y-direction +domain_name = 'refined_square' +# use isotropic meshes (probably with a square domain) +# 4x8= 64 patches +# care for the transpose +ncells = np.array([[16, 16], + [16, 16]]) + +# ncells = np.array([[8,8,16,8], +# [8,8,16,8], +# [8,8,16,8], +# [8,8,16,8]]) +# ncells = np.array([[8,8,8,8], +# [8,8,8,8], +# [8,8,8,8], +# [8,8,8,8]]) +# ncells = np.array([[8,8,16,8,8,8], +# [8,8,16,8,8,8], +# [8,8,16,8,8,8], +# [8,8,16,8,8,8]]) + +# ncells = np.array([[4, 4, 4], +# [4, 8, 4], +# [8, 16, 8], +# [4, 8, 4], +# [4, 4, 4]]) +# ncells = np.array([[4, 4, 4, 4], +# [4, 8, 8, 4], +# [8, 16, 16, 8], +# [4, 8, 8, 4], +# [4, 4, 4, 4]]).transpose() +# ncells = np.array([[4, 4, 4, 4], +# [4, 4, 4, 4], +# [4, 8, 8, 4], +# [8, 16, 16, 8], +# [8, 16, 16, 8], +# [4, 8, 8, 4], +# [4, 4, 4, 4], +# [4, 4, 4, 4]]) + + +cfl_max = 0.8 +# 'P_geom' # projection used for initial E0 (B0 = 0 in all cases) +E0_proj = 'P_geom' +backend = 'pyccel-gcc' +project_sol = True # whether cP1 E_h is plotted instead of E_h +# multiplicative parameter for quadrature order in (bi)linear forms +# discretizaion +quad_param = 4 +gamma_h = 0 # jump dissipation parameter (not used in paper) +# 'BSP' # type of conforming projection operators (averaging B-spline or Geometric-splines coefficients) +conf_proj = 'GSP' +hide_plots = True +plot_divE = True +# time interval between scalar diagnostics (if None, compute every time step) +diag_dt = None + +# Parameters that depend on test case +if test_case == 'E0_pulse_no_source': + + E0_type = 'pulse_2' # non-zero initial conditions + source_type = 'zero' # no current source + source_omega = None + final_time = 9.02 # wave transit time in domain is > 4 + dt_max = None + plot_source = False + + plot_a_lot = True + if plot_a_lot: + plot_time_ranges = [[[0, final_time], 0.1]] + else: + plot_time_ranges = [ + [[0, 2], 0.1], + [[final_time - 1, final_time], 0.1], + ] + + cb_min_sol = 0 + cb_max_sol = 5 + +# TODO: check +elif test_case == 'Issautier_like_source': + + E0_type = 'zero' # zero initial conditions + source_type = 'Il_pulse' + source_omega = None + final_time = 20 + plot_source = True + dt_max = None + if deg_s == [3] and final_time == 20: + + plot_time_ranges = [ + [[1.9, 2], 0.1], + [[4.9, 5], 0.1], + [[9.9, 10], 0.1], + [[19.9, 20], 0.1], + ] + + # plot_time_ranges = [ + # ] + # if nc_s == [8]: + # Nt_pp = 10 + + cb_min_sol = 0 # None + cb_max_sol = 0.3 # None + +# TODO: check +elif test_case == 'transient_to_harmonic': + + E0_type = 'th_sol' + source_type = 'elliptic_J' + source_omega = np.sqrt(50) # source time pulsation + plot_source = True + + source_period = 2 * np.pi / source_omega + nb_t_periods = 100 + Nt_pp = 20 + + dt_max = source_period / Nt_pp + final_time = nb_t_periods * source_period + + plot_time_ranges = [ + [[(nb_t_periods - 2) * source_period, final_time], dt_max] + ] + + cb_min_sol = 0 + cb_max_sol = 1 + +else: + raise ValueError(test_case) + + +# projection used for the source J +if J_proj_case == 'P_geom': + source_proj = 'P_geom' + filter_source = False + +elif J_proj_case == 'P_L2': + source_proj = 'P_L2' + filter_source = False + +elif J_proj_case == 'tilde Pi_1': + source_proj = 'P_L2' + filter_source = True + +else: + raise ValueError(J_proj_case) + +case_dir = 'nov14_' + test_case + '_J_proj=' + \ + J_proj_case + '_qp{}'.format(quad_param) +if filter_source: + case_dir += '_Jfilter' +else: + case_dir += '_Jnofilter' +if not project_sol: + case_dir += '_E_noproj' + +if source_omega is not None: + case_dir += f'_omega={source_omega}' + +case_dir += f'_tend={final_time}' + +# +# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- + +common_diag_filename = './' + case_dir + '_diags.txt' + + +run_dir = get_run_dir( + domain_name, + sum(ncells), + deg, + source_type=source_type, + conf_proj=conf_proj) +plot_dir = get_plot_dir(case_dir, run_dir) +diag_filename = plot_dir + '/' + \ + diag_fn(source_type=source_type, source_proj=source_proj) + +# to save and load matrices +m_load_dir = get_mat_dir(domain_name, sum(ncells), deg, quad_param=quad_param) + +if E0_type == 'th_sol': + # initial E0 will be loaded from time-harmonic FEM solution + th_case_dir = 'maxwell_hom_eta=50' + th_sol_dir = get_sol_dir(th_case_dir, domain_name, sum(ncells), deg) + th_sol_filename = th_sol_dir + '/' + \ + FEM_sol_fn(source_type=source_type, source_proj=source_proj) +else: + # no initial solution to load + th_sol_filename = '' + +params = { + 'nc': ncells, + 'deg': deg, + 'final_time': final_time, + 'cfl_max': cfl_max, + 'dt_max': dt_max, + 'domain_name': domain_name, + 'backend': backend, + 'source_type': source_type, + 'source_omega': source_omega, + 'source_proj': source_proj, + 'conf_proj': conf_proj, + 'gamma_h': gamma_h, + 'project_sol': project_sol, + 'filter_source': filter_source, + 'quad_param': quad_param, + 'E0_type': E0_type, + 'E0_proj': E0_proj, + 'hide_plots': hide_plots, + 'plot_dir': plot_dir, + 'plot_time_ranges': plot_time_ranges, + 'plot_source': plot_source, + 'plot_divE': plot_divE, + 'diag_dt': diag_dt, + 'cb_min_sol': cb_min_sol, + 'cb_max_sol': cb_max_sol, + 'm_load_dir': m_load_dir, + 'th_sol_filename': th_sol_filename, + 'domain_lims': domain +} + +print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') +print(' Calling solve_td_maxwell_pbm() with params = {}'.format(params)) +print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') + +diags = solve_td_maxwell_pbm(**params) + +write_diags_to_file( + diags, + script_filename=__file__, + diag_filename=diag_filename, + params=params) +write_diags_to_file( + diags, + script_filename=__file__, + diag_filename=common_diag_filename, + params=params) + +time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py b/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py deleted file mode 100644 index f47734832..000000000 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwells_testcase.py +++ /dev/null @@ -1,251 +0,0 @@ -import numpy as np -from psydac.feec.multipatch.examples_nc.timedomain_maxwell_nc import solve_td_maxwell_pbm -from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn -from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file - -t_stamp_full = time_count() - -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -# -# main test-cases and parameters used for the ppc paper: - -test_case = 'E0_pulse_no_source' # used in paper -#test_case = 'Issautier_like_source' # used in paper -#test_case = 'transient_to_harmonic' # actually, not used in paper - -# J_proj_case = 'P_geom' -J_proj_case = 'P_L2' -#J_proj_case = 'tilde Pi_1' - -# -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- - -# Parameters to be changed in the batch run -deg = 3 - -# Common simulation parameters -#domain_name = 'square_6' -#ncells = [4,4,4,4,4,4] -#domain_name = 'pretzel_f' - -#non-conf domains -domain=[[0, 2*np.pi],[0, 2*np.pi]] # interval in x- and y-direction -domain_name = 'refined_square' -#use isotropic meshes (probably with a square domain) -# 4x8= 64 patches -#care for the transpose -ncells = np.array([[16, 16], - [16, 16]]) - -#ncells = np.array([[8,8,16,8], -# [8,8,16,8], -# [8,8,16,8], -# [8,8,16,8]]) -# ncells = np.array([[8,8,8,8], -# [8,8,8,8], -# [8,8,8,8], -# [8,8,8,8]]) -# ncells = np.array([[8,8,16,8,8,8], -# [8,8,16,8,8,8], -# [8,8,16,8,8,8], -# [8,8,16,8,8,8]]) - -# ncells = np.array([[4, 4, 4], -# [4, 8, 4], -# [8, 16, 8], -# [4, 8, 4], -# [4, 4, 4]]) -# ncells = np.array([[4, 4, 4, 4], -# [4, 8, 8, 4], -# [8, 16, 16, 8], -# [4, 8, 8, 4], -# [4, 4, 4, 4]]).transpose() -# ncells = np.array([[4, 4, 4, 4], -# [4, 4, 4, 4], -# [4, 8, 8, 4], -# [8, 16, 16, 8], -# [8, 16, 16, 8], -# [4, 8, 8, 4], -# [4, 4, 4, 4], -# [4, 4, 4, 4]]) - - - - -cfl_max = 0.8 -E0_proj = 'P_geom' # 'P_geom' # projection used for initial E0 (B0 = 0 in all cases) -backend = 'pyccel-gcc' -project_sol = True # whether cP1 E_h is plotted instead of E_h -quad_param = 4 # multiplicative parameter for quadrature order in (bi)linear forms discretizaion -gamma_h = 0 # jump dissipation parameter (not used in paper) -conf_proj = 'GSP' # 'BSP' # type of conforming projection operators (averaging B-spline or Geometric-splines coefficients) -hide_plots = True -plot_divE = True -diag_dt = None # time interval between scalar diagnostics (if None, compute every time step) - -# Parameters that depend on test case -if test_case == 'E0_pulse_no_source': - - E0_type = 'pulse_2' # non-zero initial conditions - source_type = 'zero' # no current source - source_omega = None - final_time = 9.02 # wave transit time in domain is > 4 - dt_max = None - plot_source = False - - plot_a_lot = True - if plot_a_lot: - plot_time_ranges = [[[0, final_time], 0.1]] - else: - plot_time_ranges = [ - [[0, 2], 0.1], - [[final_time - 1, final_time], 0.1], - ] - - cb_min_sol = 0 - cb_max_sol = 5 - -# TODO: check -elif test_case == 'Issautier_like_source': - - E0_type = 'zero' # zero initial conditions - source_type = 'Il_pulse' - source_omega = None - final_time = 20 - plot_source = True - dt_max = None - if deg_s == [3] and final_time == 20: - - plot_time_ranges = [ - [[ 1.9, 2], 0.1], - [[ 4.9, 5], 0.1], - [[ 9.9, 10], 0.1], - [[19.9, 20], 0.1], - ] - - # plot_time_ranges = [ - # ] - # if nc_s == [8]: - # Nt_pp = 10 - - cb_min_sol = 0 # None - cb_max_sol = 0.3 # None - -# TODO: check -elif test_case == 'transient_to_harmonic': - - E0_type = 'th_sol' - source_type = 'elliptic_J' - source_omega = np.sqrt(50) # source time pulsation - plot_source = True - - source_period = 2 * np.pi / source_omega - nb_t_periods = 100 - Nt_pp = 20 - - dt_max = source_period / Nt_pp - final_time = nb_t_periods * source_period - - plot_time_ranges = [ - [[(nb_t_periods-2) * source_period, final_time], dt_max] - ] - - cb_min_sol = 0 - cb_max_sol = 1 - -else: - raise ValueError(test_case) - - -# projection used for the source J -if J_proj_case == 'P_geom': - source_proj = 'P_geom' - filter_source = False - -elif J_proj_case == 'P_L2': - source_proj = 'P_L2' - filter_source = False - -elif J_proj_case == 'tilde Pi_1': - source_proj = 'P_L2' - filter_source = True - -else: - raise ValueError(J_proj_case) - -case_dir = 'nov14_' + test_case + '_J_proj=' + J_proj_case + '_qp{}'.format(quad_param) -if filter_source: - case_dir += '_Jfilter' -else: - case_dir += '_Jnofilter' -if not project_sol: - case_dir += '_E_noproj' - -if source_omega is not None: - case_dir += f'_omega={source_omega}' - -case_dir += f'_tend={final_time}' - -# -# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- - -common_diag_filename = './'+case_dir+'_diags.txt' - - -run_dir = get_run_dir(domain_name, sum(ncells), deg, source_type=source_type, conf_proj=conf_proj) -plot_dir = get_plot_dir(case_dir, run_dir) -diag_filename = plot_dir+'/'+diag_fn(source_type=source_type, source_proj=source_proj) - -# to save and load matrices -m_load_dir = get_mat_dir(domain_name, sum(ncells), deg, quad_param=quad_param) - -if E0_type == 'th_sol': - # initial E0 will be loaded from time-harmonic FEM solution - th_case_dir = 'maxwell_hom_eta=50' - th_sol_dir = get_sol_dir(th_case_dir, domain_name, sum(ncells), deg) - th_sol_filename = th_sol_dir+'/'+FEM_sol_fn(source_type=source_type, source_proj=source_proj) -else: - # no initial solution to load - th_sol_filename = '' - -params = { - 'nc' : ncells, - 'deg' : deg, - 'final_time' : final_time, - 'cfl_max' : cfl_max, - 'dt_max' : dt_max, - 'domain_name' : domain_name, - 'backend' : backend, - 'source_type' : source_type, - 'source_omega' : source_omega, - 'source_proj' : source_proj, - 'conf_proj' : conf_proj, - 'gamma_h' : gamma_h, - 'project_sol' : project_sol, - 'filter_source' : filter_source, - 'quad_param' : quad_param, - 'E0_type' : E0_type, - 'E0_proj' : E0_proj, - 'hide_plots' : hide_plots, - 'plot_dir' : plot_dir, - 'plot_time_ranges': plot_time_ranges, - 'plot_source' : plot_source, - 'plot_divE' : plot_divE, - 'diag_dt' : diag_dt, - 'cb_min_sol' : cb_min_sol, - 'cb_max_sol' : cb_max_sol, - 'm_load_dir' : m_load_dir, - 'th_sol_filename' : th_sol_filename, - 'domain_lims' : domain -} - -print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') -print(' Calling solve_td_maxwell_pbm() with params = {}'.format(params)) -print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') - -diags = solve_td_maxwell_pbm(**params) - -write_diags_to_file(diags, script_filename=__file__, diag_filename=diag_filename, params=params) -write_diags_to_file(diags, script_filename=__file__, diag_filename=common_diag_filename, params=params) - -time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/non_matching_operators.py b/psydac/feec/multipatch/non_matching_operators.py index 8d5d2a94d..461509646 100644 --- a/psydac/feec/multipatch/non_matching_operators.py +++ b/psydac/feec/multipatch/non_matching_operators.py @@ -282,8 +282,7 @@ def get_extension_restriction(coarse_space_1d, fine_space_1d, p_moments=-1): Extension-restriction matrix. """ matching_interfaces = (coarse_space_1d.ncells == fine_space_1d.ncells) - # assert (coarse_space_1d.breaks[0] == fine_space_1d.breaks[0]) and ( - # coarse_space_1d.breaks[-1] == fine_space_1d.breaks[-1]) + assert (coarse_space_1d.degree == fine_space_1d.degree) assert (coarse_space_1d.basis == fine_space_1d.basis) spl_type = coarse_space_1d.basis @@ -299,7 +298,7 @@ def get_extension_restriction(coarse_space_1d, fine_space_1d, p_moments=-1): domain=coarse_space_1d_k_plus, codomain=fine_space_1d) R_1D = construct_restriction_operator_1D( - coarse_space_1d, fine_space_1d, E_1D, p_moments) + coarse_space_1d_k_plus, fine_space_1d, E_1D, p_moments) ER_1D = E_1D @ R_1D From df34fe9b016d0af33db7f902e3b625a8bfca9b91 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Thu, 23 May 2024 14:12:18 +0200 Subject: [PATCH 43/88] fix loop stencil --- psydac/linalg/topetsc.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 65a8d94e4..b7dfea74a 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -401,7 +401,6 @@ def mat_topetsc( mat ): cndims = [ccart.ndim for ccart in ccarts] # Get global number of points per block: dnpts = [dcart.npts for dcart in dcarts] # indexed [block, dimension]. Same for all processes. - cnpts = [ccart.npts for ccart in ccarts] # indexed [block, dimension]. Same for all processes. # Get the number of points local to the current process: dnpts_local = get_npts_local(mat.domain) # indexed [block, dimension]. Different for each process. @@ -439,8 +438,6 @@ def mat_topetsc( mat ): mat_block = mat.blocks[bc][bd] cs = ccarts[bc].starts - dp = dcarts[bd].pads - dm = dcarts[bd].shifts cghost_size = [pi*mi for pi,mi in zip(ccarts[bc].pads, ccarts[bc].shifts)] if dndims[bd] == 1 and cndims[bc] == 1: @@ -450,10 +447,12 @@ def mat_topetsc( mat ): i1_n = cs[0] + i1 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n,)) - for k1 in range(-dp[0]*dm[0], dp[0]*dm[0] + 1): - value = mat_block._data[i1 + cghost_size[0], (k1 + dp[0]*dm[0])%(2*dp[0]*dm[0] + 1)] + stencil_size = mat_block._data[i1 + cghost_size[0],:].shape - j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC + for k1 in range(stencil_size[0]): + value = mat_block._data[i1 + cghost_size[0], k1] + + j1_n = (i1_n + k1 - stencil_size[0]//2) % dnpts[bd][0] # modulus is necessary for periodic BC if value != 0: j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, )) @@ -479,12 +478,14 @@ def mat_topetsc( mat ): i2_n = cs[1] + i2 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n)) - for k1 in range(- dp[0]*dm[0], dp[0]*dm[0] + 1): - for k2 in range(- dp[1]*dm[1], dp[1]*dm[1] + 1): - value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], (k1 + dp[0]*dm[0])%(2*dp[0]*dm[0] + 1), (k2 + dp[1]*dm[1])%(2*dp[1]*dm[1] + 1)] + stencil_size = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1],:,:].shape + + for k1 in range(stencil_size[0]): + for k2 in range(stencil_size[1]): + value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], k1, k2] - j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC - j2_n = (i2_n + k2) % dnpts[bd][1] # modulus is necessary for periodic BC + j1_n = (i1_n + k1 - stencil_size[0]//2) % dnpts[bd][0] # modulus is necessary for periodic BC + j2_n = (i2_n + k2 - stencil_size[1]//2) % dnpts[bd][1] # modulus is necessary for periodic BC if value != 0: j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n)) @@ -510,14 +511,16 @@ def mat_topetsc( mat ): i3_n = cs[2] + i3 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n, i3_n)) - for k1 in range(-dp[0]*dm[0], dp[0]*dm[0] + 1): - for k2 in range(-dp[1]*dm[1], dp[1]*dm[1] + 1): - for k3 in range(-dp[2]*dm[2], dp[2]*dm[2] + 1): - value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], i3 + cghost_size[2], (k1 + dp[0]*dm[0])%(2*dp[0]*dm[0] + 1), (k2 + dp[1]*dm[1])%(2*dp[1]*dm[1] + 1), (k3 + dp[2]*dm[2])%(2*dp[2]*dm[2] + 1)] + stencil_size = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], i3 + cghost_size[2],:,:,:].shape + + for k1 in range(stencil_size[0]): + for k2 in range(stencil_size[1]): + for k3 in range(stencil_size[2]): + value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], i3 + cghost_size[2], k1, k2, k3] - j1_n = (i1_n + k1) % dnpts[bd][0] # modulus is necessary for periodic BC - j2_n = (i2_n + k2) % dnpts[bd][1] # modulus is necessary for periodic BC - j3_n = (i3_n + k3) % dnpts[bd][2] # modulus is necessary for periodic BC + j1_n = (i1_n + k1 - stencil_size[0]//2) % dnpts[bd][0] # modulus is necessary for periodic BC + j2_n = (i2_n + k2 - stencil_size[1]//2) % dnpts[bd][1] # modulus is necessary for periodic BC + j3_n = (i3_n + k3 - stencil_size[2]//2) % dnpts[bd][2] # modulus is necessary for periodic BC if value != 0: j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n, j3_n)) From a853a47b84007dc9f32e48d178f62ecdcb80c7da Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Thu, 23 May 2024 16:06:27 +0200 Subject: [PATCH 44/88] work in comments --- .../multipatch/multipatch_domain_utilities.py | 1136 +++++++++++------ ...on_matching_multipatch_domain_utilities.py | 91 +- psydac/feec/multipatch/operators.py | 577 +++++---- psydac/feec/multipatch/plotting_utilities.py | 387 ++++-- psydac/feec/multipatch/utilities.py | 106 +- psydac/feec/multipatch/utils_conga_2d.py | 146 ++- 6 files changed, 1568 insertions(+), 875 deletions(-) diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index bfb1cbbef..2e8848130 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -5,13 +5,21 @@ import numpy as np from sympde.topology import Square, Domain -from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping #TransposedPolarMapping +# TransposedPolarMapping +from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping + +__all__ = ( + 'TransposedPolarMapping', + 'create_domain', + 'get_2D_rotation_mapping', + 'flip_axis', + 'build_multipatch_domain', + 'get_ref_eigenvalues') + +# ============================================================================== +# small extension to SymPDE: -__all__ = ('TransposedPolarMapping', 'create_domain', 'get_2D_rotation_mapping', 'flip_axis', - 'build_multipatch_domain', 'get_ref_eigenvalues') -#============================================================================== -# small extension to SymPDE: class TransposedPolarMapping(Mapping): """ Represents a Transposed (x1 <> x2) Polar 2D Mapping object (Annulus). @@ -20,16 +28,18 @@ class TransposedPolarMapping(Mapping): _expressions = {'x': 'c1 + (rmin*(1-x2)+rmax*x2)*cos(x1)', 'y': 'c2 + (rmin*(1-x2)+rmax*x2)*sin(x1)'} - _ldim = 2 - _pdim = 2 - + _ldim = 2 + _pdim = 2 def create_domain(patches, interfaces, name): connectivity = [] patches_interiors = [D.interior for D in patches] for I in interfaces: - connectivity.append(((patches_interiors.index(I[0].domain),I[0].axis, I[0].ext), (patches_interiors.index(I[1].domain), I[1].axis, I[1].ext), I[2])) + connectivity.append( + ((patches_interiors.index( + I[0].domain), I[0].axis, I[0].ext), (patches_interiors.index( + I[1].domain), I[1].axis, I[1].ext), I[2])) return Domain.join(patches, connectivity, name) # def get_annulus_fourpatches(r_min, r_max): @@ -61,7 +71,7 @@ def create_domain(patches, interfaces, name): # return domain -def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=np.pi/2): +def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=np.pi / 2): # AffineMapping: # _expressions = {'x': 'c1 + a11*x1 + a12*x2 + a13*x3', @@ -74,6 +84,7 @@ def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=np.pi/2): a21=np.sin(alpha), a22=np.cos(alpha), ) + def flip_axis(name='no_name', c1=0., c2=0.): # AffineMapping: @@ -87,6 +98,7 @@ def flip_axis(name='no_name', c1=0., c2=0.): a21=1, a22=0, ) + def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): """ Create a 2D multipatch domain among the many available. @@ -110,27 +122,32 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): # mp structure: # 2 # 1 - OmegaLog1 = Square('OmegaLog1',bounds1=(0., np.pi), bounds2=(0., np.pi/2)) - mapping_1 = IdentityMapping('M1',2) - domain_1 = mapping_1(OmegaLog1) - - OmegaLog2 = Square('OmegaLog2',bounds1=(0., np.pi), bounds2=(np.pi/2, np.pi)) - mapping_2 = IdentityMapping('M2',2) - domain_2 = mapping_2(OmegaLog2) + OmegaLog1 = Square( + 'OmegaLog1', bounds1=( + 0., np.pi), bounds2=( + 0., np.pi / 2)) + mapping_1 = IdentityMapping('M1', 2) + domain_1 = mapping_1(OmegaLog1) + + OmegaLog2 = Square( + 'OmegaLog2', bounds1=( + 0., np.pi), bounds2=( + np.pi / 2, np.pi)) + mapping_2 = IdentityMapping('M2', 2) + domain_2 = mapping_2(OmegaLog2) patches = [domain_1, domain_2] - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1] - ] + interfaces = [[domain_1.get_boundary( + axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1]] elif domain_name == 'square_4': # C D # A B - A = Square('A', bounds1=(0, np.pi/2), bounds2=(0, np.pi/2)) - B = Square('B', bounds1=(np.pi/2, np.pi), bounds2=(0, np.pi/2)) - C = Square('C', bounds1=(0, np.pi/2), bounds2=(np.pi/2, np.pi)) - D = Square('D', bounds1=(np.pi/2, np.pi), bounds2=(np.pi/2, np.pi)) + A = Square('A', bounds1=(0, np.pi / 2), bounds2=(0, np.pi / 2)) + B = Square('B', bounds1=(np.pi / 2, np.pi), bounds2=(0, np.pi / 2)) + C = Square('C', bounds1=(0, np.pi / 2), bounds2=(np.pi / 2, np.pi)) + D = Square('D', bounds1=(np.pi / 2, np.pi), bounds2=(np.pi / 2, np.pi)) M1 = IdentityMapping('M1', dim=2) M2 = IdentityMapping('M2', dim=2) M3 = IdentityMapping('M3', dim=2) @@ -140,10 +157,10 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): C = M3(C) D = M4(D) - patches = [A,B,C,D] + patches = [A, B, C, D] interfaces = [ - [A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], + [A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1], @@ -155,81 +172,186 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): # 5 6 # 3 4 # 1 2 - OmegaLog1 = Square('OmegaLog1',bounds1=(0., np.pi/2), bounds2=(0., np.pi/3)) - mapping_1 = IdentityMapping('M1',2) - domain_1 = mapping_1(OmegaLog1) - - OmegaLog2 = Square('OmegaLog2',bounds1=(np.pi/2, np.pi), bounds2=(0., np.pi/3)) - mapping_2 = IdentityMapping('M2',2) - domain_2 = mapping_2(OmegaLog2) - - OmegaLog3 = Square('OmegaLog3',bounds1=(0., np.pi/2), bounds2=(np.pi/3, np.pi*2/3)) - mapping_3 = IdentityMapping('M3',2) - domain_3 = mapping_3(OmegaLog3) - - OmegaLog4 = Square('OmegaLog4',bounds1=(np.pi/2, np.pi), bounds2=(np.pi/3, np.pi*2/3)) - mapping_4 = IdentityMapping('M4',2) - domain_4 = mapping_4(OmegaLog4) - - OmegaLog5 = Square('OmegaLog5',bounds1=(0., np.pi/2), bounds2=(np.pi*2/3, np.pi)) - mapping_5 = IdentityMapping('M5',2) - domain_5 = mapping_5(OmegaLog5) - - OmegaLog6 = Square('OmegaLog6',bounds1=(np.pi/2, np.pi), bounds2=(np.pi*2/3, np.pi)) - mapping_6 = IdentityMapping('M6',2) - domain_6 = mapping_6(OmegaLog6) + OmegaLog1 = Square( + 'OmegaLog1', + bounds1=( + 0., + np.pi / 2), + bounds2=( + 0., + np.pi / 3)) + mapping_1 = IdentityMapping('M1', 2) + domain_1 = mapping_1(OmegaLog1) + + OmegaLog2 = Square( + 'OmegaLog2', + bounds1=( + np.pi / 2, + np.pi), + bounds2=( + 0., + np.pi / 3)) + mapping_2 = IdentityMapping('M2', 2) + domain_2 = mapping_2(OmegaLog2) + + OmegaLog3 = Square( + 'OmegaLog3', + bounds1=( + 0., + np.pi / 2), + bounds2=( + np.pi / 3, + np.pi * 2 / 3)) + mapping_3 = IdentityMapping('M3', 2) + domain_3 = mapping_3(OmegaLog3) + + OmegaLog4 = Square( + 'OmegaLog4', + bounds1=( + np.pi / 2, + np.pi), + bounds2=( + np.pi / 3, + np.pi * 2 / 3)) + mapping_4 = IdentityMapping('M4', 2) + domain_4 = mapping_4(OmegaLog4) + + OmegaLog5 = Square( + 'OmegaLog5', + bounds1=( + 0., + np.pi / 2), + bounds2=( + np.pi * 2 / 3, + np.pi)) + mapping_5 = IdentityMapping('M5', 2) + domain_5 = mapping_5(OmegaLog5) + + OmegaLog6 = Square( + 'OmegaLog6', + bounds1=( + np.pi / 2, + np.pi), + bounds2=( + np.pi * 2 / 3, + np.pi)) + mapping_6 = IdentityMapping('M6', 2) + domain_6 = mapping_6(OmegaLog6) patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6] interfaces = [ - [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1),1], - [domain_3.get_boundary(axis=0, ext=+1), domain_4.get_boundary(axis=0, ext=-1),1], - [domain_5.get_boundary(axis=0, ext=+1), domain_6.get_boundary(axis=0, ext=-1),1], - [domain_1.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1),1], - [domain_2.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1),1], - [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1),1], + [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], + [domain_3.get_boundary(axis=0, ext=+1), domain_4.get_boundary(axis=0, ext=-1), 1], + [domain_5.get_boundary(axis=0, ext=+1), domain_6.get_boundary(axis=0, ext=-1), 1], + [domain_1.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + [domain_2.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], + [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], ] elif domain_name in ['square_8', 'square_9']: # square with third-length patches, with or without a hole: - OmegaLog1 = Square('OmegaLog1',bounds1=(0., np.pi/3), bounds2=(0., np.pi/3)) - mapping_1 = IdentityMapping('M1',2) - domain_1 = mapping_1(OmegaLog1) - - OmegaLog2 = Square('OmegaLog2',bounds1=(np.pi/3, np.pi*2/3), bounds2=(0., np.pi/3)) - mapping_2 = IdentityMapping('M2',2) - domain_2 = mapping_2(OmegaLog2) - - OmegaLog3 = Square('OmegaLog3',bounds1=(np.pi*2/3, np.pi), bounds2=(0., np.pi/3)) - mapping_3 = IdentityMapping('M3',2) - domain_3 = mapping_3(OmegaLog3) - - OmegaLog4 = Square('OmegaLog4',bounds1=(0., np.pi/3), bounds2=(np.pi/3, np.pi*2/3)) - mapping_4 = IdentityMapping('M4',2) - domain_4 = mapping_4(OmegaLog4) - - OmegaLog5 = Square('OmegaLog5',bounds1=(np.pi*2/3, np.pi), bounds2=(np.pi/3, np.pi*2/3)) - mapping_5 = IdentityMapping('M5',2) - domain_5 = mapping_5(OmegaLog5) - - OmegaLog6 = Square('OmegaLog6',bounds1=(0., np.pi/3), bounds2=(np.pi*2/3, np.pi)) - mapping_6 = IdentityMapping('M6',2) - domain_6 = mapping_6(OmegaLog6) - - OmegaLog7 = Square('OmegaLog7',bounds1=(np.pi/3, np.pi*2/3), bounds2=(np.pi*2/3, np.pi)) - mapping_7 = IdentityMapping('M7',2) - domain_7 = mapping_7(OmegaLog7) - - OmegaLog8 = Square('OmegaLog8',bounds1=(np.pi*2/3, np.pi), bounds2=(np.pi*2/3, np.pi)) - mapping_8 = IdentityMapping('M8',2) - domain_8 = mapping_8(OmegaLog8) + OmegaLog1 = Square( + 'OmegaLog1', + bounds1=( + 0., + np.pi / 3), + bounds2=( + 0., + np.pi / 3)) + mapping_1 = IdentityMapping('M1', 2) + domain_1 = mapping_1(OmegaLog1) + + OmegaLog2 = Square( + 'OmegaLog2', + bounds1=( + np.pi / 3, + np.pi * 2 / 3), + bounds2=( + 0., + np.pi / 3)) + mapping_2 = IdentityMapping('M2', 2) + domain_2 = mapping_2(OmegaLog2) + + OmegaLog3 = Square( + 'OmegaLog3', + bounds1=( + np.pi * 2 / 3, + np.pi), + bounds2=( + 0., + np.pi / 3)) + mapping_3 = IdentityMapping('M3', 2) + domain_3 = mapping_3(OmegaLog3) + + OmegaLog4 = Square( + 'OmegaLog4', + bounds1=( + 0., + np.pi / 3), + bounds2=( + np.pi / 3, + np.pi * 2 / 3)) + mapping_4 = IdentityMapping('M4', 2) + domain_4 = mapping_4(OmegaLog4) + + OmegaLog5 = Square( + 'OmegaLog5', + bounds1=( + np.pi * 2 / 3, + np.pi), + bounds2=( + np.pi / 3, + np.pi * 2 / 3)) + mapping_5 = IdentityMapping('M5', 2) + domain_5 = mapping_5(OmegaLog5) + + OmegaLog6 = Square( + 'OmegaLog6', + bounds1=( + 0., + np.pi / 3), + bounds2=( + np.pi * 2 / 3, + np.pi)) + mapping_6 = IdentityMapping('M6', 2) + domain_6 = mapping_6(OmegaLog6) + + OmegaLog7 = Square( + 'OmegaLog7', + bounds1=( + np.pi / 3, + np.pi * 2 / 3), + bounds2=( + np.pi * 2 / 3, + np.pi)) + mapping_7 = IdentityMapping('M7', 2) + domain_7 = mapping_7(OmegaLog7) + + OmegaLog8 = Square( + 'OmegaLog8', + bounds1=( + np.pi * 2 / 3, + np.pi), + bounds2=( + np.pi * 2 / 3, + np.pi)) + mapping_8 = IdentityMapping('M8', 2) + domain_8 = mapping_8(OmegaLog8) # center domain - OmegaLog9 = Square('OmegaLog9',bounds1=(np.pi/3, np.pi*2/3), bounds2=(np.pi/3, np.pi*2/3)) - mapping_9 = IdentityMapping('M9',2) - domain_9 = mapping_9(OmegaLog9) + OmegaLog9 = Square( + 'OmegaLog9', + bounds1=( + np.pi / 3, + np.pi * 2 / 3), + bounds2=( + np.pi / 3, + np.pi * 2 / 3)) + mapping_9 = IdentityMapping('M9', 2) + domain_9 = mapping_9(OmegaLog9) if domain_name == 'square_8': # square domain with a hole: @@ -237,18 +359,25 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): # 4 * 5 # 1 2 3 - - patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6, domain_7, domain_8] + patches = [ + domain_1, + domain_2, + domain_3, + domain_4, + domain_5, + domain_6, + domain_7, + domain_8] interfaces = [ - [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1),1], - [domain_2.get_boundary(axis=0, ext=+1), domain_3.get_boundary(axis=0, ext=-1),1], - [domain_6.get_boundary(axis=0, ext=+1), domain_7.get_boundary(axis=0, ext=-1),1], - [domain_7.get_boundary(axis=0, ext=+1), domain_8.get_boundary(axis=0, ext=-1),1], - [domain_1.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1),1], - [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1),1], - [domain_5.get_boundary(axis=1, ext=+1), domain_8.get_boundary(axis=1, ext=-1),1], + [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], + [domain_2.get_boundary(axis=0, ext=+1), domain_3.get_boundary(axis=0, ext=-1), 1], + [domain_6.get_boundary(axis=0, ext=+1), domain_7.get_boundary(axis=0, ext=-1), 1], + [domain_7.get_boundary(axis=0, ext=+1), domain_8.get_boundary(axis=0, ext=-1), 1], + [domain_1.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], + [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + [domain_5.get_boundary(axis=1, ext=+1), domain_8.get_boundary(axis=1, ext=-1), 1], ] elif domain_name == 'square_9': @@ -257,22 +386,30 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): # 4 9 5 # 1 2 3 - - patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6, domain_7, domain_8, domain_9] + patches = [ + domain_1, + domain_2, + domain_3, + domain_4, + domain_5, + domain_6, + domain_7, + domain_8, + domain_9] interfaces = [ - [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1),1], - [domain_2.get_boundary(axis=0, ext=+1), domain_3.get_boundary(axis=0, ext=-1),1], - [domain_4.get_boundary(axis=0, ext=+1), domain_9.get_boundary(axis=0, ext=-1),1], - [domain_9.get_boundary(axis=0, ext=+1), domain_5.get_boundary(axis=0, ext=-1),1], - [domain_6.get_boundary(axis=0, ext=+1), domain_7.get_boundary(axis=0, ext=-1),1], - [domain_7.get_boundary(axis=0, ext=+1), domain_8.get_boundary(axis=0, ext=-1),1], - [domain_1.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1),1], - [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1),1], - [domain_2.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1),1], - [domain_9.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1),1], - [domain_5.get_boundary(axis=1, ext=+1), domain_8.get_boundary(axis=1, ext=-1),1], + [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], + [domain_2.get_boundary(axis=0, ext=+1), domain_3.get_boundary(axis=0, ext=-1), 1], + [domain_4.get_boundary(axis=0, ext=+1), domain_9.get_boundary(axis=0, ext=-1), 1], + [domain_9.get_boundary(axis=0, ext=+1), domain_5.get_boundary(axis=0, ext=-1), 1], + [domain_6.get_boundary(axis=0, ext=+1), domain_7.get_boundary(axis=0, ext=-1), 1], + [domain_7.get_boundary(axis=0, ext=+1), domain_8.get_boundary(axis=0, ext=-1), 1], + [domain_1.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], + [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], + [domain_2.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], + [domain_9.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + [domain_5.get_boundary(axis=1, ext=+1), domain_8.get_boundary(axis=1, ext=-1), 1], ] else: @@ -280,99 +417,143 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): elif domain_name in ['pretzel', 'pretzel_f', 'pretzel_annulus', 'pretzel_debug']: # pretzel-shaped domain with quarter-annuli and quadrangles -- setting parameters - # note: 'pretzel_f' is a bit finer than 'pretzel', to have a roughly uniform resolution (patches of approx same size) + # note: 'pretzel_f' is a bit finer than 'pretzel', to have a roughly + # uniform resolution (patches of approx same size) if r_min is None: - r_min=1 # smaller radius of quarter-annuli + r_min = 1 # smaller radius of quarter-annuli if r_max is None: - r_max=2 # larger radius of quarter-annuli + r_max = 2 # larger radius of quarter-annuli assert 0 < r_min assert r_min < r_max dr = r_max - r_min h = dr # offset from axes of quarter-annuli - hr = dr/2 - cr = h +(r_max+r_min)/2 - - dom_log_1 = Square('dom1',bounds1=(r_min, r_max), bounds2=(0, np.pi/2)) - mapping_1 = PolarMapping('M1',2, c1= h, c2= h, rmin = 0., rmax=1.) - domain_1 = mapping_1(dom_log_1) - - dom_log_1_1 = Square('dom1_1',bounds1=(r_min, r_max), bounds2=(0, np.pi/4)) - mapping_1_1 = PolarMapping('M1_1',2, c1= h, c2= h, rmin = 0., rmax=1.) - domain_1_1 = mapping_1_1(dom_log_1_1) - - dom_log_1_2 = Square('dom1_2',bounds1=(r_min, r_max), bounds2=(np.pi/4, np.pi/2)) - mapping_1_2 = PolarMapping('M1_2',2, c1= h, c2= h, rmin = 0., rmax=1.) - domain_1_2 = mapping_1_2(dom_log_1_2) - - dom_log_2 = Square('dom2',bounds1=(r_min, r_max), bounds2=(np.pi/2, np.pi)) - mapping_2 = PolarMapping('M2',2, c1= -h, c2= h, rmin = 0., rmax=1.) - domain_2 = mapping_2(dom_log_2) - - dom_log_2_1 = Square('dom2_1',bounds1=(r_min, r_max), bounds2=(np.pi/2, np.pi*3/4)) - mapping_2_1 = PolarMapping('M2_1',2, c1= -h, c2= h, rmin = 0., rmax=1.) - domain_2_1 = mapping_2_1(dom_log_2_1) - - dom_log_2_2 = Square('dom2_2',bounds1=(r_min, r_max), bounds2=(np.pi*3/4, np.pi)) - mapping_2_2 = PolarMapping('M2_2',2, c1= -h, c2= h, rmin = 0., rmax=1.) - domain_2_2 = mapping_2_2(dom_log_2_2) + hr = dr / 2 + cr = h + (r_max + r_min) / 2 + + dom_log_1 = Square( + 'dom1', bounds1=( + r_min, r_max), bounds2=( + 0, np.pi / 2)) + mapping_1 = PolarMapping('M1', 2, c1=h, c2=h, rmin=0., rmax=1.) + domain_1 = mapping_1(dom_log_1) + + dom_log_1_1 = Square( + 'dom1_1', bounds1=( + r_min, r_max), bounds2=( + 0, np.pi / 4)) + mapping_1_1 = PolarMapping('M1_1', 2, c1=h, c2=h, rmin=0., rmax=1.) + domain_1_1 = mapping_1_1(dom_log_1_1) + + dom_log_1_2 = Square( + 'dom1_2', bounds1=( + r_min, r_max), bounds2=( + np.pi / 4, np.pi / 2)) + mapping_1_2 = PolarMapping('M1_2', 2, c1=h, c2=h, rmin=0., rmax=1.) + domain_1_2 = mapping_1_2(dom_log_1_2) + + dom_log_2 = Square( + 'dom2', bounds1=( + r_min, r_max), bounds2=( + np.pi / 2, np.pi)) + mapping_2 = PolarMapping('M2', 2, c1=-h, c2=h, rmin=0., rmax=1.) + domain_2 = mapping_2(dom_log_2) + + dom_log_2_1 = Square( + 'dom2_1', bounds1=( + r_min, r_max), bounds2=( + np.pi / 2, np.pi * 3 / 4)) + mapping_2_1 = PolarMapping('M2_1', 2, c1=-h, c2=h, rmin=0., rmax=1.) + domain_2_1 = mapping_2_1(dom_log_2_1) + + dom_log_2_2 = Square( + 'dom2_2', bounds1=( + r_min, r_max), bounds2=( + np.pi * 3 / 4, np.pi)) + mapping_2_2 = PolarMapping('M2_2', 2, c1=-h, c2=h, rmin=0., rmax=1.) + domain_2_2 = mapping_2_2(dom_log_2_2) # for debug: - dom_log_10 = Square('dom10',bounds1=(r_min, r_max), bounds2=(np.pi/2, np.pi)) - mapping_10 = PolarMapping('M10',2, c1= h, c2= h, rmin = 0., rmax=1.) - domain_10 = mapping_10(dom_log_10) - - dom_log_3 = Square('dom3',bounds1=(r_min, r_max), bounds2=(np.pi, np.pi*3/2)) - mapping_3 = PolarMapping('M3',2, c1= -h, c2= 0, rmin = 0., rmax=1.) - domain_3 = mapping_3(dom_log_3) - - dom_log_3_1 = Square('dom3_1',bounds1=(r_min, r_max), bounds2=(np.pi, np.pi*5/4)) - mapping_3_1 = PolarMapping('M3_1',2, c1= -h, c2= 0, rmin = 0., rmax=1.) - domain_3_1 = mapping_3_1(dom_log_3_1) - - dom_log_3_2 = Square('dom3_2',bounds1=(r_min, r_max), bounds2=(np.pi*5/4, np.pi*3/2)) - mapping_3_2 = PolarMapping('M3_2',2, c1= -h, c2= 0, rmin = 0., rmax=1.) - domain_3_2 = mapping_3_2(dom_log_3_2) - - dom_log_4 = Square('dom4',bounds1=(r_min, r_max), bounds2=(np.pi*3/2, np.pi*2)) - mapping_4 = PolarMapping('M4',2, c1= h, c2= 0, rmin = 0., rmax=1.) - domain_4 = mapping_4(dom_log_4) - - dom_log_4_1 = Square('dom4_1',bounds1=(r_min, r_max), bounds2=(np.pi*3/2, np.pi*7/4)) - mapping_4_1 = PolarMapping('M4_1',2, c1= h, c2= 0, rmin = 0., rmax=1.) - domain_4_1 = mapping_4_1(dom_log_4_1) - - dom_log_4_2 = Square('dom4_2',bounds1=(r_min, r_max), bounds2=(np.pi*7/4, np.pi*2)) - mapping_4_2 = PolarMapping('M4_2',2, c1= h, c2= 0, rmin = 0., rmax=1.) - domain_4_2 = mapping_4_2(dom_log_4_2) - - dom_log_5 = Square('dom5',bounds1=(-hr,hr) , bounds2=(-h/2, h/2)) - mapping_5 = get_2D_rotation_mapping('M5', c1=h/2, c2=cr , alpha=np.pi/2) - domain_5 = mapping_5(dom_log_5) - - dom_log_6 = Square('dom6',bounds1=(-hr,hr) , bounds2=(-h/2, h/2)) - mapping_6 = flip_axis('M6', c1=-h/2, c2=cr) - domain_6 = mapping_6(dom_log_6) - - dom_log_7 = Square('dom7',bounds1=(-hr, hr), bounds2=(-h/2, h/2)) - mapping_7 = get_2D_rotation_mapping('M7', c1=-cr, c2=h/2 , alpha=np.pi) - domain_7 = mapping_7(dom_log_7) + dom_log_10 = Square( + 'dom10', bounds1=( + r_min, r_max), bounds2=( + np.pi / 2, np.pi)) + mapping_10 = PolarMapping('M10', 2, c1=h, c2=h, rmin=0., rmax=1.) + domain_10 = mapping_10(dom_log_10) + + dom_log_3 = Square( + 'dom3', bounds1=( + r_min, r_max), bounds2=( + np.pi, np.pi * 3 / 2)) + mapping_3 = PolarMapping('M3', 2, c1=-h, c2=0, rmin=0., rmax=1.) + domain_3 = mapping_3(dom_log_3) + + dom_log_3_1 = Square( + 'dom3_1', bounds1=( + r_min, r_max), bounds2=( + np.pi, np.pi * 5 / 4)) + mapping_3_1 = PolarMapping('M3_1', 2, c1=-h, c2=0, rmin=0., rmax=1.) + domain_3_1 = mapping_3_1(dom_log_3_1) + + dom_log_3_2 = Square( + 'dom3_2', bounds1=( + r_min, r_max), bounds2=( + np.pi * 5 / 4, np.pi * 3 / 2)) + mapping_3_2 = PolarMapping('M3_2', 2, c1=-h, c2=0, rmin=0., rmax=1.) + domain_3_2 = mapping_3_2(dom_log_3_2) + + dom_log_4 = Square( + 'dom4', bounds1=( + r_min, r_max), bounds2=( + np.pi * 3 / 2, np.pi * 2)) + mapping_4 = PolarMapping('M4', 2, c1=h, c2=0, rmin=0., rmax=1.) + domain_4 = mapping_4(dom_log_4) + + dom_log_4_1 = Square( + 'dom4_1', bounds1=( + r_min, r_max), bounds2=( + np.pi * 3 / 2, np.pi * 7 / 4)) + mapping_4_1 = PolarMapping('M4_1', 2, c1=h, c2=0, rmin=0., rmax=1.) + domain_4_1 = mapping_4_1(dom_log_4_1) + + dom_log_4_2 = Square( + 'dom4_2', bounds1=( + r_min, r_max), bounds2=( + np.pi * 7 / 4, np.pi * 2)) + mapping_4_2 = PolarMapping('M4_2', 2, c1=h, c2=0, rmin=0., rmax=1.) + domain_4_2 = mapping_4_2(dom_log_4_2) + + dom_log_5 = Square('dom5', bounds1=(-hr, hr), bounds2=(-h / 2, h / 2)) + mapping_5 = get_2D_rotation_mapping( + 'M5', c1=h / 2, c2=cr, alpha=np.pi / 2) + domain_5 = mapping_5(dom_log_5) + + dom_log_6 = Square('dom6', bounds1=(-hr, hr), bounds2=(-h / 2, h / 2)) + mapping_6 = flip_axis('M6', c1=-h / 2, c2=cr) + domain_6 = mapping_6(dom_log_6) + + dom_log_7 = Square('dom7', bounds1=(-hr, hr), bounds2=(-h / 2, h / 2)) + mapping_7 = get_2D_rotation_mapping( + 'M7', c1=-cr, c2=h / 2, alpha=np.pi) + domain_7 = mapping_7(dom_log_7) # dom_log_8 = Square('dom8',bounds1=(-hr, hr), bounds2=(-h/2, h/2)) # mapping_8 = get_2D_rotation_mapping('M8', c1=-cr, c2=-h/2 , alpha=np.pi) # domain_8 = mapping_8(dom_log_8) - dom_log_9 = Square('dom9',bounds1=(-hr,hr) , bounds2=(-h, h)) - mapping_9 = get_2D_rotation_mapping('M9', c1=0, c2=h-cr , alpha=np.pi*3/2) - domain_9 = mapping_9(dom_log_9) - - dom_log_9_1 = Square('dom9_1',bounds1=(-hr,hr) , bounds2=(-h, 0)) - mapping_9_1 = get_2D_rotation_mapping('M9_1', c1=0, c2=h-cr , alpha=np.pi*3/2) - domain_9_1 = mapping_9_1(dom_log_9_1) + dom_log_9 = Square('dom9', bounds1=(-hr, hr), bounds2=(-h, h)) + mapping_9 = get_2D_rotation_mapping( + 'M9', c1=0, c2=h - cr, alpha=np.pi * 3 / 2) + domain_9 = mapping_9(dom_log_9) - dom_log_9_2 = Square('dom9_2',bounds1=(-hr,hr) , bounds2=(0, h)) - mapping_9_2 = get_2D_rotation_mapping('M9_2', c1=0, c2=h-cr , alpha=np.pi*3/2) - domain_9_2 = mapping_9_2(dom_log_9_2) + dom_log_9_1 = Square('dom9_1', bounds1=(-hr, hr), bounds2=(-h, 0)) + mapping_9_1 = get_2D_rotation_mapping( + 'M9_1', c1=0, c2=h - cr, alpha=np.pi * 3 / 2) + domain_9_1 = mapping_9_1(dom_log_9_1) + dom_log_9_2 = Square('dom9_2', bounds1=(-hr, hr), bounds2=(0, h)) + mapping_9_2 = get_2D_rotation_mapping( + 'M9_2', c1=0, c2=h - cr, alpha=np.pi * 3 / 2) + domain_9_2 = mapping_9_2(dom_log_9_2) # dom_log_10 = Square('dom10',bounds1=(-hr,hr) , bounds2=(-h/2, h/2)) # mapping_10 = get_2D_rotation_mapping('M10', c1=h/2, c2=h-cr , alpha=np.pi*3/2) @@ -382,34 +563,91 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): # mapping_11 = get_2D_rotation_mapping('M11', c1=cr, c2=-h/2 , alpha=0) # domain_11 = mapping_11(dom_log_11) - dom_log_12 = Square('dom12',bounds1=(-hr, hr), bounds2=(-h/2, h/2)) + dom_log_12 = Square('dom12', bounds1=(-hr, hr), + bounds2=(-h / 2, h / 2)) # mapping_12 = get_2D_rotation_mapping('M12', c1=cr, c2=h/2 , alpha=0) - mapping_12 = AffineMapping('M12', 2, c1=cr, c2=h/2, a11=1, a22=-1, a21=0, a12=0) - domain_12 = mapping_12(dom_log_12) - - dom_log_13 = Square('dom13',bounds1=(np.pi*3/2, np.pi*2), bounds2=(r_min, r_max)) - mapping_13 = TransposedPolarMapping('M13',2, c1= -r_min-h, c2= r_min+h, rmin = 0., rmax=1.) - domain_13 = mapping_13(dom_log_13) - - dom_log_13_1 = Square('dom13_1',bounds1=(np.pi*3/2, np.pi*7/4), bounds2=(r_min, r_max)) - mapping_13_1 = TransposedPolarMapping('M13_1',2, c1= -r_min-h, c2= r_min+h, rmin = 0., rmax=1.) - domain_13_1 = mapping_13_1(dom_log_13_1) - - dom_log_13_2 = Square('dom13_2',bounds1=(np.pi*7/4, np.pi*2), bounds2=(r_min, r_max)) - mapping_13_2 = TransposedPolarMapping('M13_2',2, c1= -r_min-h, c2= r_min+h, rmin = 0., rmax=1.) - domain_13_2 = mapping_13_2(dom_log_13_2) - - dom_log_14 = Square('dom14',bounds1=(np.pi, np.pi*3/2), bounds2=(r_min, r_max)) - mapping_14 = TransposedPolarMapping('M14',2, c1= r_min+h, c2= r_min+h, rmin = 0., rmax=1.) - domain_14 = mapping_14(dom_log_14) - - dom_log_14_1 = Square('dom14_1',bounds1=(np.pi, np.pi*5/4), bounds2=(r_min, r_max)) # STOP ICI: check domain - mapping_14_1 = TransposedPolarMapping('M14_1',2, c1= r_min+h, c2= r_min+h, rmin = 0., rmax=1.) - domain_14_1 = mapping_14_1(dom_log_14_1) - - dom_log_14_2 = Square('dom14_2',bounds1=(np.pi*5/4, np.pi*3/2), bounds2=(r_min, r_max)) - mapping_14_2 = TransposedPolarMapping('M14_2',2, c1= r_min+h, c2= r_min+h, rmin = 0., rmax=1.) - domain_14_2 = mapping_14_2(dom_log_14_2) + mapping_12 = AffineMapping( + 'M12', + 2, + c1=cr, + c2=h / 2, + a11=1, + a22=-1, + a21=0, + a12=0) + domain_12 = mapping_12(dom_log_12) + + dom_log_13 = Square( + 'dom13', + bounds1=( + np.pi * 3 / 2, + np.pi * 2), + bounds2=( + r_min, + r_max)) + mapping_13 = TransposedPolarMapping( + 'M13', 2, c1=-r_min - h, c2=r_min + h, rmin=0., rmax=1.) + domain_13 = mapping_13(dom_log_13) + + dom_log_13_1 = Square( + 'dom13_1', + bounds1=( + np.pi * 3 / 2, + np.pi * 7 / 4), + bounds2=( + r_min, + r_max)) + mapping_13_1 = TransposedPolarMapping( + 'M13_1', 2, c1=-r_min - h, c2=r_min + h, rmin=0., rmax=1.) + domain_13_1 = mapping_13_1(dom_log_13_1) + + dom_log_13_2 = Square( + 'dom13_2', + bounds1=( + np.pi * 7 / 4, + np.pi * 2), + bounds2=( + r_min, + r_max)) + mapping_13_2 = TransposedPolarMapping( + 'M13_2', 2, c1=-r_min - h, c2=r_min + h, rmin=0., rmax=1.) + domain_13_2 = mapping_13_2(dom_log_13_2) + + dom_log_14 = Square( + 'dom14', + bounds1=( + np.pi, + np.pi * 3 / 2), + bounds2=( + r_min, + r_max)) + mapping_14 = TransposedPolarMapping( + 'M14', 2, c1=r_min + h, c2=r_min + h, rmin=0., rmax=1.) + domain_14 = mapping_14(dom_log_14) + + dom_log_14_1 = Square( + 'dom14_1', + bounds1=( + np.pi, + np.pi * 5 / 4), + bounds2=( + r_min, + r_max)) # STOP ICI: check domain + mapping_14_1 = TransposedPolarMapping( + 'M14_1', 2, c1=r_min + h, c2=r_min + h, rmin=0., rmax=1.) + domain_14_1 = mapping_14_1(dom_log_14_1) + + dom_log_14_2 = Square( + 'dom14_2', + bounds1=( + np.pi * 5 / 4, + np.pi * 3 / 2), + bounds2=( + r_min, + r_max)) + mapping_14_2 = TransposedPolarMapping( + 'M14_2', 2, c1=r_min + h, c2=r_min + h, rmin=0., rmax=1.) + domain_14_2 = mapping_14_2(dom_log_14_2) # dom_log_15 = Square('dom15', bounds1=(-r_min-h, r_min+h), bounds2=(0, h)) # mapping_15 = IdentityMapping('M15', 2) @@ -417,79 +655,79 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): if domain_name == 'pretzel': patches = ([ - domain_1, - domain_2, - domain_3, - domain_4, - domain_5, - domain_6, - domain_7, - domain_9, - domain_12, - domain_13, - domain_14, - ]) + domain_1, + domain_2, + domain_3, + domain_4, + domain_5, + domain_6, + domain_7, + domain_9, + domain_12, + domain_13, + domain_14, + ]) interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], - [domain_6.get_boundary(axis=1, ext=-1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], - [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], + [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], + [domain_6.get_boundary(axis=1, ext=-1), domain_2.get_boundary(axis=1, ext=-1), 1], + [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], + [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], + [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], + [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], [domain_12.get_boundary(axis=1, ext=-1), domain_1.get_boundary(axis=1, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=1), 1], + [domain_6.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=1), 1], [domain_7.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=-1), 1], [domain_5.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=-1), 1], - [domain_12.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=+1),1], - ] + [domain_12.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=+1), 1], + ] elif domain_name == 'pretzel_f': patches = ([ - domain_1_1, - domain_1_2, - domain_2_1, - domain_2_2, - domain_3_1, - domain_3_2, - domain_4_1, - domain_4_2, - domain_5, - domain_6, - domain_7, - domain_9_1, - domain_9_2, - domain_12, - domain_13_1, - domain_13_2, - domain_14_1, - domain_14_2, - ]) + domain_1_1, + domain_1_2, + domain_2_1, + domain_2_2, + domain_3_1, + domain_3_2, + domain_4_1, + domain_4_2, + domain_5, + domain_6, + domain_7, + domain_9_1, + domain_9_2, + domain_12, + domain_13_1, + domain_13_2, + domain_14_1, + domain_14_2, + ]) interfaces = [ [domain_1_1.get_boundary(axis=1, ext=+1), domain_1_2.get_boundary(axis=1, ext=-1), 1], - [domain_1_2.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], - [domain_6.get_boundary(axis=1, ext=-1), domain_2_1.get_boundary(axis=1, ext=-1), 1], - [domain_2_1.get_boundary(axis=1, ext=+1), domain_2_2.get_boundary(axis=1, ext=-1), 1], - [domain_2_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3_1.get_boundary(axis=1, ext=-1), 1], - [domain_3_1.get_boundary(axis=1, ext=+1), domain_3_2.get_boundary(axis=1, ext=-1), 1], - [domain_3_2.get_boundary(axis=1, ext=+1), domain_9_1.get_boundary(axis=1, ext=-1), 1], - [domain_9_1.get_boundary(axis=1, ext=+1), domain_9_2.get_boundary(axis=1, ext=-1), 1], - [domain_9_2.get_boundary(axis=1, ext=+1), domain_4_1.get_boundary(axis=1, ext=-1), 1], - [domain_4_1.get_boundary(axis=1, ext=+1), domain_4_2.get_boundary(axis=1, ext=-1), 1], - [domain_4_2.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], + [domain_1_2.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], + [domain_6.get_boundary(axis=1, ext=-1), domain_2_1.get_boundary(axis=1, ext=-1), 1], + [domain_2_1.get_boundary(axis=1, ext=+1), domain_2_2.get_boundary(axis=1, ext=-1), 1], + [domain_2_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], + [domain_7.get_boundary(axis=1, ext=+1), domain_3_1.get_boundary(axis=1, ext=-1), 1], + [domain_3_1.get_boundary(axis=1, ext=+1), domain_3_2.get_boundary(axis=1, ext=-1), 1], + [domain_3_2.get_boundary(axis=1, ext=+1), domain_9_1.get_boundary(axis=1, ext=-1), 1], + [domain_9_1.get_boundary(axis=1, ext=+1), domain_9_2.get_boundary(axis=1, ext=-1), 1], + [domain_9_2.get_boundary(axis=1, ext=+1), domain_4_1.get_boundary(axis=1, ext=-1), 1], + [domain_4_1.get_boundary(axis=1, ext=+1), domain_4_2.get_boundary(axis=1, ext=-1), 1], + [domain_4_2.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], [domain_12.get_boundary(axis=1, ext=-1), domain_1_1.get_boundary(axis=1, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=-1), domain_13_2.get_boundary(axis=0, ext=1), 1], - [domain_13_2.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=1), 1], + [domain_6.get_boundary(axis=0, ext=-1), domain_13_2.get_boundary(axis=0, ext=1), 1], + [domain_13_2.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=1), 1], [domain_7.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=-1), 1], [domain_5.get_boundary(axis=0, ext=-1), domain_14_1.get_boundary(axis=0, ext=-1), 1], [domain_14_1.get_boundary(axis=0, ext=+1), domain_14_2.get_boundary(axis=0, ext=-1), 1], - [domain_12.get_boundary(axis=0, ext=-1), domain_14_2.get_boundary(axis=0, ext=+1),1], - ] + [domain_12.get_boundary(axis=0, ext=-1), domain_14_2.get_boundary(axis=0, ext=+1), 1], + ] # reste: 13 et 14 @@ -497,121 +735,146 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): # only the annulus part of the pretzel (not the inner arcs) patches = ([ - domain_1, - domain_5, - domain_6, - domain_2, - domain_7, - domain_3, - domain_9, - domain_4, - domain_12, - ]) + domain_1, + domain_5, + domain_6, + domain_2, + domain_7, + domain_3, + domain_9, + domain_4, + domain_12, + ]) interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1),1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1),1], - [domain_6.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1], - [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1),1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1),1], - [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1),1], - [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=-1),1], - [domain_12.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1),1], - ] + [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], + [domain_6.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], + [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], + [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], + [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], + [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=-1), 1], + [domain_12.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1), 1], + ] elif domain_name == 'pretzel_debug': patches = ([ - domain_1, - domain_10, - ]) + domain_1, + domain_10, + ]) - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_10.get_boundary(axis=1, ext=-1),1], - ] + interfaces = [[domain_1.get_boundary( + axis=1, ext=+1), domain_10.get_boundary(axis=1, ext=-1), 1], ] else: raise NotImplementedError - elif domain_name == 'curved_L_shape': # Curved L-shape benchmark domain of Monique Dauge, see 2DomD in https://perso.univ-rennes1.fr/monique.dauge/core/index.html # here with 3 patches - dom_log_1 = Square('dom1',bounds1=(2, 3), bounds2=(0., np.pi/8)) - mapping_1 = PolarMapping('M1',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_1 = mapping_1(dom_log_1) - - dom_log_2 = Square('dom2',bounds1=(2, 3), bounds2=(np.pi/8, np.pi/4)) - mapping_2 = PolarMapping('M2',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_2 = mapping_2(dom_log_2) - - dom_log_3 = Square('dom3',bounds1=(1, 2), bounds2=(np.pi/8, np.pi/4)) - mapping_3 = PolarMapping('M3',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_3 = mapping_3(dom_log_3) + dom_log_1 = Square('dom1', bounds1=(2, 3), bounds2=(0., np.pi / 8)) + mapping_1 = PolarMapping('M1', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_1 = mapping_1(dom_log_1) + + dom_log_2 = Square( + 'dom2', bounds1=( + 2, 3), bounds2=( + np.pi / 8, np.pi / 4)) + mapping_2 = PolarMapping('M2', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_2 = mapping_2(dom_log_2) + + dom_log_3 = Square( + 'dom3', bounds1=( + 1, 2), bounds2=( + np.pi / 8, np.pi / 4)) + mapping_3 = PolarMapping('M3', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_3 = mapping_3(dom_log_3) patches = ([ - domain_1, - domain_2, - domain_3, - ]) + domain_1, + domain_2, + domain_3, + ]) interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1),1], + [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], ] elif domain_name in ['annulus_3', 'annulus_4']: # regular annulus if r_min is None: - r_min=0.5 # smaller radius + r_min = 0.5 # smaller radius if r_max is None: - r_max=1. # larger radius + r_max = 1. # larger radius if domain_name == 'annulus_3': - OmegaLog1 = Square('OmegaLog1',bounds1=(r_min, r_max), bounds2=(0., np.pi/2)) - mapping_1 = PolarMapping('M1',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_1 = mapping_1(OmegaLog1) - - OmegaLog2 = Square('OmegaLog2',bounds1=(r_min, r_max), bounds2=(np.pi/2, np.pi)) - mapping_2 = PolarMapping('M2',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_2 = mapping_2(OmegaLog2) - - OmegaLog3 = Square('OmegaLog3',bounds1=(r_min, r_max), bounds2=(np.pi, 2*np.pi)) - mapping_3 = PolarMapping('M3',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_3 = mapping_3(OmegaLog3) + OmegaLog1 = Square( + 'OmegaLog1', bounds1=( + r_min, r_max), bounds2=( + 0., np.pi / 2)) + mapping_1 = PolarMapping('M1', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_1 = mapping_1(OmegaLog1) + + OmegaLog2 = Square( + 'OmegaLog2', bounds1=( + r_min, r_max), bounds2=( + np.pi / 2, np.pi)) + mapping_2 = PolarMapping('M2', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_2 = mapping_2(OmegaLog2) + + OmegaLog3 = Square( + 'OmegaLog3', bounds1=( + r_min, r_max), bounds2=( + np.pi, 2 * np.pi)) + mapping_3 = PolarMapping('M3', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_3 = mapping_3(OmegaLog3) patches = [domain_1, domain_2, domain_3] interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1], - [domain_2.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1),1], + [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], + [domain_2.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1), 1], ] elif domain_name == 'annulus_4': - OmegaLog1 = Square('OmegaLog1',bounds1=(r_min, r_max), bounds2=(0., np.pi/2)) - mapping_1 = PolarMapping('M1',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_1 = mapping_1(OmegaLog1) - - OmegaLog2 = Square('OmegaLog2',bounds1=(r_min, r_max), bounds2=(np.pi/2, np.pi)) - mapping_2 = PolarMapping('M2',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_2 = mapping_2(OmegaLog2) - - OmegaLog3 = Square('OmegaLog3',bounds1=(r_min, r_max), bounds2=(np.pi, np.pi*3/2)) - mapping_3 = PolarMapping('M3',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_3 = mapping_3(OmegaLog3) - - OmegaLog4 = Square('OmegaLog4',bounds1=(r_min, r_max), bounds2=(np.pi*3/2, np.pi*2)) - mapping_4 = PolarMapping('M4',2, c1= 0., c2= 0., rmin = 0., rmax=1.) - domain_4 = mapping_4(OmegaLog4) + OmegaLog1 = Square( + 'OmegaLog1', bounds1=( + r_min, r_max), bounds2=( + 0., np.pi / 2)) + mapping_1 = PolarMapping('M1', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_1 = mapping_1(OmegaLog1) + + OmegaLog2 = Square( + 'OmegaLog2', bounds1=( + r_min, r_max), bounds2=( + np.pi / 2, np.pi)) + mapping_2 = PolarMapping('M2', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_2 = mapping_2(OmegaLog2) + + OmegaLog3 = Square( + 'OmegaLog3', bounds1=( + r_min, r_max), bounds2=( + np.pi, np.pi * 3 / 2)) + mapping_3 = PolarMapping('M3', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_3 = mapping_3(OmegaLog3) + + OmegaLog4 = Square( + 'OmegaLog4', bounds1=( + r_min, r_max), bounds2=( + np.pi * 3 / 2, np.pi * 2)) + mapping_4 = PolarMapping('M4', 2, c1=0., c2=0., rmin=0., rmax=1.) + domain_4 = mapping_4(OmegaLog4) patches = [domain_1, domain_2, domain_3, domain_4] interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1),1], - [domain_2.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1),1], - [domain_3.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1),1], - [domain_4.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1),1], + [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], + [domain_2.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], + [domain_3.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], + [domain_4.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1), 1], ] else: raise NotImplementedError @@ -629,7 +892,21 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): return domain -def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np.pi, y_min=0, y_max=np.pi, perio=(True,True), ncells=(4,4), comm=None, F_name='Identity'): +def build_multipatch_rectangle( + nb_patch_x=2, + nb_patch_y=2, + x_min=0, + x_max=np.pi, + y_min=0, + y_max=np.pi, + perio=( + True, + True), + ncells=( + 4, + 4), + comm=None, + F_name='Identity'): """ Create a 2D multipatch rectangle domain with the prescribed number of patch in each direction. (copied from Valentin's code) @@ -641,7 +918,7 @@ def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np nb_patch_y: number of patch in y direction - + x_min: x cordinate for the left boundary of the domain @@ -656,7 +933,7 @@ def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np perio: list of periodicity of the domain in each direction - + F_name: name of a (global) mapping to apply to all the patches @@ -666,29 +943,46 @@ def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np The symbolic multipatch domain """ - x_diff=x_max-x_min - y_diff=y_max-y_min - - list_Omega = [[Square('OmegaLog_'+str(i)+'_'+str(j), - bounds1 = (x_min+i/nb_patch_x*x_diff,x_min+(i+1)/nb_patch_x*x_diff), - bounds2 = (y_min+j/nb_patch_y*y_diff,y_min+(j+1)/nb_patch_y*y_diff)) for j in range(nb_patch_y)] for i in range(nb_patch_x)] + x_diff = x_max - x_min + y_diff = y_max - y_min + + list_Omega = [[Square('OmegaLog_' + + str(i) + + '_' + + str(j), bounds1=(x_min + + i / + nb_patch_x * + x_diff, x_min + + (i + + 1) / + nb_patch_x * + x_diff), bounds2=(y_min + + j / + nb_patch_y * + y_diff, y_min + + (j + + 1) / + nb_patch_y * + y_diff)) for j in range(nb_patch_y)] for i in range(nb_patch_x)] if F_name == 'Identity': - F = lambda name: IdentityMapping(name, 2) + def F(name): return IdentityMapping(name, 2) elif F_name == 'Collela': - F = lambda name: CollelaMapping2D(name, eps=0.5) + def F(name): return CollelaMapping2D(name, eps=0.5) else: raise NotImplementedError(F_name) - list_mapping = [[F('M_'+str(i)+'_'+str(j)) for j in range(nb_patch_y)] for i in range(nb_patch_x)] + list_mapping = [[F('M_' + str(i) + '_' + str(j)) + for j in range(nb_patch_y)] for i in range(nb_patch_x)] - list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range(nb_patch_y)] for i in range(nb_patch_x)] + list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range( + nb_patch_y)] for i in range(nb_patch_x)] patches = [] for i in range(nb_patch_x): patches.extend(list_domain[i]) - + # domain = union([domain_1, domain_2, domain_3, domain_4, domain_5, domain_6], name = 'domain') # patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6] @@ -696,60 +990,81 @@ def build_multipatch_rectangle(nb_patch_x = 2, nb_patch_y = 2, x_min=0, x_max=np # domain = union(flat_list, name='domain') interfaces = [] - #interfaces in x + # interfaces in x list_right_bnd = [] - list_left_bnd = [] + list_left_bnd = [] list_top_bnd = [] - list_bottom_bnd1 = [] - list_bottom_bnd2 = [] + list_bottom_bnd1 = [] + list_bottom_bnd2 = [] for j in range(nb_patch_y): - interfaces.extend([[list_domain[i][j].get_boundary(axis=0, ext=+1), list_domain[i+1][j].get_boundary(axis=0, ext=-1), 1] for i in range(nb_patch_x-1)]) - #periodic boundaries + interfaces.extend([[list_domain[i][j].get_boundary(axis=0, + ext=+1), + list_domain[i + 1][j].get_boundary(axis=0, + ext=-1), + 1] for i in range(nb_patch_x - 1)]) + # periodic boundaries if perio[0]: - interfaces.append([list_domain[nb_patch_x-1][j].get_boundary(axis=0, ext=+1), list_domain[0][j].get_boundary(axis=0, ext=-1), 1]) + interfaces.append([list_domain[nb_patch_x - + 1][j].get_boundary(axis=0, ext=+ + 1), list_domain[0][j].get_boundary(axis=0, ext=- + 1), 1]) else: - list_right_bnd.append(list_domain[nb_patch_x-1][j].get_boundary(axis=0, ext=+1)) - list_left_bnd.append(list_domain[0][j].get_boundary(axis=0, ext=-1)) - + list_right_bnd.append( + list_domain[nb_patch_x - 1][j].get_boundary(axis=0, ext=+1)) + list_left_bnd.append( + list_domain[0][j].get_boundary( + axis=0, ext=-1)) - #interfaces in y + # interfaces in y for i in range(nb_patch_x): - interfaces.extend([[list_domain[i][j].get_boundary(axis=1, ext=+1), list_domain[i][j+1].get_boundary(axis=1, ext=-1), 1] for j in range(nb_patch_y-1)]) - #periodic boundariesnb_patch_y-1 + interfaces.extend([[list_domain[i][j].get_boundary(axis=1, + ext=+1), + list_domain[i][j + 1].get_boundary(axis=1, + ext=-1), + 1] for j in range(nb_patch_y - 1)]) + # periodic boundariesnb_patch_y-1 if perio[1]: - interfaces.append([list_domain[i][nb_patch_y-1].get_boundary(axis=1, ext=+1), list_domain[i][0].get_boundary(axis=1, ext=-1), 1]) + interfaces.append([list_domain[i][nb_patch_y - + 1].get_boundary(axis=1, ext=+ + 1), list_domain[i][0].get_boundary(axis=1, ext=- + 1), 1]) else: - list_top_bnd.append(list_domain[i][nb_patch_y-1].get_boundary(axis=1, ext=+1)) - if i0: - bottom_bnd2 = union_bnd(list_bottom_bnd2) - else : + bottom_bnd1 = union_bnd(list_bottom_bnd1) + if len(list_bottom_bnd2) > 0: + bottom_bnd2 = union_bnd(list_bottom_bnd2) + else: bottom_bnd2 = None - if nb_patch_x>1 and nb_patch_y>1: + if nb_patch_x > 1 and nb_patch_y > 1: # domain = set_interfaces(domain, interfaces) domain_h = discretize(domain, ncells=ncells, comm=comm) else: domain_h = discretize(domain, ncells=ncells, periodic=perio, comm=comm) - return domain, domain_h, [right_bnd, left_bnd, top_bnd, bottom_bnd1, bottom_bnd2] - + return domain, domain_h, [right_bnd, left_bnd, + top_bnd, bottom_bnd1, bottom_bnd2] def get_ref_eigenvalues(domain_name, operator): @@ -760,36 +1075,36 @@ def get_ref_eigenvalues(domain_name, operator): assert operator in ['curl_curl', 'hodge_laplacian'] ref_sigmas = [] - if domain_name in ['square_2','square_6']: + if domain_name in ['square_2', 'square_6']: # todo if operator == 'curl_curl': ref_sigmas = [ 1, 2, 2, - ] + ] raise NotImplementedError else: ref_sigmas = [ 1, 2, 2, - ] + ] raise NotImplementedError - elif domain_name in ['annulus_3','annulus_4']: + elif domain_name in ['annulus_3', 'annulus_4']: if operator == 'curl_curl': ref_sigmas = [ 1, 2, 2, - ] + ] raise NotImplementedError else: ref_sigmas = [ 1, 2, 2, - ] + ] raise NotImplementedError elif domain_name == 'curved_L_shape': @@ -801,7 +1116,7 @@ def get_ref_eigenvalues(domain_name, operator): 0.100656015004E+02, 0.101118862307E+02, 0.124355372484E+02, - ] + ] elif operator == 'hodge_laplacian': raise NotImplementedError else: @@ -826,7 +1141,6 @@ def get_ref_eigenvalues(domain_name, operator): else: raise NotImplementedError - sigma = ref_sigmas[len(ref_sigmas)//2] + sigma = ref_sigmas[len(ref_sigmas) // 2] return sigma, ref_sigmas - diff --git a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py index 548690def..44ac67461 100644 --- a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py @@ -2,7 +2,7 @@ import numpy as np from sympde.topology import Square from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping -from sympde.topology import Boundary, Interface, Union +from sympde.topology import Boundary, Interface, Union from scipy.sparse import eye as sparse_eye from scipy.sparse import csr_matrix @@ -11,32 +11,27 @@ from scipy.sparse.linalg import inv as sp_inv from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.fem.splines import SplineSpace +from psydac.feec.multipatch.api import discretize +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.fem.splines import SplineSpace from psydac.feec.multipatch.multipatch_domain_utilities import create_domain -def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): + +def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): """ - Create a 2D multipatch square domain with the prescribed number of patch in each direction. + Create a 2D multipatch square domain with the prescribed number of patches in each direction. Parameters ---------- ncells: + |2| + _____ + |4|2| - |2| - _____ - |4|2| - - [[2, None], + [[2, None], [4, 2]] - - [[2, 2, 0, 0], - [2, 4, 0, 0], - [4, 8, 4, 2], - [4, 4, 2, 2]] - number of patch in each direction + number of patches in each direction Returns ------- @@ -44,56 +39,74 @@ def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): The symbolic multipatch domain """ ax, bx = interval_x - ay, by = interval_y + ay, by = interval_y nb_patchx, nb_patchy = np.shape(ncells) - list_Omega = [[Square('OmegaLog_'+str(i)+'_'+str(j), - bounds1 = (ax + i/nb_patchx * (bx-ax),ax + (i+1)/nb_patchx * (bx-ax)), - bounds2 = (ay + j/nb_patchy * (by-ay),ay + (j+1)/nb_patchy * (by-ay))) for j in range(nb_patchy)] for i in range(nb_patchx)] - - + list_Omega = [[Square('OmegaLog_' + str(i) + '_' + str(j), + bounds1=(ax + i / nb_patchx * (bx - ax), + ax + (i + 1) / nb_patchx * (bx - ax)), + bounds2=(ay + j / nb_patchy * (by - ay), ay + (j + 1) / nb_patchy * (by - ay))) + for j in range(nb_patchy)] for i in range(nb_patchx)] + if mapping == 'identity': - list_mapping = [[IdentityMapping('M_'+str(i)+'_'+str(j),2) for j in range(nb_patchy)] for i in range(nb_patchx)] + list_mapping = [[IdentityMapping('M_' + str(i) + '_' + str(j), 2) + for j in range(nb_patchy)] for i in range(nb_patchx)] elif mapping == 'polar': - list_mapping = [[PolarMapping('M_'+str(i)+'_'+str(j),2, c1= 0., c2= 0., rmin = 0., rmax=1.) for j in range(nb_patchy)] for i in range(nb_patchx)] + list_mapping = [ + [ + PolarMapping('M_' + str(i) + '_' + str(j), 2, + c1=0., + c2=0., + rmin=0., + rmax=1.) for j in range(nb_patchy)] for i in range(nb_patchx)] + + list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range( + nb_patchy)] for i in range(nb_patchx)] - list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range(nb_patchy)] for i in range(nb_patchx)] flat_list = [] for i in range(nb_patchx): for j in range(nb_patchy): - if ncells[i, j] != None: + if ncells[i, j] is not None: flat_list.append(list_domain[i][j]) domains = flat_list interfaces = [] - #interfaces in y + # interfaces in y for j in range(nb_patchy): - interfaces.extend([[list_domain[i][j].get_boundary(axis=0, ext=+1), list_domain[i+1][j].get_boundary(axis=0, ext=-1), 1] for i in range(nb_patchx-1) if ncells[i][j] != None and ncells[i+1][j] != None]) + interfaces.extend([[list_domain[i][j].get_boundary(axis=0, ext=+1), + list_domain[i +1][j].get_boundary(axis=0, ext=-1), 1] + for i in range(nb_patchx -1) if ncells[i][j] is not None and ncells[i +1][j] is not None]) - #interfaces in x + # interfaces in x for i in range(nb_patchx): - interfaces.extend([[list_domain[i][j].get_boundary(axis=1, ext=+1), list_domain[i][j+1].get_boundary(axis=1, ext=-1), 1] for j in range(nb_patchy-1) if ncells[i][j] != None and ncells[i][j+1] != None]) + interfaces.extend([[list_domain[i][j].get_boundary(axis=1, ext=+ + 1), list_domain[i][j + + 1].get_boundary(axis=1, ext=- + 1), 1] for j in range(nb_patchy - + 1) if ncells[i][j] is not None and ncells[i][j + + 1] is not None]) domain = create_domain(domains, interfaces, name='domain') return domain + def get_L_shape_ncells(patches, n0): - ncells = np.zeros((patches, patches), dtype = object) + ncells = np.zeros((patches, patches), dtype=object) - pm = int(patches/2) - assert patches/2 == pm + pm = int(patches / 2) + assert patches / 2 == pm for i in range(pm): for j in range(pm): - ncells[i,j] = None + ncells[i, j] = None for i in range(pm, patches): for j in range(patches): - exp = 1+patches - (abs(i-pm)+abs(j-pm)) - ncells[i,j] = n0**exp - ncells[j,i] = n0**exp + exp = 1 + patches - (abs(i - pm) + abs(j - pm)) + ncells[i, j] = n0**exp + ncells[j, i] = n0**exp - return ncells \ No newline at end of file + return ncells diff --git a/psydac/feec/multipatch/operators.py b/psydac/feec/multipatch/operators.py index 618b7b2ce..87368a1eb 100644 --- a/psydac/feec/multipatch/operators.py +++ b/psydac/feec/multipatch/operators.py @@ -2,6 +2,7 @@ # Conga operators on piecewise (broken) de Rham sequences +from sympy import Tuple from mpi4py import MPI import os import numpy as np @@ -10,30 +11,31 @@ from scipy.sparse import kron, block_diag from scipy.sparse.linalg import inv -from sympde.topology import Boundary, Interface, Union -from sympde.topology import element_of, elements_of -from sympde.topology.space import ScalarFunction -from sympde.calculus import grad, dot, inner, rot, div -from sympde.calculus import laplace, bracket, convect -from sympde.calculus import jump, avg, Dn, minus, plus +from sympde.topology import Boundary, Interface, Union +from sympde.topology import element_of, elements_of +from sympde.topology.space import ScalarFunction +from sympde.calculus import grad, dot, inner, rot, div +from sympde.calculus import laplace, bracket, convect +from sympde.calculus import jump, avg, Dn, minus, plus from sympde.expr.expr import LinearForm, BilinearForm from sympde.expr.expr import integral -from psydac.core.bsplines import collocation_matrix, histopolation_matrix +from psydac.core.bsplines import collocation_matrix, histopolation_matrix -from psydac.api.discretization import discretize -from psydac.api.essential_bc import apply_essential_bc_stencil -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator -from psydac.linalg.stencil import StencilVector, StencilMatrix, StencilInterfaceMatrix -from psydac.linalg.solvers import inverse -from psydac.fem.basic import FemField +from psydac.api.discretization import discretize +from psydac.api.essential_bc import apply_essential_bc_stencil +from psydac.api.settings import PSYDAC_BACKENDS +from psydac.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator +from psydac.linalg.stencil import StencilVector, StencilMatrix, StencilInterfaceMatrix +from psydac.linalg.solvers import inverse +from psydac.fem.basic import FemField -from psydac.feec.global_projectors import Projector_H1, Projector_Hcurl, Projector_L2 -from psydac.feec.derivatives import Gradient_2D, ScalarCurl_2D +from psydac.feec.global_projectors import Projector_H1, Projector_Hcurl, Projector_L2 +from psydac.feec.derivatives import Gradient_2D, ScalarCurl_2D from psydac.feec.multipatch.fem_linear_operators import FemLinearOperator + def get_patch_index_from_face(domain, face): """ Return the patch index of subdomain/boundary @@ -58,13 +60,15 @@ def get_patch_index_from_face(domain, face): domains = domain.interior.args if isinstance(face, Interface): - raise NotImplementedError("This face is an interface, it has several indices -- I am a machine, I cannot choose. Help.") + raise NotImplementedError( + "This face is an interface, it has several indices -- I am a machine, I cannot choose. Help.") elif isinstance(face, Boundary): i = domains.index(face.domain) else: i = domains.index(face) return i + def get_interface_from_corners(corner1, corner2, domain): """ Return the interface between two corners from two different patches that correspond to a single (physical) vertex. @@ -103,14 +107,16 @@ def get_interface_from_corners(corner1, corner2, domain): new_interface = [] for i in interface: - if i.minus in bd1+bd2: - if i.plus in bd2+bd1: + if i.minus in bd1 + bd2: + if i.plus in bd2 + bd1: new_interface.append(i) if len(new_interface) == 1: return new_interface[0] - if len(new_interface)>1: - raise ValueError('found more than one interface for the corners {} and {}'.format(corner1, corner2)) + if len(new_interface) > 1: + raise ValueError( + 'found more than one interface for the corners {} and {}'.format( + corner1, corner2)) return None @@ -144,52 +150,57 @@ def get_row_col_index(corner1, corner2, interface, axis, V1, V2): The StencilInterfaceMatrix index of the corner, it has the form (i1, i2, k1, k2) in 2D, where (i1, i2) identifies the row and (k1, k2) the diagonal. """ - start = V1.vector_space.starts - end = V1.vector_space.ends - degree = V2.degree + start = V1.vector_space.starts + end = V1.vector_space.ends + degree = V2.degree start_end = (start, end) - row = [None]*len(start) - col = [0]*len(start) + row = [None] * len(start) + col = [0] * len(start) assert corner1.boundaries[0].axis == corner2.boundaries[0].axis for bd in corner1.boundaries: - row[bd.axis] = start_end[(bd.ext+1)//2][bd.axis] + row[bd.axis] = start_end[(bd.ext + 1) // 2][bd.axis] if interface is None and corner1.domain != corner2.domain: - bd = [i for i in corner1.boundaries if i.axis==axis][0] - if bd.ext == 1:row[bd.axis] = degree[bd.axis] + bd = [i for i in corner1.boundaries if i.axis == axis][0] + if bd.ext == 1: + row[bd.axis] = degree[bd.axis] if interface is None: - return row+col + return row + col axis = interface.axis if interface.minus.domain == corner1.domain: - if interface.minus.ext == -1:row[axis] = 0 - else:row[axis] = degree[axis] + if interface.minus.ext == -1: + row[axis] = 0 + else: + row[axis] = degree[axis] else: - if interface.plus.ext == -1:row[axis] = 0 - else:row[axis] = degree[axis] + if interface.plus.ext == -1: + row[axis] = 0 + else: + row[axis] = degree[axis] if interface.minus.ext == interface.plus.ext: pass elif interface.minus.domain == corner1.domain: if interface.minus.ext == -1: - col[axis] = degree[axis] + col[axis] = degree[axis] else: - col[axis] = -degree[axis] + col[axis] = -degree[axis] else: if interface.plus.ext == -1: - col[axis] = degree[axis] + col[axis] = degree[axis] else: - col[axis] = -degree[axis] + col[axis] = -degree[axis] - return row+col + return row + col -#=============================================================================== +# =============================================================================== def allocate_interface_matrix(corners, test_space, trial_space): """ Allocate the interface matrix for a vertex shared by two patches @@ -214,41 +225,52 @@ def allocate_interface_matrix(corners, test_space, trial_space): flips = [] k = 0 - while k primal basis @@ -856,7 +935,7 @@ class HodgeOperator( FemLinearOperator ): The discrete domain of the projector metric : - the metric of the de Rham complex + the metric of the de Rham complex backend_language: The backend used to accelerate the code @@ -871,11 +950,18 @@ class HodgeOperator( FemLinearOperator ): ----- Either we use a storage, or these matrices are only computed on demand # todo: we compute the sparse matrix when to_sparse_matrix is called -- but never the stencil matrix (should be fixed...) - We only support the identity metric, this implies that the dual Hodge is the inverse of the primal one. + We only support the identity metric, this implies that the dual Hodge is the inverse of the primal one. # todo: allow for non-identity metrics """ - - def __init__( self, Vh, domain_h, metric='identity', backend_language='python', load_dir=None, load_space_index=''): + + def __init__( + self, + Vh, + domain_h, + metric='identity', + backend_language='python', + load_dir=None, + load_space_index=''): FemLinearOperator.__init__(self, fem_domain=Vh) self._domain_h = domain_h @@ -889,25 +975,37 @@ def __init__( self, Vh, domain_h, metric='identity', backend_language='python', if not os.path.exists(load_dir): os.makedirs(load_dir) assert str(load_space_index) in ['0', '1', '2', '3'] - primal_Hodge_storage_fn = load_dir+'/H{}_m.npz'.format(load_space_index) - dual_Hodge_storage_fn = load_dir+'/dH{}_m.npz'.format(load_space_index) + primal_Hodge_storage_fn = load_dir + \ + '/H{}_m.npz'.format(load_space_index) + dual_Hodge_storage_fn = load_dir + \ + '/dH{}_m.npz'.format(load_space_index) primal_Hodge_is_stored = os.path.exists(primal_Hodge_storage_fn) dual_Hodge_is_stored = os.path.exists(dual_Hodge_storage_fn) if dual_Hodge_is_stored: assert primal_Hodge_is_stored - print(" ... loading dual Hodge sparse matrix from "+dual_Hodge_storage_fn) - self._dual_Hodge_sparse_matrix = load_npz(dual_Hodge_storage_fn) - print("[HodgeOperator] loading primal Hodge sparse matrix from "+primal_Hodge_storage_fn) + print( + " ... loading dual Hodge sparse matrix from " + + dual_Hodge_storage_fn) + self._dual_Hodge_sparse_matrix = load_npz( + dual_Hodge_storage_fn) + print( + "[HodgeOperator] loading primal Hodge sparse matrix from " + + primal_Hodge_storage_fn) self._sparse_matrix = load_npz(primal_Hodge_storage_fn) else: assert not primal_Hodge_is_stored - print("[HodgeOperator] assembling both sparse matrices for storage...") + print( + "[HodgeOperator] assembling both sparse matrices for storage...") self.assemble_primal_Hodge_matrix() - print("[HodgeOperator] storing primal Hodge sparse matrix in "+primal_Hodge_storage_fn) + print( + "[HodgeOperator] storing primal Hodge sparse matrix in " + + primal_Hodge_storage_fn) save_npz(primal_Hodge_storage_fn, self._sparse_matrix) self.assemble_dual_Hodge_matrix() - print("[HodgeOperator] storing dual Hodge sparse matrix in "+dual_Hodge_storage_fn) + print( + "[HodgeOperator] storing dual Hodge sparse matrix in " + + dual_Hodge_storage_fn) save_npz(dual_Hodge_storage_fn, self._dual_Hodge_sparse_matrix) else: # matrices are not stored, we will probably compute them later @@ -942,12 +1040,13 @@ def assemble_primal_Hodge_matrix(self): u, v = elements_of(V, names='u, v') if isinstance(u, ScalarFunction): - expr = u*v + expr = u * v else: - expr = dot(u,v) + expr = dot(u, v) - a = BilinearForm((u,v), integral(domain, expr)) - ah = discretize(a, self._domain_h, [Vh, Vh], backend=PSYDAC_BACKENDS[self._backend_language]) + a = BilinearForm((u, v), integral(domain, expr)) + ah = discretize(a, self._domain_h, [ + Vh, Vh], backend=PSYDAC_BACKENDS[self._backend_language]) self._matrix = ah.assemble() # Mass matrix in stencil format self._sparse_matrix = self._matrix.tosparse() @@ -974,7 +1073,7 @@ def assemble_dual_Hodge_matrix(self): inv_M_blocks = [] for i in range(nrows): - Mii = M[i,i].tosparse() + Mii = M[i, i].tosparse() inv_Mii = inv(Mii.tocsc()) inv_Mii.eliminate_zeros() inv_M_blocks.append(inv_Mii) @@ -982,7 +1081,9 @@ def assemble_dual_Hodge_matrix(self): inv_M = block_diag(inv_M_blocks) self._dual_Hodge_sparse_matrix = inv_M -#============================================================================== +# ============================================================================== + + class BrokenGradient_2D(FemLinearOperator): def __init__(self, V0h, V1h): @@ -991,31 +1092,33 @@ def __init__(self, V0h, V1h): D0s = [Gradient_2D(V0, V1) for V0, V1 in zip(V0h.spaces, V1h.spaces)] - self._matrix = BlockLinearOperator(self.domain, self.codomain, \ - blocks={(i, i): D0i._matrix for i, D0i in enumerate(D0s)}) + self._matrix = BlockLinearOperator(self.domain, self.codomain, blocks={ + (i, i): D0i._matrix for i, D0i in enumerate(D0s)}) def transpose(self, conjugate=False): # todo (MCP): define as the dual differential operator return BrokenTransposedGradient_2D(self.fem_domain, self.fem_codomain) -#============================================================================== -class BrokenTransposedGradient_2D( FemLinearOperator ): +# ============================================================================== + - def __init__( self, V0h, V1h): +class BrokenTransposedGradient_2D(FemLinearOperator): + + def __init__(self, V0h, V1h): FemLinearOperator.__init__(self, fem_domain=V1h, fem_codomain=V0h) D0s = [Gradient_2D(V0, V1) for V0, V1 in zip(V0h.spaces, V1h.spaces)] - self._matrix = BlockLinearOperator(self.domain, self.codomain, \ - blocks={(i, i): D0i._matrix.T for i, D0i in enumerate(D0s)}) + self._matrix = BlockLinearOperator(self.domain, self.codomain, blocks={ + (i, i): D0i._matrix.T for i, D0i in enumerate(D0s)}) def transpose(self, conjugate=False): # todo (MCP): discard return BrokenGradient_2D(self.fem_codomain, self.fem_domain) -#============================================================================== +# ============================================================================== class BrokenScalarCurl_2D(FemLinearOperator): def __init__(self, V1h, V2h): @@ -1023,34 +1126,34 @@ def __init__(self, V1h, V2h): D1s = [ScalarCurl_2D(V1, V2) for V1, V2 in zip(V1h.spaces, V2h.spaces)] - self._matrix = BlockLinearOperator(self.domain, self.codomain, \ - blocks={(i, i): D1i._matrix for i, D1i in enumerate(D1s)}) + self._matrix = BlockLinearOperator(self.domain, self.codomain, blocks={ + (i, i): D1i._matrix for i, D1i in enumerate(D1s)}) def transpose(self, conjugate=False): - return BrokenTransposedScalarCurl_2D(V1h=self.fem_domain, V2h=self.fem_codomain) + return BrokenTransposedScalarCurl_2D( + V1h=self.fem_domain, V2h=self.fem_codomain) -#============================================================================== -class BrokenTransposedScalarCurl_2D( FemLinearOperator ): +# ============================================================================== +class BrokenTransposedScalarCurl_2D(FemLinearOperator): - def __init__( self, V1h, V2h): + def __init__(self, V1h, V2h): FemLinearOperator.__init__(self, fem_domain=V2h, fem_codomain=V1h) D1s = [ScalarCurl_2D(V1, V2) for V1, V2 in zip(V1h.spaces, V2h.spaces)] - self._matrix = BlockLinearOperator(self.domain, self.codomain, \ - blocks={(i, i): D1i._matrix.T for i, D1i in enumerate(D1s)}) + self._matrix = BlockLinearOperator(self.domain, self.codomain, blocks={ + (i, i): D1i._matrix.T for i, D1i in enumerate(D1s)}) def transpose(self, conjugate=False): return BrokenScalarCurl_2D(V1h=self.fem_codomain, V2h=self.fem_domain) - -#============================================================================== -from sympy import Tuple +# ============================================================================== # def multipatch_Moments_Hcurl(f, V1h, domain_h): + def ortho_proj_Hcurl(EE, V1h, domain_h, M1, backend_language='python'): """ return orthogonal projection of E on V1h, given M1 the mass matrix @@ -1058,23 +1161,30 @@ def ortho_proj_Hcurl(EE, V1h, domain_h, M1, backend_language='python'): assert isinstance(EE, Tuple) V1 = V1h.symbolic_space v = element_of(V1, name='v') - l = LinearForm(v, integral(V1.domain, dot(v,EE))) - lh = discretize(l, domain_h, V1h, backend=PSYDAC_BACKENDS[backend_language]) + l = LinearForm(v, integral(V1.domain, dot(v, EE))) + lh = discretize( + l, + domain_h, + V1h, + backend=PSYDAC_BACKENDS[backend_language]) b = lh.assemble() M1_inv = inverse(M1.mat(), 'pcg', pc='jacobi', tol=1e-10) sol_coeffs = M1_inv @ b return FemField(V1h, coeffs=sol_coeffs) -#============================================================================== +# ============================================================================== + + class Multipatch_Projector_H1: """ to apply the H1 projection (2D) on every patch """ + def __init__(self, V0h): self._P0s = [Projector_H1(V) for V in V0h.spaces] - self._V0h = V0h # multipatch Fem Space + self._V0h = V0h # multipatch Fem Space def __call__(self, funs_log): """ @@ -1082,21 +1192,24 @@ def __call__(self, funs_log): """ u0s = [P(fun) for P, fun, in zip(self._P0s, funs_log)] - u0_coeffs = BlockVector(self._V0h.vector_space, \ - blocks = [u0j.coeffs for u0j in u0s]) + u0_coeffs = BlockVector(self._V0h.vector_space, + blocks=[u0j.coeffs for u0j in u0s]) + + return FemField(self._V0h, coeffs=u0_coeffs) + +# ============================================================================== - return FemField(self._V0h, coeffs = u0_coeffs) -#============================================================================== class Multipatch_Projector_Hcurl: """ to apply the Hcurl projection (2D) on every patch """ + def __init__(self, V1h, nquads=None): self._P1s = [Projector_Hcurl(V, nquads=nquads) for V in V1h.spaces] - self._V1h = V1h # multipatch Fem Space + self._V1h = V1h # multipatch Fem Space def __call__(self, funs_log): """ @@ -1104,21 +1217,24 @@ def __call__(self, funs_log): """ E1s = [P(fun) for P, fun, in zip(self._P1s, funs_log)] - E1_coeffs = BlockVector(self._V1h.vector_space, \ - blocks = [E1j.coeffs for E1j in E1s]) + E1_coeffs = BlockVector(self._V1h.vector_space, + blocks=[E1j.coeffs for E1j in E1s]) + + return FemField(self._V1h, coeffs=E1_coeffs) + +# ============================================================================== - return FemField(self._V1h, coeffs = E1_coeffs) -#============================================================================== class Multipatch_Projector_L2: """ to apply the L2 projection (2D) on every patch """ + def __init__(self, V2h, nquads=None): self._P2s = [Projector_L2(V, nquads=nquads) for V in V2h.spaces] - self._V2h = V2h # multipatch Fem Space + self._V2h = V2h # multipatch Fem Space def __call__(self, funs_log): """ @@ -1126,8 +1242,7 @@ def __call__(self, funs_log): """ B2s = [P(fun) for P, fun, in zip(self._P2s, funs_log)] - B2_coeffs = BlockVector(self._V2h.vector_space, \ - blocks = [B2j.coeffs for B2j in B2s]) - - return FemField(self._V2h, coeffs = B2_coeffs) + B2_coeffs = BlockVector(self._V2h.vector_space, + blocks=[B2j.coeffs for B2j in B2s]) + return FemField(self._V2h, coeffs=B2_coeffs) diff --git a/psydac/feec/multipatch/plotting_utilities.py b/psydac/feec/multipatch/plotting_utilities.py index 25c6db2db..26351055b 100644 --- a/psydac/feec/multipatch/plotting_utilities.py +++ b/psydac/feec/multipatch/plotting_utilities.py @@ -1,30 +1,43 @@ # coding: utf-8 from mpi4py import MPI -from sympy import lambdify +from sympy import lambdify import numpy as np import matplotlib import matplotlib.pyplot as plt -from matplotlib import cm, colors +from matplotlib import cm, colors from mpl_toolkits import mplot3d from collections import OrderedDict from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.utilities.utils import refine_array_1d -from psydac.feec.pull_push import push_2d_h1, push_2d_hcurl, push_2d_hdiv, push_2d_l2 +from psydac.fem.basic import FemField +from psydac.utilities.utils import refine_array_1d +from psydac.feec.pull_push import push_2d_h1, push_2d_hcurl, push_2d_hdiv, push_2d_l2 + +__all__ = ( + 'is_vector_valued', + 'get_grid_vals', + 'get_grid_quad_weights', + 'get_plotting_grid', + 'get_diag_grid', + 'get_patch_knots_gridlines', + 'plot_field', + 'my_small_plot', + 'my_small_streamplot') + +# ============================================================================== -__all__ = ('is_vector_valued', 'get_grid_vals', 'get_grid_quad_weights', 'get_plotting_grid', - 'get_diag_grid', 'get_patch_knots_gridlines', 'plot_field', 'my_small_plot', 'my_small_streamplot') -#============================================================================== def is_vector_valued(u): # small utility function, only tested for FemFields in multi-patch spaces of the 2D grad-curl sequence - # todo: a proper interface returning the number of components of a general FemField would be nice + # todo: a proper interface returning the number of components of a general + # FemField would be nice return u.fields[0].space.is_product -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ + + def get_grid_vals(u, etas, mappings_list, space_kind='hcurl'): """ get the physical field values, given the logical field and the logical grid @@ -33,24 +46,26 @@ def get_grid_vals(u, etas, mappings_list, space_kind='hcurl'): :param space_kind: specifies the push-forward for the physical values """ n_patches = len(mappings_list) - vector_valued = is_vector_valued(u) if isinstance(u, FemField) else isinstance(u[0],(list, tuple)) + vector_valued = is_vector_valued(u) if isinstance( + u, FemField) else isinstance(u[0], (list, tuple)) if vector_valued: # WARNING: here we assume 2D ! - u_vals_components = [n_patches*[None], n_patches*[None]] + u_vals_components = [n_patches * [None], n_patches * [None]] else: - u_vals_components = [n_patches*[None]] + u_vals_components = [n_patches * [None]] for k in range(n_patches): eta_1, eta_2 = np.meshgrid(etas[k][0], etas[k][1], indexing='ij') for vals in u_vals_components: vals[k] = np.empty_like(eta_1) uk_field_1 = None - if isinstance(u,FemField): + if isinstance(u, FemField): if vector_valued: uk_field_0 = u[k].fields[0] uk_field_1 = u[k].fields[1] else: - uk_field_0 = u.fields[k] # it would be nice to just write u[k].fields[0] here... + # it would be nice to just write u[k].fields[0] here... + uk_field_0 = u.fields[k] else: # then u should be callable if vector_valued: @@ -63,22 +78,48 @@ def get_grid_vals(u, etas, mappings_list, space_kind='hcurl'): if space_kind == 'h1' or space_kind == 'V0': assert not vector_valued # todo (MCP): add 2d_hcurl_vector - push_field = lambda eta1, eta2: push_2d_h1(uk_field_0, eta1, eta2) + + def push_field( + eta1, eta2): return push_2d_h1( + uk_field_0, eta1, eta2) elif space_kind == 'hcurl' or space_kind == 'V1': # todo (MCP): specify 2d_hcurl_scalar in push functions - push_field = lambda eta1, eta2: push_2d_hcurl(uk_field_0, uk_field_1, eta1, eta2, mappings_list[k].get_callable_mapping()) + def push_field( + eta1, + eta2): return push_2d_hcurl( + uk_field_0, + uk_field_1, + eta1, + eta2, + mappings_list[k].get_callable_mapping()) elif space_kind == 'hdiv' or space_kind == 'V2': - push_field = lambda eta1, eta2: push_2d_hdiv(uk_field_0, uk_field_1, eta1, eta2, mappings_list[k].get_callable_mapping()) + def push_field( + eta1, + eta2): return push_2d_hdiv( + uk_field_0, + uk_field_1, + eta1, + eta2, + mappings_list[k].get_callable_mapping()) elif space_kind == 'l2': assert not vector_valued - push_field = lambda eta1, eta2: push_2d_l2(uk_field_0, eta1, eta2, mappings_list[k].get_callable_mapping()) + + def push_field( + eta1, + eta2): return push_2d_l2( + uk_field_0, + eta1, + eta2, + mappings_list[k].get_callable_mapping()) else: - raise ValueError('unknown value for space_kind = {}'.format(space_kind)) + raise ValueError( + 'unknown value for space_kind = {}'.format(space_kind)) for i, x1i in enumerate(eta_1[:, 0]): for j, x2j in enumerate(eta_2[0, :]): if vector_valued: - u_vals_components[0][k][i, j], u_vals_components[1][k][i, j] = push_field(x1i, x2j) + u_vals_components[0][k][i, j], u_vals_components[1][k][i, j] = push_field( + x1i, x2j) else: u_vals_components[0][k][i, j] = push_field(x1i, x2j) @@ -88,23 +129,26 @@ def get_grid_vals(u, etas, mappings_list, space_kind='hcurl'): else: return u_vals_components -#------------------------------------------------------------------------------ -def get_grid_quad_weights(etas, patch_logvols, mappings_list): #_obj): +# ------------------------------------------------------------------------------ + + +def get_grid_quad_weights(etas, patch_logvols, mappings_list): # _obj): # get approximate weights for a physical quadrature, namely # |J_F(xi1, xi2)| * log_weight with uniform log_weight = h1*h2 for (xi1, xi2) in the logical grid, - # in the same format as the fields value in get_grid_vals_scalar and get_grid_vals_vector + # in the same format as the fields value in get_grid_vals_scalar and + # get_grid_vals_vector n_patches = len(mappings_list) - quad_weights = n_patches*[None] + quad_weights = n_patches * [None] for k in range(n_patches): eta_1, eta_2 = np.meshgrid(etas[k][0], etas[k][1], indexing='ij') quad_weights[k] = np.empty_like(eta_1) - one_field = lambda xi1, xi2: 1 + def one_field(xi1, xi2): return 1 N0 = eta_1.shape[0] N1 = eta_1.shape[1] - log_weight = patch_logvols[k]/(N0*N1) + log_weight = patch_logvols[k] / (N0 * N1) Fk = mappings_list[k].get_callable_mapping() for i, x1i in enumerate(eta_1[:, 0]): for j, x2j in enumerate(eta_2[0, :]): @@ -113,65 +157,90 @@ def get_grid_quad_weights(etas, patch_logvols, mappings_list): #_obj): return quad_weights -#------------------------------------------------------------------------------ -def get_plotting_grid(mappings, N, centered_nodes=False, return_patch_logvols=False): +# ------------------------------------------------------------------------------ + + +def get_plotting_grid( + mappings, + N, + centered_nodes=False, + return_patch_logvols=False): # if centered_nodes == False, returns a regular grid with (N+1)x(N+1) nodes, starting and ending at patch boundaries # (useful for plotting the full patches) # if centered_nodes == True, returns the grid consisting of the NxN centers of the latter # (useful for quadratures and to avoid evaluating at patch boundaries) - # if return_patch_logvols == True, return the logival volume (area) of the patches + # if return_patch_logvols == True, return the logival volume (area) of the + # patches nb_patches = len(mappings) grid_min_coords = [np.array(D.min_coords) for D in mappings] grid_max_coords = [np.array(D.max_coords) for D in mappings] if return_patch_logvols: - patch_logvols = [(D.max_coords[1]-D.min_coords[1])*(D.max_coords[0]-D.min_coords[0]) for D in mappings] + patch_logvols = [(D.max_coords[1] - D.min_coords[1]) * + (D.max_coords[0] - D.min_coords[0]) for D in mappings] else: patch_logvols = None if centered_nodes: for k in range(nb_patches): for dim in range(2): - h_grid = (grid_max_coords[k][dim] - grid_min_coords[k][dim])/N - grid_max_coords[k][dim] -= h_grid/2 - grid_min_coords[k][dim] += h_grid/2 - N_cells = N-1 + h_grid = (grid_max_coords[k][dim] - + grid_min_coords[k][dim]) / N + grid_max_coords[k][dim] -= h_grid / 2 + grid_min_coords[k][dim] += h_grid / 2 + N_cells = N - 1 else: N_cells = N # etas = [[refine_array_1d( bounds, N ) for bounds in zip(D.min_coords, D.max_coords)] for D in mappings] - etas = [[refine_array_1d( bounds, N_cells ) for bounds in zip(grid_min_coords[k], grid_max_coords[k])] for k in range(nb_patches)] - mappings_lambda = [lambdify(M.logical_coordinates, M.expressions) for d,M in mappings.items()] + etas = [[refine_array_1d(bounds, N_cells) for bounds in zip( + grid_min_coords[k], grid_max_coords[k])] for k in range(nb_patches)] + mappings_lambda = [ + lambdify( + M.logical_coordinates, + M.expressions) for d, + M in mappings.items()] - pcoords = [np.array( [[f(e1,e2) for e2 in eta[1]] for e1 in eta[0]] ) for f,eta in zip(mappings_lambda, etas)] + pcoords = [np.array([[f(e1, e2) for e2 in eta[1]] for e1 in eta[0]]) + for f, eta in zip(mappings_lambda, etas)] - xx = [pcoords[k][:,:,0] for k in range(nb_patches)] - yy = [pcoords[k][:,:,1] for k in range(nb_patches)] + xx = [pcoords[k][:, :, 0] for k in range(nb_patches)] + yy = [pcoords[k][:, :, 1] for k in range(nb_patches)] if return_patch_logvols: return etas, xx, yy, patch_logvols else: return etas, xx, yy -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ + + def get_diag_grid(mappings, N): nb_patches = len(mappings) - etas = [[refine_array_1d( bounds, N ) for bounds in zip(D.min_coords, D.max_coords)] for D in mappings] - mappings_lambda = [lambdify(M.logical_coordinates, M.expressions) for d,M in mappings.items()] + etas = [[refine_array_1d(bounds, N) for bounds in zip( + D.min_coords, D.max_coords)] for D in mappings] + mappings_lambda = [ + lambdify( + M.logical_coordinates, + M.expressions) for d, + M in mappings.items()] - pcoords = [np.array( [[f(e1,e2) for e2 in eta[1]] for e1 in eta[0]] ) for f,eta in zip(mappings_lambda, etas)] + pcoords = [np.array([[f(e1, e2) for e2 in eta[1]] for e1 in eta[0]]) + for f, eta in zip(mappings_lambda, etas)] # pcoords = np.concatenate(pcoords, axis=1) # xx = pcoords[:,:,0] # yy = pcoords[:,:,1] - xx = [pcoords[k][:,:,0] for k in range(nb_patches)] - yy = [pcoords[k][:,:,1] for k in range(nb_patches)] + xx = [pcoords[k][:, :, 0] for k in range(nb_patches)] + yy = [pcoords[k][:, :, 1] for k in range(nb_patches)] return etas, xx, yy -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ + + def get_patch_knots_gridlines(Vh, N, mappings, plotted_patch=-1): # get gridlines for one patch grid - F = [M.get_callable_mapping() for d,M in mappings.items()] + F = [M.get_callable_mapping() for d, M in mappings.items()] if plotted_patch in range(len(mappings)): grid_x1 = Vh.spaces[plotted_patch].spaces[0].breaks[0] @@ -183,7 +252,7 @@ def get_patch_knots_gridlines(Vh, N, mappings, plotted_patch=-1): x1, x2 = np.meshgrid(x1, x2, indexing='ij') x, y = F[plotted_patch](x1, x2) - gridlines_x1 = (x[:, ::N], y[:, ::N] ) + gridlines_x1 = (x[:, ::N], y[:, ::N]) gridlines_x2 = (x[::N, :].T, y[::N, :].T) # gridlines = (gridlines_x1, gridlines_x2) else: @@ -192,20 +261,48 @@ def get_patch_knots_gridlines(Vh, N, mappings, plotted_patch=-1): return gridlines_x1, gridlines_x2 -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ + + def plot_field( - fem_field=None, stencil_coeffs=None, numpy_coeffs=None, Vh=None, domain=None, surface_plot=False, cb_min=None, cb_max=None, - plot_type='amplitude', cmap='hsv', space_kind=None, title=None, filename='dummy_plot.png', subtitles=None, N_vis=20, vf_skip=2, hide_plot=True): + fem_field=None, + stencil_coeffs=None, + numpy_coeffs=None, + Vh=None, + domain=None, + surface_plot=False, + cb_min=None, + cb_max=None, + plot_type='amplitude', + cmap='hsv', + space_kind=None, + title=None, + filename='dummy_plot.png', + subtitles=None, + N_vis=20, + vf_skip=2, + hide_plot=True): """ plot a discrete field (given as a FemField or by its coeffs in numpy or stencil format) on the given domain - :param Vh: Fem space needed if v is given by its coeffs - :param space_kind: type of the push-forward defining the physical Fem Space - :param subtitles: in case one would like to have several subplots # todo: then v should be given as a list of fields... - :param N_vis: nb of visualization points per patch (per dimension) + Parameters + ---------- + numpy_coeffs : (np.ndarray) + Coefficients of the field to plot + + Vh : TensorFemSpace + Fem space needed if v is given by its coeffs + + space_kind : (str) + type of the push-forward defining the physical Fem Space + + N_vis : (int) + nb of visualization points per patch (per dimension) """ - if not space_kind in ['h1', 'hcurl', 'l2']: - raise ValueError('invalid value for space_kind = {}'.format(space_kind)) + + if space_kind not in ['h1', 'hcurl', 'l2']: + raise ValueError( + 'invalid value for space_kind = {}'.format(space_kind)) vh = fem_field if vh is None: @@ -214,14 +311,18 @@ def plot_field( stencil_coeffs = array_to_psydac(numpy_coeffs, Vh.vector_space) vh = FemField(Vh, coeffs=stencil_coeffs) - mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) + mappings = OrderedDict([(P.logical_domain, P.mapping) + for P in domain.interior]) mappings_list = list(mappings.values()) - etas, xx, yy = get_plotting_grid(mappings, N=N_vis) - grid_vals = lambda v: get_grid_vals(v, etas, mappings_list, space_kind=space_kind) + etas, xx, yy = get_plotting_grid(mappings, N=N_vis) + + def grid_vals(v): return get_grid_vals( + v, etas, mappings_list, space_kind=space_kind) vh_vals = grid_vals(vh) if plot_type == 'vector_field' and not is_vector_valued(vh): - print("WARNING [plot_field]: vector_field plot is not possible with a scalar field, plotting the amplitude instead") + print( + "WARNING [plot_field]: vector_field plot is not possible with a scalar field, plotting the amplitude instead") plot_type = 'amplitude' if plot_type == 'vector_field': @@ -236,29 +337,34 @@ def plot_field( amp_factor=2, save_fig=filename, hide_plot=hide_plot, - dpi = 200, + dpi=200, ) else: # computing plot_vals_list: may have several elements for several plots - if plot_type=='amplitude': + if plot_type == 'amplitude': if is_vector_valued(vh): - # then vh_vals[d] contains the values of the d-component of vh (as a patch-indexed list) - plot_vals = [np.sqrt(abs(v[0])**2 + abs(v[1])**2) for v in zip(vh_vals[0],vh_vals[1])] + # then vh_vals[d] contains the values of the d-component of vh + # (as a patch-indexed list) + plot_vals = [np.sqrt(abs(v[0])**2 + abs(v[1])**2) + for v in zip(vh_vals[0], vh_vals[1])] else: - # then vh_vals just contains the values of vh (as a patch-indexed list) + # then vh_vals just contains the values of vh (as a + # patch-indexed list) plot_vals = np.abs(vh_vals) plot_vals_list = [plot_vals] - elif plot_type=='components': + elif plot_type == 'components': if is_vector_valued(vh): - # then vh_vals[d] contains the values of the d-component of vh (as a patch-indexed list) + # then vh_vals[d] contains the values of the d-component of vh + # (as a patch-indexed list) plot_vals_list = vh_vals if subtitles is None: subtitles = ['x-component', 'y-component'] else: - # then vh_vals just contains the values of vh (as a patch-indexed list) + # then vh_vals just contains the values of vh (as a + # patch-indexed list) plot_vals_list = [vh_vals] else: raise ValueError(plot_type) @@ -273,10 +379,10 @@ def plot_field( cb_min=cb_min, cb_max=cb_max, save_fig=filename, - save_vals = False, + save_vals=False, hide_plot=hide_plot, - cmap=cmap, - dpi = 300, + cmap=cmap, + dpi=300, ) # if is_vector_valued(vh): @@ -300,7 +406,9 @@ def plot_field( # dpi = 400, # ) -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ + + def my_small_plot( title, vals, titles=None, xx=None, yy=None, @@ -311,11 +419,31 @@ def my_small_plot( cb_min=None, cb_max=None, save_fig=None, - save_vals = False, + save_vals=False, hide_plot=False, dpi='figure', show_xylabel=True, ): + """ + plot a list of scalar fields on a list of patches + + Parameters + ---------- + title : (str) + title of the plot + + vals : (list) + list of scalar fields to plot + + titles : (list) + list of titles for each plot + + xx : (list) + list of x-coordinates of the grid points + + yy : (list) + list of y-coordinates of the grid points + """ # titles is discarded if only one plot # cmap = 'jet' is nice too, but not so uniform. 'plasma' or 'magma' are uniform also. # cmap = 'hsv' is good for singular fields, for its rapid color change @@ -323,10 +451,11 @@ def my_small_plot( n_plots = len(vals) if n_plots > 1: if titles is None or n_plots != len(titles): - titles = n_plots*[title] + titles = n_plots * [title] else: if titles: - print('Warning [my_small_plot]: will discard argument titles for a single plot') + print( + 'Warning [my_small_plot]: will discard argument titles for a single plot') titles = [title] n_patches = len(xx) @@ -335,8 +464,8 @@ def my_small_plot( if save_vals: # saving as vals.npz np.savez('vals', xx=xx, yy=yy, vals=vals) - - fig = plt.figure(figsize=(2.6+4.8*n_plots, 4.8)) + + fig = plt.figure(figsize=(2.6 + 4.8 * n_plots, 4.8)) fig.suptitle(title, fontsize=14) for i in range(n_plots): @@ -351,31 +480,44 @@ def my_small_plot( cnorm = colors.Normalize(vmin=vmin, vmax=vmax) assert n_patches == len(vals[i]) - ax = fig.add_subplot(1, n_plots, i+1) + ax = fig.add_subplot(1, n_plots, i + 1) for k in range(n_patches): - ax.contourf(xx[k], yy[k], vals[i][k], 50, norm=cnorm, cmap=cmap, zorder=-10) #, extend='both') + ax.contourf( + xx[k], + yy[k], + vals[i][k], + 50, + norm=cnorm, + cmap=cmap, + zorder=- + 10) # , extend='both') ax.set_rasterization_zorder(0) - cbar = fig.colorbar(cm.ScalarMappable(norm=cnorm, cmap=cmap), ax=ax, pad=0.05) + cbar = fig.colorbar( + cm.ScalarMappable( + norm=cnorm, + cmap=cmap), + ax=ax, + pad=0.05) if gridlines_x1 is not None: ax.plot(*gridlines_x1, color='k') ax.plot(*gridlines_x2, color='k') if show_xylabel: - ax.set_xlabel( r'$x$', rotation='horizontal' ) - ax.set_ylabel( r'$y$', rotation='horizontal' ) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') if n_plots > 1: - ax.set_title ( titles[i] ) + ax.set_title(titles[i]) ax.set_aspect('equal') if save_fig: - print('saving contour plot in file '+save_fig) - plt.savefig(save_fig, bbox_inches='tight',dpi=dpi) + print('saving contour plot in file ' + save_fig) + plt.savefig(save_fig, bbox_inches='tight', dpi=dpi) if not hide_plot: plt.show() if surface_plot: - fig = plt.figure(figsize=(2.6+4.8*n_plots, 4.8)) - fig.suptitle(title+' -- surface', fontsize=14) + fig = plt.figure(figsize=(2.6 + 4.8 * n_plots, 4.8)) + fig.suptitle(title + ' -- surface', fontsize=14) for i in range(n_plots): if cb_min is None: @@ -388,28 +530,43 @@ def my_small_plot( vmax = cb_max cnorm = colors.Normalize(vmin=vmin, vmax=vmax) assert n_patches == len(vals[i]) - ax = fig.add_subplot(1, n_plots, i+1, projection='3d') + ax = fig.add_subplot(1, n_plots, i + 1, projection='3d') for k in range(n_patches): - ax.plot_surface(xx[k], yy[k], vals[i][k], norm=cnorm, rstride=10, cstride=10, cmap=cmap, - linewidth=0, antialiased=False) - cbar = fig.colorbar(cm.ScalarMappable(norm=cnorm, cmap=cmap), ax=ax, pad=0.05) + ax.plot_surface( + xx[k], + yy[k], + vals[i][k], + norm=cnorm, + rstride=10, + cstride=10, + cmap=cmap, + linewidth=0, + antialiased=False) + cbar = fig.colorbar( + cm.ScalarMappable( + norm=cnorm, + cmap=cmap), + ax=ax, + pad=0.05) if show_xylabel: - ax.set_xlabel( r'$x$', rotation='horizontal' ) - ax.set_ylabel( r'$y$', rotation='horizontal' ) - ax.set_title ( titles[i] ) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(titles[i]) if save_fig: ext = save_fig[-4:] if ext[0] != '.': - print('WARNING: extension unclear for file_name '+save_fig) - save_fig_surf = save_fig[:-4]+'_surf'+ext - print('saving surface plot in file '+save_fig_surf) + print('WARNING: extension unclear for file_name ' + save_fig) + save_fig_surf = save_fig[:-4] + '_surf' + ext + print('saving surface plot in file ' + save_fig_surf) plt.savefig(save_fig_surf, bbox_inches='tight', dpi=dpi) - + if not hide_plot: plt.show() -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ + + def my_small_streamplot( title, vals_x, vals_y, xx, yy, skip=2, @@ -426,29 +583,37 @@ def my_small_streamplot( assert n_patches == len(yy) # fig = plt.figure(figsize=(2.6+4.8, 4.8)) - - fig, ax = plt.subplots(1,1, figsize=(2.6+4.8, 4.8)) - + + fig, ax = plt.subplots(1, 1, figsize=(2.6 + 4.8, 4.8)) + fig.suptitle(title, fontsize=14) delta = 0.25 # x = y = np.arange(-3.0, 3.01, delta) # X, Y = np.meshgrid(x, y) max_val = max(np.max(vals_x), np.max(vals_y)) - #print('max_val = {}'.format(max_val)) - vf_amp = amp_factor/max_val + # print('max_val = {}'.format(max_val)) + vf_amp = amp_factor / max_val for k in range(n_patches): - ax.quiver(xx[k][::skip, ::skip], yy[k][::skip, ::skip], vals_x[k][::skip, ::skip], vals_y[k][::skip, ::skip], - scale=1/(vf_amp*0.05), width=0.002) # width=) units='width', pivot='mid', + ax.quiver(xx[k][::skip, + ::skip], + yy[k][::skip, + ::skip], + vals_x[k][::skip, + ::skip], + vals_y[k][::skip, + ::skip], + scale=1 / (vf_amp * 0.05), + width=0.002) # width=) units='width', pivot='mid', if show_xylabel: - ax.set_xlabel( r'$x$', rotation='horizontal' ) - ax.set_ylabel( r'$y$', rotation='horizontal' ) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') ax.set_aspect('equal') if save_fig: - print('saving vector field (stream) plot in file '+save_fig) + print('saving vector field (stream) plot in file ' + save_fig) plt.savefig(save_fig, bbox_inches='tight', dpi=dpi) if not hide_plot: diff --git a/psydac/feec/multipatch/utilities.py b/psydac/feec/multipatch/utilities.py index 16b891fdc..2b856f37c 100644 --- a/psydac/feec/multipatch/utilities.py +++ b/psydac/feec/multipatch/utilities.py @@ -1,109 +1,157 @@ # coding: utf-8 import time + + def time_count(t_stamp=None, msg=None): new_t_stamp = time.time() if msg is None: msg = '' else: - msg = '['+msg+']' + msg = '[' + msg + ']' if t_stamp: - print('time elapsed '+msg+': '+repr(new_t_stamp - t_stamp)) + print('time elapsed ' + msg + ': ' + repr(new_t_stamp - t_stamp)) elif len(msg) > 0: - print('time stamp set for '+msg) + print('time stamp set for ' + msg) return new_t_stamp # --------------------------------------------------------------------------------------------------------------- # small/temporary utility for saving/loading sparse matrices, plots... # (should be cleaned !) + def source_name(source_type=None, source_proj=None): """ Get the source term name""" assert source_type and source_proj - return source_type+'_'+source_proj + return source_type + '_' + source_proj + def sol_ref_fn(source_type, N_diag, source_proj=None): """ Get the reference solution filename based on the source term type""" - fn = 'u_ref_'+source_name(source_type, source_proj)+'_N'+repr(N_diag)+'.npz' + fn = 'u_ref_' + source_name(source_type, + source_proj) + '_N' + repr(N_diag) + '.npz' return fn -def error_fn(source_type=None, method=None, conf_proj=None, k=None, domain_name=None,deg=None): + +def error_fn( + source_type=None, + method=None, + conf_proj=None, + k=None, + domain_name=None, + deg=None): """ Get the error filename based on the method used to solve the multpatch problem""" - return 'errors/error_'+domain_name+'_'+source_type+'_'+'_deg'+repr(deg)+'_'+get_method_name(method, k, conf_proj=conf_proj)+'.txt' + return 'errors/error_' + domain_name + '_' + source_type + '_' + '_deg' + \ + repr(deg) + '_' + get_method_name(method, k, conf_proj=conf_proj) + '.txt' + def get_method_name(method=None, k=None, conf_proj=None, penal_regime=None): """ Get method name used to solve the multpatch problem""" if method == 'nitsche': method_name = method - if k==1: + if k == 1: method_name += '_SIP' - elif k==-1: + elif k == -1: method_name += '_NIP' - elif k==0: + elif k == 0: method_name += '_IIP' else: assert k is None elif method == 'conga': method_name = method if conf_proj is not None: - method_name += '_'+conf_proj + method_name += '_' + conf_proj else: raise ValueError(method) if penal_regime is not None: - method_name += '_pr'+repr(penal_regime) + method_name += '_pr' + repr(penal_regime) return method_name -def get_fem_name(method=None, k=None, DG_full=False, conf_proj=None, domain_name=None,nc=None,deg=None,hom_seq=True): + +def get_fem_name( + method=None, + k=None, + DG_full=False, + conf_proj=None, + domain_name=None, + nc=None, + deg=None, + hom_seq=True): """ Get Fem name used to solve the multipatch problem""" assert domain_name - fn = domain_name+(('_nc'+repr(nc)) if nc else '') +(('_deg'+repr(deg)) if deg else '') + fn = domain_name + (('_nc' + repr(nc)) if nc else '') + \ + (('_deg' + repr(deg)) if deg else '') if DG_full: fn += '_fDG' if method is not None: - fn += '_'+get_method_name(method, k, conf_proj) + fn += '_' + get_method_name(method, k, conf_proj) if not hom_seq: fn += '_inhom' return fn + def FEM_sol_fn(source_type=None, source_proj=None): """ Get the filename for FEM solution coeffs in numpy array format """ - fn = 'sol_'+source_name(source_type, source_proj)+'.npy' + fn = 'sol_' + source_name(source_type, source_proj) + '.npy' return fn - -def get_load_dir(method=None, DG_full=False, domain_name=None,nc=None,deg=None,data='matrices'): + + +def get_load_dir( + method=None, + DG_full=False, + domain_name=None, + nc=None, + deg=None, + data='matrices'): """ get load directory name based on the fem name""" - assert data in ['matrices','solutions','rhs'] + assert data in ['matrices', 'solutions', 'rhs'] if method is None: assert data == 'rhs' - fem_name = get_fem_name(domain_name=domain_name,method=method, nc=nc,deg=deg, DG_full=DG_full) - return './saved_'+data+'/'+fem_name+'/' + fem_name = get_fem_name( + domain_name=domain_name, + method=method, + nc=nc, + deg=deg, + DG_full=DG_full) + return './saved_' + data + '/' + fem_name + '/' + def get_run_dir(domain_name, nc, deg, source_type=None, conf_proj=None): + """ Get the run directory name""" rdir = domain_name if source_type: - rdir += '_'+source_type + rdir += '_' + source_type if conf_proj: - rdir += '_P='+conf_proj + rdir += '_P=' + conf_proj rdir += '_nc={}_deg={}'.format(nc, deg) return rdir + def get_plot_dir(case_dir, run_dir): - return './plots/'+case_dir+'/'+run_dir + """ Get the plot directory name""" + return './plots/' + case_dir + '/' + run_dir + def get_mat_dir(domain_name, nc, deg, quad_param=None): - mat_dir = './saved_matrices/matrices_{}_nc={}_deg={}'.format(domain_name, nc, deg) + """ Get the directory name where matrices are stored""" + mat_dir = './saved_matrices/matrices_{}_nc={}_deg={}'.format( + domain_name, nc, deg) if quad_param is not None: mat_dir += '_qp={}'.format(quad_param) return mat_dir + def get_sol_dir(case_dir, domain_name, nc, deg): - return './saved_solutions/'+case_dir+'/solutions_{}_nc={}_deg={}'.format(domain_name, nc, deg) - + """ Get the directory name where solutions are stored""" + return './saved_solutions/' + case_dir + \ + '/solutions_{}_nc={}_deg={}'.format(domain_name, nc, deg) + + def diag_fn(source_type=None, source_proj=None): """ Get the diagnostics filename""" if source_type is not None: - fn = 'diag_'+source_name(source_type, source_proj)+'.txt' + fn = 'diag_' + source_name(source_type, source_proj) + '.txt' else: fn = 'diag.txt' - return fn \ No newline at end of file + return fn diff --git a/psydac/feec/multipatch/utils_conga_2d.py b/psydac/feec/multipatch/utils_conga_2d.py index bc3fb83ad..05ee2bae3 100644 --- a/psydac/feec/multipatch/utils_conga_2d.py +++ b/psydac/feec/multipatch/utils_conga_2d.py @@ -4,38 +4,43 @@ import numpy as np from sympy import lambdify -from sympde.topology import Derham - -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.pull_push import pull_2d_hcurl +from sympde.topology import Derham +from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_l2 -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.utilities import time_count #, export_sol, import_sol -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField +from psydac.feec.multipatch.api import discretize +from psydac.feec.multipatch.utilities import time_count # , export_sol, import_sol +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField from psydac.feec.multipatch.plotting_utilities import get_plotting_grid, get_grid_quad_weights, get_grid_vals -# commuting projections on the physical domain (should probably be in the interface) +# commuting projections on the physical domain (should probably be in the +# interface) def P0_phys(f_phys, P0, domain, mappings_list): f = lambdify(domain.coordinates, f_phys) f_log = [pull_2d_h1(f, m.get_callable_mapping()) for m in mappings_list] return P0(f_log) + def P1_phys(f_phys, P1, domain, mappings_list): f_x = lambdify(domain.coordinates, f_phys[0]) f_y = lambdify(domain.coordinates, f_phys[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) for m in mappings_list] + f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) + for m in mappings_list] return P1(f_log) + def P2_phys(f_phys, P2, domain, mappings_list): f = lambdify(domain.coordinates, f_phys) f_log = [pull_2d_l2(f, m.get_callable_mapping()) for m in mappings_list] return P2(f_log) -# commuting projections on the physical domain (should probably be in the interface) +# commuting projections on the physical domain (should probably be in the +# interface) + + def P_phys_h1(f_phys, P0, domain, mappings_list): f = lambdify(domain.coordinates, f_phys) if len(mappings_list) == 1: @@ -45,18 +50,21 @@ def P_phys_h1(f_phys, P0, domain, mappings_list): f_log = [pull_2d_h1(f, m) for m in mappings_list] return P0(f_log) + def P_phys_hcurl(f_phys, P1, domain, mappings_list): f_x = lambdify(domain.coordinates, f_phys[0]) f_y = lambdify(domain.coordinates, f_phys[1]) f_log = [pull_2d_hcurl([f_x, f_y], m) for m in mappings_list] return P1(f_log) + def P_phys_hdiv(f_phys, P1, domain, mappings_list): f_x = lambdify(domain.coordinates, f_phys[0]) f_y = lambdify(domain.coordinates, f_phys[1]) f_log = [pull_2d_hdiv([f_x, f_y], m) for m in mappings_list] return P1(f_log) + def P_phys_l2(f_phys, P2, domain, mappings_list): f = lambdify(domain.coordinates, f_phys) f_log = [pull_2d_l2(f, m) for m in mappings_list] @@ -66,17 +74,17 @@ def P_phys_l2(f_phys, P2, domain, mappings_list): def get_kind(space='V*'): # temp helper if space == 'V0': - kind='h1' + kind = 'h1' elif space == 'V1': - kind='hcurl' + kind = 'hcurl' elif space == 'V2': - kind='l2' + kind = 'l2' else: - raise ValueError(space) - return kind + raise ValueError(space) + return kind -#=============================================================================== +# =============================================================================== class DiagGrid(): """ Class storing: @@ -85,105 +93,125 @@ class DiagGrid(): - a ref solution to compare solutions from different FEM spaces on same domain """ + def __init__(self, mappings=None, N_diag=None): mappings_list = list(mappings.values()) - etas, xx, yy, patch_logvols = get_plotting_grid(mappings, N=N_diag, centered_nodes=True, return_patch_logvols=True) - quad_weights = get_grid_quad_weights(etas, patch_logvols, mappings_list) - + etas, xx, yy, patch_logvols = get_plotting_grid( + mappings, N=N_diag, centered_nodes=True, return_patch_logvols=True) + quad_weights = get_grid_quad_weights( + etas, patch_logvols, mappings_list) + self.etas = etas self.xx = xx self.yy = yy self.patch_logvols = patch_logvols self.quad_weights = quad_weights self.mappings_list = mappings_list - + self.sol_ref = {} # Fem fields self.sol_vals = {} # values on diag grid self.sol_ref_vals = {} # values on diag grid def grid_vals_h1(self, v): return get_grid_vals(v, self.etas, self.mappings_list, space_kind='h1') - + def grid_vals_hcurl(self, v): - return get_grid_vals(v, self.etas, self.mappings_list, space_kind='hcurl') + return get_grid_vals( + v, + self.etas, + self.mappings_list, + space_kind='hcurl') def create_ref_fem_spaces(self, domain=None, ref_nc=None, ref_deg=None): print('[DiagGrid] Discretizing the ref FEM space...') degree = [ref_deg, ref_deg] - derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham = Derham(domain, ["H1", "Hcurl", "L2"]) ref_nc = {patch.name: [ref_nc, ref_nc] for patch in domain.interior} domain_h = discretize(domain, ncells=ref_nc) - derham_h = discretize(derham, domain_h, degree=degree) #, backend=PSYDAC_BACKENDS[backend_language]) + # , backend=PSYDAC_BACKENDS[backend_language]) + derham_h = discretize(derham, domain_h, degree=degree) self.V0h = derham_h.V0 self.V1h = derham_h.V1 - + def import_ref_sol_from_coeffs(self, sol_ref_filename=None, space='V*'): - print('[DiagGrid] loading coeffs of ref_sol from {}...'.format(sol_ref_filename)) + print('[DiagGrid] loading coeffs of ref_sol from {}...'.format( + sol_ref_filename)) if space == 'V0': Vh = self.V0h elif space == 'V1': Vh = self.V1h else: - raise ValueError(space) + raise ValueError(space) try: coeffs = np.load(sol_ref_filename) except OSError: print("-- WARNING: file not found, setting sol_ref = 0") coeffs = np.zeros(Vh.nbasis) if space in self.sol_ref: - print('WARNING !! sol_ref[{}] exists -- will be overwritten !! '.format(space)) + print( + 'WARNING !! sol_ref[{}] exists -- will be overwritten !! '.format(space)) print('use refined labels if several solutions are needed in the same space') - self.sol_ref[space] = FemField(Vh, coeffs=array_to_psydac(coeffs, Vh.vector_space)) + self.sol_ref[space] = FemField( + Vh, coeffs=array_to_psydac( + coeffs, Vh.vector_space)) def write_sol_values(self, v, space='V*'): """ v: FEM field """ if space in self.sol_vals: - print('WARNING !! sol_vals[{}] exists -- will be overwritten !! '.format(space)) + print( + 'WARNING !! sol_vals[{}] exists -- will be overwritten !! '.format(space)) print('use refined labels if several solutions are needed in the same space') - self.sol_vals[space] = get_grid_vals(v, self.etas, self.mappings_list, space_kind=get_kind(space)) + self.sol_vals[space] = get_grid_vals( + v, self.etas, self.mappings_list, space_kind=get_kind(space)) def write_sol_ref_values(self, v=None, space='V*'): """ if no FemField v is provided, then use the self.sol_ref (must have been imported) - """ + """ if space in self.sol_vals: - print('WARNING !! sol_ref_vals[{}] exists -- will be overwritten !! '.format(space)) + print( + 'WARNING !! sol_ref_vals[{}] exists -- will be overwritten !! '.format(space)) print('use refined labels if several solutions are needed in the same space') if v is None: # then sol_ref must have been imported v = self.sol_ref[space] - self.sol_ref_vals[space] = get_grid_vals(v, self.etas, self.mappings_list, space_kind=get_kind(space)) + self.sol_ref_vals[space] = get_grid_vals( + v, self.etas, self.mappings_list, space_kind=get_kind(space)) def compute_l2_error(self, space='V*'): if space in ['V0', 'V2']: - u = self.sol_ref_vals[space] + u = self.sol_ref_vals[space] uh = self.sol_vals[space] abs_u = [np.abs(p) for p in u] abs_uh = [np.abs(p) for p in uh] - errors = [np.abs(p-q) for p, q in zip(u, uh)] + errors = [np.abs(p - q) for p, q in zip(u, uh)] elif space == 'V1': - u_x, u_y = self.sol_ref_vals[space] + u_x, u_y = self.sol_ref_vals[space] uh_x, uh_y = self.sol_vals[space] - abs_u = [np.sqrt( (u1)**2 + (u2)**2 ) for u1, u2 in zip(u_x, u_y)] - abs_uh = [np.sqrt( (u1)**2 + (u2)**2 ) for u1, u2 in zip(uh_x, uh_y)] - errors = [np.sqrt( (u1-v1)**2 + (u2-v2)**2 ) for u1, v1, u2, v2 in zip(u_x, uh_x, u_y, uh_y)] + abs_u = [np.sqrt((u1)**2 + (u2)**2) for u1, u2 in zip(u_x, u_y)] + abs_uh = [np.sqrt((u1)**2 + (u2)**2) for u1, u2 in zip(uh_x, uh_y)] + errors = [np.sqrt((u1 - v1)**2 + (u2 - v2)**2) + for u1, v1, u2, v2 in zip(u_x, uh_x, u_y, uh_y)] else: - raise ValueError(space) - - l2_norm_uh = (np.sum([J_F * v**2 for v, J_F in zip(abs_uh, self.quad_weights)]))**0.5 - l2_norm_u = (np.sum([J_F * v**2 for v, J_F in zip(abs_u, self.quad_weights)]))**0.5 - l2_error = (np.sum([J_F * v**2 for v, J_F in zip(errors, self.quad_weights)]))**0.5 + raise ValueError(space) + + l2_norm_uh = ( + np.sum([J_F * v**2 for v, J_F in zip(abs_uh, self.quad_weights)]))**0.5 + l2_norm_u = ( + np.sum([J_F * v**2 for v, J_F in zip(abs_u, self.quad_weights)]))**0.5 + l2_error = ( + np.sum([J_F * v**2 for v, J_F in zip(errors, self.quad_weights)]))**0.5 return l2_norm_uh, l2_norm_u, l2_error def get_diags_for(self, v, space='V*', print_diags=True): self.write_sol_values(v, space) sol_norm, sol_ref_norm, l2_error = self.compute_l2_error(space) - rel_l2_error = l2_error/(max(sol_norm, sol_ref_norm)) + rel_l2_error = l2_error / (max(sol_norm, sol_ref_norm)) diags = { 'sol_norm': sol_norm, 'sol_ref_norm': sol_ref_norm, @@ -196,7 +224,12 @@ def get_diags_for(self, v, space='V*', print_diags=True): return diags -def get_Vh_diags_for(v=None, v_ref=None, M_m=None, print_diags=True, msg='error between ?? and ?? in Vh'): +def get_Vh_diags_for( + v=None, + v_ref=None, + M_m=None, + print_diags=True, + msg='error between ?? and ?? in Vh'): """ v, v_ref: FemField M_m: mass matrix in scipy format @@ -207,7 +240,7 @@ def get_Vh_diags_for(v=None, v_ref=None, M_m=None, print_diags=True, msg='error l2_error = np.dot(err_c, M_m.dot(err_c))**0.5 sol_norm = np.dot(uh_c, M_m.dot(uh_c))**0.5 sol_ref_norm = np.dot(uh_ref_c, M_m.dot(uh_ref_c))**0.5 - rel_l2_error = l2_error/(max(sol_norm, sol_ref_norm)) + rel_l2_error = l2_error / (max(sol_norm, sol_ref_norm)) diags = { 'sol_norm': sol_norm, 'sol_ref_norm': sol_ref_norm, @@ -215,21 +248,25 @@ def get_Vh_diags_for(v=None, v_ref=None, M_m=None, print_diags=True, msg='error } if print_diags: print(' .. l2 norms ({}): '.format(msg)) - print(diags) + print(diags) return diags def write_diags_to_file(diags, script_filename, diag_filename, params=None): + """ write diagnostics to file """ print(' -- writing diags to file {} --'.format(diag_filename)) if not os.path.exists(diag_filename): open(diag_filename, 'w') with open(diag_filename, 'a') as a_writer: a_writer.write('\n') - a_writer.write(' ---------- ---------- ---------- ---------- ---------- ---------- \n') + a_writer.write( + ' ---------- ---------- ---------- ---------- ---------- ---------- \n') a_writer.write(' run script: \n {}\n'.format(script_filename)) - a_writer.write(' executed on: \n {}\n\n'.format(datetime.datetime.now())) + a_writer.write( + ' executed on: \n {}\n\n'.format( + datetime.datetime.now())) a_writer.write(' params: \n') for key, value in params.items(): a_writer.write(' {}: {} \n'.format(key, value)) @@ -237,5 +274,6 @@ def write_diags_to_file(diags, script_filename, diag_filename, params=None): a_writer.write(' diags: \n') for key, value in diags.items(): a_writer.write(' {}: {} \n'.format(key, value)) - a_writer.write(' ---------- ---------- ---------- ---------- ---------- ---------- \n') - a_writer.write('\n') \ No newline at end of file + a_writer.write( + ' ---------- ---------- ---------- ---------- ---------- ---------- \n') + a_writer.write('\n') From 5371468957e06a45a0189052bfe363204f88dcb7 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Thu, 23 May 2024 16:52:36 +0200 Subject: [PATCH 45/88] update tests --- .../tests/test_feec_maxwell_multipatch_2d.py | 74 ++++++++++--------- .../tests/test_feec_poisson_multipatch_2d.py | 53 +++++++------ 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py index 5b9668c18..3db73b8d9 100644 --- a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py @@ -9,54 +9,54 @@ from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_nc from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_dg + def test_time_harmonic_maxwell_pretzel_f(): - nc,deg = 10,2 + nc, deg = 10, 2 source_type = 'manu_maxwell' domain_name = 'pretzel_f' - omega = np.sqrt(170) # source - roundoff = 1e4 - eta = int(-omega**2 * roundoff)/roundoff + eta = -170.0 # source l2_error = solve_hcurl_source_pbm( nc=nc, deg=deg, eta=eta, nu=0, - mu=1, #1, + mu=1, domain_name=domain_name, source_type=source_type, backend_language='pyccel-gcc') - assert abs(l2_error - 0.06246693595198972)<1e-10 + assert abs(l2_error - 0.06247745643640749) < 1e-10 + def test_time_harmonic_maxwell_pretzel_f_nc(): - deg = 2 - nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10, 20, 20, 20, 10, 10]) - + deg = 2 + nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, + 10, 10, 10, 10, 20, 20, 20, 10, 10]) + source_type = 'manu_maxwell' domain_name = 'pretzel_f' source_proj = 'tilde_Pi' - omega = np.sqrt(170) # source - roundoff = 1e4 - eta = int(-omega**2 * roundoff)/roundoff + eta = -170.0 l2_error = solve_hcurl_source_pbm_nc( nc=nc, deg=deg, eta=eta, nu=0, - mu=1, + mu=1, domain_name=domain_name, source_type=source_type, source_proj=source_proj, plot_dir='./plots/th_maxell_nc', - backend_language='pyccel-gcc', + backend_language='pyccel-gcc', test=True) - assert abs(l2_error - 0.04753982587323614)<1e-10 + assert abs(l2_error - 0.04753613858909066) < 1e-10 + def test_maxwell_eigen_curved_L_shape(): - domain_name = 'curved_L_shape' + domain_name = 'curved_L_shape' nc = 10 deg = 2 @@ -67,7 +67,7 @@ def test_maxwell_eigen_curved_L_shape(): 0.100656015004E+02, 0.101118862307E+02, 0.124355372484E+02, - ] + ] sigma = 7 nb_eigs_solve = 7 nb_eigs_plot = 7 @@ -90,17 +90,18 @@ def test_maxwell_eigen_curved_L_shape(): error = 0 n_errs = min(len(ref_sigmas), len(eigenvalues)) for k in range(n_errs): - error += (eigenvalues[k]-ref_sigmas[k])**2 + error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) - assert abs(error - 0.023413963252245817)<1e-10 + assert abs(error - 0.023395836648441557) < 1e-10 + def test_maxwell_eigen_curved_L_shape_nc(): - domain_name = 'curved_L_shape' - domain=[[1, 3],[0, np.pi/4]] + domain_name = 'curved_L_shape' + domain = [[1, 3], [0, np.pi / 4]] ncells = np.array([[None, 10], - [10, 20]]) + [10, 20]]) degree = [2, 2] @@ -110,7 +111,7 @@ def test_maxwell_eigen_curved_L_shape_nc(): 0.100656015004E+02, 0.101118862307E+02, 0.124355372484E+02, - ] + ] sigma = 7 nb_eigs_solve = 7 nb_eigs_plot = 7 @@ -135,17 +136,18 @@ def test_maxwell_eigen_curved_L_shape_nc(): error = 0 n_errs = min(len(ref_sigmas), len(eigenvalues)) for k in range(n_errs): - error += (eigenvalues[k]-ref_sigmas[k])**2 + error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) - - assert abs(error - 0.004289103786542442)<1e-10 + + assert abs(error - 0.004301175400024398) < 1e-10 + def test_maxwell_eigen_curved_L_shape_dg(): - domain_name = 'curved_L_shape' - domain=[[1, 3],[0, np.pi/4]] + domain_name = 'curved_L_shape' + domain = [[1, 3], [0, np.pi / 4]] ncells = np.array([[None, 10], - [10, 20]]) + [10, 20]]) degree = [2, 2] @@ -155,7 +157,7 @@ def test_maxwell_eigen_curved_L_shape_dg(): 0.100656015004E+02, 0.101118862307E+02, 0.124355372484E+02, - ] + ] sigma = 7 nb_eigs_solve = 7 nb_eigs_plot = 7 @@ -180,19 +182,21 @@ def test_maxwell_eigen_curved_L_shape_dg(): error = 0 n_errs = min(len(ref_sigmas), len(eigenvalues)) for k in range(n_errs): - error += (eigenvalues[k]-ref_sigmas[k])**2 + error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) - assert abs(error - 0.004208158031148591)<1e-10 + assert abs(error - 0.004208158031148591) < 1e-10 -#============================================================================== +# ============================================================================== # CLEAN UP SYMPY NAMESPACE -#============================================================================== +# ============================================================================== + def teardown_module(): from sympy.core import cache cache.clear_cache() + def teardown_function(): from sympy.core import cache - cache.clear_cache() \ No newline at end of file + cache.clear_cache() diff --git a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py index 59e19ca3b..7441c8312 100644 --- a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py @@ -3,51 +3,56 @@ from psydac.feec.multipatch.examples.h1_source_pbms_conga_2d import solve_h1_source_pbm from psydac.feec.multipatch.examples_nc.h1_source_pbms_nc import solve_h1_source_pbm_nc + def test_poisson_pretzel_f(): source_type = 'manu_poisson_2' domain_name = 'pretzel_f' - nc = 10 + nc = 10 deg = 2 run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) l2_error = solve_h1_source_pbm( - nc=nc, deg=deg, - eta=0, - mu=1, - domain_name=domain_name, - source_type=source_type, - backend_language='pyccel-gcc', - plot_source=False, - plot_dir='./plots/h1_tests_source_february/'+run_dir) + nc=nc, deg=deg, + eta=0, + mu=1, + domain_name=domain_name, + source_type=source_type, + backend_language='pyccel-gcc', + plot_source=False, + plot_dir='./plots/h1_tests_source_february/' + run_dir) + + assert abs(l2_error - 8.054935880166114e-05) < 1e-10 - assert abs(l2_error-0.1173467869129417)<1e-10 def test_poisson_pretzel_f_nc(): source_type = 'manu_poisson_2' domain_name = 'pretzel_f' - nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10, 20, 20, 20, 10, 10]) + nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, + 10, 10, 10, 10, 20, 20, 20, 10, 10]) deg = 2 run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) l2_error = solve_h1_source_pbm_nc( - nc=nc, deg=deg, - eta=0, - mu=1, - domain_name=domain_name, - source_type=source_type, - backend_language='pyccel-gcc', - plot_source=False, - plot_dir='./plots/h1_tests_source_february/'+run_dir) - print(l2_error) - assert abs(l2_error-0.03821274975800339)<1e-10 -#============================================================================== + nc=nc, deg=deg, + eta=0, + mu=1, + domain_name=domain_name, + source_type=source_type, + backend_language='pyccel-gcc', + plot_source=False, + plot_dir='./plots/h1_tests_source_february/' + run_dir) + + assert abs(l2_error - 4.6086851224995065e-05) < 1e-10 +# ============================================================================== # CLEAN UP SYMPY NAMESPACE -#============================================================================== +# ============================================================================== + def teardown_module(): from sympy.core import cache cache.clear_cache() + def teardown_function(): from sympy.core import cache - cache.clear_cache() \ No newline at end of file + cache.clear_cache() From c8b4c075916342ed3c1f608a181fbee9142d7ead Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Mon, 27 May 2024 22:34:29 +0200 Subject: [PATCH 46/88] forgotten prints --- psydac/linalg/tests/test_stencil_matrix.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/psydac/linalg/tests/test_stencil_matrix.py b/psydac/linalg/tests/test_stencil_matrix.py index 7b54d1ace..404a0f970 100644 --- a/psydac/linalg/tests/test_stencil_matrix.py +++ b/psydac/linalg/tests/test_stencil_matrix.py @@ -24,9 +24,6 @@ def compute_global_starts_ends(domain_decomposition, npts, pads): global_starts = [None] * ndims global_ends = [None] * ndims - print('\ndomain_decomposition.global_element_starts', domain_decomposition.global_element_starts) - print('domain_decomposition.global_element_ends', domain_decomposition.global_element_ends) - for axis in range(ndims): ee = domain_decomposition.global_element_ends[axis] From 4de21ca7b17f07a3a679da133a579e92b3d1b542 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 29 May 2024 16:15:49 +0200 Subject: [PATCH 47/88] add pyccel kernel --- psydac/linalg/kernels/stencil2IJV_kernels.py | 215 +++++++++++++++++++ psydac/linalg/topetsc.py | 150 ++++++++++++- 2 files changed, 358 insertions(+), 7 deletions(-) create mode 100644 psydac/linalg/kernels/stencil2IJV_kernels.py diff --git a/psydac/linalg/kernels/stencil2IJV_kernels.py b/psydac/linalg/kernels/stencil2IJV_kernels.py new file mode 100644 index 000000000..bff3d3843 --- /dev/null +++ b/psydac/linalg/kernels/stencil2IJV_kernels.py @@ -0,0 +1,215 @@ +# coding: utf-8 +from pyccel.decorators import template + + +#======================================================================================================== +@template(name='T', types=[float, complex]) +def stencil2IJV_1d_C(A:'T[:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmapb:'int64[:]', + cnl1:'int64', dng1:'int64', cs1:'int64', cp1:'int64', cm1:'int64', + dsh:'int64[:]', csh:'int64[:]', dgs1:'int64[:]', dge1:'int64[:]', + cgs1:'int64[:]', cge1:'int64[:]', dnlb1:'int64[:]', cnlb1:'int64[:]'): + + nnz = 0 + nnz_rows = 0 + gr1 = cp1*cm1 + + for i1 in range(cnl1): + nnz_in_row = 0 + i1_n = cs1 + i1 + + pr_i1 = 0 + for k in range(cgs1.size): + if i1_n < cgs1[k] or i1_n > cge1[k]:continue + pr_i1 = k + + i_g = csh[pr_i1] + i1_n - cgs1[pr_i1] + + stencil_size1 = A[i1 + gr1].size + + for k1 in range(stencil_size1): + + j1_n = (i1_n + k1 - stencil_size1//2) % dng1 + value = A[i1 + gr1, k1] + + if abs(value) == 0.0:continue + + pr_j1 = 0 + for k in range(dgs1.size): + if j1_n < dgs1[k] or j1_n > dge1[k]:continue + pr_j1 = k + + j_g = dsh[pr_j1] + j1_n - dgs1[pr_j1] + + if nnz_in_row == 0: + rowmapb[nnz_rows] = i_g + + Jb[nnz] = j_g + Vb[nnz] = value + nnz += 1 + + nnz_in_row += 1 + + if nnz_in_row > 0: + Ib[1 + nnz_rows] = Ib[nnz_rows] + nnz_in_row + nnz_rows += 1 + + return nnz_rows, nnz + +#======================================================================================================== +@template(name='T', types=[float, complex]) +def stencil2IJV_2d_C(A:'T[:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmapb:'int64[:]', + cnl1:'int64', cnl2:'int64', dng1:'int64', dng2:'int64', cs1:'int64', + cs2:'int64', cp1:'int64', cp2:'int64', cm1:'int64', cm2:'int64', + dsh:'int64[:]', csh:'int64[:]', dgs1:'int64[:]', dgs2:'int64[:]', + dge1:'int64[:]', dge2:'int64[:]', cgs1:'int64[:]', cgs2:'int64[:]', + cge1:'int64[:]', cge2:'int64[:]', dnlb1:'int64[:]', dnlb2:'int64[:]', + cnlb1:'int64[:]', cnlb2:'int64[:]'): + + nnz = 0 + nnz_rows = 0 + gr1 = cp1*cm1 + gr2 = cp2*cm2 + + for i1 in range(cnl1): + for i2 in range(cnl2): + nnz_in_row = 0 + i1_n = cs1 + i1 + i2_n = cs2 + i2 + + pr_i1 = 0 + for k in range(cgs1.size): + if i1_n < cgs1[k] or i1_n > cge1[k]:continue + pr_i1 = k + pr_i2 = 0 + for k in range(cgs2.size): + if i2_n < cgs2[k] or i2_n > cge2[k]:continue + pr_i2 = k + + pr_i = pr_i2 + pr_i1 * cgs2.size + + i_g = csh[pr_i] + i2_n - cgs2[pr_i2] + (i1_n - cgs1[pr_i1]) * cnlb2[pr_i] + + stencil_size1, stencil_size2 = A.shape[2:] + + for k1 in range(stencil_size1): + for k2 in range(stencil_size2): + j1_n = (i1_n + k1 - stencil_size1//2) % dng1 + j2_n = (i2_n + k2 - stencil_size2//2) % dng2 + + value = A[i1 + gr1, i2 + gr2, k1, k2] + if abs(value) == 0.0:continue + + pr_j1 = 0 + for k in range(dgs1.size): + if j1_n < dgs1[k] or j1_n > dge1[k]:continue + pr_j1 = k + pr_j2 = 0 + for k in range(dgs2.size): + if j2_n < dgs2[k] or j2_n > dge2[k]:continue + pr_j2 = k + + pr_j = pr_j2 + pr_j1 * dgs2.size + + j_g = dsh[pr_j] + j2_n - dgs2[pr_j2] + (j1_n - dgs1[pr_j1]) * dnlb2[pr_j] + + if nnz_in_row == 0: + rowmapb[nnz_rows] = i_g + + Jb[nnz] = j_g + Vb[nnz] = value + nnz += 1 + + nnz_in_row += 1 + + if nnz_in_row > 0: + Ib[1 + nnz_rows] = Ib[nnz_rows] + nnz_in_row + nnz_rows += 1 + + return nnz_rows, nnz + + +#======================================================================================================== +@template(name='T', types=[float, complex]) +def stencil2IJV_3d_C(A:'T[:,:,:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmapb:'int64[:]', + cnl1:'int64', cnl2:'int64', cnl3:'int64', dng1:'int64', dng2:'int64', dng3:'int64', + cs1:'int64', cs2:'int64', cs3:'int64', cp1:'int64', cp2:'int64', cp3:'int64', + cm1:'int64', cm2:'int64', cm3:'int64', dsh:'int64[:]', csh:'int64[:]', + dgs1:'int64[:]', dgs2:'int64[:]', dgs3:'int64[:]', dge1:'int64[:]', dge2:'int64[:]', + dge3:'int64[:]', cgs1:'int64[:]', cgs2:'int64[:]', cgs3:'int64[:]', + cge1:'int64[:]', cge2:'int64[:]', cge3:'int64[:]', dnlb1:'int64[:]', dnlb2:'int64[:]', + dnlb3:'int64[:]', cnlb1:'int64[:]', cnlb2:'int64[:]', cnlb3:'int64[:]'): + + nnz = 0 + nnz_rows = 0 + gr1 = cp1*cm1 + gr2 = cp2*cm2 + gr3 = cp3*cm3 + + for i1 in range(cnl1): + for i2 in range(cnl2): + for i3 in range(cnl3): + nnz_in_row = 0 + i1_n = cs1 + i1 + i2_n = cs2 + i2 + i3_n = cs3 + i3 + + pr_i1 = 0 + for k in range(cgs1.size): + if i1_n < cgs1[k] or i1_n > cge1[k]:continue + pr_i1 = k + pr_i2 = 0 + for k in range(cgs2.size): + if i2_n < cgs2[k] or i2_n > cge2[k]:continue + pr_i2 = k + pr_i3 = 0 + for k in range(cgs3.size): + if i3_n < cgs3[k] or i3_n > cge3[k]:continue + pr_i3 = k + + pr_i = pr_i3 + pr_i2 * cgs3.size + pr_i1 * cgs2.size * cgs3.size + + i_g = csh[pr_i] + i3_n - cgs3[pr_i3] + (i2_n - cgs2[pr_i2]) * cnlb3[pr_i] + (i1_n - cgs1[pr_i1]) * cnlb2[pr_i] * cnlb3[pr_i] + + stencil_size1, stencil_size2, stencil_size3 = A.shape[3:] + + for k1 in range(stencil_size1): + for k2 in range(stencil_size2): + for k3 in range(stencil_size3): + j1_n = (i1_n + k1 - stencil_size1//2) % dng1 + j2_n = (i2_n + k2 - stencil_size2//2) % dng2 + j3_n = (i3_n + k3 - stencil_size3//2) % dng3 + + value = A[i1 + gr1, i2 + gr2, i3 + gr3, k1, k2, k3] + if abs(value) == 0.0:continue + + pr_j1 = 0 + for k in range(dgs1.size): + if j1_n < dgs1[k] or j1_n > dge1[k]:continue + pr_j1 = k + pr_j2 = 0 + for k in range(dgs2.size): + if j2_n < dgs2[k] or j2_n > dge2[k]:continue + pr_j2 = k + pr_j3 = 0 + for k in range(dgs3.size): + if j3_n < dgs3[k] or j3_n > dge3[k]:continue + pr_j3 = k + + pr_j = pr_j3 + pr_j2 * dgs3.size + pr_j1 * dgs2.size * dgs3.size + + j_g = dsh[pr_j] + j3_n - dgs3[pr_j3] + (j2_n - dgs2[pr_j2]) * dnlb3[pr_j] + (j1_n - dgs1[pr_j1]) * dnlb2[pr_j] * dnlb3[pr_j] + + if nnz_in_row == 0: + rowmapb[nnz_rows] = i_g + + Jb[nnz] = j_g + Vb[nnz] = value + nnz += 1 + + nnz_in_row += 1 + + if nnz_in_row > 0: + Ib[1 + nnz_rows] = Ib[nnz_rows] + nnz_in_row + nnz_rows += 1 + + return nnz_rows, nnz diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index b7dfea74a..4efb36666 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -10,10 +10,103 @@ __all__ = ('petsc_local_to_psydac', 'psydac_to_petsc_global', 'get_npts_local', 'get_npts_per_block', 'vec_topetsc', 'mat_topetsc') +from .kernels.stencil2IJV_kernels import stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C +# Dictionary used to select correct kernel functions based on dimensionality +kernels = { + 'stencil2IJV': {'F': None, + 'C': (None, stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C)} +} +def get_index_shift_per_block_per_process(V): + npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d + local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + + n_blocks = npts_local_per_block_per_process.shape[0] + n_procs = npts_local_per_block_per_process.shape[1] + + index_shift_per_block_per_process = [[0 + np.sum(local_sizes_per_block_per_process[:,:k]) + np.sum(local_sizes_per_block_per_process[:b,k]) for k in range(n_procs)] for b in range(n_blocks)] + + return index_shift_per_block_per_process #Global variable indexed as [b][k] fo block b, process k + +def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, order='C'): + + '''# Get the number of points per block, per process and per dimension: + dnpts_local_per_block_per_process = np.array(get_npts_per_block(dspace)) #indexed [b,k,d] for block b and process k and dimension d + cnpts_local_per_block_per_process = np.array(get_npts_per_block(cspace)) + # Get the local sizes per block and per process: + dlocal_sizes_per_block_per_process = np.prod(dnpts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k + clocal_sizes_per_block_per_process = np.prod(cnpts_local_per_block_per_process, axis=-1) + + dn_blocks = dnpts_local_per_block_per_process.shape[0] + dn_procs = dnpts_local_per_block_per_process.shape[1] + dindex_shift = [[0 + np.sum(dlocal_sizes_per_block_per_process[:,:k]) + np.sum(dlocal_sizes_per_block_per_process[:b,k]) for k in range(dn_procs)] for b in range(dn_blocks)] + + cn_blocks = cnpts_local_per_block_per_process.shape[0] + cn_procs = cnpts_local_per_block_per_process.shape[1] + cindex_shift = [[0 + np.sum(clocal_sizes_per_block_per_process[:,:k]) + np.sum(clocal_sizes_per_block_per_process[:b,k]) for k in range(cn_procs)] for b in range(cn_blocks)] + ''' + + dnpts_local_per_block_per_process = np.array(get_npts_per_block(dspace)) + cnpts_local_per_block_per_process = np.array(get_npts_per_block(cspace)) + + dindex_shift = get_index_shift_per_block_per_process(dspace) + cindex_shift = get_index_shift_per_block_per_process(cspace) + # Extract Cartesian decomposition of the Block where the node is: + dspace_block = dspace if isinstance(dspace, StencilVectorSpace) else dspace.spaces[bd] + cspace_block = cspace if isinstance(cspace, StencilVectorSpace) else cspace.spaces[bc] + + # Shortcuts + cnl = [np.int64(n) for n in get_npts_local(cspace_block)[0]] + dng = [np.int64(n) for n in dspace_block.cart.npts] + cs = [np.int64(s) for s in cspace_block.cart.starts] + cp = [np.int64(p) for p in cspace_block.cart.pads] + cm = [np.int64(m) for m in cspace_block.cart.shifts] + dsh = np.array(dindex_shift[bd], dtype='int64') #[np.array(sh, dtype='int64') for sh in dindex_shift[bd]] + csh = np.array(cindex_shift[bc], dtype='int64') #[np.array(sh, dtype='int64') for sh in cindex_shift[bc]] + + dgs = [np.array(gs, dtype='int64') for gs in dspace_block.cart.global_starts] # Global variable + dge = [np.array(ge, dtype='int64') for ge in dspace_block.cart.global_ends] # Global variable + cgs = [np.array(gs, dtype='int64') for gs in cspace_block.cart.global_starts] # Global variable + cge = [np.array(ge, dtype='int64') for ge in cspace_block.cart.global_ends] # Global variable + + #dnlb = [np.array(n, dtype='int64') for n in dnpts_local_per_block_per_process[bd]] + #cnlb = [np.array(n, dtype='int64') for n in cnpts_local_per_block_per_process[bc]] + + dnlb = [np.array([n[d] for n in dnpts_local_per_block_per_process[bd]], dtype='int64') for d in range(dspace_block.cart.ndim)] + cnlb = [np.array([n[d] for n in cnpts_local_per_block_per_process[bc]] , dtype='int64') for d in range(cspace_block.cart.ndim)] + + # Range of data owned by local process (no ghost regions) + local = tuple( [slice(m*p,-m*p) for p,m in zip(cp, cm)] + [slice(None)] * dspace_block.cart.ndim ) + shape = mat_block._data[local].shape + + nrows = np.prod(shape[0:dspace_block.cart.ndim]) + nentries = np.prod(shape) + # I, J, V, rowmap storage + Ib = np.zeros(nrows + 1, dtype='int64') + Jb = np.zeros(nentries, dtype='int64') + rowmapb = np.zeros(nrows, dtype='int64') + Vb = np.zeros(nentries, dtype=mat_block._data.dtype) + + Ib[0] += I[-1] + + + stencil2IJV = kernels['stencil2IJV'][order][dspace_block.cart.ndim] + + nnz_rows, nnz = stencil2IJV(mat_block._data, Ib, Jb, Vb, rowmapb, + *cnl, *dng, *cs, *cp, *cm, + dsh, csh, *dgs, *dge, *cgs, *cge, *dnlb, *cnlb + ) + + I += list(Ib[1:nnz_rows + 1]) + rowmap += list(rowmapb[:nnz_rows]) + J += list(Jb[:nnz]) + V += list(Vb[:nnz]) + + return I, J, V, rowmap + def petsc_local_to_psydac( V : VectorSpace, - petsc_index : int) -> tuple[tuple[int], tuple[int]]: + petsc_index : int): """ Convert the PETSc local index (starting from 0 in each process) to a Psydac local index (natural multi-index, as grid coordinates). @@ -433,16 +526,28 @@ def mat_topetsc( mat ): mat_block = mat + import time + + output = open('output.txt', 'a') + comm=dcarts[0].global_comm + + time_loop = np.empty((comm.Get_size(),)) + time_setValues = np.empty((comm.Get_size(),)) + time_assemble = np.empty((comm.Get_size(),)) + + + t_prev = time.time() for bc, bd in nonzero_block_indices: if isinstance(mat, BlockLinearOperator): mat_block = mat.blocks[bc][bd] + I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain) - cs = ccarts[bc].starts + """ cs = ccarts[bc].starts cghost_size = [pi*mi for pi,mi in zip(ccarts[bc].pads, ccarts[bc].shifts)] if dndims[bd] == 1 and cndims[bc] == 1: - - for i1 in range(cnpts_local[bc][0]): + I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain) + '''for i1 in range(cnpts_local[bc][0]): nnz_in_row = 0 i1_n = cs[0] + i1 i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n,)) @@ -466,10 +571,11 @@ def mat_topetsc( mat ): nnz_in_row += 1 if nnz_in_row > 0: - I.append(I[-1] + nnz_in_row) + I.append(I[-1] + nnz_in_row)''' elif dndims[bd] == 2 and cndims[bc] == 2: - for i1 in np.arange(cnpts_local[bc][0]): + I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain) + '''for i1 in np.arange(cnpts_local[bc][0]): for i2 in np.arange(cnpts_local[bc][1]): nnz_in_row = 0 @@ -499,7 +605,7 @@ def mat_topetsc( mat ): nnz_in_row += 1 if nnz_in_row > 0: - I.append(I[-1] + nnz_in_row) + I.append(I[-1] + nnz_in_row)''' elif dndims[bd] == 3 and cndims[bc] == 3: for i1 in np.arange(cnpts_local[bc][0]): @@ -535,11 +641,41 @@ def mat_topetsc( mat ): if nnz_in_row > 0: I.append(I[-1] + nnz_in_row) + """ + time_loop[comm.Get_rank()] = time.time() - t_prev + print('Time for the loop: ', time.time() - t_prev) + t_prev = time.time() # Set the values using IJV&rowmap format. The values are stored in a cache memory. gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC + time_setValues[comm.Get_rank()] = time.time() - t_prev + + print('Time for the setValuesIJV: ', time.time() - t_prev) + + t_prev = time.time() # Assemble the matrix with the values from the cache. Here it is where PETSc exchanges global communication. gmat.assemble() + time_assemble[comm.Get_rank()] = time.time() - t_prev + print('Time for the assemble: ', time.time() - t_prev) + + + if comm.Get_rank() == 0: + print(f'\nProcess & global size & local size & Time loop & Time setValuesIJV & Time assemble ', file=output, flush=True) + + for k in range(comm.Get_size()): + if k == comm.Get_rank(): + ls, gs = gmat.getSizes()[0] + print(f'{k} & {gs} & {ls} & {time_loop[k]:.2f} & {time_setValues[k]:.2f} & {time_assemble[k]:.2f}', file=output, flush=True) + comm.Barrier() + + avg_time_loop = comm.reduce(time_loop) + avg_time_setValues = comm.reduce(time_setValues, op=MPI.SUM, root=0) + avg_time_assemble = comm.reduce(time_assemble, op=MPI.SUM, root=0) + + if comm.Get_rank() == 0: + print(f'Average & {np.mean(avg_time_loop):.2f} & {np.mean(avg_time_setValues):.2f} & {np.mean(avg_time_assemble):.2f}', file=output, flush=True) + + output.close() return gmat From ce1126afd77db0b6422d7e3f8b73e069866d46f2 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 29 May 2024 16:29:44 +0200 Subject: [PATCH 48/88] string for type annotations --- psydac/linalg/topetsc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 4efb36666..f0f1a736a 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -177,8 +177,8 @@ def petsc_local_to_psydac( def psydac_to_petsc_global( V : VectorSpace, - block_indices : tuple[int], - ndarray_indices : tuple[int]) -> int: + block_indices : 'tuple[int]', + ndarray_indices : 'tuple[int]') -> int: """ Convert the Psydac local index (natural multi-index, as grid coordinates) to a PETSc global index. Performs a search to find the process owning the multi-index. From 1a457001defa0a34e0525b5860485fe42ecb228d Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Wed, 29 May 2024 17:29:36 +0200 Subject: [PATCH 49/88] clean up plus fix serial case --- psydac/linalg/topetsc.py | 225 +++++++++------------------------------ 1 file changed, 50 insertions(+), 175 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index f0f1a736a..dd8945d7f 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -27,29 +27,7 @@ def get_index_shift_per_block_per_process(V): return index_shift_per_block_per_process #Global variable indexed as [b][k] fo block b, process k -def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, order='C'): - - '''# Get the number of points per block, per process and per dimension: - dnpts_local_per_block_per_process = np.array(get_npts_per_block(dspace)) #indexed [b,k,d] for block b and process k and dimension d - cnpts_local_per_block_per_process = np.array(get_npts_per_block(cspace)) - # Get the local sizes per block and per process: - dlocal_sizes_per_block_per_process = np.prod(dnpts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k - clocal_sizes_per_block_per_process = np.prod(cnpts_local_per_block_per_process, axis=-1) - - dn_blocks = dnpts_local_per_block_per_process.shape[0] - dn_procs = dnpts_local_per_block_per_process.shape[1] - dindex_shift = [[0 + np.sum(dlocal_sizes_per_block_per_process[:,:k]) + np.sum(dlocal_sizes_per_block_per_process[:b,k]) for k in range(dn_procs)] for b in range(dn_blocks)] - - cn_blocks = cnpts_local_per_block_per_process.shape[0] - cn_procs = cnpts_local_per_block_per_process.shape[1] - cindex_shift = [[0 + np.sum(clocal_sizes_per_block_per_process[:,:k]) + np.sum(clocal_sizes_per_block_per_process[:b,k]) for k in range(cn_procs)] for b in range(cn_blocks)] - ''' - - dnpts_local_per_block_per_process = np.array(get_npts_per_block(dspace)) - cnpts_local_per_block_per_process = np.array(get_npts_per_block(cspace)) - - dindex_shift = get_index_shift_per_block_per_process(dspace) - cindex_shift = get_index_shift_per_block_per_process(cspace) +def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, dnpts_block, cnpts_block, dshift_block, cshift_block, order='C'): # Extract Cartesian decomposition of the Block where the node is: dspace_block = dspace if isinstance(dspace, StencilVectorSpace) else dspace.spaces[bd] cspace_block = cspace if isinstance(cspace, StencilVectorSpace) else cspace.spaces[bc] @@ -60,27 +38,24 @@ def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, order='C'): cs = [np.int64(s) for s in cspace_block.cart.starts] cp = [np.int64(p) for p in cspace_block.cart.pads] cm = [np.int64(m) for m in cspace_block.cart.shifts] - dsh = np.array(dindex_shift[bd], dtype='int64') #[np.array(sh, dtype='int64') for sh in dindex_shift[bd]] - csh = np.array(cindex_shift[bc], dtype='int64') #[np.array(sh, dtype='int64') for sh in cindex_shift[bc]] + dsh = np.array(dshift_block, dtype='int64') + csh = np.array(cshift_block, dtype='int64') dgs = [np.array(gs, dtype='int64') for gs in dspace_block.cart.global_starts] # Global variable dge = [np.array(ge, dtype='int64') for ge in dspace_block.cart.global_ends] # Global variable cgs = [np.array(gs, dtype='int64') for gs in cspace_block.cart.global_starts] # Global variable cge = [np.array(ge, dtype='int64') for ge in cspace_block.cart.global_ends] # Global variable - #dnlb = [np.array(n, dtype='int64') for n in dnpts_local_per_block_per_process[bd]] - #cnlb = [np.array(n, dtype='int64') for n in cnpts_local_per_block_per_process[bc]] - - dnlb = [np.array([n[d] for n in dnpts_local_per_block_per_process[bd]], dtype='int64') for d in range(dspace_block.cart.ndim)] - cnlb = [np.array([n[d] for n in cnpts_local_per_block_per_process[bc]] , dtype='int64') for d in range(cspace_block.cart.ndim)] + dnlb = [np.array([n[d] for n in dnpts_block], dtype='int64') for d in range(dspace_block.cart.ndim)] + cnlb = [np.array([n[d] for n in cnpts_block] , dtype='int64') for d in range(cspace_block.cart.ndim)] # Range of data owned by local process (no ghost regions) local = tuple( [slice(m*p,-m*p) for p,m in zip(cp, cm)] + [slice(None)] * dspace_block.cart.ndim ) shape = mat_block._data[local].shape - nrows = np.prod(shape[0:dspace_block.cart.ndim]) nentries = np.prod(shape) - # I, J, V, rowmap storage + + # locally block I, J, V, rowmap storage Ib = np.zeros(nrows + 1, dtype='int64') Jb = np.zeros(nentries, dtype='int64') rowmapb = np.zeros(nrows, dtype='int64') @@ -88,7 +63,6 @@ def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, order='C'): Ib[0] += I[-1] - stencil2IJV = kernels['stencil2IJV'][order][dspace_block.cart.ndim] nnz_rows, nnz = stencil2IJV(mat_block._data, Ib, Jb, Vb, rowmapb, @@ -177,8 +151,8 @@ def petsc_local_to_psydac( def psydac_to_petsc_global( V : VectorSpace, - block_indices : 'tuple[int]', - ndarray_indices : 'tuple[int]') -> int: + block_indices, + ndarray_indices) -> int: """ Convert the Psydac local index (natural multi-index, as grid coordinates) to a PETSc global index. Performs a search to find the process owning the multi-index. @@ -294,7 +268,7 @@ def get_npts_local(V : VectorSpace) -> list: -------- list Local number of nodes per dimension owned by the actual process. - In case of a StencilVectorSpace the list has length equal the number of dimensions in the domain. + In case of a StencilVectorSpace the list contains a single list with length equal the number of dimensions in the domain. In case of a BlockVectorSpace the list has length equal the number of blocks. """ if isinstance(V, StencilVectorSpace): @@ -463,53 +437,43 @@ def mat_topetsc( mat ): assert isinstance(mat, StencilMatrix) or isinstance(mat, BlockLinearOperator), 'Conversion only implemented for StencilMatrix and BlockLinearOperator.' - if (isinstance(mat.domain, BlockVectorSpace) and any([isinstance(mat.domain.spaces[b], BlockVectorSpace) for b in range(len(mat.domain.spaces))]))\ or (isinstance(mat.codomain, BlockVectorSpace) and any([isinstance(mat.codomain.spaces[b], BlockVectorSpace) for b in range(len(mat.codomain.spaces))])): raise NotImplementedError('Conversion for block of blocks not implemented.') - - if isinstance(mat.domain, StencilVectorSpace): - dcarts = [mat.domain.cart] + comm = mat.domain.cart.global_comm elif isinstance(mat.domain, BlockVectorSpace): - dcarts = [] - for b in range(len(mat.domain.spaces)): - dcarts.append(mat.domain.spaces[b].cart) - - if isinstance(mat.codomain, StencilVectorSpace): - ccarts = [mat.codomain.cart] - elif isinstance(mat.codomain, BlockVectorSpace): - ccarts = [] - for b in range(len(mat.codomain.spaces)): - ccarts.append(mat.codomain.spaces[b].cart) + comm = mat.domain.spaces[0].cart.global_comm nonzero_block_indices = ((0,0),) if isinstance(mat, StencilMatrix) else mat.nonzero_block_indices mat.update_ghost_regions() mat.remove_spurious_entries() - # Number of dimensions for each cart: - dndims = [dcart.ndim for dcart in dcarts] - cndims = [ccart.ndim for ccart in ccarts] - # Get global number of points per block: - dnpts = [dcart.npts for dcart in dcarts] # indexed [block, dimension]. Same for all processes. - # Get the number of points local to the current process: dnpts_local = get_npts_local(mat.domain) # indexed [block, dimension]. Different for each process. cnpts_local = get_npts_local(mat.codomain) # indexed [block, dimension]. Different for each process. + # Get the number of points per block, per process and per dimension: + dnpts_per_block_per_process = np.array(get_npts_per_block(mat.domain)) # global variable, indexed as [block, process, dimension] + cnpts_per_block_per_process = np.array(get_npts_per_block(mat.codomain)) # global variable, indexed as [block, process, dimension] + + # Get the index shift for each block and each process: + dindex_shift = get_index_shift_per_block_per_process(mat.domain) # global variable, indexed as [block, process, dimension] + cindex_shift = get_index_shift_per_block_per_process(mat.codomain) # global variable, indexed as [block, process, dimension] + globalsize = mat.shape # Sum over the blocks to get the total local size localsize = (np.sum(np.prod(cnpts_local, axis=1)), np.sum(np.prod(dnpts_local, axis=1))) - gmat = PETSc.Mat().create(comm=dcarts[0].global_comm) + gmat = PETSc.Mat().create(comm=comm) # Set global and local sizes: size=((local_rows, rows), (local_columns, columns)) gmat.setSizes(size=((localsize[0], globalsize[0]), (localsize[1], globalsize[1]))) - if dcarts[0].global_comm: + if comm: # Set PETSc sparse parallel matrix type gmat.setType("mpiaij") else: @@ -529,151 +493,62 @@ def mat_topetsc( mat ): import time output = open('output.txt', 'a') - comm=dcarts[0].global_comm - time_loop = np.empty((comm.Get_size(),)) - time_setValues = np.empty((comm.Get_size(),)) - time_assemble = np.empty((comm.Get_size(),)) + comm_size = 1 if not comm else comm.Get_size() + time_loop = np.empty((comm_size,)) + time_setValues = np.empty((comm_size,)) + time_assemble = np.empty((comm_size,)) - t_prev = time.time() for bc, bd in nonzero_block_indices: if isinstance(mat, BlockLinearOperator): mat_block = mat.blocks[bc][bd] - I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain) - - """ cs = ccarts[bc].starts - cghost_size = [pi*mi for pi,mi in zip(ccarts[bc].pads, ccarts[bc].shifts)] - - if dndims[bd] == 1 and cndims[bc] == 1: - I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain) - '''for i1 in range(cnpts_local[bc][0]): - nnz_in_row = 0 - i1_n = cs[0] + i1 - i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n,)) - - stencil_size = mat_block._data[i1 + cghost_size[0],:].shape - - for k1 in range(stencil_size[0]): - value = mat_block._data[i1 + cghost_size[0], k1] + dnpts_block = dnpts_per_block_per_process[bd] + cnpts_block = cnpts_per_block_per_process[bc] + dshift_block = dindex_shift[bd] + cshift_block = cindex_shift[bc] - j1_n = (i1_n + k1 - stencil_size[0]//2) % dnpts[bd][0] # modulus is necessary for periodic BC - - if value != 0: - j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, )) - - if nnz_in_row == 0: - rowmap.append(i_g) - - J.append(j_g) - V.append(value) - - nnz_in_row += 1 - - if nnz_in_row > 0: - I.append(I[-1] + nnz_in_row)''' - - elif dndims[bd] == 2 and cndims[bc] == 2: - I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain) - '''for i1 in np.arange(cnpts_local[bc][0]): - for i2 in np.arange(cnpts_local[bc][1]): - - nnz_in_row = 0 - - i1_n = cs[0] + i1 - i2_n = cs[1] + i2 - i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n)) - - stencil_size = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1],:,:].shape - - for k1 in range(stencil_size[0]): - for k2 in range(stencil_size[1]): - value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], k1, k2] - - j1_n = (i1_n + k1 - stencil_size[0]//2) % dnpts[bd][0] # modulus is necessary for periodic BC - j2_n = (i2_n + k2 - stencil_size[1]//2) % dnpts[bd][1] # modulus is necessary for periodic BC - - if value != 0: - j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n)) - - if nnz_in_row == 0: - rowmap.append(i_g) + I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain, dnpts_block, cnpts_block, dshift_block, cshift_block) - J.append(j_g) - V.append(value) - nnz_in_row += 1 - - if nnz_in_row > 0: - I.append(I[-1] + nnz_in_row)''' - - elif dndims[bd] == 3 and cndims[bc] == 3: - for i1 in np.arange(cnpts_local[bc][0]): - for i2 in np.arange(cnpts_local[bc][1]): - for i3 in np.arange(cnpts_local[bc][2]): - nnz_in_row = 0 - i1_n = cs[0] + i1 - i2_n = cs[1] + i2 - i3_n = cs[2] + i3 - i_g = psydac_to_petsc_global(mat.codomain, (bc,), (i1_n, i2_n, i3_n)) - - stencil_size = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], i3 + cghost_size[2],:,:,:].shape - - for k1 in range(stencil_size[0]): - for k2 in range(stencil_size[1]): - for k3 in range(stencil_size[2]): - value = mat_block._data[i1 + cghost_size[0], i2 + cghost_size[1], i3 + cghost_size[2], k1, k2, k3] - - j1_n = (i1_n + k1 - stencil_size[0]//2) % dnpts[bd][0] # modulus is necessary for periodic BC - j2_n = (i2_n + k2 - stencil_size[1]//2) % dnpts[bd][1] # modulus is necessary for periodic BC - j3_n = (i3_n + k3 - stencil_size[2]//2) % dnpts[bd][2] # modulus is necessary for periodic BC - - if value != 0: - j_g = psydac_to_petsc_global(mat.domain, (bd,), (j1_n, j2_n, j3_n)) - - if nnz_in_row == 0: - rowmap.append(i_g) - - J.append(j_g) - V.append(value) - - nnz_in_row += 1 - - if nnz_in_row > 0: - I.append(I[-1] + nnz_in_row) - """ - time_loop[comm.Get_rank()] = time.time() - t_prev + comm_rk = 0 if not comm else comm.Get_rank() + time_loop[comm_rk] = time.time() - t_prev print('Time for the loop: ', time.time() - t_prev) t_prev = time.time() # Set the values using IJV&rowmap format. The values are stored in a cache memory. gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC - time_setValues[comm.Get_rank()] = time.time() - t_prev + time_setValues[comm_rk] = time.time() - t_prev print('Time for the setValuesIJV: ', time.time() - t_prev) t_prev = time.time() # Assemble the matrix with the values from the cache. Here it is where PETSc exchanges global communication. gmat.assemble() - time_assemble[comm.Get_rank()] = time.time() - t_prev + time_assemble[comm_rk] = time.time() - t_prev print('Time for the assemble: ', time.time() - t_prev) + if comm_rk == 0: + print(f'\nnprocs={comm_size}\nProcess & global size & local size & Time loop & Time setValuesIJV & Time assemble ', file=output, flush=True) - if comm.Get_rank() == 0: - print(f'\nProcess & global size & local size & Time loop & Time setValuesIJV & Time assemble ', file=output, flush=True) - - for k in range(comm.Get_size()): - if k == comm.Get_rank(): + for k in range(comm_size): + if k == comm_rk: ls, gs = gmat.getSizes()[0] print(f'{k} & {gs} & {ls} & {time_loop[k]:.2f} & {time_setValues[k]:.2f} & {time_assemble[k]:.2f}', file=output, flush=True) - comm.Barrier() + if comm: + comm.Barrier() - avg_time_loop = comm.reduce(time_loop) - avg_time_setValues = comm.reduce(time_setValues, op=MPI.SUM, root=0) - avg_time_assemble = comm.reduce(time_assemble, op=MPI.SUM, root=0) + if comm: + avg_time_loop = comm.reduce(time_loop) + avg_time_setValues = comm.reduce(time_setValues, op=MPI.SUM, root=0) + avg_time_assemble = comm.reduce(time_assemble, op=MPI.SUM, root=0) + else: + avg_time_loop = time_loop + avg_time_setValues = time_setValues + avg_time_assemble = time_assemble - if comm.Get_rank() == 0: + if comm_rk == 0: print(f'Average & {np.mean(avg_time_loop):.2f} & {np.mean(avg_time_setValues):.2f} & {np.mean(avg_time_assemble):.2f}', file=output, flush=True) output.close() From ba230ba281ea352eb0a1e6cb28478332290a6fe6 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Fri, 31 May 2024 17:27:23 +0200 Subject: [PATCH 50/88] stop using create_domain() -- wip --- psydac/api/tests/build_domain.py | 35 ++- .../multipatch/multipatch_domain_utilities.py | 289 +++++++++--------- 2 files changed, 171 insertions(+), 153 deletions(-) diff --git a/psydac/api/tests/build_domain.py b/psydac/api/tests/build_domain.py index c34645dfc..cfc068213 100644 --- a/psydac/api/tests/build_domain.py +++ b/psydac/api/tests/build_domain.py @@ -189,23 +189,28 @@ def build_pretzel(domain_name='pretzel', r_min=None, r_max=None): domain_14, ]) - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], - [domain_6.get_boundary(axis=1, ext=-1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], - [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], - [domain_12.get_boundary(axis=1, ext=-1), domain_1.get_boundary(axis=1, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=1), 1], - [domain_7.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=-1), 1], - [domain_5.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=-1), 1], - [domain_12.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=+1),1], + axis_0 = 0 + axis_1 = 1 + ext_0 = -1 + ext_1 = +1 + + connectivity = [ + [(domain_1, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_5, axis_1, ext_1), (domain_6, axis_1, ext_1), 1], + [(domain_6, axis_1, ext_0), (domain_2, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_7, axis_1, ext_0), 1], + [(domain_7, axis_1, ext_1), (domain_3, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_9, axis_1, ext_0), 1], + [(domain_9, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_12, axis_1, ext_1), 1], + [(domain_12, axis_1, ext_0), (domain_1, axis_1, ext_0), 1], + [(domain_6, axis_0, ext_0), (domain_13, axis_0, ext_1), 1], + [(domain_7, axis_0, ext_0), (domain_13, axis_0, ext_0), 1], + [(domain_5, axis_0, ext_0), (domain_14, axis_0, ext_0), 1], + [(domain_12, axis_0, ext_0), (domain_14, axis_0, ext_1), 1], ] + domain = Domain.join(patches, connectivity, name=domain_name) - domain = create_domain(patches, interfaces, domain_name) return domain diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 2e8848130..4afa45c5d 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -42,35 +42,6 @@ def create_domain(patches, interfaces, name): I[1].domain), I[1].axis, I[1].ext), I[2])) return Domain.join(patches, connectivity, name) -# def get_annulus_fourpatches(r_min, r_max): -# -# dom_log_1 = Square('dom1',bounds1=(r_min, r_max), bounds2=(0, np.pi/2)) -# dom_log_2 = Square('dom2',bounds1=(r_min, r_max), bounds2=(np.pi/2, np.pi)) -# dom_log_3 = Square('dom3',bounds1=(r_min, r_max), bounds2=(np.pi, np.pi*3/2)) -# dom_log_4 = Square('dom4',bounds1=(r_min, r_max), bounds2=(np.pi*3/2, np.pi*2)) -# -# mapping_1 = PolarMapping('M1',2, c1= 0., c2= 0., rmin = 0., rmax=1.) -# mapping_2 = PolarMapping('M2',2, c1= 0., c2= 0., rmin = 0., rmax=1.) -# mapping_3 = PolarMapping('M3',2, c1= 0., c2= 0., rmin = 0., rmax=1.) -# mapping_4 = PolarMapping('M4',2, c1= 0., c2= 0., rmin = 0., rmax=1.) -# -# domain_1 = mapping_1(dom_log_1) -# domain_2 = mapping_2(dom_log_2) -# domain_3 = mapping_3(dom_log_3) -# domain_4 = mapping_4(dom_log_4) -# -# interfaces = [ -# [domain_1.get_boundary(axis=1, ext=1), domain_2.get_boundary(axis=1, ext=-1), 1], -# [domain_2.get_boundary(axis=1, ext=1), domain_3.get_boundary(axis=1, ext=-1), 1], -# [domain_3.get_boundary(axis=1, ext=1), domain_4.get_boundary(axis=1, ext=-1), 1], -# [domain_4.get_boundary(axis=1, ext=1), domain_1.get_boundary(axis=1, ext=-1), 1] -# ] -# patches = [domain_1, domain_2, domain_3, domain_4] -# domain = create_domain(patches, interfaces, name='domain') -# -# return domain - - def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=np.pi / 2): # AffineMapping: @@ -117,6 +88,15 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): The symbolic multipatch domain """ + connectivity = None + + # for readability + axis_0 = 0 + axis_1 = 1 + ext_0 = -1 + ext_1 = +1 + + # create the patches if domain_name == 'square_2': # reference square [0,pi]x[0,pi] with 2 patches # mp structure: @@ -138,8 +118,7 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): patches = [domain_1, domain_2] - interfaces = [[domain_1.get_boundary( - axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1]] + connectivity = [[(domain_1, axis_1, ext_1), (domain_2, axis_1, ext_0), 1]] elif domain_name == 'square_4': # C D # A B @@ -159,11 +138,11 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): patches = [A, B, C, D] - interfaces = [ - [A.get_boundary(axis=0, ext=1), B.get_boundary(axis=0, ext=-1), 1], - [A.get_boundary(axis=1, ext=1), C.get_boundary(axis=1, ext=-1), 1], - [C.get_boundary(axis=0, ext=1), D.get_boundary(axis=0, ext=-1), 1], - [B.get_boundary(axis=1, ext=1), D.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(A, axis_0, ext_1), (B, axis_0, ext_0), 1], + [(A, axis_1, ext_1), (C, axis_1, ext_0), 1], + [(C, axis_0, ext_1), (D, axis_0, ext_0), 1], + [(B, axis_1, ext_1), (D, axis_1, ext_0), 1], ] elif domain_name == 'square_6': @@ -240,14 +219,14 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6] - interfaces = [ - [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], - [domain_3.get_boundary(axis=0, ext=+1), domain_4.get_boundary(axis=0, ext=-1), 1], - [domain_5.get_boundary(axis=0, ext=+1), domain_6.get_boundary(axis=0, ext=-1), 1], - [domain_1.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(domain_1, axis_0, ext_1), (domain_2, axis_0, ext_0), 1], + [(domain_3, axis_0, ext_1), (domain_4, axis_0, ext_0), 1], + [(domain_5, axis_0, ext_1), (domain_6, axis_0, ext_0), 1], + [(domain_1, axis_1, ext_1), (domain_3, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_6, axis_1, ext_0), 1], ] elif domain_name in ['square_8', 'square_9']: @@ -369,15 +348,15 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_7, domain_8] - interfaces = [ - [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], - [domain_2.get_boundary(axis=0, ext=+1), domain_3.get_boundary(axis=0, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=+1), domain_7.get_boundary(axis=0, ext=-1), 1], - [domain_7.get_boundary(axis=0, ext=+1), domain_8.get_boundary(axis=0, ext=-1), 1], - [domain_1.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_8.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(domain_1, axis_0, ext_1), (domain_2, axis_0, ext_0), 1], + [(domain_2, axis_0, ext_1), (domain_3, axis_0, ext_0), 1], + [(domain_6, axis_0, ext_1), (domain_7, axis_0, ext_0), 1], + [(domain_7, axis_0, ext_1), (domain_8, axis_0, ext_0), 1], + [(domain_1, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_6, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_5, axis_1, ext_1), (domain_8, axis_1, ext_0), 1], ] elif domain_name == 'square_9': @@ -397,19 +376,19 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_8, domain_9] - interfaces = [ - [domain_1.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], - [domain_2.get_boundary(axis=0, ext=+1), domain_3.get_boundary(axis=0, ext=-1), 1], - [domain_4.get_boundary(axis=0, ext=+1), domain_9.get_boundary(axis=0, ext=-1), 1], - [domain_9.get_boundary(axis=0, ext=+1), domain_5.get_boundary(axis=0, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=+1), domain_7.get_boundary(axis=0, ext=-1), 1], - [domain_7.get_boundary(axis=0, ext=+1), domain_8.get_boundary(axis=0, ext=-1), 1], - [domain_1.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], - [domain_9.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_8.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(domain_1, axis_0, ext_1), (domain_2, axis_0, ext_0), 1], + [(domain_2, axis_0, ext_1), (domain_3, axis_0, ext_0), 1], + [(domain_4, axis_0, ext_1), (domain_9, axis_0, ext_0), 1], + [(domain_9, axis_0, ext_1), (domain_5, axis_0, ext_0), 1], + [(domain_6, axis_0, ext_1), (domain_7, axis_0, ext_0), 1], + [(domain_7, axis_0, ext_1), (domain_8, axis_0, ext_0), 1], + [(domain_1, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_6, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_9, axis_1, ext_0), 1], + [(domain_9, axis_1, ext_1), (domain_7, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_5, axis_1, ext_1), (domain_8, axis_1, ext_0), 1], ] else: @@ -668,20 +647,20 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_14, ]) - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], - [domain_6.get_boundary(axis=1, ext=-1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], - [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], - [domain_12.get_boundary(axis=1, ext=-1), domain_1.get_boundary(axis=1, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=1), 1], - [domain_7.get_boundary(axis=0, ext=-1), domain_13.get_boundary(axis=0, ext=-1), 1], - [domain_5.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=-1), 1], - [domain_12.get_boundary(axis=0, ext=-1), domain_14.get_boundary(axis=0, ext=+1), 1], + connectivity = [ + [(domain_1, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_5, axis_1, ext_1), (domain_6, axis_1, ext_1), 1], + [(domain_6, axis_1, ext_0), (domain_2, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_7, axis_1, ext_0), 1], + [(domain_7, axis_1, ext_1), (domain_3, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_9, axis_1, ext_0), 1], + [(domain_9, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_12, axis_1, ext_1), 1], + [(domain_12, axis_1, ext_0), (domain_1, axis_1, ext_0), 1], + [(domain_6, axis_0, ext_0), (domain_13, axis_0, ext_1), 1], + [(domain_7, axis_0, ext_0), (domain_13, axis_0, ext_0), 1], + [(domain_5, axis_0, ext_0), (domain_14, axis_0, ext_0), 1], + [(domain_12, axis_0, ext_0), (domain_14, axis_0, ext_1), 1], ] elif domain_name == 'pretzel_f': @@ -706,30 +685,76 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_14_2, ]) - interfaces = [ - [domain_1_1.get_boundary(axis=1, ext=+1), domain_1_2.get_boundary(axis=1, ext=-1), 1], - [domain_1_2.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], - [domain_6.get_boundary(axis=1, ext=-1), domain_2_1.get_boundary(axis=1, ext=-1), 1], - [domain_2_1.get_boundary(axis=1, ext=+1), domain_2_2.get_boundary(axis=1, ext=-1), 1], - [domain_2_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3_1.get_boundary(axis=1, ext=-1), 1], - [domain_3_1.get_boundary(axis=1, ext=+1), domain_3_2.get_boundary(axis=1, ext=-1), 1], - [domain_3_2.get_boundary(axis=1, ext=+1), domain_9_1.get_boundary(axis=1, ext=-1), 1], - [domain_9_1.get_boundary(axis=1, ext=+1), domain_9_2.get_boundary(axis=1, ext=-1), 1], - [domain_9_2.get_boundary(axis=1, ext=+1), domain_4_1.get_boundary(axis=1, ext=-1), 1], - [domain_4_1.get_boundary(axis=1, ext=+1), domain_4_2.get_boundary(axis=1, ext=-1), 1], - [domain_4_2.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], - [domain_12.get_boundary(axis=1, ext=-1), domain_1_1.get_boundary(axis=1, ext=-1), 1], - [domain_6.get_boundary(axis=0, ext=-1), domain_13_2.get_boundary(axis=0, ext=1), 1], - [domain_13_2.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=1), 1], - [domain_7.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=-1), 1], - [domain_5.get_boundary(axis=0, ext=-1), domain_14_1.get_boundary(axis=0, ext=-1), 1], - [domain_14_1.get_boundary(axis=0, ext=+1), domain_14_2.get_boundary(axis=0, ext=-1), 1], - [domain_12.get_boundary(axis=0, ext=-1), domain_14_2.get_boundary(axis=0, ext=+1), 1], + # interfaces = [ + # [domain_1_1.get_boundary(axis=1, ext=+1), domain_1_2.get_boundary(axis=1, ext=-1), 1], + # [domain_1_2.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + # [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], + # [domain_6.get_boundary(axis=1, ext=-1), domain_2_1.get_boundary(axis=1, ext=-1), 1], + # [domain_2_1.get_boundary(axis=1, ext=+1), domain_2_2.get_boundary(axis=1, ext=-1), 1], + # [domain_2_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], + # [domain_7.get_boundary(axis=1, ext=+1), domain_3_1.get_boundary(axis=1, ext=-1), 1], + # [domain_3_1.get_boundary(axis=1, ext=+1), domain_3_2.get_boundary(axis=1, ext=-1), 1], + # [domain_3_2.get_boundary(axis=1, ext=+1), domain_9_1.get_boundary(axis=1, ext=-1), 1], + # [domain_9_1.get_boundary(axis=1, ext=+1), domain_9_2.get_boundary(axis=1, ext=-1), 1], + # [domain_9_2.get_boundary(axis=1, ext=+1), domain_4_1.get_boundary(axis=1, ext=-1), 1], + # [domain_4_1.get_boundary(axis=1, ext=+1), domain_4_2.get_boundary(axis=1, ext=-1), 1], + # [domain_4_2.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], + # [domain_12.get_boundary(axis=1, ext=-1), domain_1_1.get_boundary(axis=1, ext=-1), 1], + # [domain_6.get_boundary(axis=0, ext=-1), domain_13_2.get_boundary(axis=0, ext=1), 1], + # [domain_13_2.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=1), 1], + # [domain_7.get_boundary(axis=0, ext=-1), domain_13_1.get_boundary(axis=0, ext=-1), 1], + # [domain_5.get_boundary(axis=0, ext=-1), domain_14_1.get_boundary(axis=0, ext=-1), 1], + # [domain_14_1.get_boundary(axis=0, ext=+1), domain_14_2.get_boundary(axis=0, ext=-1), 1], + # [domain_12.get_boundary(axis=0, ext=-1), domain_14_2.get_boundary(axis=0, ext=+1), 1], + # ] + + # interfaces = [ + # [domain_1_1.get_boundary(axis=1, ext=+1), domain_1_2.get_boundary(axis=1, ext=-1), 1], + # [domain_1_2.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], + # [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=1), 1], + # [domain_6.get_boundary(axis=1, ext=-1), domain_2_1.get_boundary(axis=1, ext=-1), 1], + # [domain_2_1.get_boundary(axis=1, ext=+1), domain_2_2.get_boundary(axis=1, ext=-1), 1], + # [domain_2_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], + # [domain_7.get_boundary(axis=1, ext=+1), domain_3_1.get_boundary(axis=1, ext=-1), 1], + # [domain_3_1.get_boundary(axis=1, ext=+1), domain_3_2.get_boundary(axis=1, ext=-1), 1], + # [domain_3_2.get_boundary(axis=1, ext=+1), domain_9_1.get_boundary(axis=1, ext=-1), 1], + # [domain_9_1.get_boundary(axis=1, ext=+1), domain_9_2.get_boundary(axis=1, ext=-1), 1], + # [domain_9_2.get_boundary(axis=1, ext=+1), domain_4_1.get_boundary(axis=1, ext=-1), 1], + # [domain_4_1.get_boundary(axis=1, ext=+1), domain_4_2.get_boundary(axis=1, ext=-1), 1], + # [domain_4_2.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=1), 1], + # [domain_12.get_boundary(axis=1, ext=-1), domain_1_1.get_boundary(axis=1, ext=-1), 1], + # [domain_6, axis_0, ext=-1), domain_13_2, axis_0, ext=1), 1], + # [domain_13_2, axis_0, ext=-1), domain_13_1, axis_0, ext=1), 1], + # [domain_7, axis_0, ext=-1), domain_13_1, axis_0, ext=-1), 1], + # [domain_5, axis_0, ext=-1), domain_14_1, axis_0, ext=-1), 1], + # [domain_14_1, axis_0, ext=+1), domain_14_2, axis_0, ext=-1), 1], + # [domain_12, axis_0, ext=-1), domain_14_2, axis_0, ext=+1), 1], + # ] + + connectivity = [ + [(domain_1_1, axis_1, ext_1), (domain_1_2, axis_1, ext_0), 1], + [(domain_1_2, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_5, axis_1, ext_1), (domain_6, axis_1, ext_1), 1], + [(domain_6, axis_1, ext_0), (domain_2_1, axis_1, ext_0), 1], + [(domain_2_1, axis_1, ext_1), (domain_2_2, axis_1, ext_0), 1], + [(domain_2_2, axis_1, ext_1), (domain_7, axis_1, ext_0), 1], + [(domain_7, axis_1, ext_1), (domain_3_1, axis_1, ext_0), 1], + [(domain_3_1, axis_1, ext_1), (domain_3_2, axis_1, ext_0), 1], + [(domain_3_2, axis_1, ext_1), (domain_9_1, axis_1, ext_0), 1], + [(domain_9_1, axis_1, ext_1), (domain_9_2, axis_1, ext_0), 1], + [(domain_9_2, axis_1, ext_1), (domain_4_1, axis_1, ext_0), 1], + [(domain_4_1, axis_1, ext_1), (domain_4_2, axis_1, ext_0), 1], + [(domain_4_2, axis_1, ext_1), (domain_12, axis_1, ext_1), 1], + [(domain_12, axis_1, ext_0), (domain_1_1, axis_1, ext_0), 1], + [(domain_6, axis_0, ext_0), (domain_13_2, axis_0, ext_1), 1], + [(domain_13_2, axis_0, ext_0), (domain_13_1, axis_0, ext_1), 1], + [(domain_7, axis_0, ext_0), (domain_13_1, axis_0, ext_0), 1], + [(domain_5, axis_0, ext_0), (domain_14_1, axis_0, ext_0), 1], + [(domain_14_1, axis_0, ext_1), (domain_14_2, axis_0, ext_0), 1], + [(domain_12, axis_0, ext_0), (domain_14_2, axis_0, ext_1), 1], ] - # reste: 13 et 14 + # reste: 13 et 14 elif domain_name == 'pretzel_annulus': # only the annulus part of the pretzel (not the inner arcs) @@ -746,16 +771,16 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_12, ]) - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_5.get_boundary(axis=1, ext=-1), 1], - [domain_5.get_boundary(axis=1, ext=+1), domain_6.get_boundary(axis=1, ext=-1), 1], - [domain_6.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_7.get_boundary(axis=1, ext=-1), 1], - [domain_7.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_9.get_boundary(axis=1, ext=-1), 1], - [domain_9.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_12.get_boundary(axis=1, ext=-1), 1], - [domain_12.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(domain_1, axis_1, ext_1), (domain_5, axis_1, ext_0), 1], + [(domain_5, axis_1, ext_1), (domain_6, axis_1, ext_0), 1], + [(domain_6, axis_1, ext_1), (domain_2, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_7, axis_1, ext_0), 1], + [(domain_7, axis_1, ext_1), (domain_3, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_9, axis_1, ext_0), 1], + [(domain_9, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_12, axis_1, ext_0), 1], + [(domain_12, axis_1, ext_1), (domain_1, axis_1, ext_0), 1], ] elif domain_name == 'pretzel_debug': @@ -764,8 +789,7 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_10, ]) - interfaces = [[domain_1.get_boundary( - axis=1, ext=+1), domain_10.get_boundary(axis=1, ext=-1), 1], ] + connectivity = [[(domain_1, axis_1, ext_1), (domain_10, axis_1, ext_0), 1], ] else: raise NotImplementedError @@ -797,9 +821,9 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): domain_3, ]) - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=0, ext=+1), domain_2.get_boundary(axis=0, ext=-1), 1], + connectivity = [ + [(domain_1, axis_1, ext_1), (domain_2, axis_1, ext_0), 1], + [(domain_3, axis_0, ext_1), (domain_2, axis_0, ext_0), 1], ] elif domain_name in ['annulus_3', 'annulus_4']: @@ -833,10 +857,10 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): patches = [domain_1, domain_2, domain_3] - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(domain_1, axis_1, ext_1), (domain_2, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_3, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_1, axis_1, ext_0), 1], ] elif domain_name == 'annulus_4': @@ -870,11 +894,11 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): patches = [domain_1, domain_2, domain_3, domain_4] - interfaces = [ - [domain_1.get_boundary(axis=1, ext=+1), domain_2.get_boundary(axis=1, ext=-1), 1], - [domain_2.get_boundary(axis=1, ext=+1), domain_3.get_boundary(axis=1, ext=-1), 1], - [domain_3.get_boundary(axis=1, ext=+1), domain_4.get_boundary(axis=1, ext=-1), 1], - [domain_4.get_boundary(axis=1, ext=+1), domain_1.get_boundary(axis=1, ext=-1), 1], + connectivity = [ + [(domain_1, axis_1, ext_1), (domain_2, axis_1, ext_0), 1], + [(domain_2, axis_1, ext_1), (domain_3, axis_1, ext_0), 1], + [(domain_3, axis_1, ext_1), (domain_4, axis_1, ext_0), 1], + [(domain_4, axis_1, ext_1), (domain_1, axis_1, ext_0), 1], ] else: raise NotImplementedError @@ -882,12 +906,7 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): else: raise NotImplementedError - domain = create_domain(patches, interfaces, name='domain') - - # print("int: ", domain.interior) - # print("bound: ", domain.boundary) - # print("len(bound): ", len(domain.boundary)) - # print("interfaces: ", domain.interfaces) + domain = Domain.join(patches, connectivity, name='domain') return domain @@ -983,12 +1002,6 @@ def F(name): return CollelaMapping2D(name, eps=0.5) for i in range(nb_patch_x): patches.extend(list_domain[i]) - # domain = union([domain_1, domain_2, domain_3, domain_4, domain_5, domain_6], name = 'domain') - - # patches = [domain_1, domain_2, domain_3, domain_4, domain_5, domain_6] - - # domain = union(flat_list, name='domain') - interfaces = [] # interfaces in x list_right_bnd = [] From 685f4a5584082375e4626789cdcc87d45af03245 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Sun, 2 Jun 2024 19:46:05 +0200 Subject: [PATCH 51/88] use Domain.join() to build non-matching domains --- ...on_matching_multipatch_domain_utilities.py | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py index 44ac67461..81b0bcfbe 100644 --- a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py @@ -1,6 +1,6 @@ from mpi4py import MPI import numpy as np -from sympde.topology import Square +from sympde.topology import Square, Domain from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping from sympde.topology import Boundary, Interface, Union @@ -20,6 +20,8 @@ def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): """ + todo: rename this function and improve docstring (see comments on PR #320) + Create a 2D multipatch square domain with the prescribed number of patches in each direction. Parameters @@ -70,25 +72,31 @@ def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): if ncells[i, j] is not None: flat_list.append(list_domain[i][j]) - domains = flat_list - interfaces = [] + patches = flat_list + axis_0 = 0 + axis_1 = 1 + ext_0 = -1 + ext_1 = +1 + connectivity = [] # interfaces in y for j in range(nb_patchy): - interfaces.extend([[list_domain[i][j].get_boundary(axis=0, ext=+1), - list_domain[i +1][j].get_boundary(axis=0, ext=-1), 1] - for i in range(nb_patchx -1) if ncells[i][j] is not None and ncells[i +1][j] is not None]) + connectivity.extend([ + [(list_domain[i ][j], axis_0, ext_1), + (list_domain[i+1][j], axis_0, ext_0), + 1] + for i in range(nb_patchx -1) if ncells[i][j] is not None and ncells[i+1][j] is not None]) # interfaces in x for i in range(nb_patchx): - interfaces.extend([[list_domain[i][j].get_boundary(axis=1, ext=+ - 1), list_domain[i][j + - 1].get_boundary(axis=1, ext=- - 1), 1] for j in range(nb_patchy - - 1) if ncells[i][j] is not None and ncells[i][j + - 1] is not None]) - - domain = create_domain(domains, interfaces, name='domain') + connectivity.extend([ + [(list_domain[i][j ], axis_1, ext_1), + (list_domain[i][j+1], axis_1, ext_0), + 1] + + for j in range(nb_patchy -1) if ncells[i][j] is not None and ncells[i][j+1] is not None]) + + domain = Domain.join(patches, connectivity, name='domain') return domain From 842f7fcdcdd35bbfd2038093cc65fa919598465f Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Mon, 3 Jun 2024 14:53:50 +0200 Subject: [PATCH 52/88] clean time measurements --- psydac/linalg/topetsc.py | 51 ---------------------------------------- 1 file changed, 51 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index dd8945d7f..3161d5c41 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -3,11 +3,8 @@ from psydac.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix from psydac.linalg.basic import VectorSpace -from scipy.sparse import coo_matrix, bmat from itertools import product as cartesian_prod -from mpi4py import MPI - __all__ = ('petsc_local_to_psydac', 'psydac_to_petsc_global', 'get_npts_local', 'get_npts_per_block', 'vec_topetsc', 'mat_topetsc') from .kernels.stencil2IJV_kernels import stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C @@ -77,7 +74,6 @@ def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, dnpts_block, return I, J, V, rowmap - def petsc_local_to_psydac( V : VectorSpace, petsc_index : int): @@ -490,16 +486,6 @@ def mat_topetsc( mat ): mat_block = mat - import time - - output = open('output.txt', 'a') - - comm_size = 1 if not comm else comm.Get_size() - time_loop = np.empty((comm_size,)) - time_setValues = np.empty((comm_size,)) - time_assemble = np.empty((comm_size,)) - - t_prev = time.time() for bc, bd in nonzero_block_indices: if isinstance(mat, BlockLinearOperator): mat_block = mat.blocks[bc][bd] @@ -510,47 +496,10 @@ def mat_topetsc( mat ): I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain, dnpts_block, cnpts_block, dshift_block, cshift_block) - - comm_rk = 0 if not comm else comm.Get_rank() - time_loop[comm_rk] = time.time() - t_prev - - print('Time for the loop: ', time.time() - t_prev) - t_prev = time.time() # Set the values using IJV&rowmap format. The values are stored in a cache memory. gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC - time_setValues[comm_rk] = time.time() - t_prev - - print('Time for the setValuesIJV: ', time.time() - t_prev) - - t_prev = time.time() # Assemble the matrix with the values from the cache. Here it is where PETSc exchanges global communication. gmat.assemble() - time_assemble[comm_rk] = time.time() - t_prev - print('Time for the assemble: ', time.time() - t_prev) - - if comm_rk == 0: - print(f'\nnprocs={comm_size}\nProcess & global size & local size & Time loop & Time setValuesIJV & Time assemble ', file=output, flush=True) - - for k in range(comm_size): - if k == comm_rk: - ls, gs = gmat.getSizes()[0] - print(f'{k} & {gs} & {ls} & {time_loop[k]:.2f} & {time_setValues[k]:.2f} & {time_assemble[k]:.2f}', file=output, flush=True) - if comm: - comm.Barrier() - - if comm: - avg_time_loop = comm.reduce(time_loop) - avg_time_setValues = comm.reduce(time_setValues, op=MPI.SUM, root=0) - avg_time_assemble = comm.reduce(time_assemble, op=MPI.SUM, root=0) - else: - avg_time_loop = time_loop - avg_time_setValues = time_setValues - avg_time_assemble = time_assemble - - if comm_rk == 0: - print(f'Average & {np.mean(avg_time_loop):.2f} & {np.mean(avg_time_setValues):.2f} & {np.mean(avg_time_assemble):.2f}', file=output, flush=True) - - output.close() return gmat From a3a534110df5a78d63efce97a297dbf93ccc8fdc Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 4 Jun 2024 13:32:32 +0200 Subject: [PATCH 53/88] using temp function for sympde Domain.join --- psydac/api/tests/build_domain.py | 12 ++++++++++- .../multipatch/multipatch_domain_utilities.py | 20 +++++++++++++++++-- ...on_matching_multipatch_domain_utilities.py | 7 ++++--- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/psydac/api/tests/build_domain.py b/psydac/api/tests/build_domain.py index cfc068213..a6ca8ec33 100644 --- a/psydac/api/tests/build_domain.py +++ b/psydac/api/tests/build_domain.py @@ -1,10 +1,16 @@ # coding: utf-8 +# todo: this file has a lot of redundant code with psydac/feec/multipatch/multipatch_domain_utilities.py +# it should probably be removed in the future + import numpy as np from sympde.topology import Square, Domain from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping +# remove after sympde PR #155 is merged and call Domain.join instead +from psydac.feec.multipatch.multipatch_domain_utilities import sympde_Domain_join + #============================================================================== # small extension to SymPDE: class TransposedPolarMapping(Mapping): @@ -20,6 +26,7 @@ class TransposedPolarMapping(Mapping): _ldim = 2 _pdim = 2 +# todo: remove this def create_domain(patches, interfaces, name): connectivity = [] patches_interiors = [D.interior for D in patches] @@ -54,6 +61,8 @@ def flip_axis(name='no_name', c1=0., c2=0.): ) #============================================================================== + +# todo: use build_multipatch_domain instead def build_pretzel(domain_name='pretzel', r_min=None, r_max=None): """ design pretzel-like domain @@ -210,7 +219,8 @@ def build_pretzel(domain_name='pretzel', r_min=None, r_max=None): [(domain_12, axis_0, ext_0), (domain_14, axis_0, ext_1), 1], ] - domain = Domain.join(patches, connectivity, name=domain_name) + # domain = Domain.join(patches, connectivity, name=domain_name) + domain = sympde_Domain_join(patches, connectivity, name=domain_name) return domain diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 4afa45c5d..07ef83a7d 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -33,6 +33,7 @@ class TransposedPolarMapping(Mapping): def create_domain(patches, interfaces, name): + # todo: remove this function and just use Domain.join connectivity = [] patches_interiors = [D.interior for D in patches] for I in interfaces: @@ -42,6 +43,20 @@ def create_domain(patches, interfaces, name): I[1].domain), I[1].axis, I[1].ext), I[2])) return Domain.join(patches, connectivity, name) + +def sympde_Domain_join(patches, connectivity, name): + """ + temporary fix while sympde PR #155 is not merged + """ + connectivity_by_indices = [] + for I in connectivity: + connectivity_by_indices.append( + [(patches.index(I[0][0]), I[0][1], I[0][2]), + (patches.index(I[1][0]), I[1][1], I[1][2]), + I[2]]) + return Domain.join(patches, connectivity_by_indices, name) + + def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=np.pi / 2): # AffineMapping: @@ -906,8 +921,9 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): else: raise NotImplementedError - domain = Domain.join(patches, connectivity, name='domain') - + # domain = Domain.join(patches, connectivity, name='domain') + domain = sympde_Domain_join(patches, connectivity, name='domain') + return domain diff --git a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py index 81b0bcfbe..6cdbd6607 100644 --- a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py @@ -15,8 +15,7 @@ from psydac.api.settings import PSYDAC_BACKENDS from psydac.fem.splines import SplineSpace -from psydac.feec.multipatch.multipatch_domain_utilities import create_domain - +from psydac.feec.multipatch.multipatch_domain_utilities import sympde_Domain_join def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): """ @@ -96,7 +95,9 @@ def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): for j in range(nb_patchy -1) if ncells[i][j] is not None and ncells[i][j+1] is not None]) - domain = Domain.join(patches, connectivity, name='domain') + # domain = Domain.join(patches, connectivity, name='domain') + domain = sympde_Domain_join(patches, connectivity, name='domain') + return domain From 220f1cf3852779ddf9f8d93f6dd66da33ddc075d Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 4 Jun 2024 13:49:22 +0200 Subject: [PATCH 54/88] adding ref --- psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py index c37b73238..f01875911 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py +++ b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py @@ -1,5 +1,7 @@ """ - Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization + Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization, following + A. Buffa and I. Perugia, “Discontinuous Galerkin Approximation of the Maxwell Eigenproblem” + SIAM Journal on Numerical Analysis 44 (2006) """ import os From 73019d189fafc1ad48321eef2b07303b7b79d236 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Fri, 7 Jun 2024 16:57:29 +0200 Subject: [PATCH 55/88] call exposed domain and codomain in linalg/basic.py the `__neg__` function in class `LinearOperator` uses the `_domain` and `_codomain` attributes, which is wrong since these may not be defined. It should use the exposed (interface) attributes `domain` and `codomain` --- psydac/linalg/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 07ff42670..81079a5c7 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -279,7 +279,7 @@ def __neg__(self): a new object of the class ScaledLinearOperator. """ - return ScaledLinearOperator(self._domain, self._codomain, -1.0, self) + return ScaledLinearOperator(self.domain, self.codomain, -1.0, self) def __mul__(self, c): """ From 52fec60e74018caf5fa435419e6c0a6deffe3920 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 10 Jun 2024 16:50:32 +0200 Subject: [PATCH 56/88] Merge example scripts and get rid of get_source_and_solution_OBSOLETE --- .../examples/h1_source_pbms_conga_2d.py | 239 +++++----- .../examples/hcurl_eigen_pbms_conga_2d.py | 319 ++++++++----- .../hcurl_eigen_pbms_dg_2d.py} | 104 ++--- .../hcurl_eigen_testcases.py} | 23 +- .../examples/hcurl_source_pbms_conga_2d.py | 310 ++++++------- .../hcurl_source_testcase.py | 55 +-- .../multipatch/examples/ppc_test_cases.py | 266 ----------- .../timedomain_maxwell.py} | 14 +- .../timedomain_maxwell_testcase.py | 0 .../feec/multipatch/examples_nc/__init__.py | 0 .../examples_nc/h1_source_pbms_nc.py | 322 -------------- .../examples_nc/hcurl_eigen_pbms_nc.py | 379 ---------------- .../examples_nc/hcurl_source_pbms_nc.py | 419 ------------------ .../tests/test_feec_maxwell_multipatch_2d.py | 61 ++- .../tests/test_feec_poisson_multipatch_2d.py | 21 +- 15 files changed, 555 insertions(+), 1977 deletions(-) rename psydac/feec/multipatch/{examples_nc/hcurl_eigen_pbms_dg.py => examples/hcurl_eigen_pbms_dg_2d.py} (79%) rename psydac/feec/multipatch/{examples_nc/hcurl_eigen_testcase.py => examples/hcurl_eigen_testcases.py} (94%) rename psydac/feec/multipatch/{examples_nc => examples}/hcurl_source_testcase.py (75%) rename psydac/feec/multipatch/{examples_nc/timedomain_maxwell_nc.py => examples/timedomain_maxwell.py} (99%) rename psydac/feec/multipatch/{examples_nc => examples}/timedomain_maxwell_testcase.py (100%) delete mode 100644 psydac/feec/multipatch/examples_nc/__init__.py delete mode 100644 psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py delete mode 100644 psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py delete mode 100644 psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index 9e792c3c3..702897d42 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -1,4 +1,17 @@ -# coding: utf-8 +""" + solver for the problem: find u in H^1, such that + + A u = f on \\Omega + u = u_bc on \\partial \\Omega + + where the operator + + A u := eta * u - mu * div grad u + + is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, + + V0h --grad-> V1h -—curl-> V2h +""" from mpi4py import MPI @@ -9,6 +22,7 @@ from sympy import lambdify from scipy.sparse.linalg import spsolve +from sympde.calculus import dot from sympde.expr.expr import LinearForm from sympde.expr.expr import integral, Norm from sympde.topology import Derham @@ -17,23 +31,26 @@ from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.multipatch.api import discretize from psydac.feec.pull_push import pull_2d_h1 +from psydac.feec.multipatch.utils_conga_2d import P0_phys from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_h1 from psydac.feec.multipatch.utilities import time_count from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection +from psydac.api.postprocessing import OutputManager, PostProcessManager from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField +from psydac.api.postprocessing import OutputManager, PostProcessManager + def solve_h1_source_pbm( - nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2', source_type='manu_poisson', - eta=-10., mu=1., gamma_h=10., - plot_source=False, plot_dir=None, hide_plots=True + nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_type='manu_poisson_elliptic', + eta=-10., mu=1., gamma_h=10., plot_dir=None, ): """ solver for the problem: find u in H^1, such that @@ -61,24 +78,22 @@ def solve_h1_source_pbm( :param nc: nb of cells per dimension, in each patch :param deg: coordinate degree in each patch + :param domain_name: name of the domain + :param backend_language: backend language for the operators + :param source_type: must be implemented in get_source_and_solution_h1 + :param eta: coefficient of the elliptic operator + :param mu: coefficient of the elliptic operator :param gamma_h: jump penalization parameter - :param source_proj: approximation operator for the source, possible values are 'P_geom' or 'P_L2' - :param source_type: must be implemented in get_source_and_solution() + :param plot_dir: directory for the plots (if None, no plots are generated) """ - ncells = [nc, nc] degree = [deg, deg] - # if backend_language is None: - # backend_language='python' - # print('[note: using '+backend_language+ ' backends in discretize functions]') - print('---------------------------------------------------------------------------------------------------------') print('Starting solve_h1_source_pbm function with: ') - print(' ncells = {}'.format(ncells)) + print(' ncells = {}'.format(nc)) print(' degree = {}'.format(degree)) print(' domain_name = {}'.format(domain_name)) - print(' source_proj = {}'.format(source_proj)) print(' backend_language = {}'.format(backend_language)) print('---------------------------------------------------------------------------------------------------------') @@ -87,6 +102,13 @@ def solve_h1_source_pbm( mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) mappings_list = list(mappings.values()) + + if type(nc) == int: + ncells = [nc, nc] + else: + ncells = {patch.name: [nc[i], nc[i]] + for (i, patch) in enumerate(domain.interior)} + domain_h = discretize(domain, ncells=ncells) print('building the symbolic and discrete deRham sequences...') @@ -105,7 +127,6 @@ def solve_h1_source_pbm( # broken (patch-wise) differential operators bD0, bD1 = derham_h.broken_derivatives_as_operators bD0_m = bD0.to_sparse_matrix() - # bD1_m = bD1.to_sparse_matrix() print('building the discrete operators:') print('commuting projection operators...') @@ -120,35 +141,21 @@ def solve_h1_source_pbm( H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language) H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) - H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions # of the continuous deRham sequence) cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) - # cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) - - if not os.path.exists(plot_dir): - os.makedirs(plot_dir) def lift_u_bc(u_bc): if u_bc is not None: - print( - 'lifting the boundary condition in V0h... [warning: Not Tested Yet!]') - # note: for simplicity we apply the full P1 on u_bc, but we only - # need to set the boundary dofs - u_bc = lambdify(domain.coordinates, u_bc) - u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) - for m in mappings_list] - # it's a bit weird to apply P1 on the list of (pulled back) logical - # fields -- why not just apply it on u_bc ? - uh_bc = P0(u_bc_log) - ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very - # good approximation of uh_c ...) + print('lifting the boundary condition in V0h... [warning: Not Tested Yet!]') + d_ubc_c = derham_h.get_dual_dofs(space='V0', f=u_bc, backend_language=backend_language, return_format='numpy_array') + ubc_c = dH0_m.dot(d_ubc_c) + ubc_c = ubc_c - cP0_m.dot(ubc_c) else: ubc_c = None @@ -160,58 +167,22 @@ def lift_u_bc(u_bc): # jump penalization: jump_penal_m = I0_m - cP0_m - JP0_m = jump_penal_m.transpose() * H0_m * jump_penal_m + JP0_m = jump_penal_m.transpose() @ H0_m @ jump_penal_m # useful for the boundary condition (if present) pre_A_m = cP0_m.transpose() @ (eta * H0_m - mu * pre_DG_m) A_m = pre_A_m @ cP0_m + gamma_h * JP0_m print('getting the source and ref solution...') - # (not all the returned functions are useful here) - N_diag = 200 - method = 'conga' - f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( + f_scal, u_bc, u_ex = get_source_and_solution_h1( source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - refsol_params=[N_diag, method, source_proj], ) # compute approximate source f_h - b_c = f_c = None - if source_proj == 'P_geom': - print('projecting the source with commuting projection P0...') - f = lambdify(domain.coordinates, f_scal) - f_log = [pull_2d_h1(f, m.get_callable_mapping()) - for m in mappings_list] - f_h = P0(f_log) - f_c = f_h.coeffs.toarray() - b_c = H0_m.dot(f_c) - - elif source_proj == 'P_L2': - print('projecting the source with L2 projection...') - v = element_of(V0h.symbolic_space, name='v') - expr = f_scal * v - l = LinearForm(v, integral(domain, expr)) - lh = discretize(l, domain_h, V0h) - b = lh.assemble() - b_c = b.toarray() - if plot_source: - f_c = dH0_m.dot(b_c) - else: - raise ValueError(source_proj) - - if plot_source: - plot_field( - numpy_coeffs=f_c, - Vh=V0h, - space_kind='h1', - domain=domain, - title='f_h with P = ' + - source_proj, - filename=plot_dir + - 'fh_' + - source_proj + - '.png', - hide_plot=hide_plots) + b_c = derham_h.get_dual_dofs(space='V0', f=f_scal, backend_language=backend_language, return_format='numpy_array') + # source in primal sequence for plotting + f_c = dH0_m.dot(b_c) + b_c = cP0_m.transpose() @ b_c ubc_c = lift_u_bc(u_bc) @@ -234,58 +205,83 @@ def lift_u_bc(u_bc): uh_c += ubc_c print('getting and plotting the FEM solution from numpy coefs array...') - title = r'solution $\phi_h$ (amplitude)' - params_str = 'eta={}_mu={}_gamma_h={}'.format(eta, mu, gamma_h) - plot_field( - numpy_coeffs=uh_c, - Vh=V0h, - space_kind='h1', - domain=domain, - title=title, - filename=plot_dir + - params_str + - '_phi_h.png', - hide_plot=hide_plots) if u_ex: - u = element_of(V0h.symbolic_space, name='u') - l2norm = Norm(u - u_ex, domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V0h) - uh_c = array_to_psydac(uh_c, V0h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) - return l2_error - + u_ex_c = derham_h.get_dual_dofs(space='V0', f=u_ex, backend_language=backend_language, return_format='numpy_array') + u_ex_c = dH0_m.dot(u_ex_c) + + if plot_dir is not None: + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + + OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') + OM.add_spaces(V0h=V0h) + OM.set_static() + + stencil_coeffs = array_to_psydac(uh_c, V0h.vector_space) + vh = FemField(V0h, coeffs=stencil_coeffs) + OM.export_fields(vh=vh) + + stencil_coeffs = array_to_psydac(f_c, V0h.vector_space) + fh = FemField(V0h, coeffs=stencil_coeffs) + OM.export_fields(fh=fh) + + if u_ex: + stencil_coeffs = array_to_psydac(u_ex_c, V0h.vector_space) + uh_ex = FemField(V0h, coeffs=stencil_coeffs) + OM.export_fields(uh_ex=uh_ex) + + OM.export_space_info() + OM.close() + + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + '/spaces.yml', + fields_file=plot_dir + '/fields.h5') + + PM.export_to_vtk( + plot_dir + "/u_h", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='vh') + + PM.export_to_vtk( + plot_dir + "/f_h", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='fh') + + if u_ex: + PM.export_to_vtk( + plot_dir + "/uh_ex", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='uh_ex') + + PM.close() -if __name__ == '__main__': + if u_ex: + err = uh_c - u_ex_c + rel_err = np.sqrt(np.dot(err, H0_m.dot(err)))/np.sqrt(np.dot(u_ex_c,H0_m.dot(u_ex_c))) + + return rel_err - t_stamp_full = time_count() - quick_run = True - # quick_run = False +if __name__ == '__main__': omega = np.sqrt(170) # source - roundoff = 1e4 - eta = int(-omega**2 * roundoff) / roundoff - # print(eta) - # source_type = 'elliptic_J' - source_type = 'manu_poisson' - - # if quick_run: - # domain_name = 'curved_L_shape' - # nc = 4 - # deg = 2 - # else: - # nc = 8 - # deg = 4 + eta = -omega**2 + + source_type = 'manu_poisson_elliptic' domain_name = 'pretzel_f' - # domain_name = 'curved_L_shape' + nc = 10 deg = 2 - # nc = 2 - # deg = 2 - run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) solve_h1_source_pbm( nc=nc, deg=deg, @@ -293,11 +289,6 @@ def lift_u_bc(u_bc): mu=1, # 1, domain_name=domain_name, source_type=source_type, - source_proj='P_geom', backend_language='pyccel-gcc', - plot_source=True, - plot_dir='./plots/h1_tests_source_february/' + run_dir, - hide_plots=True, - ) - - time_count(t_stamp_full, msg='full program') + plot_dir='./plots/h1_source_pbms_conga_2d/' + run_dir, + ) \ No newline at end of file diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index b1256a356..421b66e7d 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -1,14 +1,12 @@ +""" + Solve the eigenvalue problem for the curl-curl operator in 2D with a FEEC discretization +""" +import os from mpi4py import MPI -import os import numpy as np import matplotlib.pyplot as plt from collections import OrderedDict - -from scipy.sparse.linalg import spilu, lgmres -from scipy.sparse.linalg import LinearOperator, eigsh, minres -from scipy.linalg import norm - from sympde.topology import Derham from psydac.feec.multipatch.api import discretize @@ -17,43 +15,69 @@ from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection - - -def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language='python', mu=1, nu=0, gamma_h=10, - sigma=None, nb_eigs=4, nb_eigs_plot=4, - plot_dir=None, hide_plots=True, m_load_dir="", skip_eigs_threshold=1e-7,): - """ - solver for the eigenvalue problem: find lambda in R and u in H0(curl), such that +from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn +from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file - A u = lambda * u on \\Omega +from sympde.topology import Square +from sympde.topology import IdentityMapping, PolarMapping +from psydac.fem.vector import ProductFemSpace - with an operator - - A u := mu * curl curl u - nu * grad div u - - discretized as Ah: V1h -> V1h with a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, +from scipy.sparse.linalg import spilu, lgmres +from scipy.sparse.linalg import LinearOperator, eigsh, minres +from scipy.sparse import csr_matrix +from scipy.linalg import norm - V0h --grad-> V1h -—curl-> V2h +from psydac.linalg.utilities import array_to_psydac +from psydac.fem.basic import FemField - Examples: +from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain +from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection - - curl-curl eigenvalue problem with - mu = 1 - nu = 0 +from psydac.api.postprocessing import OutputManager, PostProcessManager - - Hodge-Laplacian eigenvalue problem with - mu = 1 - nu = 1 - :param nc: nb of cells per dimension, in each patch - :param deg: coordinate degree in each patch - :param gamma_h: jump penalization parameter +def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), domain=([0, np.pi], [0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, + generalized_pbm=False, sigma=5, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, + plot_dir=None, m_load_dir=None,): + """ + Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization + + Parameters + ---------- + ncells : array + Number of cells in each direction + degree : tuple + Degree of the basis functions + domain : list + Interval in x- and y-direction + domain_name : str + Name of the domain + backend_language : str + Language used for the backend + mu : float + Coefficient in the curl-curl operator + nu : float + Coefficient in the curl-curl operator + gamma_h : float + Coefficient in the curl-curl operator + generalized_pbm : bool + If True, solve the generalized eigenvalue problem + sigma : float + Calculate eigenvalues close to sigma + nb_eigs_solve : int + Number of eigenvalues to solve + nb_eigs_plot : int + Number of eigenvalues to plot + skip_eigs_threshold : float + Threshold for the eigenvalues to skip + plot_dir : str + Directory for the plots + m_load_dir : str + Directory to save and load the matrices """ - ncells = [nc, nc] - degree = [deg, deg] + diags = {} + if sigma is None: raise ValueError('please specify a value for sigma') @@ -64,16 +88,47 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print(' domain_name = {}'.format(domain_name)) print(' backend_language = {}'.format(backend_language)) print('---------------------------------------------------------------------------------------------------------') - + t_stamp = time_count() print('building symbolic and discrete domain...') - domain = build_multipatch_domain(domain_name=domain_name) + + int_x, int_y = domain + if type(ncells) == int: + domain = build_multipatch_domain(domain_name=domain_name) + + elif domain_name == 'refined_square' or domain_name == 'square_L_shape': + domain = create_square_domain(ncells, int_x, int_y, mapping='identity') + + elif domain_name == 'curved_L_shape': + domain = create_square_domain(ncells, int_x, int_y, mapping='polar') + + else: + domain = build_multipatch_domain(domain_name=domain_name) + + if type(ncells) == int: + ncells = [ncells, ncells] + elif ncells.ndim == 1: + ncells = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} + elif ncells.ndim == 2: + ncells = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], + ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + + mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) mappings_list = list(mappings.values()) - domain_h = discretize(domain, ncells=ncells) + + t_stamp = time_count(t_stamp) + print(' .. discrete domain...') + domain_h = discretize(domain, ncells=ncells) # Vh space print('building symbolic and discrete derham sequences...') + t_stamp = time_count() + print(' .. derham sequence...') derham = Derham(domain, ["H1", "Hcurl", "L2"]) + + t_stamp = time_count(t_stamp) + print(' .. discrete derham sequence...') derham_h = discretize(derham, domain_h, degree=degree) V0h = derham_h.V0 @@ -82,15 +137,18 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language print('dim(V0h) = {}'.format(V0h.nbasis)) print('dim(V1h) = {}'.format(V1h.nbasis)) print('dim(V2h) = {}'.format(V2h.nbasis)) + diags['ndofs_V0'] = V0h.nbasis + diags['ndofs_V1'] = V1h.nbasis + diags['ndofs_V2'] = V2h.nbasis + t_stamp = time_count(t_stamp) print('building the discrete operators:') print('commuting projection operators...') - nquads = [4 * (d + 1) for d in degree] - P0, P1, P2 = derham_h.projectors(nquads=nquads) I1 = IdLinearOperator(V1h) I1_m = I1.to_sparse_matrix() + t_stamp = time_count(t_stamp) print('Hodge operators...') # multi-patch (broken) linear operators / matrices H0 = HodgeOperator( @@ -112,58 +170,77 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language load_dir=m_load_dir, load_space_index=2) - H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 - H2_m = H2.to_sparse_matrix() # = mass matrix of V2 - # dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 + H0_m = H0.to_sparse_matrix() # = mass matrix of V0 + dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + H1_m = H1.to_sparse_matrix() # = mass matrix of V1 + dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 + H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 + t_stamp = time_count(t_stamp) print('conforming projection operators...') # conforming Projections (should take into account the boundary conditions # of the continuous deRham sequence) cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) + t_stamp = time_count(t_stamp) print('broken differential operators...') bD0, bD1 = derham_h.broken_derivatives_as_operators bD0_m = bD0.to_sparse_matrix() bD1_m = bD1.to_sparse_matrix() - if not os.path.exists(plot_dir): - os.makedirs(plot_dir) - - # Conga (projection-based) stiffness matrices - # curl curl: - print('curl-curl stiffness matrix...') - pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m - CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix + t_stamp = time_count(t_stamp) + print('converting some matrices to csr format...') - # grad div: - print('grad-div stiffness matrix...') - pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m - GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix + H1_m = H1_m.tocsr() + dH1_m = dH1_m.tocsr() + H2_m = H2_m.tocsr() + bD1_m = bD1_m.tocsr() - # jump penalization in V1h: - jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) print('computing the full operator matrix...') - print('mu = {}'.format(mu)) - print('nu = {}'.format(nu)) - A_m = mu * CC_m - nu * GD_m + gamma_h * JP_m + A_m = np.zeros_like(H1_m) - if False: # gneralized problen + # Conga (projection-based) stiffness matrices + if mu != 0: + # curl curl: + t_stamp = time_count(t_stamp) + print('mu = {}'.format(mu)) + print('curl-curl stiffness matrix...') + + pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m + CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix + A_m += mu * CC_m + + if nu != 0: + pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m + GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix + A_m -= nu * GD_m + + # jump stabilization in V1h: + if gamma_h != 0 or generalized_pbm: + t_stamp = time_count(t_stamp) + print('jump stabilization matrix...') + jump_stab_m = I1_m - cP1_m + JS_m = jump_stab_m.transpose() @ H1_m @ jump_stab_m + A_m += gamma_h * JS_m + + if generalized_pbm: print('adding jump stabilization to RHS of generalized eigenproblem...') B_m = cP1_m.transpose() @ H1_m @ cP1_m + JS_m else: B_m = H1_m + t_stamp = time_count(t_stamp) print('solving matrix eigenproblem...') all_eigenvalues, all_eigenvectors_transp = get_eigenvalues( - nb_eigs, sigma, A_m, B_m) + nb_eigs_solve, sigma, A_m, B_m) # Eigenvalue processing - + t_stamp = time_count(t_stamp) + print('sorting out eigenvalues...') zero_eigenvalues = [] if skip_eigs_threshold is not None: eigenvalues = [] @@ -179,24 +256,68 @@ def hcurl_solve_eigen_pbm(nc=4, deg=4, domain_name='pretzel_f', backend_language eigenvalues = all_eigenvalues eigenvectors = all_eigenvectors_transp.T - # plot first eigenvalues + for k, val in enumerate(eigenvalues): + diags['eigenvalue_{}'.format(k)] = val # eigenvalues[k] - for i in range(min(nb_eigs_plot, len(eigenvalues))): + for k, val in enumerate(zero_eigenvalues): + diags['skipped eigenvalue_{}'.format(k)] = val - lambda_i = eigenvalues[i] - print('looking at emode i = {}: {}... '.format(i, lambda_i)) + t_stamp = time_count(t_stamp) + print('plotting the eigenmodes...') + + OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') + OM.add_spaces(V1h=V1h) + OM.export_space_info() + nb_eigs = len(eigenvalues) + for i in range(min(nb_eigs_plot, nb_eigs)): + + print('looking at emode i = {}... '.format(i)) + lambda_i = eigenvalues[i] emode_i = np.real(eigenvectors[i]) norm_emode_i = np.dot(emode_i, H1_m.dot(emode_i)) - print('norm of computed eigenmode: ', norm_emode_i) - eh_c = emode_i / norm_emode_i # numpy coeffs of the normalized eigenmode - plot_field(numpy_coeffs=eh_c, Vh=V1h, space_kind='hcurl', domain=domain, title='mode e_{}, lambda_{}={}'.format(i, i, lambda_i), - filename=plot_dir + 'e_{}.png'.format(i), hide_plot=hide_plots) + eh_c = emode_i / norm_emode_i - return eigenvalues, eigenvectors + stencil_coeffs = array_to_psydac(cP1_m @ eh_c, V1h.vector_space) + vh = FemField(V1h, coeffs=stencil_coeffs) + OM.add_snapshot(i, i) + OM.export_fields(vh=vh) + + OM.close() + + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + '/spaces.yml', + fields_file=plot_dir + '/fields.h5') + PM.export_to_vtk( + plot_dir + "/eigenvalues", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='vh') + PM.close() + + t_stamp = time_count(t_stamp) + + return diags, eigenvalues def get_eigenvalues(nb_eigs, sigma, A_m, M_m): + """ + Compute the eigenvalues of the matrix A close to sigma and right-hand-side M + + Parameters + ---------- + nb_eigs : int + Number of eigenvalues to compute + sigma : float + Value close to which the eigenvalues are computed + A_m : sparse matrix + Matrix A + M_m : sparse matrix + Matrix M + """ + print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') print( 'computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format( @@ -210,7 +331,7 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): ncv = 4 * nb_eigs print('A_m.shape = ', A_m.shape) try_lgmres = True - max_shape_splu = 17000 + max_shape_splu = 24000 # OK for nc=20, deg=6 on pretzel_f if A_m.shape[0] < max_shape_splu: print('(via sparse LU decomposition)') OPinv = None @@ -254,49 +375,3 @@ def get_eigenvalues(nb_eigs, sigma, A_m, M_m): print("done: eigenvalues found: " + repr(eigenvalues)) return eigenvalues, eigenvectors - - -if __name__ == '__main__': - - t_stamp_full = time_count() - - # quick_run = True - quick_run = False - - if quick_run: - domain_name = 'curved_L_shape' - nc = 4 - deg = 2 - else: - nc = 8 - deg = 4 - - # domain_name = 'pretzel_f' - domain_name = 'curved_L_shape' - nc = 10 - deg = 3 - - sigma = 7 - nb_eigs_solve = 7 - nb_eigs_plot = 7 - skip_eigs_threshold = 1e-7 - - m_load_dir = 'matrices_{}_nc={}_deg={}/'.format(domain_name, nc, deg) - run_dir = 'eigenpbm_{}_nc={}_deg={}/'.format(domain_name, nc, deg) - hcurl_solve_eigen_pbm( - nc=nc, deg=deg, - nu=0, - mu=1, # 1, - domain_name=domain_name, - backend_language='pyccel-gcc', - plot_dir='./plots/tests_source_february/' + run_dir, - hide_plots=True, - m_load_dir=m_load_dir, - gamma_h=0, - sigma=sigma, - nb_eigs=nb_eigs_solve, - nb_eigs_plot=nb_eigs_plot, - skip_eigs_threshold=skip_eigs_threshold, - ) - - time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py similarity index 79% rename from psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py rename to psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py index f01875911..a32483dde 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_dg.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py @@ -31,15 +31,16 @@ from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL from psydac.feec.pull_push import pull_2d_hcurl +from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.api import discretize from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain from psydac.api.postprocessing import OutputManager, PostProcessManager -def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), domain=([0, np.pi], [0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, - generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, - plot_dir=None, hide_plots=True, m_load_dir="",): +def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), domain=([0, np.pi], [0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, + sigma=5, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, + plot_dir=None,): """ Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization @@ -59,14 +60,8 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d Coefficient in the curl-curl operator nu : float Coefficient in the curl-curl operator - gamma_h : float - Coefficient in the curl-curl operator - generalized_pbm : bool - If True, solve the generalized eigenvalue problem sigma : float Calculate eigenvalues close to sigma - ref_sigmas : list - List of reference eigenvalues nb_eigs_solve : int Number of eigenvalues to solve nb_eigs_plot : int @@ -75,10 +70,6 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d Threshold for the eigenvalues to skip plot_dir : str Directory for the plots - hide_plots : bool - If True, hide the plots - m_load_dir : str - Directory to save and load the matrices """ diags = {} @@ -97,27 +88,27 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d print('building symbolic and discrete domain...') int_x, int_y = domain + if type(ncells) == int: + domain = build_multipatch_domain(domain_name=domain_name) - if domain_name == 'refined_square' or domain_name == 'square_L_shape': + elif domain_name == 'refined_square' or domain_name == 'square_L_shape': domain = create_square_domain(ncells, int_x, int_y, mapping='identity') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( - patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + elif domain_name == 'curved_L_shape': domain = create_square_domain(ncells, int_x, int_y, mapping='polar') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( - patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - elif domain_name == 'pretzel_f': - domain = build_multipatch_domain(domain_name=domain_name) - ncells_h = {patch.name: [ncells[i], ncells[i]] - for (i, patch) in enumerate(domain.interior)} else: - ValueError("Domain not defined.") + domain = build_multipatch_domain(domain_name=domain_name) + + if type(ncells) == int: + ncells = [ncells, ncells] + elif ncells.ndim == 1: + ncells = {patch.name: [ncells[i], ncells[i]] + for (i, patch) in enumerate(domain.interior)} + elif ncells.ndim == 2: + ncells = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], + ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - # domain = build_multipatch_domain(domain_name = 'curved_L_shape') - # - # ncells = np.array([4,8,4]) - # ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) mappings_list = list(mappings.values()) @@ -164,7 +155,7 @@ def avr(w): return 0.5 * plus(w) + 0.5 * minus(w) # 2. Discretization # +++++++++++++++++++++++++++++++ - domain_h = discretize(domain, ncells=ncells_h) + domain_h = discretize(domain, ncells=ncells) Vh = discretize(V, domain_h, degree=degree) ah = discretize(a, domain_h, [Vh, Vh]) @@ -182,17 +173,17 @@ def avr(w): return 0.5 * plus(w) + 0.5 * minus(w) zero_eigenvalues = [] if skip_eigs_threshold is not None: eigenvalues = [] - eigenvectors2 = [] + eigenvectors = [] for val, vect in zip(all_eigenvalues_2, all_eigenvectors_transp_2.T): if abs(val) < skip_eigs_threshold: zero_eigenvalues.append(val) # we skip the eigenvector else: eigenvalues.append(val) - eigenvectors2.append(vect) + eigenvectors.append(vect) else: eigenvalues = all_eigenvalues_2 - eigenvectors2 = all_eigenvectors_transp_2.T + eigenvectors = all_eigenvectors_transp_2.T diags['DG'] = True for k, val in enumerate(eigenvalues): diags['eigenvalue2_{}'.format(k)] = val # eigenvalues[k] @@ -206,46 +197,39 @@ def avr(w): return 0.5 * plus(w) + 0.5 * minus(w) if not os.path.exists(plot_dir): os.makedirs(plot_dir) - # OM = OutputManager('spaces.yml', 'fields.h5') - # OM.add_spaces(V1h=V1h) + OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') + OM.add_spaces(Vh=Vh) + OM.export_space_info() nb_eigs = len(eigenvalues) for i in range(min(nb_eigs_plot, nb_eigs)): - OM = OutputManager(plot_dir + '/spaces2.yml', plot_dir + '/fields2.h5') - OM.add_spaces(V1h=Vh) + print('looking at emode i = {}... '.format(i)) lambda_i = eigenvalues[i] - emode_i = np.real(eigenvectors2[i]) + emode_i = np.real(eigenvectors[i]) norm_emode_i = np.dot(emode_i, Bh_m.dot(emode_i)) eh_c = emode_i / norm_emode_i + stencil_coeffs = array_to_psydac(eh_c, Vh.vector_space) vh = FemField(Vh, coeffs=stencil_coeffs) - OM.set_static() - # OM.add_snapshot(t=i , ts=0) + OM.add_snapshot(i, i) OM.export_fields(vh=vh) - # print('norm of computed eigenmode: ', norm_emode_i) - # plot the broken eigenmode: - OM.export_space_info() - OM.close() - - PM = PostProcessManager( - domain=domain, - space_file=plot_dir + - '/spaces2.yml', - fields_file=plot_dir + - '/fields2.h5') - PM.export_to_vtk( - plot_dir + - "/eigen2_{}".format(i), - grid=None, - npts_per_cell=[6] * - 2, - snapshots='all', - fields='vh') - PM.close() - - t_stamp = time_count(t_stamp) + OM.close() + + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + '/spaces.yml', + fields_file=plot_dir + '/fields.h5') + PM.export_to_vtk( + plot_dir + "/eigenvalues", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='vh') + PM.close() + + t_stamp = time_count(t_stamp) return diags, eigenvalues diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py b/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py similarity index 94% rename from psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py rename to psydac/feec/multipatch/examples/hcurl_eigen_testcases.py index 513260779..7670a579b 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_testcase.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py @@ -5,8 +5,8 @@ import os import numpy as np -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_nc -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_dg +from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import hcurl_solve_eigen_pbm +from psydac.feec.multipatch.examples.hcurl_eigen_pbms_dg_2d import hcurl_solve_eigen_pbm_dg from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -28,7 +28,7 @@ # ncells = np.array([4 for _ in range(18)]) # domain onlyneeded for square like domains -domain = [[0, np.pi], [0, np.pi]] # interval in x- and y-direction +# domain = [[0, np.pi], [0, np.pi]] # interval in x- and y-direction # refined square domain # domain_name = 'refined_square' @@ -72,7 +72,7 @@ ncells = np.array([[None, 5], [5, 10]]) - +# ncells = 5 # ncells = np.array([[None, None, 2, 2], # [None, None, 4, 2], @@ -211,8 +211,8 @@ # backend_language = 'numba' backend_language = 'pyccel-gcc' -dims = ncells.shape -sz = ncells[ncells is not None].sum() +dims = 1 if type(ncells) == int else ncells.shape +sz = 1 if type(ncells) == int else ncells[ncells != None].sum() print(dims) # get_run_dir(domain_name, nc, deg) run_dir = domain_name + str(dims) + 'patches_' + 'size_{}'.format(sz) @@ -241,43 +241,36 @@ # - we look for nb_eigs_solve eigenvalues close to sigma (skip zero eigenvalues if skip_zero_eigs==True) # - we plot nb_eigs_plot eigenvectors if method == 'feec': - diags, eigenvalues = hcurl_solve_eigen_pbm_nc( + diags, eigenvalues = hcurl_solve_eigen_pbm( ncells=ncells, degree=degree, gamma_h=gamma_h, generalized_pbm=generalized_pbm, nu=nu, mu=mu, sigma=sigma, - ref_sigmas=ref_sigmas, skip_eigs_threshold=skip_eigs_threshold, nb_eigs_solve=nb_eigs_solve, nb_eigs_plot=nb_eigs_plot, domain_name=domain_name, domain=domain, backend_language=backend_language, plot_dir=plot_dir, - hide_plots=True, m_load_dir=m_load_dir, ) + elif method == 'dg': diags, eigenvalues = hcurl_solve_eigen_pbm_dg( ncells=ncells, degree=degree, - gamma_h=gamma_h, - generalized_pbm=generalized_pbm, nu=nu, mu=mu, sigma=sigma, - ref_sigmas=ref_sigmas, skip_eigs_threshold=skip_eigs_threshold, nb_eigs_solve=nb_eigs_solve, nb_eigs_plot=nb_eigs_plot, domain_name=domain_name, domain=domain, backend_language=backend_language, plot_dir=plot_dir, - hide_plots=True, - m_load_dir=m_load_dir, ) - if ref_sigmas is not None: errors = [] n_errs = min(len(ref_sigmas), len(eigenvalues)) diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index 71bb84f4f..ee08e11ca 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -1,8 +1,20 @@ -# coding: utf-8 +""" + solver for the problem: find u in H(curl), such that -from mpi4py import MPI + A u = f on \\Omega + n x u = n x u_bc on \\partial \\Omega + + where the operator + + A u := eta * u + mu * curl curl u - nu * grad div u + + is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, + + V0h --grad-> V1h -—curl-> V2h +""" import os +from mpi4py import MPI import numpy as np from collections import OrderedDict @@ -24,18 +36,19 @@ from psydac.feec.multipatch.operators import HodgeOperator from psydac.feec.multipatch.plotting_utilities import plot_field from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE +from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl +from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for from psydac.feec.multipatch.utilities import time_count from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField - from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection +from psydac.api.postprocessing import OutputManager, PostProcessManager def solve_hcurl_source_pbm( nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', eta=-10., mu=1., nu=1., gamma_h=10., - plot_source=False, plot_dir=None, hide_plots=True, + project_sol=False, plot_dir=None, m_load_dir=None, ): """ @@ -67,71 +80,83 @@ def solve_hcurl_source_pbm( :param nc: nb of cells per dimension, in each patch :param deg: coordinate degree in each patch :param gamma_h: jump penalization parameter - :param source_proj: approximation operator for the source, possible values are 'P_geom' or 'P_L2' + :param source_proj: approximation operator (in V1h) for the source, possible values are + - 'tilde_Pi': dual commuting projection, an L2 projection filtered by the adjoint conforming projection) :param source_type: must be implemented in get_source_and_solution() :param m_load_dir: directory for matrix storage """ + diags = {} - ncells = [nc, nc] degree = [deg, deg] - # if backend_language is None: - # backend_language='python' - # print('[note: using '+backend_language+ ' backends in discretize functions]') if m_load_dir is not None: if not os.path.exists(m_load_dir): os.makedirs(m_load_dir) print('---------------------------------------------------------------------------------------------------------') print('Starting solve_hcurl_source_pbm function with: ') - print(' ncells = {}'.format(ncells)) + print(' ncells = {}'.format(nc)) print(' degree = {}'.format(degree)) print(' domain_name = {}'.format(domain_name)) print(' source_proj = {}'.format(source_proj)) print(' backend_language = {}'.format(backend_language)) print('---------------------------------------------------------------------------------------------------------') + print() + print(' -- building discrete spaces and operators --') + t_stamp = time_count() - print('building symbolic domain sequence...') + print(' .. multi-patch domain...') domain = build_multipatch_domain(domain_name=domain_name) mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) mappings_list = list(mappings.values()) + if type(nc) == int: + ncells = [nc, nc] + else: + ncells = {patch.name: [nc[i], nc[i]] + for (i, patch) in enumerate(domain.interior)} + + # for diagnosttics + diag_grid = DiagGrid(mappings=mappings, N_diag=100) + t_stamp = time_count(t_stamp) - print('building derham sequence...') + print(' .. derham sequence...') derham = Derham(domain, ["H1", "Hcurl", "L2"]) t_stamp = time_count(t_stamp) - print('building discrete domain...') + print(' .. discrete domain...') domain_h = discretize(domain, ncells=ncells) t_stamp = time_count(t_stamp) - print('building discrete derham sequence...') + print(' .. discrete derham sequence...') derham_h = discretize(derham, domain_h, degree=degree) t_stamp = time_count(t_stamp) - print('building commuting projection operators...') + print(' .. commuting projection operators...') nquads = [4 * (d + 1) for d in degree] P0, P1, P2 = derham_h.projectors(nquads=nquads) - # multi-patch (broken) spaces t_stamp = time_count(t_stamp) - print('calling the multi-patch spaces...') + print(' .. multi-patch spaces...') V0h = derham_h.V0 V1h = derham_h.V1 V2h = derham_h.V2 print('dim(V0h) = {}'.format(V0h.nbasis)) print('dim(V1h) = {}'.format(V1h.nbasis)) print('dim(V2h) = {}'.format(V2h.nbasis)) + diags['ndofs_V0'] = V0h.nbasis + diags['ndofs_V1'] = V1h.nbasis + diags['ndofs_V2'] = V2h.nbasis t_stamp = time_count(t_stamp) - print('building the Id operator and matrix...') + print(' .. Id operator and matrix...') I1 = IdLinearOperator(V1h) I1_m = I1.to_sparse_matrix() t_stamp = time_count(t_stamp) - print('instanciating the Hodge operators...') + print(' .. Hodge operators...') # multi-patch (broken) linear operators / matrices # other option: define as Hodge Operators: H0 = HodgeOperator( @@ -154,37 +179,33 @@ def solve_hcurl_source_pbm( load_space_index=2) t_stamp = time_count(t_stamp) - print('building the primal Hodge matrix H0_m = M0_m ...') - H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - + print(' .. Hodge matrix H0_m = M0_m ...') + H0_m = H0.to_sparse_matrix() t_stamp = time_count(t_stamp) - print('building the dual Hodge matrix dH0_m = inv_M0_m ...') - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 + print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') + dH0_m = H0.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) - print('building the primal Hodge matrix H1_m = M1_m ...') - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - + print(' .. Hodge matrix H1_m = M1_m ...') + H1_m = H1.to_sparse_matrix() t_stamp = time_count(t_stamp) - print('building the dual Hodge matrix dH1_m = inv_M1_m ...') - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 - - # print("dH1_m @ H1_m == I1_m: {}".format(np.allclose((dH1_m @ - # H1_m).todense(), I1_m.todense())) ) # CHECK: OK + print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') + dH1_m = H1.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) - print('building the primal Hodge matrix H2_m = M2_m ...') - H2_m = H2.to_sparse_matrix() # = mass matrix of V2 + print(' .. Hodge matrix H2_m = M2_m ...') + H2_m = H2.to_sparse_matrix() + dH2_m = H2.get_dual_Hodge_sparse_matrix() t_stamp = time_count(t_stamp) - print('building the conforming Projection operators and matrices...') + print(' .. conforming Projection operators...') # conforming Projections (should take into account the boundary conditions # of the continuous deRham sequence) cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) t_stamp = time_count(t_stamp) - print('building the broken differential operators and matrices...') + print(' .. broken differential operators...') # broken (patch-wise) differential operators bD0, bD1 = derham_h.broken_derivatives_as_operators bD0_m = bD0.to_sparse_matrix() @@ -198,13 +219,7 @@ def lift_u_bc(u_bc): print('lifting the boundary condition in V1h...') # note: for simplicity we apply the full P1 on u_bc, but we only # need to set the boundary dofs - u_bc_x = lambdify(domain.coordinates, u_bc[0]) - u_bc_y = lambdify(domain.coordinates, u_bc[1]) - u_bc_log = [pull_2d_hcurl( - [u_bc_x, u_bc_y], m.get_callable_mapping()) for m in mappings_list] - # it's a bit weird to apply P1 on the list of (pulled back) logical - # fields -- why not just apply it on u_bc ? - uh_bc = P1(u_bc_log) + uh_bc = P1_phys(u_bc, P1, domain, mappings_list) ubc_c = uh_bc.coeffs.toarray() # removing internal dofs (otherwise ubc_c may already be a very # good approximation of uh_c ...) @@ -216,181 +231,130 @@ def lift_u_bc(u_bc): # Conga (projection-based) stiffness matrices # curl curl: t_stamp = time_count(t_stamp) - print('computing the curl-curl stiffness matrix...') + print(' .. curl-curl stiffness matrix...') print(bD1_m.shape, H2_m.shape) pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix # grad div: t_stamp = time_count(t_stamp) - print('computing the grad-div stiffness matrix...') + print(' .. grad-div stiffness matrix...') pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m # GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix - # jump penalization: + # jump stabilization: t_stamp = time_count(t_stamp) - print('computing the jump penalization matrix...') + print(' .. jump stabilization matrix...') jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m + JP_m = jump_penal_m.transpose() @ H1_m @ jump_penal_m t_stamp = time_count(t_stamp) - print('computing the full operator matrix...') + print(' .. full operator matrix...') print('eta = {}'.format(eta)) print('mu = {}'.format(mu)) print('nu = {}'.format(nu)) + print('STABILIZATION: gamma_h = {}'.format(gamma_h)) # useful for the boundary condition (if present) pre_A_m = cP1_m.transpose() @ (eta * H1_m + mu * pre_CC_m - nu * pre_GD_m) A_m = pre_A_m @ cP1_m + gamma_h * JP_m - # get exact source, bc's, ref solution... - # (not all the returned functions are useful here) t_stamp = time_count(t_stamp) - print('getting the source and ref solution...') - N_diag = 200 - method = 'conga' - f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( - source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - ) + print() + print(' -- getting source --') + + f_vect, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( + source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name,) # compute approximate source f_h t_stamp = time_count(t_stamp) - b_c = f_c = None - if source_proj == 'P_geom': - # f_h = P1-geometric (commuting) projection of f_vect - print('projecting the source with commuting projection...') - f_x = lambdify(domain.coordinates, f_vect[0]) - f_y = lambdify(domain.coordinates, f_vect[1]) - f_log = [pull_2d_hcurl([f_x, f_y], m.get_callable_mapping()) - for m in mappings_list] - f_h = P1(f_log) - f_c = f_h.coeffs.toarray() - b_c = H1_m.dot(f_c) - - elif source_proj == 'P_L2': - # f_h = L2 projection of f_vect - print('projecting the source with L2 projection...') - v = element_of(V1h.symbolic_space, name='v') - expr = dot(f_vect, v) - l = LinearForm(v, integral(domain, expr)) - lh = discretize(l, domain_h, V1h) - b = lh.assemble() - b_c = b.toarray() - if plot_source: - f_c = dH1_m.dot(b_c) - else: - raise ValueError(source_proj) - if plot_source: - plot_field( - numpy_coeffs=f_c, - Vh=V1h, - space_kind='hcurl', - domain=domain, - title='f_h with P = ' + - source_proj, - filename=plot_dir + - '/fh_' + - source_proj + - '.png', - hide_plot=hide_plots) + # f_h = L2 projection of f_vect, with filtering if tilde_Pi + print(' .. projecting the source with ' + + source_proj +' projection...') + + tilde_f_c = derham_h.get_dual_dofs( + space='V1', + f=f_vect, + backend_language=backend_language, + return_format='numpy_array') + if source_proj == 'tilde_Pi': + print(' .. filtering the discrete source with P0.T ...') + tilde_f_c = cP1_m.transpose() @ tilde_f_c - ubc_c = lift_u_bc(u_bc) + ubc_c = lift_u_bc(u_bc) if ubc_c is not None: # modified source for the homogeneous pbm t_stamp = time_count(t_stamp) - print('modifying the source with lifted bc solution...') - b_c = b_c - pre_A_m.dot(ubc_c) + print(' .. modifying the source with lifted bc solution...') + tilde_f_c = tilde_f_c - pre_A_m.dot(ubc_c) # direct solve with scipy spsolve t_stamp = time_count(t_stamp) - print('solving source problem with scipy.spsolve...') - uh_c = spsolve(A_m, b_c) + print() + print(' -- solving source problem with scipy.spsolve...') + uh_c = spsolve(A_m, tilde_f_c) # project the homogeneous solution on the conforming problem space - t_stamp = time_count(t_stamp) - print('projecting the homogeneous solution on the conforming problem space...') - uh_c = cP1_m.dot(uh_c) + if project_sol: + t_stamp = time_count(t_stamp) + print(' .. projecting the homogeneous solution on the conforming problem space...') + uh_c = cP1_m.dot(uh_c) + else: + print(' .. NOT projecting the homogeneous solution on the conforming problem space') if ubc_c is not None: # adding the lifted boundary condition t_stamp = time_count(t_stamp) - print('adding the lifted boundary condition...') + print(' .. adding the lifted boundary condition...') uh_c += ubc_c + uh = FemField(V1h, coeffs=array_to_psydac(uh_c, V1h.vector_space)) + #need cp1 here? + f_c = dH1_m.dot(tilde_f_c) + jh = FemField(V1h, coeffs=array_to_psydac(f_c, V1h.vector_space)) + t_stamp = time_count(t_stamp) - print('getting and plotting the FEM solution from numpy coefs array...') - title = r'solution $u_h$ (amplitude) for $\eta = $' + repr(eta) - params_str = 'eta={}_mu={}_nu={}_gamma_h={}'.format(eta, mu, nu, gamma_h) + print(' -- plots and diagnostics --') if plot_dir: - plot_field( - numpy_coeffs=uh_c, - Vh=V1h, - space_kind='hcurl', + OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') + OM.add_spaces(V1h=V1h) + OM.set_static() + OM.export_fields(vh=uh) + OM.export_fields(jh=jh) + OM.export_space_info() + OM.close() + + PM = PostProcessManager( domain=domain, - title=title, - filename=plot_dir + - params_str + - '_uh.png', - hide_plot=hide_plots) + space_file=plot_dir + + '/spaces.yml', + fields_file=plot_dir + + '/fields.h5') + PM.export_to_vtk( + plot_dir + "/sol", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='vh') + PM.export_to_vtk( + plot_dir + "/source", + grid=None, + npts_per_cell=[6] * 2, + snapshots='all', + fields='jh') + + PM.close() time_count(t_stamp) if u_ex: - u = element_of(V1h.symbolic_space, name='u') - l2norm = Norm( - Matrix([u[0] - u_ex[0], u[1] - u_ex[1]]), domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V1h) - uh_c = array_to_psydac(uh_c, V1h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) - return l2_error - - -if __name__ == '__main__': - - t_stamp_full = time_count() - - quick_run = True - # quick_run = False - - omega = np.sqrt(170) # source - roundoff = 1e4 - eta = int(-omega**2 * roundoff) / roundoff - - source_type = 'manu_maxwell' - # source_type = 'manu_J' - - if quick_run: - domain_name = 'curved_L_shape' - nc = 4 - deg = 2 - else: - nc = 8 - deg = 4 - - domain_name = 'pretzel_f' - # domain_name = 'curved_L_shape' - nc = 20 - deg = 2 - - # nc = 2 - # deg = 2 - - run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) - m_load_dir = 'matrices_{}_nc={}_deg={}/'.format(domain_name, nc, deg) - solve_hcurl_source_pbm( - nc=nc, deg=deg, - eta=eta, - nu=0, - mu=1, # 1, - domain_name=domain_name, - source_type=source_type, - backend_language='pyccel-gcc', - plot_source=True, - plot_dir='./plots/tests_source_feb_13/' + run_dir, - hide_plots=True, - m_load_dir=m_load_dir - ) - - time_count(t_stamp_full, msg='full program') + u_ex_c = P1_phys(u_ex, P1, domain, mappings_list).coeffs.toarray() + err = u_ex_c - uh_c + l2_error = np.sqrt(np.dot(err, H1_m.dot(err)))/np.sqrt(np.dot(u_ex_c,H1_m.dot(u_ex_c))) + print(l2_error) + #return l2_error + diags['err'] = l2_error + + return diags diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py b/psydac/feec/multipatch/examples/hcurl_source_testcase.py similarity index 75% rename from psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py rename to psydac/feec/multipatch/examples/hcurl_source_testcase.py index 066a287bd..35aa79dd6 100644 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_testcase.py +++ b/psydac/feec/multipatch/examples/hcurl_source_testcase.py @@ -4,7 +4,7 @@ import os import numpy as np -from psydac.feec.multipatch.examples_nc.hcurl_source_pbms_nc import solve_hcurl_source_pbm_nc +from psydac.feec.multipatch.examples.hcurl_source_pbms_conga_2d import solve_hcurl_source_pbm from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file @@ -16,8 +16,8 @@ # main test-cases used for the ppc paper: # test_case = 'maxwell_hom_eta=50' # used in paper -test_case = 'maxwell_hom_eta=170' # used in paper -# test_case = 'maxwell_inhom' # used in paper +#test_case = 'maxwell_hom_eta=170' # used in paper +test_case = 'maxwell_inhom' # used in paper # ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- @@ -25,18 +25,15 @@ domain_name = 'pretzel_f' # domain_name = 'curved_L_shape' +# currently only 'tilde_Pi' is implemented source_proj = 'tilde_Pi' -# other values are: - -# source_proj = 'P_L2' # L2 projection in broken space -# source_proj = 'P_geom' # geometric projection (primal commuting proj) # nc_s = [np.array([16 for _ in range(18)])] # corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0 ] nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16, 8, 8])] - +# nc_s = [10] # refine handles only # nc_s = [np.array([16, 16, 16, 16, 16, 8, 8, 8, 8, 4, 2, 2, 4, 16, 16, 16, 2, 2])] @@ -50,46 +47,21 @@ source_type = 'elliptic_J' omega = np.sqrt(50) # source time pulsation - cb_min_sol = 0 - cb_max_sol = 1 - - # ref solution (no exact solution) - ref_nc = 10 - ref_deg = 6 - elif test_case == 'maxwell_hom_eta=170': homogeneous = True source_type = 'elliptic_J' omega = np.sqrt(170) # source time pulsation - cb_min_sol = 0 - cb_max_sol = 1 - - # ref solution (no exact solution) - ref_nc = 10 - ref_deg = 6 - - elif test_case == 'maxwell_inhom': - homogeneous = False source_type = 'manu_maxwell_inhom' omega = np.pi - cb_min_sol = 0 - cb_max_sol = 1 - - # dummy ref solution (there is an exact solution) - ref_nc = 2 - ref_deg = 2 - else: raise ValueError(test_case) case_dir = test_case -ref_case_dir = case_dir -roundoff = 1e4 eta = int(-omega**2 * roundoff) / roundoff project_sol = True # True # (use conf proj of solution for visualization) @@ -113,8 +85,6 @@ 'project_sol': project_sol, 'omega': omega, 'gamma_h': gamma_h, - 'ref_nc': ref_nc, - 'ref_deg': ref_deg, } # backend_language = 'numba' backend_language = 'pyccel-gcc' @@ -128,10 +98,6 @@ m_load_dir = get_mat_dir(domain_name, nc, deg) # to save the FEM sol - # to load the ref FEM sol - sol_ref_dir = get_sol_dir(ref_case_dir, domain_name, ref_nc, ref_deg) - sol_ref_filename = sol_ref_dir + '/' + \ - FEM_sol_fn(source_type=source_type, source_proj=source_proj) print('\n --- --- --- --- --- --- --- --- --- --- --- --- --- --- \n') print(' Calling solve_hcurl_source_pbm() with params = {}'.format(params)) @@ -146,7 +112,7 @@ # with # A u := eta * u + mu * curl curl u - nu * grad div u - diags = solve_hcurl_source_pbm_nc( + diags = solve_hcurl_source_pbm( nc=nc, deg=deg, eta=eta, nu=0, @@ -155,19 +121,10 @@ source_type=source_type, source_proj=source_proj, backend_language=backend_language, - plot_source=False, project_sol=project_sol, gamma_h=gamma_h, plot_dir=plot_dir, - hide_plots=True, - skip_plot_titles=False, - cb_min_sol=cb_min_sol, - cb_max_sol=cb_max_sol, m_load_dir=m_load_dir, - sol_filename=None, - sol_ref_filename=sol_ref_filename, - ref_nc=ref_nc, - ref_deg=ref_deg, ) # diff --git a/psydac/feec/multipatch/examples/ppc_test_cases.py b/psydac/feec/multipatch/examples/ppc_test_cases.py index c95a0d6b8..122a68dca 100644 --- a/psydac/feec/multipatch/examples/ppc_test_cases.py +++ b/psydac/feec/multipatch/examples/ppc_test_cases.py @@ -422,269 +422,3 @@ def get_source_and_solution_h1(source_type=None, eta=0, mu=0, raise ValueError(source_type) return f_scal, u_bc, u_ex - - -def get_source_and_solution_OBSOLETE(source_type=None, eta=0, mu=0, nu=0, - domain=None, domain_name=None, - refsol_params=None): - """ - OBSOLETE: kept for some test-cases - """ - - # exact solutions (if available) - u_ex = None - p_ex = None - - # bc solution: describe the bc on boundary. Inside domain, values should - # not matter. Homogeneous bc will be used if None - u_bc = None - # only hom bc on p (for now...) - - # source terms - f_vect = None - f_scal = None - - # auxiliary term (for more diagnostics) - grad_phi = None - phi = None - - x, y = domain.coordinates - - if source_type == 'manu_J': - # todo: remove if not used ? - # use a manufactured solution, with ad-hoc (homogeneous or - # inhomogeneous) bc - if domain_name in ['square_2', 'square_6', 'square_8', 'square_9']: - t = 1 - else: - t = pi - - u_ex = Tuple(sin(t * y), sin(t * x) * cos(t * y)) - f_vect = Tuple( - sin(t * y) * (eta + t**2 * (mu - cos(t * x) * (mu - nu))), - sin(t * x) * cos(t * y) * (eta + t**2 * (mu + nu)) - ) - - # boundary condition: (here we only need to coincide with u_ex on the - # boundary !) - if domain_name in ['square_2', 'square_6', 'square_9']: - u_bc = None - else: - u_bc = u_ex - - elif source_type == 'manutor_poisson': - # todo: remove if not used ? - # same as manu_poisson_ellip, with arbitrary value for tor - x0 = 1.5 - y0 = 1.5 - s = (x - x0) - (y - y0) - t = (x - x0) + (y - y0) - a = (1 / 1.9)**2 - b = (1 / 1.2)**2 - sigma2 = 0.0121 - tor = 2 - tau = a * s**2 + b * t**2 - 1 - phi = exp(-tau**tor / (2 * sigma2)) - dx_tau = 2 * (a * s + b * t) - dy_tau = 2 * (-a * s + b * t) - dxx_tau = 2 * (a + b) - dyy_tau = 2 * (a + b) - f_scal = -((tor * tau**(tor - 1) * dx_tau / (2 * sigma2))**2 - (tau**(tor - 1) * dxx_tau + (tor - 1) * tau**(tor - 2) * dx_tau**2) * tor / (2 * sigma2) - + (tor * tau**(tor - 1) * dy_tau / (2 * sigma2))**2 - (tau**(tor - 1) * dyy_tau + (tor - 1) * tau**(tor - 2) * dy_tau**2) * tor / (2 * sigma2)) * phi - p_ex = phi - - elif source_type == 'manu_maxwell': - # used for Maxwell equation with manufactured solution - alpha = eta - u_ex = Tuple(sin(pi * y), sin(pi * x) * cos(pi * y)) - f_vect = Tuple(alpha * sin(pi * y) - pi**2 * sin(pi * y) * cos(pi * x) + pi**2 * sin(pi * y), - alpha * sin(pi * x) * cos(pi * y) + pi**2 * sin(pi * x) * cos(pi * y)) - u_bc = u_ex - - elif source_type in ['manu_poisson', 'elliptic_J']: - # 'manu_poisson': used for Poisson pbm with manufactured solution - # 'elliptic_J': used for Maxwell pbm (no manufactured solution) -- (was 'ellnew_J' in previous version) - x0 = 1.5 - y0 = 1.5 - s = (x - x0) - (y - y0) - t = (x - x0) + (y - y0) - a = (1 / 1.9)**2 - b = (1 / 1.2)**2 - sigma2 = 0.0121 - tau = a * s**2 + b * t**2 - 1 - phi = exp(-tau**2 / (2 * sigma2)) - dx_tau = 2 * (a * s + b * t) - dy_tau = 2 * (-a * s + b * t) - dxx_tau = 2 * (a + b) - dyy_tau = 2 * (a + b) - - dx_phi = (-tau * dx_tau / sigma2) * phi - dy_phi = (-tau * dy_tau / sigma2) * phi - grad_phi = Tuple(dx_phi, dy_phi) - - f_scal = -((tau * dx_tau / sigma2)**2 - (tau * dxx_tau + dx_tau**2) / sigma2 - + (tau * dy_tau / sigma2)**2 - (tau * dyy_tau + dy_tau**2) / sigma2) * phi - - # exact solution of -p'' = f with hom. bc's on pretzel domain - p_ex = phi - - if source_type == 'manu_poisson' and mu == 1 and eta == 0: - u_ex = phi - - if not domain_name in ['pretzel', 'pretzel_f']: - print( - "WARNING (87656547) -- I'm not sure we have an exact solution -- check the bc's on the domain " + - domain_name) - # raise NotImplementedError(domain_name) - - f_x = dy_tau * phi - f_y = - dx_tau * phi - f_vect = Tuple(f_x, f_y) - - elif source_type == 'manu_poisson_2': - f_scal = -4 - p_ex = x**2 + y**2 - phi = p_ex - u_bc = p_ex - u_ex = p_ex - elif source_type == 'curl_dipole_J': - # used for the magnetostatic problem - - # was 'dicurl_J' in previous version - - # here, f is the curl of a dipole current j = phi_0 - phi_1 (two blobs) that correspond to a scalar current density - # - # the solution u of the curl-curl problem with free-divergence constraint - # curl curl u = curl j - # - # then corresponds to a magnetic density, - # see Beirão da Veiga, Brezzi, Dassi, Marini and Russo, Virtual Element - # approx of 2D magnetostatic pbms, CMAME 327 (2017) - - x_0 = 1.0 - y_0 = 1.0 - ds2_0 = (0.02)**2 - sigma_0 = (x - x_0)**2 + (y - y_0)**2 - phi_0 = exp(-sigma_0**2 / (2 * ds2_0)) - dx_sig_0 = 2 * (x - x_0) - dy_sig_0 = 2 * (y - y_0) - dx_phi_0 = - dx_sig_0 * sigma_0 / ds2_0 * phi_0 - dy_phi_0 = - dy_sig_0 * sigma_0 / ds2_0 * phi_0 - - x_1 = 2.0 - y_1 = 2.0 - ds2_1 = (0.02)**2 - sigma_1 = (x - x_1)**2 + (y - y_1)**2 - phi_1 = exp(-sigma_1**2 / (2 * ds2_1)) - dx_sig_1 = 2 * (x - x_1) - dy_sig_1 = 2 * (y - y_1) - dx_phi_1 = - dx_sig_1 * sigma_1 / ds2_1 * phi_1 - dy_phi_1 = - dy_sig_1 * sigma_1 / ds2_1 * phi_1 - - f_x = dy_phi_0 - dy_phi_1 - f_y = - dx_phi_0 + dx_phi_1 - f_scal = 0 # phi_0 - phi_1 - f_vect = Tuple(f_x, f_y) - - elif source_type == 'old_ellip_J': - - # divergence-free f field along an ellipse curve - if domain_name in ['pretzel', 'pretzel_f']: - dr = 0.2 - r0 = 1 - x0 = 1.5 - y0 = 1.5 - # s0 = x0-y0 - # t0 = x0+y0 - s = (x - x0) - (y - y0) - t = (x - x0) + (y - y0) - aa = (1 / 1.7)**2 - bb = (1 / 1.1)**2 - dsigpsi2 = 0.01 - sigma = aa * s**2 + bb * t**2 - 1 - psi = exp(-sigma**2 / (2 * dsigpsi2)) - dx_sig = 2 * (aa * s + bb * t) - dy_sig = 2 * (-aa * s + bb * t) - f_x = dy_sig * psi - f_y = - dx_sig * psi - - dsigphi2 = 0.01 # this one gives approx 1e-10 at boundary for phi - # dsigphi2 = 0.005 # if needed: smaller support for phi, to have - # a smaller value at boundary - phi = exp(-sigma**2 / (2 * dsigphi2)) - dx_phi = phi * (-dx_sig * sigma / dsigphi2) - dy_phi = phi * (-dy_sig * sigma / dsigphi2) - - grad_phi = Tuple(dx_phi, dy_phi) - f_vect = Tuple(f_x, f_y) - - else: - raise NotImplementedError - - elif source_type in ['ring_J', 'sring_J']: - # used for the magnetostatic problem - # 'rotating' (divergence-free) f field: - - if domain_name in ['square_2', 'square_6', 'square_8', 'square_9']: - r0 = np.pi / 4 - dr = 0.1 - x0 = np.pi / 2 - y0 = np.pi / 2 - omega = 43 / 2 - # alpha = -omega**2 # not a square eigenvalue - f_factor = 100 - - elif domain_name in ['curved_L_shape']: - r0 = np.pi / 4 - dr = 0.1 - x0 = np.pi / 2 - y0 = np.pi / 2 - omega = 43 / 2 - # alpha = -omega**2 # not a square eigenvalue - f_factor = 100 - - else: - # for pretzel - - # omega = 8 # ? - # alpha = -omega**2 - - source_option = 2 - - if source_option == 1: - # big circle: - r0 = 2.4 - dr = 0.05 - x0 = 0 - y0 = 0.5 - f_factor = 10 - - elif source_option == 2: - # small circle in corner: - if source_type == 'ring_J': - dr = 0.2 - else: - # smaller ring - dr = 0.1 - assert source_type == 'sring_J' - r0 = 1 - x0 = 1.5 - y0 = 1.5 - f_factor = 10 - - else: - raise NotImplementedError - - # note: some other currents give sympde error, see below [1] - phi = f_factor * \ - exp(- .5 * (((x - x0)**2 + (y - y0)**2 - r0**2) / dr)**2) - - f_x = - (y - y0) * phi - f_y = (x - x0) * phi - - f_vect = Tuple(f_x, f_y) - - else: - raise ValueError(source_type) - - return f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py b/psydac/feec/multipatch/examples/timedomain_maxwell.py similarity index 99% rename from psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py rename to psydac/feec/multipatch/examples/timedomain_maxwell.py index 456b7d121..6292adacc 100644 --- a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_nc.py +++ b/psydac/feec/multipatch/examples/timedomain_maxwell.py @@ -261,12 +261,18 @@ def solve_td_maxwell_pbm(*, if domain_name == 'refined_square' or domain_name == 'square_L_shape': int_x, int_y = domain_lims domain = create_square_domain(nc, int_x, int_y, mapping='identity') - ncells_h = {patch.name: [nc[int(patch.name[2])][int(patch.name[4])], nc[int( - patch.name[2])][int(patch.name[4])]] for patch in domain.interior} + else: domain = build_multipatch_domain(domain_name=domain_name) - ncells_h = {patch.name: [ncells[i], ncells[i]] + + if type(nc) == int: + ncells = [nc, nc] + elif ncells.ndim == 1: + ncells = {patch.name: [nc[i], nc[i]] for (i, patch) in enumerate(domain.interior)} + elif ncells.ndim == 2: + ncells = {patch.name: [nc[int(patch.name[2])][int(patch.name[4])], + nc[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) @@ -281,7 +287,7 @@ def solve_td_maxwell_pbm(*, t_stamp = time_count(t_stamp) print(' .. discrete domain...') - domain_h = discretize(domain, ncells=ncells_h) + domain_h = discretize(domain, ncells=ncells) t_stamp = time_count(t_stamp) print(' .. discrete derham sequence...') diff --git a/psydac/feec/multipatch/examples_nc/timedomain_maxwell_testcase.py b/psydac/feec/multipatch/examples/timedomain_maxwell_testcase.py similarity index 100% rename from psydac/feec/multipatch/examples_nc/timedomain_maxwell_testcase.py rename to psydac/feec/multipatch/examples/timedomain_maxwell_testcase.py diff --git a/psydac/feec/multipatch/examples_nc/__init__.py b/psydac/feec/multipatch/examples_nc/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py deleted file mode 100644 index 9f12c6f58..000000000 --- a/psydac/feec/multipatch/examples_nc/h1_source_pbms_nc.py +++ /dev/null @@ -1,322 +0,0 @@ -""" - solver for the problem: find u in H^1, such that - - A u = f on \\Omega - u = u_bc on \\partial \\Omega - - where the operator - - A u := eta * u - mu * div grad u - - is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, - - V0h --grad-> V1h -—curl-> V2h -""" - -from mpi4py import MPI - -import os -import numpy as np -from collections import OrderedDict - -from sympy import lambdify -from scipy.sparse.linalg import spsolve - -from sympde.expr.expr import LinearForm -from sympde.expr.expr import integral, Norm -from sympde.topology import Derham -from sympde.topology import element_of - -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.multipatch.api import discretize -from psydac.feec.pull_push import pull_2d_h1 - -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE -from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection -from psydac.api.postprocessing import OutputManager, PostProcessManager - -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField - - -def solve_h1_source_pbm_nc( - nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_L2', source_type='manu_poisson', - eta=-10., mu=1., gamma_h=10., - plot_source=False, plot_dir=None, hide_plots=True -): - """ - solver for the problem: find u in H^1, such that - - A u = f on \\Omega - u = u_bc on \\partial \\Omega - - where the operator - - A u := eta * u - mu * div grad u - - is discretized as Ah: V0h -> V0h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, - - V0h --grad-> V1h -—curl-> V2h - - Examples: - - - Helmholtz equation with - eta = -omega**2 - mu = 1 - - - Poisson equation with Laplace operator L = A, - eta = 0 - mu = 1 - - :param nc: nb of cells per dimension, in each patch - :param deg: coordinate degree in each patch - :param gamma_h: jump penalization parameter - :param source_proj: approximation operator for the source, possible values are 'P_geom' or 'P_L2' - :param source_type: must be implemented in get_source_and_solution() - """ - - ncells = nc - degree = [deg, deg] - - # if backend_language is None: - # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: - # backend_language='numba' - # else: - # backend_language='python' - # print('[note: using '+backend_language+ ' backends in discretize functions]') - - print('---------------------------------------------------------------------------------------------------------') - print('Starting solve_h1_source_pbm function with: ') - print(' ncells = {}'.format(ncells)) - print(' degree = {}'.format(degree)) - print(' domain_name = {}'.format(domain_name)) - print(' source_proj = {}'.format(source_proj)) - print(' backend_language = {}'.format(backend_language)) - print('---------------------------------------------------------------------------------------------------------') - - print('building the multipatch domain...') - domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) - for P in domain.interior]) - mappings_list = list(mappings.values()) - ncells_h = {patch.name: [ncells[i], ncells[i]] - for (i, patch) in enumerate(domain.interior)} - domain_h = discretize(domain, ncells=ncells_h) - - print('building the symbolic and discrete deRham sequences...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree) - - # multi-patch (broken) spaces - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - print('dim(V0h) = {}'.format(V0h.nbasis)) - print('dim(V1h) = {}'.format(V1h.nbasis)) - print('dim(V2h) = {}'.format(V2h.nbasis)) - - print('broken differential operators...') - # broken (patch-wise) differential operators - bD0, bD1 = derham_h.broken_derivatives_as_operators - bD0_m = bD0.to_sparse_matrix() - # bD1_m = bD1.to_sparse_matrix() - - print('building the discrete operators:') - print('commuting projection operators...') - nquads = [4 * (d + 1) for d in degree] - P0, P1, P2 = derham_h.projectors(nquads=nquads) - - I0 = IdLinearOperator(V0h) - I0_m = I0.to_sparse_matrix() - - print('Hodge operators...') - # multi-patch (broken) linear operators / matrices - H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language) - H1 = HodgeOperator(V1h, domain_h, backend_language=backend_language) - - H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - dH0_m = H0.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - - print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions - # of the continuous deRham sequence) - cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) - # cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) - - if not os.path.exists(plot_dir): - os.makedirs(plot_dir) - - def lift_u_bc(u_bc): - if u_bc is not None: - print( - 'lifting the boundary condition in V0h... [warning: Not Tested Yet!]') - # note: for simplicity we apply the full P1 on u_bc, but we only - # need to set the boundary dofs - u_bc = lambdify(domain.coordinates, u_bc) - u_bc_log = [pull_2d_h1(u_bc, m.get_callable_mapping()) - for m in mappings_list] - # it's a bit weird to apply P1 on the list of (pulled back) logical - # fields -- why not just apply it on u_bc ? - uh_bc = P0(u_bc_log) - ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very - # good approximation of uh_c ...) - ubc_c = ubc_c - cP0_m.dot(ubc_c) - else: - ubc_c = None - return ubc_c - - # Conga (projection-based) stiffness matrices: - # div grad: - pre_DG_m = - bD0_m.transpose() @ H1_m @ bD0_m - - # jump penalization: - jump_penal_m = I0_m - cP0_m - JP0_m = jump_penal_m.transpose() * H0_m * jump_penal_m - - # useful for the boundary condition (if present) - pre_A_m = cP0_m.transpose() @ (eta * H0_m - mu * pre_DG_m) - A_m = pre_A_m @ cP0_m + gamma_h * JP0_m - - print('getting the source and ref solution...') - # (not all the returned functions are useful here) - N_diag = 200 - method = 'conga' - f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( - source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - refsol_params=[N_diag, method, source_proj], - ) - - # compute approximate source f_h - b_c = f_c = None - if source_proj == 'P_geom': - print('projecting the source with commuting projection P0...') - f = lambdify(domain.coordinates, f_scal) - f_log = [pull_2d_h1(f, m.get_callable_mapping()) - for m in mappings_list] - f_h = P0(f_log) - f_c = f_h.coeffs.toarray() - b_c = H0_m.dot(f_c) - - elif source_proj == 'P_L2': - print('projecting the source with L2 projection...') - v = element_of(V0h.symbolic_space, name='v') - expr = f_scal * v - l = LinearForm(v, integral(domain, expr)) - lh = discretize(l, domain_h, V0h) - b = lh.assemble() - b_c = b.toarray() - if plot_source: - f_c = dH0_m.dot(b_c) - else: - raise ValueError(source_proj) - - if plot_source: - plot_field( - numpy_coeffs=f_c, - Vh=V0h, - space_kind='h1', - domain=domain, - title='f_h with P = ' + - source_proj, - filename=plot_dir + - '/fh_' + - source_proj + - '.png', - hide_plot=hide_plots) - - ubc_c = lift_u_bc(u_bc) - - if ubc_c is not None: - # modified source for the homogeneous pbm - print('modifying the source with lifted bc solution...') - b_c = b_c - pre_A_m.dot(ubc_c) - - # direct solve with scipy spsolve - print('solving source problem with scipy.spsolve...') - uh_c = spsolve(A_m, b_c) - - # project the homogeneous solution on the conforming problem space - print('projecting the homogeneous solution on the conforming problem space...') - uh_c = cP0_m.dot(uh_c) - - if ubc_c is not None: - # adding the lifted boundary condition - print('adding the lifted boundary condition...') - uh_c += ubc_c - - print('getting and plotting the FEM solution from numpy coefs array...') - title = r'solution $\phi_h$ (amplitude)' - params_str = 'eta={}_mu={}_gamma_h={}'.format(eta, mu, gamma_h) - plot_field( - numpy_coeffs=uh_c, - Vh=V0h, - space_kind='h1', - domain=domain, - title=title, - filename=plot_dir + - params_str + - '_phi_h.png', - hide_plot=hide_plots) - - if u_ex: - u = element_of(V0h.symbolic_space, name='u') - l2norm = Norm(u - u_ex, domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V0h) - uh_c = array_to_psydac(uh_c, V0h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V0h, coeffs=uh_c)) - return l2_error - - -if __name__ == '__main__': - - t_stamp_full = time_count() - - quick_run = True - # quick_run = False - - omega = np.sqrt(170) # source - roundoff = 1e4 - eta = int(-omega**2 * roundoff) / roundoff - # print(eta) - # source_type = 'elliptic_J' - source_type = 'manu_poisson' - - # if quick_run: - # domain_name = 'curved_L_shape' - # nc = 4 - # deg = 2 - # else: - # nc = 8 - # deg = 4 - - domain_name = 'pretzel_f' - # domain_name = 'curved_L_shape' - nc = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, - 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) - - deg = 2 - - # nc = 2 - # deg = 2 - - run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) - solve_h1_source_pbm_nc( - nc=nc, deg=deg, - eta=eta, - mu=1, # 1, - domain_name=domain_name, - source_type=source_type, - backend_language='pyccel-gcc', - plot_source=True, - plot_dir='./plots/h1_tests_source_february/' + run_dir, - hide_plots=True, - ) - - time_count(t_stamp_full, msg='full program') diff --git a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py deleted file mode 100644 index ea64a6759..000000000 --- a/psydac/feec/multipatch/examples_nc/hcurl_eigen_pbms_nc.py +++ /dev/null @@ -1,379 +0,0 @@ -""" - Solve the eigenvalue problem for the curl-curl operator in 2D with non-matching FEEC discretization -""" -import os -from mpi4py import MPI - -import numpy as np -import matplotlib.pyplot as plt -from collections import OrderedDict -from sympde.topology import Derham - -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn -from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file - -from sympde.topology import Square -from sympde.topology import IdentityMapping, PolarMapping -from psydac.fem.vector import ProductFemSpace - -from scipy.sparse.linalg import spilu, lgmres -from scipy.sparse.linalg import LinearOperator, eigsh, minres -from scipy.sparse import csr_matrix -from scipy.linalg import norm - -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField - -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain -from psydac.feec.multipatch.non_matching_operators import construct_hcurl_conforming_projection - -from psydac.api.postprocessing import OutputManager, PostProcessManager - - -def hcurl_solve_eigen_pbm_nc(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), domain=([0, np.pi], [0, np.pi]), domain_name='refined_square', backend_language='pyccel-gcc', mu=1, nu=0, gamma_h=0, - generalized_pbm=False, sigma=5, ref_sigmas=None, nb_eigs_solve=8, nb_eigs_plot=5, skip_eigs_threshold=1e-7, - plot_dir=None, hide_plots=True, m_load_dir=None,): - """ - Solve the eigenvalue problem for the curl-curl operator in 2D with DG discretization - - Parameters - ---------- - ncells : array - Number of cells in each direction - degree : tuple - Degree of the basis functions - domain : list - Interval in x- and y-direction - domain_name : str - Name of the domain - backend_language : str - Language used for the backend - mu : float - Coefficient in the curl-curl operator - nu : float - Coefficient in the curl-curl operator - gamma_h : float - Coefficient in the curl-curl operator - generalized_pbm : bool - If True, solve the generalized eigenvalue problem - sigma : float - Calculate eigenvalues close to sigma - ref_sigmas : list - List of reference eigenvalues - nb_eigs_solve : int - Number of eigenvalues to solve - nb_eigs_plot : int - Number of eigenvalues to plot - skip_eigs_threshold : float - Threshold for the eigenvalues to skip - plot_dir : str - Directory for the plots - hide_plots : bool - If True, hide the plots - m_load_dir : str - Directory to save and load the matrices - """ - - diags = {} - - if sigma is None: - raise ValueError('please specify a value for sigma') - - print('---------------------------------------------------------------------------------------------------------') - print('Starting hcurl_solve_eigen_pbm function with: ') - print(' ncells = {}'.format(ncells)) - print(' degree = {}'.format(degree)) - print(' domain_name = {}'.format(domain_name)) - print(' backend_language = {}'.format(backend_language)) - print('---------------------------------------------------------------------------------------------------------') - t_stamp = time_count() - print('building symbolic and discrete domain...') - - int_x, int_y = domain - - if domain_name == 'refined_square' or domain_name == 'square_L_shape': - domain = create_square_domain(ncells, int_x, int_y, mapping='identity') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( - patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - elif domain_name == 'curved_L_shape': - domain = create_square_domain(ncells, int_x, int_y, mapping='polar') - ncells_h = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int( - patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - elif domain_name == 'pretzel_f': - domain = build_multipatch_domain(domain_name=domain_name) - ncells_h = {patch.name: [ncells[i], ncells[i]] - for (i, patch) in enumerate(domain.interior)} - - else: - ValueError("Domain not defined.") - - # domain = build_multipatch_domain(domain_name = 'curved_L_shape') - # - # ncells = np.array([4,8,4]) - # ncells_h = {patch.name: [ncells[i], ncells[i]] for (i,patch) in enumerate(domain.interior)} - mappings = OrderedDict([(P.logical_domain, P.mapping) - for P in domain.interior]) - mappings_list = list(mappings.values()) - - t_stamp = time_count(t_stamp) - print(' .. discrete domain...') - domain_h = discretize(domain, ncells=ncells_h) # Vh space - - print('building symbolic and discrete derham sequences...') - t_stamp = time_count() - print(' .. derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - - t_stamp = time_count(t_stamp) - print(' .. discrete derham sequence...') - derham_h = discretize(derham, domain_h, degree=degree) - - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - print('dim(V0h) = {}'.format(V0h.nbasis)) - print('dim(V1h) = {}'.format(V1h.nbasis)) - print('dim(V2h) = {}'.format(V2h.nbasis)) - diags['ndofs_V0'] = V0h.nbasis - diags['ndofs_V1'] = V1h.nbasis - diags['ndofs_V2'] = V2h.nbasis - - t_stamp = time_count(t_stamp) - print('building the discrete operators:') - # print('commuting projection operators...') - # nquads = [4*(d + 1) for d in degree] - # P0, P1, P2 = derham_h.projectors(nquads=nquads) - - I1 = IdLinearOperator(V1h) - I1_m = I1.to_sparse_matrix() - - t_stamp = time_count(t_stamp) - print('Hodge operators...') - # multi-patch (broken) linear operators / matrices - # H0 = HodgeOperator(V0h, domain_h, backend_language=backend_language, load_dir=m_load_dir, load_space_index=0) - H1 = HodgeOperator( - V1h, - domain_h, - backend_language=backend_language, - load_dir=m_load_dir, - load_space_index=1) - H2 = HodgeOperator( - V2h, - domain_h, - backend_language=backend_language, - load_dir=m_load_dir, - load_space_index=2) - - # H0_m = H0.to_sparse_matrix() # = mass matrix of V0 - # dH0_m = H0.get_dual_sparse_matrix() # = inverse mass matrix of V0 - H1_m = H1.to_sparse_matrix() # = mass matrix of V1 - dH1_m = H1.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V1 - H2_m = H2.to_sparse_matrix() # = mass matrix of V2 - dH2_m = H2.get_dual_Hodge_sparse_matrix() # = inverse mass matrix of V2 - - t_stamp = time_count(t_stamp) - print('conforming projection operators...') - # conforming Projections (should take into account the boundary conditions - # of the continuous deRham sequence) - cP0_m = None - cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) - - t_stamp = time_count(t_stamp) - print('broken differential operators...') - bD0, bD1 = derham_h.broken_derivatives_as_operators - # bD0_m = bD0.to_sparse_matrix() - bD1_m = bD1.to_sparse_matrix() - - t_stamp = time_count(t_stamp) - print('converting some matrices to csr format...') - - H1_m = H1_m.tocsr() - dH1_m = dH1_m.tocsr() - H2_m = H2_m.tocsr() - cP1_m = cP1_m.tocsr() - bD1_m = bD1_m.tocsr() - - if not os.path.exists(plot_dir): - os.makedirs(plot_dir) - - print('computing the full operator matrix...') - A_m = np.zeros_like(H1_m) - - # Conga (projection-based) stiffness matrices - if mu != 0: - # curl curl: - t_stamp = time_count(t_stamp) - print('mu = {}'.format(mu)) - print('curl-curl stiffness matrix...') - - pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m - CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix - A_m += mu * CC_m - - # jump stabilization in V1h: - if gamma_h != 0 or generalized_pbm: - t_stamp = time_count(t_stamp) - print('jump stabilization matrix...') - jump_stab_m = I1_m - cP1_m - JS_m = jump_stab_m.transpose() @ H1_m @ jump_stab_m - - if generalized_pbm: - print('adding jump stabilization to RHS of generalized eigenproblem...') - B_m = cP1_m.transpose() @ H1_m @ cP1_m + JS_m - else: - B_m = H1_m - - t_stamp = time_count(t_stamp) - print('solving matrix eigenproblem...') - all_eigenvalues, all_eigenvectors_transp = get_eigenvalues( - nb_eigs_solve, sigma, A_m, B_m) - # Eigenvalue processing - t_stamp = time_count(t_stamp) - print('sorting out eigenvalues...') - zero_eigenvalues = [] - if skip_eigs_threshold is not None: - eigenvalues = [] - eigenvectors = [] - for val, vect in zip(all_eigenvalues, all_eigenvectors_transp.T): - if abs(val) < skip_eigs_threshold: - zero_eigenvalues.append(val) - # we skip the eigenvector - else: - eigenvalues.append(val) - eigenvectors.append(vect) - else: - eigenvalues = all_eigenvalues - eigenvectors = all_eigenvectors_transp.T - - for k, val in enumerate(eigenvalues): - diags['eigenvalue_{}'.format(k)] = val # eigenvalues[k] - - for k, val in enumerate(zero_eigenvalues): - diags['skipped eigenvalue_{}'.format(k)] = val - - t_stamp = time_count(t_stamp) - print('plotting the eigenmodes...') - - # OM = OutputManager('spaces.yml', 'fields.h5') - # OM.add_spaces(V1h=V1h) - - nb_eigs = len(eigenvalues) - for i in range(min(nb_eigs_plot, nb_eigs)): - OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') - OM.add_spaces(V1h=V1h) - print('looking at emode i = {}... '.format(i)) - lambda_i = eigenvalues[i] - emode_i = np.real(eigenvectors[i]) - norm_emode_i = np.dot(emode_i, H1_m.dot(emode_i)) - eh_c = emode_i / norm_emode_i - stencil_coeffs = array_to_psydac(cP1_m @ eh_c, V1h.vector_space) - vh = FemField(V1h, coeffs=stencil_coeffs) - OM.set_static() - # OM.add_snapshot(t=i , ts=0) - OM.export_fields(vh=vh) - - # print('norm of computed eigenmode: ', norm_emode_i) - # plot the broken eigenmode: - OM.export_space_info() - OM.close() - - PM = PostProcessManager( - domain=domain, - space_file=plot_dir + - '/spaces.yml', - fields_file=plot_dir + - '/fields.h5') - PM.export_to_vtk( - plot_dir + - "/eigen_{}".format(i), - grid=None, - npts_per_cell=[6] * - 2, - snapshots='all', - fields='vh') - PM.close() - - t_stamp = time_count(t_stamp) - - return diags, eigenvalues - - -def get_eigenvalues(nb_eigs, sigma, A_m, M_m): - """ - Compute the eigenvalues of the matrix A close to sigma and right-hand-side M - - Parameters - ---------- - nb_eigs : int - Number of eigenvalues to compute - sigma : float - Value close to which the eigenvalues are computed - A_m : sparse matrix - Matrix A - M_m : sparse matrix - Matrix M - """ - - print('----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ') - print( - 'computing {0} eigenvalues (and eigenvectors) close to sigma={1} with scipy.sparse.eigsh...'.format( - nb_eigs, - sigma)) - mode = 'normal' - which = 'LM' - # from eigsh docstring: - # ncv = number of Lanczos vectors generated ncv must be greater than k and smaller than n; - # it is recommended that ncv > 2*k. Default: min(n, max(2*k + 1, 20)) - ncv = 4 * nb_eigs - print('A_m.shape = ', A_m.shape) - try_lgmres = True - max_shape_splu = 24000 # OK for nc=20, deg=6 on pretzel_f - if A_m.shape[0] < max_shape_splu: - print('(via sparse LU decomposition)') - OPinv = None - tol_eigsh = 0 - else: - - OP_m = A_m - sigma * M_m - tol_eigsh = 1e-7 - if try_lgmres: - print( - '(via SPILU-preconditioned LGMRES iterative solver for A_m - sigma*M1_m)') - OP_spilu = spilu(OP_m, fill_factor=15, drop_tol=5e-5) - preconditioner = LinearOperator( - OP_m.shape, lambda x: OP_spilu.solve(x)) - tol = tol_eigsh - OPinv = LinearOperator( - matvec=lambda v: lgmres(OP_m, v, x0=None, tol=tol, atol=tol, M=preconditioner, - callback=lambda x: print( - 'cg -- residual = ', norm(OP_m.dot(x) - v)) - )[0], - shape=M_m.shape, - dtype=M_m.dtype - ) - - else: - # from https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.eigsh.html: - # the user can supply the matrix or operator OPinv, which gives x = OPinv @ b = [A - sigma * M]^-1 @ b. - # > here, minres: MINimum RESidual iteration to solve Ax=b - # suggested in https://github.com/scipy/scipy/issues/4170 - print('(with minres iterative solver for A_m - sigma*M1_m)') - OPinv = LinearOperator( - matvec=lambda v: minres( - OP_m, - v, - tol=1e-10)[0], - shape=M_m.shape, - dtype=M_m.dtype) - - eigenvalues, eigenvectors = eigsh( - A_m, k=nb_eigs, M=M_m, sigma=sigma, mode=mode, which=which, ncv=ncv, tol=tol_eigsh, OPinv=OPinv) - - print("done: eigenvalues found: " + repr(eigenvalues)) - return eigenvalues, eigenvectors diff --git a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py b/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py deleted file mode 100644 index 75782b5bc..000000000 --- a/psydac/feec/multipatch/examples_nc/hcurl_source_pbms_nc.py +++ /dev/null @@ -1,419 +0,0 @@ -""" - solver for the problem: find u in H(curl), such that - - A u = f on \\Omega - n x u = n x u_bc on \\partial \\Omega - - where the operator - - A u := eta * u + mu * curl curl u - nu * grad div u - - is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, - - V0h --grad-> V1h -—curl-> V2h -""" - -import os -from mpi4py import MPI -import numpy as np -from collections import OrderedDict - -from sympy import lambdify, Matrix - -from scipy.sparse.linalg import spsolve - -from sympde.calculus import dot -from sympde.topology import element_of -from sympde.expr.expr import LinearForm -from sympde.expr.expr import integral, Norm -from sympde.topology import Derham - -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.pull_push import pull_2d_hcurl - -from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.fem_linear_operators import IdLinearOperator -from psydac.feec.multipatch.operators import HodgeOperator -from psydac.feec.multipatch.plotting_utilities import plot_field -from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_hcurl -from psydac.feec.multipatch.utils_conga_2d import DiagGrid, P0_phys, P1_phys, P2_phys, get_Vh_diags_for -from psydac.feec.multipatch.utilities import time_count -from psydac.linalg.utilities import array_to_psydac -from psydac.fem.basic import FemField -from psydac.feec.multipatch.examples.ppc_test_cases import get_source_and_solution_OBSOLETE -from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection -from psydac.api.postprocessing import OutputManager, PostProcessManager - - -def solve_hcurl_source_pbm_nc( - nc=4, deg=4, domain_name='pretzel_f', backend_language=None, source_proj='P_geom', source_type='manu_J', - eta=-10., mu=1., nu=1., gamma_h=10., - project_sol=False, - plot_source=False, plot_dir=None, hide_plots=True, skip_plot_titles=False, - cb_min_sol=None, cb_max_sol=None, - m_load_dir=None, sol_filename="", sol_ref_filename="", - ref_nc=None, ref_deg=None, test=False -): - """ - solver for the problem: find u in H(curl), such that - - A u = f on \\Omega - n x u = n x u_bc on \\partial \\Omega - - where the operator - - A u := eta * u + mu * curl curl u - nu * grad div u - - is discretized as Ah: V1h -> V1h in a broken-FEEC approach involving a discrete sequence on a 2D multipatch domain \\Omega, - - V0h --grad-> V1h -—curl-> V2h - - Examples: - - - time-harmonic maxwell equation with - eta = -omega**2 - mu = 1 - nu = 0 - - - Hodge-Laplacian operator L = A with - eta = 0 - mu = 1 - nu = 1 - - :param nc: nb of cells per dimension, in each patch - :param deg: coordinate degree in each patch - :param gamma_h: jump penalization parameter - :param source_proj: approximation operator (in V1h) for the source, possible values are - - 'P_geom': primal commuting projection based on geometric dofs - - 'P_L2': L2 projection on the broken space - - 'tilde_Pi': dual commuting projection, an L2 projection filtered by the adjoint conforming projection) - :param source_type: must be implemented in get_source_and_solution() - :param m_load_dir: directory for matrix storage - """ - diags = {} - - ncells = nc - degree = [deg, deg] - - # if backend_language is None: - # if domain_name in ['pretzel', 'pretzel_f'] and nc > 8: - # backend_language='numba' - # else: - # backend_language='python' - # print('[note: using '+backend_language+ ' backends in discretize functions]') - if m_load_dir is not None: - if not os.path.exists(m_load_dir): - os.makedirs(m_load_dir) - - print('---------------------------------------------------------------------------------------------------------') - print('Starting solve_hcurl_source_pbm function with: ') - print(' ncells = {}'.format(ncells)) - print(' degree = {}'.format(degree)) - print(' domain_name = {}'.format(domain_name)) - print(' source_proj = {}'.format(source_proj)) - print(' backend_language = {}'.format(backend_language)) - print('---------------------------------------------------------------------------------------------------------') - - print() - print(' -- building discrete spaces and operators --') - - t_stamp = time_count() - print(' .. multi-patch domain...') - domain = build_multipatch_domain(domain_name=domain_name) - mappings = OrderedDict([(P.logical_domain, P.mapping) - for P in domain.interior]) - mappings_list = list(mappings.values()) - ncells_h = {patch.name: [ncells[i], ncells[i]] - for (i, patch) in enumerate(domain.interior)} - - # corners in pretzel [2, 2, 2*,2*, 2, 1, 1, 1, 1, 1, 0, 0, 1, 2*, 2*, 2, 0, 0, 0 ] - # ncells = np.array([8, 8, 16, 16, 8, 4, 4, 4, 4, 4, 2, 2, 4, 16, 16, 8, 2, 2, 2]) - # ncells = np.array([4 for _ in range(18)]) - - # for diagnosttics - diag_grid = DiagGrid(mappings=mappings, N_diag=100) - - t_stamp = time_count(t_stamp) - print(' .. derham sequence...') - derham = Derham(domain, ["H1", "Hcurl", "L2"]) - - t_stamp = time_count(t_stamp) - print(' .. discrete domain...') - domain_h = discretize(domain, ncells=ncells_h) - - t_stamp = time_count(t_stamp) - print(' .. discrete derham sequence...') - derham_h = discretize(derham, domain_h, degree=degree) - - t_stamp = time_count(t_stamp) - print(' .. commuting projection operators...') - nquads = [4 * (d + 1) for d in degree] - P0, P1, P2 = derham_h.projectors(nquads=nquads) - - t_stamp = time_count(t_stamp) - print(' .. multi-patch spaces...') - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - print('dim(V0h) = {}'.format(V0h.nbasis)) - print('dim(V1h) = {}'.format(V1h.nbasis)) - print('dim(V2h) = {}'.format(V2h.nbasis)) - diags['ndofs_V0'] = V0h.nbasis - diags['ndofs_V1'] = V1h.nbasis - diags['ndofs_V2'] = V2h.nbasis - - t_stamp = time_count(t_stamp) - print(' .. Id operator and matrix...') - I1 = IdLinearOperator(V1h) - I1_m = I1.to_sparse_matrix() - - t_stamp = time_count(t_stamp) - print(' .. Hodge operators...') - # multi-patch (broken) linear operators / matrices - # other option: define as Hodge Operators: - H0 = HodgeOperator( - V0h, - domain_h, - backend_language=backend_language, - load_dir=m_load_dir, - load_space_index=0) - H1 = HodgeOperator( - V1h, - domain_h, - backend_language=backend_language, - load_dir=m_load_dir, - load_space_index=1) - H2 = HodgeOperator( - V2h, - domain_h, - backend_language=backend_language, - load_dir=m_load_dir, - load_space_index=2) - - t_stamp = time_count(t_stamp) - print(' .. Hodge matrix H0_m = M0_m ...') - H0_m = H0.to_sparse_matrix() - t_stamp = time_count(t_stamp) - print(' .. dual Hodge matrix dH0_m = inv_M0_m ...') - dH0_m = H0.get_dual_Hodge_sparse_matrix() - - t_stamp = time_count(t_stamp) - print(' .. Hodge matrix H1_m = M1_m ...') - H1_m = H1.to_sparse_matrix() - t_stamp = time_count(t_stamp) - print(' .. dual Hodge matrix dH1_m = inv_M1_m ...') - dH1_m = H1.get_dual_Hodge_sparse_matrix() - - t_stamp = time_count(t_stamp) - print(' .. Hodge matrix H2_m = M2_m ...') - H2_m = H2.to_sparse_matrix() - dH2_m = H2.get_dual_Hodge_sparse_matrix() - - t_stamp = time_count(t_stamp) - print(' .. conforming Projection operators...') - # conforming Projections (should take into account the boundary conditions - # of the continuous deRham sequence) - cP0_m = construct_h1_conforming_projection(V0h, hom_bc=True) - cP1_m = construct_hcurl_conforming_projection(V1h, hom_bc=True) - - t_stamp = time_count(t_stamp) - print(' .. broken differential operators...') - # broken (patch-wise) differential operators - bD0, bD1 = derham_h.broken_derivatives_as_operators - bD0_m = bD0.to_sparse_matrix() - bD1_m = bD1.to_sparse_matrix() - - if plot_dir is not None and not os.path.exists(plot_dir): - os.makedirs(plot_dir) - - def lift_u_bc(u_bc): - if u_bc is not None: - print('lifting the boundary condition in V1h...') - # note: for simplicity we apply the full P1 on u_bc, but we only - # need to set the boundary dofs - uh_bc = P1_phys(u_bc, P1, domain, mappings_list) - ubc_c = uh_bc.coeffs.toarray() - # removing internal dofs (otherwise ubc_c may already be a very - # good approximation of uh_c ...) - ubc_c = ubc_c - cP1_m.dot(ubc_c) - else: - ubc_c = None - return ubc_c - - # Conga (projection-based) stiffness matrices - # curl curl: - t_stamp = time_count(t_stamp) - print(' .. curl-curl stiffness matrix...') - print(bD1_m.shape, H2_m.shape) - pre_CC_m = bD1_m.transpose() @ H2_m @ bD1_m - # CC_m = cP1_m.transpose() @ pre_CC_m @ cP1_m # Conga stiffness matrix - - # grad div: - t_stamp = time_count(t_stamp) - print(' .. grad-div stiffness matrix...') - pre_GD_m = - H1_m @ bD0_m @ cP0_m @ dH0_m @ cP0_m.transpose() @ bD0_m.transpose() @ H1_m - # GD_m = cP1_m.transpose() @ pre_GD_m @ cP1_m # Conga stiffness matrix - - # jump stabilization: - t_stamp = time_count(t_stamp) - print(' .. jump stabilization matrix...') - jump_penal_m = I1_m - cP1_m - JP_m = jump_penal_m.transpose() * H1_m * jump_penal_m - - t_stamp = time_count(t_stamp) - print(' .. full operator matrix...') - print('eta = {}'.format(eta)) - print('mu = {}'.format(mu)) - print('nu = {}'.format(nu)) - print('STABILIZATION: gamma_h = {}'.format(gamma_h)) - # useful for the boundary condition (if present) - pre_A_m = cP1_m.transpose() @ (eta * H1_m + mu * pre_CC_m - nu * pre_GD_m) - A_m = pre_A_m @ cP1_m + gamma_h * JP_m - - t_stamp = time_count(t_stamp) - print() - print(' -- getting source --') - if source_type == 'manu_maxwell': - f_scal, f_vect, u_bc, p_ex, u_ex, phi, grad_phi = get_source_and_solution_OBSOLETE( - source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - ) - else: - f_vect, u_bc, u_ex, curl_u_ex, div_u_ex = get_source_and_solution_hcurl( - source_type=source_type, eta=eta, mu=mu, domain=domain, domain_name=domain_name, - ) - # compute approximate source f_h - t_stamp = time_count(t_stamp) - tilde_f_c = f_c = None - if source_proj == 'P_geom': - # f_h = P1-geometric (commuting) projection of f_vect - print(' .. projecting the source with primal (geometric) commuting projection...') - f_h = P1_phys(f_vect, P1, domain, mappings_list) - f_c = f_h.coeffs.toarray() - tilde_f_c = H1_m.dot(f_c) - - elif source_proj in ['P_L2', 'tilde_Pi']: - # f_h = L2 projection of f_vect, with filtering if tilde_Pi - print( - ' .. projecting the source with ' + - source_proj + - ' projection...') - tilde_f_c = derham_h.get_dual_dofs( - space='V1', - f=f_vect, - backend_language=backend_language, - return_format='numpy_array') - if source_proj == 'tilde_Pi': - print(' .. filtering the discrete source with P0.T ...') - tilde_f_c = cP1_m.transpose() @ tilde_f_c - else: - raise ValueError(source_proj) - - if plot_source: - if True: - title = '' - title_vf = '' - else: - title = 'f_h with P = ' + source_proj - title_vf = 'f_h with P = ' + source_proj - if f_c is None: - f_c = dH1_m.dot(tilde_f_c) - plot_field(numpy_coeffs=f_c, Vh=V1h, space_kind='hcurl', domain=domain, - title=title, filename=plot_dir + '/fh_' + source_proj + '.pdf', hide_plot=hide_plots) - plot_field(numpy_coeffs=f_c, Vh=V1h, plot_type='vector_field', space_kind='hcurl', domain=domain, - title=title_vf, filename=plot_dir + '/fh_' + source_proj + '_vf.pdf', hide_plot=hide_plots) - - ubc_c = lift_u_bc(u_bc) - if ubc_c is not None: - # modified source for the homogeneous pbm - t_stamp = time_count(t_stamp) - print(' .. modifying the source with lifted bc solution...') - tilde_f_c = tilde_f_c - pre_A_m.dot(ubc_c) - - # direct solve with scipy spsolve - t_stamp = time_count(t_stamp) - print() - print(' -- solving source problem with scipy.spsolve...') - uh_c = spsolve(A_m, tilde_f_c) - - # project the homogeneous solution on the conforming problem space - if project_sol: - t_stamp = time_count(t_stamp) - print(' .. projecting the homogeneous solution on the conforming problem space...') - uh_c = cP1_m.dot(uh_c) - else: - print(' .. NOT projecting the homogeneous solution on the conforming problem space') - - if ubc_c is not None: - # adding the lifted boundary condition - t_stamp = time_count(t_stamp) - print(' .. adding the lifted boundary condition...') - uh_c += ubc_c - - uh = FemField(V1h, coeffs=array_to_psydac(uh_c, V1h.vector_space)) - f_c = dH1_m.dot(tilde_f_c) - jh = FemField(V1h, coeffs=array_to_psydac(f_c, V1h.vector_space)) - - t_stamp = time_count(t_stamp) - - print() - print(' -- plots and diagnostics --') - if plot_dir: - print(' .. plotting the FEM solution...') - if skip_plot_titles: - title = '' - title_vf = '' - else: - title = r'solution $u_h$ (amplitude) for $\eta = $' + repr(eta) - title_vf = r'solution $u_h$ for $\eta = $' + repr(eta) - params_str = 'eta={}_mu={}_nu={}_gamma_h={}_Pf={}'.format( - eta, mu, nu, gamma_h, source_proj) - plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, surface_plot=False, title=title, - filename=plot_dir + '/' + params_str + '_uh.pdf', - plot_type='amplitude', cb_min=cb_min_sol, cb_max=cb_max_sol, hide_plot=hide_plots) - plot_field(numpy_coeffs=uh_c, Vh=V1h, space_kind='hcurl', domain=domain, title=title_vf, - filename=plot_dir + '/' + params_str + '_uh_vf.pdf', - plot_type='vector_field', hide_plot=hide_plots) - - OM = OutputManager(plot_dir + '/spaces.yml', plot_dir + '/fields.h5') - OM.add_spaces(V1h=V1h) - OM.set_static() - OM.export_fields(vh=uh) - OM.export_fields(jh=jh) - OM.export_space_info() - OM.close() - - PM = PostProcessManager( - domain=domain, - space_file=plot_dir + - '/spaces.yml', - fields_file=plot_dir + - '/fields.h5') - PM.export_to_vtk( - plot_dir + "/sol", - grid=None, - npts_per_cell=[6] * 2, - snapshots='all', - fields='vh') - PM.export_to_vtk( - plot_dir + "/source", - grid=None, - npts_per_cell=[6] * 2, - snapshots='all', - fields='jh') - - PM.close() - - time_count(t_stamp) - - if test: - u = element_of(V1h.symbolic_space, name='u') - l2norm = Norm( - Matrix([u[0] - u_ex[0], u[1] - u_ex[1]]), domain, kind='l2') - l2norm_h = discretize(l2norm, domain_h, V1h) - uh_c = array_to_psydac(uh_c, V1h.vector_space) - l2_error = l2norm_h.assemble(u=FemField(V1h, coeffs=uh_c)) - print(l2_error) - return l2_error - - return diags diff --git a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py index 3db73b8d9..867c17df8 100644 --- a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py @@ -3,30 +3,31 @@ import numpy as np from psydac.feec.multipatch.examples.hcurl_source_pbms_conga_2d import solve_hcurl_source_pbm -from psydac.feec.multipatch.examples_nc.hcurl_source_pbms_nc import solve_hcurl_source_pbm_nc - from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import hcurl_solve_eigen_pbm -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_nc import hcurl_solve_eigen_pbm_nc -from psydac.feec.multipatch.examples_nc.hcurl_eigen_pbms_dg import hcurl_solve_eigen_pbm_dg +from psydac.feec.multipatch.examples.hcurl_eigen_pbms_dg_2d import hcurl_solve_eigen_pbm_dg def test_time_harmonic_maxwell_pretzel_f(): - nc, deg = 10, 2 - source_type = 'manu_maxwell' + nc = 10 + deg = 2 + + source_type = 'manu_maxwell_inhom' domain_name = 'pretzel_f' + source_proj = 'tilde_Pi' - eta = -170.0 # source + omega = np.pi + eta = -omega**2 # source - l2_error = solve_hcurl_source_pbm( + diags = solve_hcurl_source_pbm( nc=nc, deg=deg, eta=eta, nu=0, mu=1, domain_name=domain_name, source_type=source_type, + source_proj=source_proj, backend_language='pyccel-gcc') - - assert abs(l2_error - 0.06247745643640749) < 1e-10 + assert abs(diags["err"] - 0.00016729140844149693) < 1e-10 def test_time_harmonic_maxwell_pretzel_f_nc(): @@ -34,13 +35,14 @@ def test_time_harmonic_maxwell_pretzel_f_nc(): nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10, 20, 20, 20, 10, 10]) - source_type = 'manu_maxwell' + source_type = 'manu_maxwell_inhom' domain_name = 'pretzel_f' source_proj = 'tilde_Pi' - eta = -170.0 + omega = np.pi + eta = -omega**2 # source - l2_error = solve_hcurl_source_pbm_nc( + diags = solve_hcurl_source_pbm( nc=nc, deg=deg, eta=eta, nu=0, @@ -48,18 +50,17 @@ def test_time_harmonic_maxwell_pretzel_f_nc(): domain_name=domain_name, source_type=source_type, source_proj=source_proj, - plot_dir='./plots/th_maxell_nc', - backend_language='pyccel-gcc', - test=True) + backend_language='pyccel-gcc') - assert abs(l2_error - 0.04753613858909066) < 1e-10 + assert abs(diags["err"] - 0.00012830429612706266) < 1e-10 def test_maxwell_eigen_curved_L_shape(): domain_name = 'curved_L_shape' - - nc = 10 - deg = 2 + domain = [[1, 3], [0, np.pi / 4]] + + ncells = 10 + degree = [2, 2] ref_sigmas = [ 0.181857115231E+01, @@ -73,16 +74,17 @@ def test_maxwell_eigen_curved_L_shape(): nb_eigs_plot = 7 skip_eigs_threshold = 1e-7 - eigenvalues, eigenvectors = hcurl_solve_eigen_pbm( - nc=nc, deg=deg, + diags, eigenvalues = hcurl_solve_eigen_pbm( + ncells=ncells, degree=degree, gamma_h=0, + generalized_pbm=True, nu=0, mu=1, sigma=sigma, skip_eigs_threshold=skip_eigs_threshold, - nb_eigs=nb_eigs_solve, + nb_eigs_solve=nb_eigs_solve, nb_eigs_plot=nb_eigs_plot, - domain_name=domain_name, + domain_name=domain_name, domain=domain, backend_language='pyccel-gcc', plot_dir='./plots/eigen_maxell', ) @@ -93,7 +95,7 @@ def test_maxwell_eigen_curved_L_shape(): error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) - assert abs(error - 0.023395836648441557) < 1e-10 + assert abs(error - 0.004697863286378944) < 1e-10 def test_maxwell_eigen_curved_L_shape_nc(): @@ -117,14 +119,13 @@ def test_maxwell_eigen_curved_L_shape_nc(): nb_eigs_plot = 7 skip_eigs_threshold = 1e-7 - diags, eigenvalues = hcurl_solve_eigen_pbm_nc( + diags, eigenvalues = hcurl_solve_eigen_pbm( ncells=ncells, degree=degree, gamma_h=0, generalized_pbm=True, nu=0, mu=1, sigma=sigma, - ref_sigmas=ref_sigmas, skip_eigs_threshold=skip_eigs_threshold, nb_eigs_solve=nb_eigs_solve, nb_eigs_plot=nb_eigs_plot, @@ -165,12 +166,9 @@ def test_maxwell_eigen_curved_L_shape_dg(): diags, eigenvalues = hcurl_solve_eigen_pbm_dg( ncells=ncells, degree=degree, - gamma_h=0, - generalized_pbm=True, nu=0, mu=1, sigma=sigma, - ref_sigmas=ref_sigmas, skip_eigs_threshold=skip_eigs_threshold, nb_eigs_solve=nb_eigs_solve, nb_eigs_plot=nb_eigs_plot, @@ -187,11 +185,10 @@ def test_maxwell_eigen_curved_L_shape_dg(): assert abs(error - 0.004208158031148591) < 1e-10 + # ============================================================================== # CLEAN UP SYMPY NAMESPACE # ============================================================================== - - def teardown_module(): from sympy.core import cache cache.clear_cache() diff --git a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py index 7441c8312..5fa51bbc1 100644 --- a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py @@ -1,7 +1,6 @@ import numpy as np from psydac.feec.multipatch.examples.h1_source_pbms_conga_2d import solve_h1_source_pbm -from psydac.feec.multipatch.examples_nc.h1_source_pbms_nc import solve_h1_source_pbm_nc def test_poisson_pretzel_f(): @@ -10,7 +9,7 @@ def test_poisson_pretzel_f(): domain_name = 'pretzel_f' nc = 10 deg = 2 - run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) + l2_error = solve_h1_source_pbm( nc=nc, deg=deg, eta=0, @@ -18,10 +17,9 @@ def test_poisson_pretzel_f(): domain_name=domain_name, source_type=source_type, backend_language='pyccel-gcc', - plot_source=False, - plot_dir='./plots/h1_tests_source_february/' + run_dir) + plot_dir=None) - assert abs(l2_error - 8.054935880166114e-05) < 1e-10 + assert abs(l2_error - 7.067946606662924e-07) < 1e-10 def test_poisson_pretzel_f_nc(): @@ -31,23 +29,22 @@ def test_poisson_pretzel_f_nc(): nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, 10, 10, 20, 20, 20, 10, 10]) deg = 2 - run_dir = '{}_{}_nc={}_deg={}/'.format(domain_name, source_type, nc, deg) - l2_error = solve_h1_source_pbm_nc( + + l2_error = solve_h1_source_pbm( nc=nc, deg=deg, eta=0, mu=1, domain_name=domain_name, source_type=source_type, backend_language='pyccel-gcc', - plot_source=False, - plot_dir='./plots/h1_tests_source_february/' + run_dir) + plot_dir=None) + + assert abs(l2_error - 3.991995932404924e-07) < 1e-10 + - assert abs(l2_error - 4.6086851224995065e-05) < 1e-10 # ============================================================================== # CLEAN UP SYMPY NAMESPACE # ============================================================================== - - def teardown_module(): from sympy.core import cache cache.clear_cache() From 4d5f78094afa6f0d087bdb3d59551a8e117cb3e4 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 11 Jun 2024 17:53:50 +0200 Subject: [PATCH 57/88] clean up multipatch domain utilities --- .../examples/hcurl_eigen_pbms_conga_2d.py | 9 +- .../examples/hcurl_eigen_pbms_dg_2d.py | 6 +- .../multipatch/examples/timedomain_maxwell.py | 4 +- .../multipatch/multipatch_domain_utilities.py | 318 +++++------------- ...on_matching_multipatch_domain_utilities.py | 121 ------- 5 files changed, 86 insertions(+), 372 deletions(-) delete mode 100644 psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index 421b66e7d..f480cb424 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -30,7 +30,7 @@ from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain +from psydac.feec.multipatch.multipatch_domain_utilities import build_cartesian_multipatch_domain from psydac.feec.multipatch.non_matching_operators import construct_h1_conforming_projection, construct_hcurl_conforming_projection from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -96,14 +96,15 @@ def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), doma domain = build_multipatch_domain(domain_name=domain_name) elif domain_name == 'refined_square' or domain_name == 'square_L_shape': - domain = create_square_domain(ncells, int_x, int_y, mapping='identity') + domain = build_cartesian_multipatch_domain(ncells, int_x, int_y, mapping='identity') elif domain_name == 'curved_L_shape': - domain = create_square_domain(ncells, int_x, int_y, mapping='polar') + domain = build_cartesian_multipatch_domain(ncells, int_x, int_y, mapping='polar') else: domain = build_multipatch_domain(domain_name=domain_name) + print(ncells) if type(ncells) == int: ncells = [ncells, ncells] elif ncells.ndim == 1: @@ -112,7 +113,7 @@ def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), doma elif ncells.ndim == 2: ncells = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - + print(ncells) mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py index a32483dde..2007f310e 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py @@ -34,7 +34,7 @@ from psydac.feec.multipatch.multipatch_domain_utilities import build_multipatch_domain from psydac.feec.multipatch.utilities import time_count, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.api import discretize -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain +from psydac.feec.multipatch.multipatch_domain_utilities import build_cartesian_multipatch_domain from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -92,10 +92,10 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d domain = build_multipatch_domain(domain_name=domain_name) elif domain_name == 'refined_square' or domain_name == 'square_L_shape': - domain = create_square_domain(ncells, int_x, int_y, mapping='identity') + domain = build_cartesian_multipatch_domain(ncells, int_x, int_y, mapping='identity') elif domain_name == 'curved_L_shape': - domain = create_square_domain(ncells, int_x, int_y, mapping='polar') + domain = _domain(ncells, int_x, int_y, mapping='polar') else: domain = build_multipatch_domain(domain_name=domain_name) diff --git a/psydac/feec/multipatch/examples/timedomain_maxwell.py b/psydac/feec/multipatch/examples/timedomain_maxwell.py index 6292adacc..a5099ea08 100644 --- a/psydac/feec/multipatch/examples/timedomain_maxwell.py +++ b/psydac/feec/multipatch/examples/timedomain_maxwell.py @@ -46,7 +46,7 @@ from psydac.linalg.utilities import array_to_psydac from psydac.fem.basic import FemField from psydac.feec.multipatch.non_matching_operators import construct_hcurl_conforming_projection, construct_h1_conforming_projection -from psydac.feec.multipatch.non_matching_multipatch_domain_utilities import create_square_domain +from psydac.feec.multipatch.multipatch_domain_utilities import build_cartesian_multipatch_domain from psydac.api.postprocessing import OutputManager, PostProcessManager @@ -260,7 +260,7 @@ def solve_td_maxwell_pbm(*, print(' .. multi-patch domain...') if domain_name == 'refined_square' or domain_name == 'square_L_shape': int_x, int_y = domain_lims - domain = create_square_domain(nc, int_x, int_y, mapping='identity') + domain = build_cartesian_multipatch_domain(nc, int_x, int_y, mapping='identity') else: domain = build_multipatch_domain(domain_name=domain_name) diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 07ef83a7d..154d4d8d4 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -32,18 +32,6 @@ class TransposedPolarMapping(Mapping): _pdim = 2 -def create_domain(patches, interfaces, name): - # todo: remove this function and just use Domain.join - connectivity = [] - patches_interiors = [D.interior for D in patches] - for I in interfaces: - connectivity.append( - ((patches_interiors.index( - I[0].domain), I[0].axis, I[0].ext), (patches_interiors.index( - I[1].domain), I[1].axis, I[1].ext), I[2])) - return Domain.join(patches, connectivity, name) - - def sympde_Domain_join(patches, connectivity, name): """ temporary fix while sympde PR #155 is not merged @@ -927,249 +915,95 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): return domain -def build_multipatch_rectangle( - nb_patch_x=2, - nb_patch_y=2, - x_min=0, - x_max=np.pi, - y_min=0, - y_max=np.pi, - perio=( - True, - True), - ncells=( - 4, - 4), - comm=None, - F_name='Identity'): +def build_cartesian_multipatch_domain(ncells, log_interval_x, log_interval_y, mapping='identity'): """ - Create a 2D multipatch rectangle domain with the prescribed number of patch in each direction. - (copied from Valentin's code) + Create a 2D multipatch Cartesian domain with the prescribed pattern of patches and with possible mappings. Parameters ---------- - nb_patch_x: - number of patch in x direction - - nb_patch_y: - number of patch in y direction - - x_min: - x cordinate for the left boundary of the domain - - x_max: - x cordinate for the right boundary of the domain - - y_min: - y cordinate for the bottom boundary of the domain - - y_max: - y cordinate for the top boundary of the domain - - perio: list of - periodicity of the domain in each direction - - F_name: - name of a (global) mapping to apply to all the patches + ncells: + (Incomplete) Cartesian grid of patches, where some patches may be empty. + The pattern of the multipatch domain is defined by the non-None entries of the matrix ncells. + (Different numerical values will give rise to the same multipatch decompostion) + + ncells can then be used (afterwards) for discretizing the domain with ncells[i,j] being the number of cells + (assumed isotropic in each patch) in the patch (i,j), and None for empty patches (removed from domain). + + Example: + + ncells = np.array([[1, None, 5], + [2, 3, 4]]) + + corresponds to a domain with 5 patches as follows: + + |X| |X| + ------- + |X|X|X| + + log_interval_x: + The interval in the x direction in the logical domain. + log_interval_y: + The interval in the y direction in the logical domain. + mapping: + The type of mapping to use. Can be 'identity' or 'polar'. Returns ------- domain : - The symbolic multipatch domain + The symbolic multipatch domain """ - - x_diff = x_max - x_min - y_diff = y_max - y_min - - list_Omega = [[Square('OmegaLog_' + - str(i) + - '_' + - str(j), bounds1=(x_min + - i / - nb_patch_x * - x_diff, x_min + - (i + - 1) / - nb_patch_x * - x_diff), bounds2=(y_min + - j / - nb_patch_y * - y_diff, y_min + - (j + - 1) / - nb_patch_y * - y_diff)) for j in range(nb_patch_y)] for i in range(nb_patch_x)] - - if F_name == 'Identity': - def F(name): return IdentityMapping(name, 2) - elif F_name == 'Collela': - def F(name): return CollelaMapping2D(name, eps=0.5) - else: - raise NotImplementedError(F_name) - - list_mapping = [[F('M_' + str(i) + '_' + str(j)) - for j in range(nb_patch_y)] for i in range(nb_patch_x)] - - list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range( - nb_patch_y)] for i in range(nb_patch_x)] - + ax, bx = log_interval_x + ay, by = log_interval_y + nb_patchx, nb_patchy = np.shape(ncells) + + # equidistant logical patches + # ensure the following lists have the same shape as ncells + list_log_patches = [[Square('Log_' + str(j) + '_' + str(i), + bounds1=(ax + j / nb_patchx * (bx - ax), ax + (j + 1) / nb_patchx * (bx - ax)), + bounds2=(by - (i + 1) / nb_patchy * (by - ay), by - i / nb_patchy * (by - ay))) + for i in range(nb_patchy)] for j in range(nb_patchx)] + + # mappings + if mapping == 'identity': + list_mapping = [[IdentityMapping('M_' + str(j) + '_' + str(i), 2) + for i in range(nb_patchy)] for j in range(nb_patchx)] + elif mapping == 'polar': + list_mapping = [[PolarMapping('M_' + str(j) + '_' + str(i), 2, c1=0., c2=0., rmin=0., rmax=1.) + for i in range(nb_patchy)] for j in range(nb_patchx)] + + # list of physical patches + list_patches = [[list_mapping[j][i](list_log_patches[j][i]) + for i in range(nb_patchy)] for j in range(nb_patchx)] + + # flatten for the join function patches = [] + for i in range(nb_patchx): + for j in range(nb_patchy): + if ncells[i, j] is not None: + patches.append(list_patches[j][i]) - for i in range(nb_patch_x): - patches.extend(list_domain[i]) - - interfaces = [] - # interfaces in x - list_right_bnd = [] - list_left_bnd = [] - list_top_bnd = [] - list_bottom_bnd1 = [] - list_bottom_bnd2 = [] - for j in range(nb_patch_y): - interfaces.extend([[list_domain[i][j].get_boundary(axis=0, - ext=+1), - list_domain[i + 1][j].get_boundary(axis=0, - ext=-1), - 1] for i in range(nb_patch_x - 1)]) - # periodic boundaries - if perio[0]: - interfaces.append([list_domain[nb_patch_x - - 1][j].get_boundary(axis=0, ext=+ - 1), list_domain[0][j].get_boundary(axis=0, ext=- - 1), 1]) - else: - list_right_bnd.append( - list_domain[nb_patch_x - 1][j].get_boundary(axis=0, ext=+1)) - list_left_bnd.append( - list_domain[0][j].get_boundary( - axis=0, ext=-1)) + axis_0 = 0 + axis_1 = 1 + ext_0 = -1 + ext_1 = +1 + connectivity = [] # interfaces in y - for i in range(nb_patch_x): - interfaces.extend([[list_domain[i][j].get_boundary(axis=1, - ext=+1), - list_domain[i][j + 1].get_boundary(axis=1, - ext=-1), - 1] for j in range(nb_patch_y - 1)]) - # periodic boundariesnb_patch_y-1 - if perio[1]: - interfaces.append([list_domain[i][nb_patch_y - - 1].get_boundary(axis=1, ext=+ - 1), list_domain[i][0].get_boundary(axis=1, ext=- - 1), 1]) - else: - list_top_bnd.append( - list_domain[i][nb_patch_y - 1].get_boundary(axis=1, ext=+1)) - if i < nb_patch_x / 2: - list_bottom_bnd1.append( - list_domain[i][0].get_boundary( - axis=1, ext=-1)) - else: - list_bottom_bnd2.append( - list_domain[i][0].get_boundary( - axis=1, ext=-1)) - - domain = create_domain(patches, interfaces, name='domain') - - right_bnd = None - left_bnd = None - top_bnd = None - bottom_bnd1 = None - bottom_bnd2 = None - if not perio[0]: - right_bnd = union_bnd(list_right_bnd) - left_bnd = union_bnd(list_left_bnd) - if not perio[1]: - top_bnd = union_bnd(list_top_bnd) - bottom_bnd1 = union_bnd(list_bottom_bnd1) - if len(list_bottom_bnd2) > 0: - bottom_bnd2 = union_bnd(list_bottom_bnd2) - else: - bottom_bnd2 = None - if nb_patch_x > 1 and nb_patch_y > 1: - # domain = set_interfaces(domain, interfaces) - domain_h = discretize(domain, ncells=ncells, comm=comm) - else: - domain_h = discretize(domain, ncells=ncells, periodic=perio, comm=comm) - - return domain, domain_h, [right_bnd, left_bnd, - top_bnd, bottom_bnd1, bottom_bnd2] - - -def get_ref_eigenvalues(domain_name, operator): - # return ref_eigenvalues for the given operator and domain - # and 'sigma' value, around which discrete eigenvalues will be searched by eigenvalue solver such as eigsh - # (Note: eigsh may yield a singular error if sigma is an exact discrete eigenvalue) + for i in range(nb_patchx): + connectivity.extend([ + [(list_patches[j ][i], axis_0, ext_1), + (list_patches[j+1][i], axis_0, ext_0), 1] + for j in range(nb_patchy -1) if ncells[i][j] is not None and ncells[i][j+1] is not None]) - assert operator in ['curl_curl', 'hodge_laplacian'] - ref_sigmas = [] - - if domain_name in ['square_2', 'square_6']: - # todo - if operator == 'curl_curl': - ref_sigmas = [ - 1, - 2, - 2, - ] - raise NotImplementedError - else: - ref_sigmas = [ - 1, - 2, - 2, - ] - raise NotImplementedError - elif domain_name in ['annulus_3', 'annulus_4']: - if operator == 'curl_curl': - ref_sigmas = [ - 1, - 2, - 2, - ] - raise NotImplementedError - else: - ref_sigmas = [ - 1, - 2, - 2, - ] - raise NotImplementedError - - elif domain_name == 'curved_L_shape': - if operator == 'curl_curl': - # sigma = 10 - ref_sigmas = [ - 0.181857115231E+01, - 0.349057623279E+01, - 0.100656015004E+02, - 0.101118862307E+02, - 0.124355372484E+02, - ] - elif operator == 'hodge_laplacian': - raise NotImplementedError - else: - raise NotImplementedError - - elif domain_name == 'pretzel': - if operator == 'curl_curl': - raise NotImplementedError - elif operator == 'hodge_laplacian': - ref_sigmas = [ - 0, - 0, - 0, - 0.1795447761871659, - 0.19922705025897117, - 0.699286528403241, - 0.8709410737744409, - 1.1945444491250097, - ] - else: - raise NotImplementedError - else: - raise NotImplementedError + # interfaces in x + for j in range(nb_patchy): + connectivity.extend([ + [(list_patches[j][i ], axis_1, ext_0), + (list_patches[j][i+1], axis_1, ext_1), 1] + for i in range(nb_patchx -1) if ncells[i][j] is not None and ncells[i+1][j] is not None]) - sigma = ref_sigmas[len(ref_sigmas) // 2] + # domain = Domain.join(patches, connectivity, name='domain') + domain = sympde_Domain_join(patches, connectivity, name='domain') - return sigma, ref_sigmas + return domain + \ No newline at end of file diff --git a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py b/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py deleted file mode 100644 index 6cdbd6607..000000000 --- a/psydac/feec/multipatch/non_matching_multipatch_domain_utilities.py +++ /dev/null @@ -1,121 +0,0 @@ -from mpi4py import MPI -import numpy as np -from sympde.topology import Square, Domain -from sympde.topology import IdentityMapping, PolarMapping, AffineMapping, Mapping -from sympde.topology import Boundary, Interface, Union - -from scipy.sparse import eye as sparse_eye -from scipy.sparse import csr_matrix -from scipy.sparse.linalg import inv -from scipy.sparse import coo_matrix, bmat -from scipy.sparse.linalg import inv as sp_inv - -from psydac.feec.multipatch.utilities import time_count -from psydac.feec.multipatch.api import discretize -from psydac.api.settings import PSYDAC_BACKENDS -from psydac.fem.splines import SplineSpace - -from psydac.feec.multipatch.multipatch_domain_utilities import sympde_Domain_join - -def create_square_domain(ncells, interval_x, interval_y, mapping='identity'): - """ - todo: rename this function and improve docstring (see comments on PR #320) - - Create a 2D multipatch square domain with the prescribed number of patches in each direction. - - Parameters - ---------- - ncells: - |2| - _____ - |4|2| - - [[2, None], - [4, 2]] - number of patches in each direction - - Returns - ------- - domain : - The symbolic multipatch domain - """ - ax, bx = interval_x - ay, by = interval_y - nb_patchx, nb_patchy = np.shape(ncells) - - list_Omega = [[Square('OmegaLog_' + str(i) + '_' + str(j), - bounds1=(ax + i / nb_patchx * (bx - ax), - ax + (i + 1) / nb_patchx * (bx - ax)), - bounds2=(ay + j / nb_patchy * (by - ay), ay + (j + 1) / nb_patchy * (by - ay))) - for j in range(nb_patchy)] for i in range(nb_patchx)] - - if mapping == 'identity': - list_mapping = [[IdentityMapping('M_' + str(i) + '_' + str(j), 2) - for j in range(nb_patchy)] for i in range(nb_patchx)] - - elif mapping == 'polar': - list_mapping = [ - [ - PolarMapping('M_' + str(i) + '_' + str(j), 2, - c1=0., - c2=0., - rmin=0., - rmax=1.) for j in range(nb_patchy)] for i in range(nb_patchx)] - - list_domain = [[list_mapping[i][j](list_Omega[i][j]) for j in range( - nb_patchy)] for i in range(nb_patchx)] - - flat_list = [] - for i in range(nb_patchx): - for j in range(nb_patchy): - if ncells[i, j] is not None: - flat_list.append(list_domain[i][j]) - - patches = flat_list - axis_0 = 0 - axis_1 = 1 - ext_0 = -1 - ext_1 = +1 - connectivity = [] - - # interfaces in y - for j in range(nb_patchy): - connectivity.extend([ - [(list_domain[i ][j], axis_0, ext_1), - (list_domain[i+1][j], axis_0, ext_0), - 1] - for i in range(nb_patchx -1) if ncells[i][j] is not None and ncells[i+1][j] is not None]) - - # interfaces in x - for i in range(nb_patchx): - connectivity.extend([ - [(list_domain[i][j ], axis_1, ext_1), - (list_domain[i][j+1], axis_1, ext_0), - 1] - - for j in range(nb_patchy -1) if ncells[i][j] is not None and ncells[i][j+1] is not None]) - - # domain = Domain.join(patches, connectivity, name='domain') - domain = sympde_Domain_join(patches, connectivity, name='domain') - - - return domain - - -def get_L_shape_ncells(patches, n0): - ncells = np.zeros((patches, patches), dtype=object) - - pm = int(patches / 2) - assert patches / 2 == pm - - for i in range(pm): - for j in range(pm): - ncells[i, j] = None - - for i in range(pm, patches): - for j in range(patches): - exp = 1 + patches - (abs(i - pm) + abs(j - pm)) - ncells[i, j] = n0**exp - ncells[j, i] = n0**exp - - return ncells From fffe43fcdeb3b9e2561c4eed602fe36d94d45af7 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Tue, 11 Jun 2024 18:31:25 +0200 Subject: [PATCH 58/88] Coarsen test runs and add timedomain dummy run --- .../examples/hcurl_eigen_pbms_dg_2d.py | 2 +- .../multipatch/examples/timedomain_maxwell.py | 100 +++++++++--------- .../examples/timedomain_maxwell_testcase.py | 2 +- .../multipatch/multipatch_domain_utilities.py | 2 +- .../tests/test_feec_maxwell_multipatch_2d.py | 31 +++--- .../tests/test_feec_poisson_multipatch_2d.py | 10 +- 6 files changed, 77 insertions(+), 70 deletions(-) diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py index 2007f310e..6f3dda773 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py @@ -95,7 +95,7 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d domain = build_cartesian_multipatch_domain(ncells, int_x, int_y, mapping='identity') elif domain_name == 'curved_L_shape': - domain = _domain(ncells, int_x, int_y, mapping='polar') + domain = build_cartesian_multipatch_domain(ncells, int_x, int_y, mapping='polar') else: domain = build_multipatch_domain(domain_name=domain_name) diff --git a/psydac/feec/multipatch/examples/timedomain_maxwell.py b/psydac/feec/multipatch/examples/timedomain_maxwell.py index a5099ea08..e93e6967c 100644 --- a/psydac/feec/multipatch/examples/timedomain_maxwell.py +++ b/psydac/feec/multipatch/examples/timedomain_maxwell.py @@ -58,7 +58,7 @@ def solve_td_maxwell_pbm(*, cfl_max=0.8, dt_max=None, domain_name='pretzel_f', - backend=None, + backend='pyccel-gcc', source_type='zero', source_omega=None, source_proj='P_geom', @@ -78,7 +78,7 @@ def solve_td_maxwell_pbm(*, # diag_dtau = None, cb_min_sol=None, cb_max_sol=None, - m_load_dir="", + m_load_dir=None, th_sol_filename="", source_is_harmonic=False, domain_lims=None @@ -982,23 +982,24 @@ def compute_diags(E_c, B_c, J_c, nt): GaussErr_norm2_diag[nt] = np.dot(GaussErr, H0_m.dot(GaussErr)) GaussErrP_norm2_diag[nt] = np.dot(GaussErrP, H0_m.dot(GaussErrP)) - OM1 = OutputManager(plot_dir + '/spaces1.yml', plot_dir + '/fields1.h5') - OM1.add_spaces(V1h=V1h) - OM1.export_space_info() + if plot_dir: + OM1 = OutputManager(plot_dir + '/spaces1.yml', plot_dir + '/fields1.h5') + OM1.add_spaces(V1h=V1h) + OM1.export_space_info() - OM2 = OutputManager(plot_dir + '/spaces2.yml', plot_dir + '/fields2.h5') - OM2.add_spaces(V2h=V2h) - OM2.export_space_info() + OM2 = OutputManager(plot_dir + '/spaces2.yml', plot_dir + '/fields2.h5') + OM2.add_spaces(V2h=V2h) + OM2.export_space_info() - stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) - Eh = FemField(V1h, coeffs=stencil_coeffs_E) - OM1.add_snapshot(t=0, ts=0) - OM1.export_fields(Eh=Eh) + stencil_coeffs_E = array_to_psydac(cP1_m @ E_c, V1h.vector_space) + Eh = FemField(V1h, coeffs=stencil_coeffs_E) + OM1.add_snapshot(t=0, ts=0) + OM1.export_fields(Eh=Eh) - stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) - Bh = FemField(V2h, coeffs=stencil_coeffs_B) - OM2.add_snapshot(t=0, ts=0) - OM2.export_fields(Bh=Bh) + stencil_coeffs_B = array_to_psydac(B_c, V2h.vector_space) + Bh = FemField(V2h, coeffs=stencil_coeffs_B) + OM2.add_snapshot(t=0, ts=0) + OM2.export_fields(Bh=Bh) # PM = PostProcessManager(domain=domain, space_file=plot_dir+'/spaces1.yml', fields_file=plot_dir+'/fields1.h5' ) # PM.export_to_vtk(plot_dir+"/Eh",grid=None, npts_per_cell=[6]*2, snapshots='all', fields='vh' ) @@ -1023,7 +1024,8 @@ def compute_diags(E_c, B_c, J_c, nt): f_c[:] = f0_c + f_harmonic_c if nt == 0: - plot_J_source_nPlusHalf(f_c, nt=0) + if plot_dir: + plot_J_source_nPlusHalf(f_c, nt=0) compute_diags(E_c, B_c, f_c, nt=0) E_c[:] = dCH1_m @ E_c + dt * (dC_m @ B_c - f_c) @@ -1077,7 +1079,7 @@ def compute_diags(E_c, B_c, J_c, nt): divE_norm2 = np.dot(divE_c, H0_m.dot(divE_c)) print('-- [{}]: || div E || = {}'.format(nt + 1, np.sqrt(divE_norm2))) - if is_plotting_time(nt + 1): + if is_plotting_time(nt + 1) and plot_dir: print("Plot Stuff") # plot_E_field(E_c, nt=nt+1, project_sol=True, plot_divE=False) # plot_B_field(B_c, nt=nt+1) @@ -1098,37 +1100,37 @@ def compute_diags(E_c, B_c, J_c, nt): # PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, # GaussErr_norm2_diag=GaussErr_norm2_diag, # GaussErrP_norm2_diag=GaussErrP_norm2_diag) - - OM1.close() - - print("Do some PP") - PM = PostProcessManager( - domain=domain, - space_file=plot_dir + - '/spaces1.yml', - fields_file=plot_dir + - '/fields1.h5') - PM.export_to_vtk( - plot_dir + "/Eh", - grid=None, - npts_per_cell=2, - snapshots='all', - fields='Eh') - PM.close() - - PM = PostProcessManager( - domain=domain, - space_file=plot_dir + - '/spaces2.yml', - fields_file=plot_dir + - '/fields2.h5') - PM.export_to_vtk( - plot_dir + "/Bh", - grid=None, - npts_per_cell=2, - snapshots='all', - fields='Bh') - PM.close() + if plot_dir: + OM1.close() + + print("Do some PP") + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces1.yml', + fields_file=plot_dir + + '/fields1.h5') + PM.export_to_vtk( + plot_dir + "/Eh", + grid=None, + npts_per_cell=2, + snapshots='all', + fields='Eh') + PM.close() + + PM = PostProcessManager( + domain=domain, + space_file=plot_dir + + '/spaces2.yml', + fields_file=plot_dir + + '/fields2.h5') + PM.export_to_vtk( + plot_dir + "/Bh", + grid=None, + npts_per_cell=2, + snapshots='all', + fields='Bh') + PM.close() # plot_time_diags(time_diag, E_norm2_diag, B_norm2_diag, divE_norm2_diag, nt_start=0, nt_end=Nt, # PE_norm2_diag=PE_norm2_diag, I_PE_norm2_diag=I_PE_norm2_diag, J_norm2_diag=J_norm2_diag, diff --git a/psydac/feec/multipatch/examples/timedomain_maxwell_testcase.py b/psydac/feec/multipatch/examples/timedomain_maxwell_testcase.py index 81ca2be3c..19c1e13d6 100644 --- a/psydac/feec/multipatch/examples/timedomain_maxwell_testcase.py +++ b/psydac/feec/multipatch/examples/timedomain_maxwell_testcase.py @@ -4,7 +4,7 @@ import numpy as np -from psydac.feec.multipatch.examples_nc.timedomain_maxwell_nc import solve_td_maxwell_pbm +from psydac.feec.multipatch.examples.timedomain_maxwell import solve_td_maxwell_pbm from psydac.feec.multipatch.utilities import time_count, FEM_sol_fn, get_run_dir, get_plot_dir, get_mat_dir, get_sol_dir, diag_fn from psydac.feec.multipatch.utils_conga_2d import write_diags_to_file diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 154d4d8d4..608ab754e 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -547,7 +547,7 @@ def build_multipatch_domain(domain_name='square_2', r_min=None, r_max=None): dom_log_12 = Square('dom12', bounds1=(-hr, hr), bounds2=(-h / 2, h / 2)) -# mapping_12 = get_2D_rotation_mapping('M12', c1=cr, c2=h/2 , alpha=0) + #mapping_12 = get_2D_rotation_mapping('M12', c1=cr, c2=h/2 , alpha=0) mapping_12 = AffineMapping( 'M12', 2, diff --git a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py index 867c17df8..bb1b09004 100644 --- a/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_maxwell_multipatch_2d.py @@ -5,10 +5,11 @@ from psydac.feec.multipatch.examples.hcurl_source_pbms_conga_2d import solve_hcurl_source_pbm from psydac.feec.multipatch.examples.hcurl_eigen_pbms_conga_2d import hcurl_solve_eigen_pbm from psydac.feec.multipatch.examples.hcurl_eigen_pbms_dg_2d import hcurl_solve_eigen_pbm_dg +from psydac.feec.multipatch.examples.timedomain_maxwell import solve_td_maxwell_pbm def test_time_harmonic_maxwell_pretzel_f(): - nc = 10 + nc = 4 deg = 2 source_type = 'manu_maxwell_inhom' @@ -27,13 +28,14 @@ def test_time_harmonic_maxwell_pretzel_f(): source_type=source_type, source_proj=source_proj, backend_language='pyccel-gcc') - assert abs(diags["err"] - 0.00016729140844149693) < 1e-10 + + assert abs(diags["err"] - 0.007201508128407582) < 1e-10 def test_time_harmonic_maxwell_pretzel_f_nc(): deg = 2 - nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, - 10, 10, 10, 10, 20, 20, 20, 10, 10]) + nc = np.array([8, 8, 8, 8, 8, 4, 4, 4, 4, + 4, 4, 4, 4, 8, 8, 8, 4, 4]) source_type = 'manu_maxwell_inhom' domain_name = 'pretzel_f' @@ -52,14 +54,14 @@ def test_time_harmonic_maxwell_pretzel_f_nc(): source_proj=source_proj, backend_language='pyccel-gcc') - assert abs(diags["err"] - 0.00012830429612706266) < 1e-10 + assert abs(diags["err"] - 0.004849165663310541) < 1e-10 def test_maxwell_eigen_curved_L_shape(): domain_name = 'curved_L_shape' domain = [[1, 3], [0, np.pi / 4]] - ncells = 10 + ncells = 4 degree = [2, 2] ref_sigmas = [ @@ -95,15 +97,15 @@ def test_maxwell_eigen_curved_L_shape(): error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) - assert abs(error - 0.004697863286378944) < 1e-10 + assert abs(error - 0.01291539899483907) < 1e-10 def test_maxwell_eigen_curved_L_shape_nc(): domain_name = 'curved_L_shape' domain = [[1, 3], [0, np.pi / 4]] - ncells = np.array([[None, 10], - [10, 20]]) + ncells = np.array([[None, 4], + [4, 8]]) degree = [2, 2] @@ -140,15 +142,15 @@ def test_maxwell_eigen_curved_L_shape_nc(): error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) - assert abs(error - 0.004301175400024398) < 1e-10 + assert abs(error - 0.010504876643873904) < 1e-10 def test_maxwell_eigen_curved_L_shape_dg(): domain_name = 'curved_L_shape' domain = [[1, 3], [0, np.pi / 4]] - ncells = np.array([[None, 10], - [10, 20]]) + ncells = np.array([[None, 4], + [4, 8]]) degree = [2, 2] @@ -182,9 +184,12 @@ def test_maxwell_eigen_curved_L_shape_dg(): for k in range(n_errs): error += (eigenvalues[k] - ref_sigmas[k])**2 error = np.sqrt(error) + + assert abs(error - 0.035139029534570064) < 1e-10 - assert abs(error - 0.004208158031148591) < 1e-10 +def test_maxwell_timedomain(): + solve_td_maxwell_pbm(nc = 4, deg = 2, final_time = 2, domain_name = 'square_2') # ============================================================================== # CLEAN UP SYMPY NAMESPACE diff --git a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py index 5fa51bbc1..7a0d94cbb 100644 --- a/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py +++ b/psydac/feec/multipatch/tests/test_feec_poisson_multipatch_2d.py @@ -7,7 +7,7 @@ def test_poisson_pretzel_f(): source_type = 'manu_poisson_2' domain_name = 'pretzel_f' - nc = 10 + nc = 4 deg = 2 l2_error = solve_h1_source_pbm( @@ -19,15 +19,15 @@ def test_poisson_pretzel_f(): backend_language='pyccel-gcc', plot_dir=None) - assert abs(l2_error - 7.067946606662924e-07) < 1e-10 + assert abs(l2_error - 1.0585687717792318e-05) < 1e-10 def test_poisson_pretzel_f_nc(): source_type = 'manu_poisson_2' domain_name = 'pretzel_f' - nc = np.array([20, 20, 20, 20, 20, 10, 10, 10, 10, - 10, 10, 10, 10, 20, 20, 20, 10, 10]) + nc = np.array([8, 8, 8, 8, 8, 4, 4, 4, 4, + 4, 4, 4, 4, 8, 8, 8, 4, 4]) deg = 2 l2_error = solve_h1_source_pbm( @@ -39,7 +39,7 @@ def test_poisson_pretzel_f_nc(): backend_language='pyccel-gcc', plot_dir=None) - assert abs(l2_error - 3.991995932404924e-07) < 1e-10 + assert abs(l2_error - 6.051557012306659e-06) < 1e-10 # ============================================================================== From d417da4c118172e9ab67adc90e2d982ac4881319 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Wed, 12 Jun 2024 10:27:10 +0200 Subject: [PATCH 59/88] Make codacy happy --- psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py | 2 +- psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py | 4 ++-- psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py | 4 ++-- psydac/feec/multipatch/examples/hcurl_eigen_testcases.py | 4 ++-- psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py | 2 +- psydac/feec/multipatch/examples/timedomain_maxwell.py | 2 +- psydac/feec/multipatch/multipatch_domain_utilities.py | 3 +-- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index 702897d42..3827ea4d5 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -103,7 +103,7 @@ def solve_h1_source_pbm( for P in domain.interior]) mappings_list = list(mappings.values()) - if type(nc) == int: + if isinstance(ncells, int): ncells = [nc, nc] else: ncells = {patch.name: [nc[i], nc[i]] diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index f480cb424..175bdb55f 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -92,7 +92,7 @@ def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), doma print('building symbolic and discrete domain...') int_x, int_y = domain - if type(ncells) == int: + if isinstance(ncells, int): domain = build_multipatch_domain(domain_name=domain_name) elif domain_name == 'refined_square' or domain_name == 'square_L_shape': @@ -105,7 +105,7 @@ def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), doma domain = build_multipatch_domain(domain_name=domain_name) print(ncells) - if type(ncells) == int: + if isinstance(ncells, int): ncells = [ncells, ncells] elif ncells.ndim == 1: ncells = {patch.name: [ncells[i], ncells[i]] diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py index 6f3dda773..50ec032fa 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_dg_2d.py @@ -88,7 +88,7 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d print('building symbolic and discrete domain...') int_x, int_y = domain - if type(ncells) == int: + if isinstance(ncells, int): domain = build_multipatch_domain(domain_name=domain_name) elif domain_name == 'refined_square' or domain_name == 'square_L_shape': @@ -100,7 +100,7 @@ def hcurl_solve_eigen_pbm_dg(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), d else: domain = build_multipatch_domain(domain_name=domain_name) - if type(ncells) == int: + if isinstance(ncells, int): ncells = [ncells, ncells] elif ncells.ndim == 1: ncells = {patch.name: [ncells[i], ncells[i]] diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py b/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py index 7670a579b..c942d26e5 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py @@ -211,8 +211,8 @@ # backend_language = 'numba' backend_language = 'pyccel-gcc' -dims = 1 if type(ncells) == int else ncells.shape -sz = 1 if type(ncells) == int else ncells[ncells != None].sum() +dims = 1 if isinstance(ncells, int) else ncells.shape +sz = 1 if isinstance(ncells, int) else ncells[ncells != None].sum() print(dims) # get_run_dir(domain_name, nc, deg) run_dir = domain_name + str(dims) + 'patches_' + 'size_{}'.format(sz) diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index ee08e11ca..82d4c2456 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -112,7 +112,7 @@ def solve_hcurl_source_pbm( for P in domain.interior]) mappings_list = list(mappings.values()) - if type(nc) == int: + if isinstance(ncells, int): ncells = [nc, nc] else: ncells = {patch.name: [nc[i], nc[i]] diff --git a/psydac/feec/multipatch/examples/timedomain_maxwell.py b/psydac/feec/multipatch/examples/timedomain_maxwell.py index e93e6967c..49baa85ad 100644 --- a/psydac/feec/multipatch/examples/timedomain_maxwell.py +++ b/psydac/feec/multipatch/examples/timedomain_maxwell.py @@ -265,7 +265,7 @@ def solve_td_maxwell_pbm(*, else: domain = build_multipatch_domain(domain_name=domain_name) - if type(nc) == int: + if isinstance(ncells, int): ncells = [nc, nc] elif ncells.ndim == 1: ncells = {patch.name: [nc[i], nc[i]] diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 608ab754e..e3abd2186 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -10,11 +10,10 @@ __all__ = ( 'TransposedPolarMapping', - 'create_domain', 'get_2D_rotation_mapping', 'flip_axis', 'build_multipatch_domain', - 'get_ref_eigenvalues') + 'build_cartesian_multipatch_domain') # ============================================================================== # small extension to SymPDE: From a748a4d8c1569a8765f6688d228f65ea6073c252 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Wed, 12 Jun 2024 11:12:42 +0200 Subject: [PATCH 60/88] fix typo --- psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py | 2 +- psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py | 2 -- psydac/feec/multipatch/examples/hcurl_eigen_testcases.py | 2 +- psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py | 2 +- psydac/feec/multipatch/examples/timedomain_maxwell.py | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py index 3827ea4d5..c0e401e8f 100644 --- a/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/h1_source_pbms_conga_2d.py @@ -103,7 +103,7 @@ def solve_h1_source_pbm( for P in domain.interior]) mappings_list = list(mappings.values()) - if isinstance(ncells, int): + if isinstance(nc, int): ncells = [nc, nc] else: ncells = {patch.name: [nc[i], nc[i]] diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py index 175bdb55f..588775e8b 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_pbms_conga_2d.py @@ -104,7 +104,6 @@ def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), doma else: domain = build_multipatch_domain(domain_name=domain_name) - print(ncells) if isinstance(ncells, int): ncells = [ncells, ncells] elif ncells.ndim == 1: @@ -113,7 +112,6 @@ def hcurl_solve_eigen_pbm(ncells=np.array([[8, 4], [4, 4]]), degree=(3, 3), doma elif ncells.ndim == 2: ncells = {patch.name: [ncells[int(patch.name[2])][int(patch.name[4])], ncells[int(patch.name[2])][int(patch.name[4])]] for patch in domain.interior} - print(ncells) mappings = OrderedDict([(P.logical_domain, P.mapping) for P in domain.interior]) diff --git a/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py b/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py index c942d26e5..4f311a7eb 100644 --- a/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py +++ b/psydac/feec/multipatch/examples/hcurl_eigen_testcases.py @@ -213,7 +213,7 @@ dims = 1 if isinstance(ncells, int) else ncells.shape sz = 1 if isinstance(ncells, int) else ncells[ncells != None].sum() -print(dims) + # get_run_dir(domain_name, nc, deg) run_dir = domain_name + str(dims) + 'patches_' + 'size_{}'.format(sz) plot_dir = get_plot_dir(case_dir, run_dir) diff --git a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py index 82d4c2456..bbff3b839 100644 --- a/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py +++ b/psydac/feec/multipatch/examples/hcurl_source_pbms_conga_2d.py @@ -112,7 +112,7 @@ def solve_hcurl_source_pbm( for P in domain.interior]) mappings_list = list(mappings.values()) - if isinstance(ncells, int): + if isinstance(nc, int): ncells = [nc, nc] else: ncells = {patch.name: [nc[i], nc[i]] diff --git a/psydac/feec/multipatch/examples/timedomain_maxwell.py b/psydac/feec/multipatch/examples/timedomain_maxwell.py index 49baa85ad..9b1ccb437 100644 --- a/psydac/feec/multipatch/examples/timedomain_maxwell.py +++ b/psydac/feec/multipatch/examples/timedomain_maxwell.py @@ -265,7 +265,7 @@ def solve_td_maxwell_pbm(*, else: domain = build_multipatch_domain(domain_name=domain_name) - if isinstance(ncells, int): + if isinstance(nc, int): ncells = [nc, nc] elif ncells.ndim == 1: ncells = {patch.name: [nc[i], nc[i]] From 25c586bfed75829c969e04a5c487d055041fc0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Moral=20S=C3=A1nchez?= <88042165+e-moral-sanchez@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:18:52 +0200 Subject: [PATCH 61/88] fix version 12 of macos in tests --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 826027fa1..b597b6529 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -23,7 +23,7 @@ jobs: - { isMerge: false, python-version: 3.9 } - { isMerge: false, python-version: '3.10' } include: - - os: macos-latest + - os: macos-12 python-version: '3.10' name: ${{ matrix.os }} / Python ${{ matrix.python-version }} From 2bceaf63045fd0cb43986ace30a1b4ea740718e8 Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Mon, 17 Jun 2024 11:19:21 +0200 Subject: [PATCH 62/88] Expose multipatch modules to docs --- docs/source/modules/feec.multipatch.rst | 4 ++++ psydac/feec/multipatch/multipatch_domain_utilities.py | 2 +- psydac/feec/multipatch/plotting_utilities.py | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/modules/feec.multipatch.rst b/docs/source/modules/feec.multipatch.rst index 03daf1d63..64bf49a12 100644 --- a/docs/source/modules/feec.multipatch.rst +++ b/docs/source/modules/feec.multipatch.rst @@ -9,5 +9,9 @@ feec.multipatch multipatch.api multipatch.fem_linear_operators + multipatch.multipatch_domain_utilities + multipatch.non_matching_operators multipatch.operators + multipatch.plotting_utilities multipatch.utilities + multipatch.utils_conga_2d diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index e3abd2186..2cb16bb6d 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -44,7 +44,7 @@ def sympde_Domain_join(patches, connectivity, name): return Domain.join(patches, connectivity_by_indices, name) -def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=np.pi / 2): +def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=1.5707963267948966): # AffineMapping: # _expressions = {'x': 'c1 + a11*x1 + a12*x2 + a13*x3', diff --git a/psydac/feec/multipatch/plotting_utilities.py b/psydac/feec/multipatch/plotting_utilities.py index 26351055b..af522c6f1 100644 --- a/psydac/feec/multipatch/plotting_utilities.py +++ b/psydac/feec/multipatch/plotting_utilities.py @@ -7,7 +7,6 @@ import matplotlib import matplotlib.pyplot as plt from matplotlib import cm, colors -from mpl_toolkits import mplot3d from collections import OrderedDict from psydac.linalg.utilities import array_to_psydac From 383d341fd013e7254fab9a295982621fc27c7acc Mon Sep 17 00:00:00 2001 From: jowezarek Date: Mon, 17 Jun 2024 16:51:14 +0200 Subject: [PATCH 63/88] minor documentation related fixes --- .gitignore | 1 + psydac/api/discretization.py | 4 ++-- psydac/feec/multipatch/multipatch_domain_utilities.py | 10 +++++----- psydac/feec/multipatch/utils_conga_2d.py | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index b65496d78..71dfeaf04 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.lock __psydac__/ __*pyccel__/ +docs/source/modules/STUBDIR/* build *build* diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 9cddd4a23..597baa731 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -102,7 +102,7 @@ def get_max_degree_of_one_space(Vh): Vh : FemSpace The finite element space under investigation. - Results + Returns ------- list[int] The maximum polynomial degre of Vh with respect to each coordinate. @@ -133,7 +133,7 @@ def get_max_degree(*spaces): *spaces : tuple[FemSpace] The finite element spaces under investigation. - Results + Returns ------- list[int] The maximum polynomial degree across all spaces, with respect to each diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 2cb16bb6d..769650a55 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -930,14 +930,14 @@ def build_cartesian_multipatch_domain(ncells, log_interval_x, log_interval_y, ma Example: - ncells = np.array([[1, None, 5], - [2, 3, 4]]) + >>> ncells = np.array([[1, None, 5], + >>> [2, 3, 4]]) corresponds to a domain with 5 patches as follows: - |X| |X| - ------- - |X|X|X| + >>> |X| |X| + >>> ------- + >>> |X|X|X| log_interval_x: The interval in the x direction in the logical domain. diff --git a/psydac/feec/multipatch/utils_conga_2d.py b/psydac/feec/multipatch/utils_conga_2d.py index 05ee2bae3..d86defda3 100644 --- a/psydac/feec/multipatch/utils_conga_2d.py +++ b/psydac/feec/multipatch/utils_conga_2d.py @@ -91,6 +91,7 @@ class DiagGrid(): - a diagnostic cell-centered grid - writing / quadrature utilities - a ref solution + to compare solutions from different FEM spaces on same domain """ From a9da50e3363822050deac42b5024185b90118ad1 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 18 Jun 2024 15:42:16 +0200 Subject: [PATCH 64/88] sphinx fix --- psydac/feec/multipatch/multipatch_domain_utilities.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/psydac/feec/multipatch/multipatch_domain_utilities.py b/psydac/feec/multipatch/multipatch_domain_utilities.py index 769650a55..8ded13651 100644 --- a/psydac/feec/multipatch/multipatch_domain_utilities.py +++ b/psydac/feec/multipatch/multipatch_domain_utilities.py @@ -44,13 +44,14 @@ def sympde_Domain_join(patches, connectivity, name): return Domain.join(patches, connectivity_by_indices, name) -def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=1.5707963267948966): +def get_2D_rotation_mapping(name='no_name', c1=0., c2=0., alpha=None): # AffineMapping: # _expressions = {'x': 'c1 + a11*x1 + a12*x2 + a13*x3', # 'y': 'c2 + a21*x1 + a22*x2 + a23*x3', - # 'z': 'c3 + a31*x1 + a32*x2 + a33*x3'} - + # 'z': 'c3 + a31*x1 + a32*x2 + a33*x3'} + if alpha is None: + alpha = np.pi/2 return AffineMapping( name, 2, c1=c1, c2=c2, a11=np.cos(alpha), a12=-np.sin(alpha), From 9995e216f39e26b36df19a6b5d3ef23e3736a76a Mon Sep 17 00:00:00 2001 From: kvrigor Date: Wed, 19 Jun 2024 14:56:36 +0200 Subject: [PATCH 65/88] Fixes for CI failures caused by new macOS runner version and numpy 2.0 See https://github.com/pyccel/psydac/pull/411 --- psydac/api/settings.py | 22 ++++++++++++++++++---- pyproject.toml | 5 ++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/psydac/api/settings.py b/psydac/api/settings.py index 98e56bcde..6d0988451 100644 --- a/psydac/api/settings.py +++ b/psydac/api/settings.py @@ -13,7 +13,7 @@ PSYDAC_BACKEND_GPYCCEL = {'name': 'pyccel', 'compiler': 'GNU', - 'flags' : '-O3 -march=native -mtune=native -ffast-math', + 'flags' : '-O3 -ffast-math', 'folder' : '__gpyccel__', 'tag' : 'gpyccel', 'openmp' : False} @@ -41,8 +41,22 @@ # ... # Platform-dependent flags -if platform.machine() == 'x86_64': - PSYDAC_BACKEND_GPYCCEL['flags'] += ' -mavx' +if platform.system() == "Darwin" and platform.machine() == 'arm64': + # Apple silicon requires architecture-specific flags (see https://github.com/pyccel/psydac/pull/411) + import subprocess # nosec B404 + cpu_brand = subprocess.check_output(['sysctl','-n','machdep.cpu.brand_string']).decode('utf-8') # nosec B603, B607 + if "Apple M1" in cpu_brand: + PSYDAC_BACKEND_GPYCCEL['flags'] += ' -mcpu=apple-m1' + else: + # TODO: Support later Apple CPU models. Perhaps the CPU naming scheme could be easily guessed + # based on the output of 'sysctl -n machdep.cpu.brand_string', but I wouldn't rely on this + # guess unless it has been manually verified. Loud errors are better than silent failures! + raise SystemError(f"Unsupported Apple CPU '{cpu_brand}'.") +else: + # Default architecture flags + PSYDAC_BACKEND_GPYCCEL['flags'] += ' -march=native -mtune=native' + if platform.machine() == 'x86_64': + PSYDAC_BACKEND_GPYCCEL['flags'] += ' -mavx' #============================================================================== @@ -53,4 +67,4 @@ 'pyccel-intel' : PSYDAC_BACKEND_IPYCCEL, 'pyccel-pgi' : PSYDAC_BACKEND_PGPYCCEL, 'pyccel-nvidia': PSYDAC_BACKEND_NVPYCCEL, -} +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 73d967b17..f99c75a10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,10 @@ dependencies = [ 'tblib', # IGAKIT - not on PyPI - 'igakit @ https://github.com/dalcinl/igakit/archive/refs/heads/master.zip' + + # !! WARNING !! Path to igakit below is from fork pyccel/igakit. This was done to + # quickly fix the numpy 2.0 issue. See https://github.com/dalcinl/igakit/pull/4 + 'igakit @ https://github.com/pyccel/igakit/archive/refs/heads/bugfix-numpy2.0.zip' ] [project.urls] From 802ab871760d48514f2364c2428d556e4b349850 Mon Sep 17 00:00:00 2001 From: kvrigor Date: Wed, 19 Jun 2024 15:27:37 +0200 Subject: [PATCH 66/88] CI: Temporarily disabled docs deployment since base repo is a fork --- .github/workflows/documentation.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 88e8065d5..0598a6849 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -36,12 +36,14 @@ jobs: make -C docs clean make -C docs html python docs/update_links.py - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Upload artifact - uses: actions/upload-pages-artifact@v1 - with: - path: 'docs/build/html' + + # Disable docs deployment for now as we're in psydac fork repo + # - name: Setup Pages + # uses: actions/configure-pages@v3 + # - name: Upload artifact + # uses: actions/upload-pages-artifact@v1 + # with: + # path: 'docs/build/html' deploy_docs: if: github.event_name != 'pull_request' From cec4c7a4f65db716ef02fe15524645a108bf7433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Moral=20S=C3=A1nchez?= <88042165+e-moral-sanchez@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:42:26 +0200 Subject: [PATCH 67/88] Update macos version --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index b597b6529..826027fa1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -23,7 +23,7 @@ jobs: - { isMerge: false, python-version: 3.9 } - { isMerge: false, python-version: '3.10' } include: - - os: macos-12 + - os: macos-latest python-version: '3.10' name: ${{ matrix.os }} / Python ${{ matrix.python-version }} From 63f3228b6c92d080f73d28b8f709f1af7906403f Mon Sep 17 00:00:00 2001 From: Frederik Schnack Date: Fri, 21 Jun 2024 10:53:43 +0200 Subject: [PATCH 68/88] Disable deploy_docs in documentation.yml --- .github/workflows/documentation.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 0598a6849..243e10f92 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -45,14 +45,14 @@ jobs: # with: # path: 'docs/build/html' - deploy_docs: - if: github.event_name != 'pull_request' - needs: build_docs - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2 + # deploy_docs: + # if: github.event_name != 'pull_request' + # needs: build_docs + # runs-on: ubuntu-latest + # environment: + # name: github-pages + # url: ${{ steps.deployment.outputs.page_url }} + # steps: + # - name: Deploy to GitHub Pages + # id: deployment + # uses: actions/deploy-pages@v2 From 4db8dcafb8eca1c0014ae20bd163fa94b8f38ee4 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Fri, 21 Jun 2024 14:29:42 +0200 Subject: [PATCH 69/88] simple implementation GeneralLinearOperator --- psydac/linalg/basic.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 07ff42670..c8fe3e1fd 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -1111,3 +1111,46 @@ def solve(self, rhs, out=None): @property def T(self): return self.transpose() + +#=============================================================================== +class GeneralLinearOperator(LinearOperator): + """ + General operator acting between two vector spaces V and W. It only requires a dot method. + + """ + + def __init__(self, domain, codomain, dot): + + assert isinstance(domain, VectorSpace) + assert isinstance(codomain, VectorSpace) + from types import LambdaType + assert isinstance(dot, LambdaType) + + self._domain = domain + self._codomain = codomain + self._dot = dot + + @property + def domain(self): + return self._domain + + @property + def codomain(self): + return self._codomain + + @property + def dtype(self): + return None + + def dot(self, v, out=None): + assert isinstance(v, Vector) + assert v.space == self.domain + + if out is not None: + assert isinstance(out, Vector) + assert out.space == self.codomain + + out = self._dot(v) + return out + else: + return self._dot(v) From 2adf6ad7f83716e84743a5b1f1100945307f8e91 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Sun, 23 Jun 2024 11:38:57 +0200 Subject: [PATCH 70/88] general linear operator needs toarray, tosparse,transpose method --- psydac/linalg/basic.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index c8fe3e1fd..d3ad57e37 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -1150,7 +1150,14 @@ def dot(self, v, out=None): assert isinstance(out, Vector) assert out.space == self.codomain - out = self._dot(v) - return out - else: - return self._dot(v) + return self._dot(v, out=out) + + def toarray(self): + raise NotImplementedError('toarray() is not defined for GeneralLinearOperators.') + + def tosparse(self): + raise NotImplementedError('tosparse() is not defined for GeneralLinearOperators.') + + def transpose(self): + raise NotImplementedError('transpose() is not defined for GeneralLinearOperators.') + From 41f3373266f6e70f4385e404656b4929f01d1221 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Thu, 4 Jul 2024 16:23:31 +0200 Subject: [PATCH 71/88] improve docstrings --- psydac/linalg/basic.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index d3ad57e37..96125daed 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -25,7 +25,8 @@ 'ComposedLinearOperator', 'PowerLinearOperator', 'InverseLinearOperator', - 'LinearSolver' + 'LinearSolver', + 'MatrixFreeLinearOperator' ) #=============================================================================== @@ -1113,9 +1114,9 @@ def T(self): return self.transpose() #=============================================================================== -class GeneralLinearOperator(LinearOperator): +class MatrixFreeLinearOperator(LinearOperator): """ - General operator acting between two vector spaces V and W. It only requires a dot method. + General operator acting between two vector spaces V and W. It only requires a callable dot method. """ @@ -1149,7 +1150,7 @@ def dot(self, v, out=None): if out is not None: assert isinstance(out, Vector) assert out.space == self.codomain - + return self._dot(v, out=out) def toarray(self): From 2067c4c42b8a8d97c16e38a046cebd528004c600 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Thu, 4 Jul 2024 16:38:16 +0200 Subject: [PATCH 72/88] fixed message notimplementederror --- psydac/linalg/basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 96050426d..88e3d757a 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -1154,11 +1154,11 @@ def dot(self, v, out=None): return self._dot(v, out=out) def toarray(self): - raise NotImplementedError('toarray() is not defined for GeneralLinearOperators.') + raise NotImplementedError('toarray() is not defined for MatrixFreeLinearOperator.') def tosparse(self): - raise NotImplementedError('tosparse() is not defined for GeneralLinearOperators.') + raise NotImplementedError('tosparse() is not defined for MatrixFreeLinearOperator.') def transpose(self): - raise NotImplementedError('transpose() is not defined for GeneralLinearOperators.') + raise NotImplementedError('transpose() is not defined for MatrixFreeLinearOperator.') From d32bd988057ffa1d409d51dc7eeb6d477beff7a5 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Thu, 4 Jul 2024 16:39:45 +0200 Subject: [PATCH 73/88] put import outside class --- psydac/linalg/basic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 88e3d757a..8210a13a9 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -11,6 +11,7 @@ import numpy as np from scipy.sparse import coo_matrix +from types import LambdaType from psydac.utilities.utils import is_real @@ -1123,8 +1124,7 @@ class MatrixFreeLinearOperator(LinearOperator): def __init__(self, domain, codomain, dot): assert isinstance(domain, VectorSpace) - assert isinstance(codomain, VectorSpace) - from types import LambdaType + assert isinstance(codomain, VectorSpace) assert isinstance(dot, LambdaType) self._domain = domain From b85ec764bffd5241977918781e4324129265a938 Mon Sep 17 00:00:00 2001 From: e-moral-sanchez Date: Mon, 8 Jul 2024 12:53:36 +0200 Subject: [PATCH 74/88] set tol to rtol in MINRES --- psydac/api/tests/test_api_2d_compatible_spaces.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/psydac/api/tests/test_api_2d_compatible_spaces.py b/psydac/api/tests/test_api_2d_compatible_spaces.py index feb7cd439..45c04a943 100644 --- a/psydac/api/tests/test_api_2d_compatible_spaces.py +++ b/psydac/api/tests/test_api_2d_compatible_spaces.py @@ -130,7 +130,7 @@ def run_stokes_2d_dir(domain, f, ue, pe, *, homogeneous, ncells, degree, scipy=F # ... solve linear system using scipy.sparse.linalg or psydac if scipy: - tol = 1e-11 + rtol = 1e-11 equation_h.assemble() A0 = equation_h.linear_system.lhs.tosparse() b0 = equation_h.linear_system.rhs.toarray() @@ -145,17 +145,17 @@ def run_stokes_2d_dir(domain, f, ue, pe, *, homogeneous, ncells, degree, scipy=F A1 = a1_h.assemble().tosparse() b1 = l1_h.assemble().toarray() - x1, info = sp_minres(A1, b1, tol=tol) + x1, info = sp_minres(A1, b1, rtol=rtol) print('Boundary solution with scipy.sparse: success = {}'.format(info == 0)) - x0, info = sp_minres(A0, b0 - A0.dot(x1), tol=tol) + x0, info = sp_minres(A0, b0 - A0.dot(x1), rtol=rtol) print('Interior solution with scipy.sparse: success = {}'.format(info == 0)) # Solution is sum of boundary and interior contributions x = x0 + x1 else: - x, info = sp_minres(A0, b0, tol=tol) + x, info = sp_minres(A0, b0, rtol=rtol) print('Solution with scipy.sparse: success = {}'.format(info == 0)) # Convert to stencil format From 273ec8fa801b390abd4f0024be3ca6052d8bea8d Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Fri, 12 Jul 2024 19:16:08 +0200 Subject: [PATCH 75/88] add features and tests to MatrixFree linear ops --- psydac/linalg/basic.py | 69 +++++++++++++++++++++++++++--- psydac/linalg/solvers.py | 2 +- psydac/linalg/tests/test_linalg.py | 18 ++++---- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 8210a13a9..33cc77d63 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -12,6 +12,7 @@ import numpy as np from scipy.sparse import coo_matrix from types import LambdaType +from inspect import signature from psydac.utilities.utils import is_real @@ -1117,11 +1118,37 @@ def T(self): #=============================================================================== class MatrixFreeLinearOperator(LinearOperator): """ - General operator acting between two vector spaces V and W. It only requires a callable dot method. + General linear operator represented by a callable dot method. + + Parameters + ---------- + domain : VectorSpace + The domain of the linear operator. + + codomain : VectorSpace + The codomain of the linear operator. + + dot : Callable + The method of the linear operator, assumed to map from domain to codomain. + This method can take out as an optional argument but this is not mandatory. + + dot_transpose: Callable + The method of the transpose of the linear operator, assumed to map from codomain to domain. + This method can take out as an optional argument but this is not mandatory. + + Examples + -------- + # example 1: a matrix encapsulated as a (fake) matrix-free linear operator + A_SM = StencilMatrix(V, W) + AT_SM = A_SM.transpose() + A = MatrixFreeLinearOperator(domain=V, codomain=W, dot=lambda v: A_SM @ v, dot_transpose=lambda v: AT_SM @ v) + + # example 2: a truly matrix-free linear operator + A = MatrixFreeLinearOperator(domain=V, codomain=V, dot=lambda v: 2*v, dot_transpose=lambda v: 2*v) """ - def __init__(self, domain, codomain, dot): + def __init__(self, domain, codomain, dot, dot_transpose=None): assert isinstance(domain, VectorSpace) assert isinstance(codomain, VectorSpace) @@ -1131,6 +1158,18 @@ def __init__(self, domain, codomain, dot): self._codomain = codomain self._dot = dot + sig = signature(dot) + self._dot_takes_out_arg = ('out' in [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]) + + if dot_transpose is not None: + assert isinstance(dot_transpose, LambdaType) + self._dot_transpose = dot_transpose + sig = signature(dot_transpose) + self._dot_transpose_takes_out_arg = ('out' in [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]) + else: + self._dot_transpose = None + self._dot_transpose_takes_out_arg = False + @property def domain(self): return self._domain @@ -1150,8 +1189,16 @@ def dot(self, v, out=None): if out is not None: assert isinstance(out, Vector) assert out.space == self.codomain + else: + out = self.codomain.zeros() - return self._dot(v, out=out) + if self._dot_takes_out_arg: + self._dot(v, out=out) + else: + # provided dot product does not take an out argument: we simply copy the result into out + self._dot(v).copy(out=out) + + return out def toarray(self): raise NotImplementedError('toarray() is not defined for MatrixFreeLinearOperator.') @@ -1159,6 +1206,18 @@ def toarray(self): def tosparse(self): raise NotImplementedError('tosparse() is not defined for MatrixFreeLinearOperator.') - def transpose(self): - raise NotImplementedError('transpose() is not defined for MatrixFreeLinearOperator.') + def transpose(self, conjugate=False): + if self._dot_transpose is None: + raise NotImplementedError('no transpose dot method was given -- cannot create the transpose operator') + + if conjugate: + if self._dot_transpose_takes_out_arg: + new_dot = lambda v, out=None: self._dot_transpose(v, out=out).conjugate() + else: + new_dot = lambda v: self._dot_transpose(v).conjugate() + else: + new_dot = self._dot_transpose + + return MatrixFreeLinearOperator(domain=self.codomain, codomain=self.domain, dot=new_dot, dot_transpose=self._dot) + \ No newline at end of file diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index 297720ab7..ca1079be7 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -39,7 +39,7 @@ def inverse(A, solver, **kwargs): A : psydac.linalg.basic.LinearOperator Left-hand-side matrix A of linear system; individual entries A[i,j] can't be accessed, but A has 'shape' attribute and provides 'dot(p)' - function (i.e. matrix-vector product A*p). + function (e.g. a matrix-vector product A*p). solver : str Preferred iterative solver. Options are: 'cg', 'pcg', 'bicg', diff --git a/psydac/linalg/tests/test_linalg.py b/psydac/linalg/tests/test_linalg.py index 8b30802b8..15d5ad312 100644 --- a/psydac/linalg/tests/test_linalg.py +++ b/psydac/linalg/tests/test_linalg.py @@ -20,7 +20,7 @@ def array_equal(a, b): def sparse_equal(a, b): return (a.tosparse() != b.tosparse()).nnz == 0 -def is_pos_def(A): +def assert_pos_def(A): assert isinstance(A, LinearOperator) A_array = A.toarray() assert np.all(np.linalg.eigvals(A_array) > 0) @@ -50,7 +50,7 @@ def get_StencilVectorSpace(n1, n2, p1, p2, P1, P2): V = StencilVectorSpace(C) return V -def get_positive_definite_stencilmatrix(V): +def get_positive_definite_StencilMatrix(V): np.random.seed(2) assert isinstance(V, StencilVectorSpace) @@ -700,9 +700,9 @@ def test_positive_definite_matrix(n1, n2, p1, p2): P1 = False P2 = False V = get_StencilVectorSpace(n1, n2, p1, p2, P1, P2) - S = get_positive_definite_stencilmatrix(V) + S = get_positive_definite_StencilMatrix(V) - is_pos_def(S) + assert_pos_def(S) #=============================================================================== @pytest.mark.parametrize('n1', [3, 5]) @@ -745,7 +745,7 @@ def test_operator_evaluation(n1, n2, p1, p2): V = get_StencilVectorSpace(n1, n2, p1, p2, P1, P2) # Initiate positive definite StencilMatrices for which the cg inverse works (necessary for certain tests) - S = get_positive_definite_stencilmatrix(V) + S = get_positive_definite_StencilMatrix(V) # Initiate StencilVectors v = StencilVector(V) @@ -769,7 +769,7 @@ def test_operator_evaluation(n1, n2, p1, p2): ### 2.1 PowerLO test Bmat = B.toarray() - is_pos_def(B) + assert_pos_def(B) uarr = u.toarray() b0 = ( B**0 @ u ).toarray() b1 = ( B**1 @ u ).toarray() @@ -799,7 +799,7 @@ def test_operator_evaluation(n1, n2, p1, p2): assert np.array_equal(zeros, z2) Smat = S.toarray() - is_pos_def(S) + assert_pos_def(S) varr = v.toarray() s0 = ( S**0 @ v ).toarray() s1 = ( S**1 @ v ).toarray() @@ -960,8 +960,8 @@ def test_x0update(solver): P1 = False P2 = False V = get_StencilVectorSpace(n1, n2, p1, p2, P1, P2) - A = get_positive_definite_stencilmatrix(V) - is_pos_def(A) + A = get_positive_definite_StencilMatrix(V) + assert_pos_def(A) b = StencilVector(V) for n in range(n1): b[n, :] = 1. From 9163b3fa0c400faf15ed47a11dd806924c10bbfa Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Fri, 12 Jul 2024 19:47:13 +0200 Subject: [PATCH 76/88] tests for matrix free linear ops --- psydac/linalg/tests/test_matrix_free.py | 127 ++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 psydac/linalg/tests/test_matrix_free.py diff --git a/psydac/linalg/tests/test_matrix_free.py b/psydac/linalg/tests/test_matrix_free.py new file mode 100644 index 000000000..e475fb15f --- /dev/null +++ b/psydac/linalg/tests/test_matrix_free.py @@ -0,0 +1,127 @@ +import pytest +import numpy as np + +from psydac.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace +from psydac.linalg.basic import LinearOperator, ZeroOperator, IdentityOperator, ComposedLinearOperator, SumLinearOperator, PowerLinearOperator, ScaledLinearOperator +from psydac.linalg.basic import MatrixFreeLinearOperator +from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix +from psydac.linalg.solvers import ConjugateGradient, inverse +from psydac.ddm.cart import DomainDecomposition, CartDecomposition + +from psydac.linalg.tests.test_linalg import get_StencilVectorSpace, get_positive_definite_StencilMatrix, assert_pos_def + +def get_random_StencilMatrix(domain, codomain): + + np.random.seed(2) + V = domain + W = codomain + assert isinstance(V, StencilVectorSpace) + assert isinstance(W, StencilVectorSpace) + [n1, n2] = V._npts + [p1, p2] = V._pads + [P1, P2] = V._periods + assert (P1 == False) and (P2 == False) + + [m1, m2] = W._npts + [q1, q2] = W._pads + [Q1, Q2] = W._periods + assert (Q1 == False) and (Q2 == False) + + S = StencilMatrix(V, W) + + for i in range(0, q1+1): + if i != 0: + for j in range(-q2, q2+1): + S[:, :, i, j] = 2*np.random.random()-1 + else: + for j in range(1, q2+1): + S[:, :, i, j] = 2*np.random.random()-1 + S.remove_spurious_entries() + + return S + +def get_random_StencilVector(V): + np.random.seed(3) + assert isinstance(V, StencilVectorSpace) + [n1, n2] = V._npts + v = StencilVector(V) + for i in range(n1): + for j in range(n2): + v[i,j] = np.random.random() + return v + +#=============================================================================== +@pytest.mark.parametrize('n1', [3, 5]) +@pytest.mark.parametrize('n2', [4, 7]) +@pytest.mark.parametrize('p1', [2, 6]) +@pytest.mark.parametrize('p2', [3, 9]) + +def test_fake_matrix_free(n1, n2, p1, p2): + P1 = False + P2 = False + m1 = (n2+n1)//2 + m2 = n1+1 + q1 = p1 # using same degrees because both spaces must have same padding for now + q2 = p2 + V1 = get_StencilVectorSpace(n1, n2, p1, p2, P1, P2) + V2 = get_StencilVectorSpace(m1, m2, q1, q2, P1, P2) + S = get_random_StencilMatrix(codomain=V2, domain=V1) + O = MatrixFreeLinearOperator(codomain=V2, domain=V1, dot=lambda v: S @ v) + + print(f'O.domain = {O.domain}') + print(f'S.domain = {S.domain}') + print(f'V1: = {V1}') + v = get_random_StencilVector(V1) + tol = 1e-10 + y = S.dot(v) + x = O.dot(v) + print(f'error = {np.linalg.norm( (x - y).toarray() )}') + assert np.linalg.norm( (x - y).toarray() ) < tol + O.dot(v, out=x) + print(f'error = {np.linalg.norm( (x - y).toarray() )}') + assert np.linalg.norm( (x - y).toarray() ) < tol + +@pytest.mark.parametrize('solver', ['cg', 'pcg', 'bicg', 'minres', 'lsmr']) + +def test_solvers_matrix_free(solver): + print(f'solver = {solver}') + n1 = 4 + n2 = 3 + p1 = 5 + p2 = 2 + P1 = False + P2 = False + V = get_StencilVectorSpace(n1, n2, p1, p2, P1, P2) + A_SM = get_positive_definite_StencilMatrix(V) + assert_pos_def(A_SM) + AT_SM = A_SM.transpose() + A = MatrixFreeLinearOperator(domain=V, codomain=V, dot=lambda v: A_SM @ v, dot_transpose=lambda v: AT_SM @ v) + + # get rhs and solution + b = get_random_StencilVector(V) + x = A.dot(b) + + # Create Inverse with A + tol = 1e-6 + if solver == 'pcg': + inv_diagonal = A_SM.diagonal(inverse=True) + A_inv = inverse(A, solver, pc=inv_diagonal, tol=tol) + else: + A_inv = inverse(A, solver, tol=tol) + + AA = A_inv._A + xx = AA.dot(b) + print(f'norm(xx) = {np.linalg.norm( xx.toarray() )}') + print(f'norm(x) = {np.linalg.norm( x.toarray() )}') + + # Apply inverse and check + y = A_inv @ x + error = np.linalg.norm( (b - y).toarray()) + assert np.linalg.norm( (b - y).toarray() ) < tol + +#=============================================================================== +# SCRIPT FUNCTIONALITY +#=============================================================================== +if __name__ == "__main__": + import sys + pytest.main( sys.argv ) From 1e53d49f8f0cf9863ed3545ef735205e5cbe86f0 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 16 Jul 2024 03:32:32 +0200 Subject: [PATCH 77/88] require scipy >= 1.14 in requirements.txt Calls to scipy `minres` now use `rtol` instead of `tol` to support version 1.14.0, but this causes previous versions of scipy to fail --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 6874bc8fd..a2f753062 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ wheel setuptools >= 61, != 67.2.0 numpy >= 1.16 +scipy >= 1.14 Cython >= 0.25, < 3.0 mpi4py >= 3.0.0 From 2e898b832e8780db564b1815a062751ee54430b3 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 16 Jul 2024 03:59:08 +0200 Subject: [PATCH 78/88] discard tests with python3.8 calls to scipy's minres now only use rtol which prevents from using scipy < 1.14. In turns this prevent from using python3.8 which is close to being unsupported anyway, see https://devguide.python.org/versions/ --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 826027fa1..83dd52cd1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - python-version: [ 3.8, 3.9, '3.10', '3.11' ] + python-version: [ 3.9, '3.10', '3.11' ] isMerge: - ${{ github.event_name == 'push' && github.ref == 'refs/heads/devel' }} exclude: From baf0231cec9552f5b8c546f9b01ffb507ff1f7a4 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 16 Jul 2024 05:43:31 +0200 Subject: [PATCH 79/88] avoid minres iteration if converged --- psydac/linalg/solvers.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index ca1079be7..862868ea2 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -1165,7 +1165,7 @@ def solve(self, b, out=None): A.dot(x, out=y) y -= b y *= -1.0 - y.copy(out=res_old) + y.copy(out=res_old) # res = b - A*x beta = sqrt(res_old.dot(res_old)) @@ -1193,8 +1193,15 @@ def solve(self, b, out=None): print( "+---------+---------------------+") template = "| {:7d} | {:19.2e} |" + # check whether solution is already converged: + if beta < tol: + istop = 1 + rnorm = beta + if verbose: + print( template.format(itn, rnorm )) - for itn in range(1, maxiter + 1 ): + while istop == 0 and itn < maxiter: + itn += 1 s = 1.0/beta y.copy(out=v) From 2dd2d3b64bf7f198e299503506a5f9a443c3da92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Wed, 31 Jul 2024 15:29:24 +0200 Subject: [PATCH 80/88] Style cleanup of stencil2IJV_kernels.py --- psydac/linalg/kernels/stencil2IJV_kernels.py | 86 +++++++++++--------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/psydac/linalg/kernels/stencil2IJV_kernels.py b/psydac/linalg/kernels/stencil2IJV_kernels.py index bff3d3843..21eae3cb4 100644 --- a/psydac/linalg/kernels/stencil2IJV_kernels.py +++ b/psydac/linalg/kernels/stencil2IJV_kernels.py @@ -11,7 +11,7 @@ def stencil2IJV_1d_C(A:'T[:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmap nnz = 0 nnz_rows = 0 - gr1 = cp1*cm1 + gr1 = cp1 * cm1 for i1 in range(cnl1): nnz_in_row = 0 @@ -19,23 +19,25 @@ def stencil2IJV_1d_C(A:'T[:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmap pr_i1 = 0 for k in range(cgs1.size): - if i1_n < cgs1[k] or i1_n > cge1[k]:continue + if i1_n < cgs1[k] or i1_n > cge1[k]: + continue pr_i1 = k i_g = csh[pr_i1] + i1_n - cgs1[pr_i1] - stencil_size1 = A[i1 + gr1].size for k1 in range(stencil_size1): - j1_n = (i1_n + k1 - stencil_size1//2) % dng1 + j1_n = (i1_n + k1 - stencil_size1 // 2) % dng1 value = A[i1 + gr1, k1] - if abs(value) == 0.0:continue - + if abs(value) == 0.0: + continue + pr_j1 = 0 for k in range(dgs1.size): - if j1_n < dgs1[k] or j1_n > dge1[k]:continue + if j1_n < dgs1[k] or j1_n > dge1[k]: + continue pr_j1 = k j_g = dsh[pr_j1] + j1_n - dgs1[pr_j1] @@ -46,7 +48,6 @@ def stencil2IJV_1d_C(A:'T[:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmap Jb[nnz] = j_g Vb[nnz] = value nnz += 1 - nnz_in_row += 1 if nnz_in_row > 0: @@ -67,8 +68,8 @@ def stencil2IJV_2d_C(A:'T[:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', ro nnz = 0 nnz_rows = 0 - gr1 = cp1*cm1 - gr2 = cp2*cm2 + gr1 = cp1 * cm1 + gr2 = cp2 * cm2 for i1 in range(cnl1): for i2 in range(cnl2): @@ -78,38 +79,42 @@ def stencil2IJV_2d_C(A:'T[:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', ro pr_i1 = 0 for k in range(cgs1.size): - if i1_n < cgs1[k] or i1_n > cge1[k]:continue + if i1_n < cgs1[k] or i1_n > cge1[k]: + continue pr_i1 = k + pr_i2 = 0 for k in range(cgs2.size): - if i2_n < cgs2[k] or i2_n > cge2[k]:continue + if i2_n < cgs2[k] or i2_n > cge2[k]: + continue pr_i2 = k pr_i = pr_i2 + pr_i1 * cgs2.size - i_g = csh[pr_i] + i2_n - cgs2[pr_i2] + (i1_n - cgs1[pr_i1]) * cnlb2[pr_i] - stencil_size1, stencil_size2 = A.shape[2:] for k1 in range(stencil_size1): for k2 in range(stencil_size2): - j1_n = (i1_n + k1 - stencil_size1//2) % dng1 - j2_n = (i2_n + k2 - stencil_size2//2) % dng2 + j1_n = (i1_n + k1 - stencil_size1 // 2) % dng1 + j2_n = (i2_n + k2 - stencil_size2 // 2) % dng2 value = A[i1 + gr1, i2 + gr2, k1, k2] - if abs(value) == 0.0:continue - + if abs(value) == 0.0: + continue + pr_j1 = 0 for k in range(dgs1.size): - if j1_n < dgs1[k] or j1_n > dge1[k]:continue + if j1_n < dgs1[k] or j1_n > dge1[k]: + continue pr_j1 = k + pr_j2 = 0 for k in range(dgs2.size): - if j2_n < dgs2[k] or j2_n > dge2[k]:continue + if j2_n < dgs2[k] or j2_n > dge2[k]: + continue pr_j2 = k pr_j = pr_j2 + pr_j1 * dgs2.size - j_g = dsh[pr_j] + j2_n - dgs2[pr_j2] + (j1_n - dgs1[pr_j1]) * dnlb2[pr_j] if nnz_in_row == 0: @@ -118,7 +123,6 @@ def stencil2IJV_2d_C(A:'T[:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', ro Jb[nnz] = j_g Vb[nnz] = value nnz += 1 - nnz_in_row += 1 if nnz_in_row > 0: @@ -127,7 +131,6 @@ def stencil2IJV_2d_C(A:'T[:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', ro return nnz_rows, nnz - #======================================================================================================== @template(name='T', types=[float, complex]) def stencil2IJV_3d_C(A:'T[:,:,:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]', rowmapb:'int64[:]', @@ -155,48 +158,56 @@ def stencil2IJV_3d_C(A:'T[:,:,:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]' pr_i1 = 0 for k in range(cgs1.size): - if i1_n < cgs1[k] or i1_n > cge1[k]:continue + if i1_n < cgs1[k] or i1_n > cge1[k]: + continue pr_i1 = k + pr_i2 = 0 for k in range(cgs2.size): - if i2_n < cgs2[k] or i2_n > cge2[k]:continue + if i2_n < cgs2[k] or i2_n > cge2[k]: + continue pr_i2 = k + pr_i3 = 0 for k in range(cgs3.size): - if i3_n < cgs3[k] or i3_n > cge3[k]:continue + if i3_n < cgs3[k] or i3_n > cge3[k]: + continue pr_i3 = k pr_i = pr_i3 + pr_i2 * cgs3.size + pr_i1 * cgs2.size * cgs3.size - i_g = csh[pr_i] + i3_n - cgs3[pr_i3] + (i2_n - cgs2[pr_i2]) * cnlb3[pr_i] + (i1_n - cgs1[pr_i1]) * cnlb2[pr_i] * cnlb3[pr_i] - stencil_size1, stencil_size2, stencil_size3 = A.shape[3:] for k1 in range(stencil_size1): for k2 in range(stencil_size2): for k3 in range(stencil_size3): - j1_n = (i1_n + k1 - stencil_size1//2) % dng1 - j2_n = (i2_n + k2 - stencil_size2//2) % dng2 - j3_n = (i3_n + k3 - stencil_size3//2) % dng3 + j1_n = (i1_n + k1 - stencil_size1 // 2) % dng1 + j2_n = (i2_n + k2 - stencil_size2 // 2) % dng2 + j3_n = (i3_n + k3 - stencil_size3 // 2) % dng3 value = A[i1 + gr1, i2 + gr2, i3 + gr3, k1, k2, k3] - if abs(value) == 0.0:continue - + if abs(value) == 0.0: + continue + pr_j1 = 0 for k in range(dgs1.size): - if j1_n < dgs1[k] or j1_n > dge1[k]:continue + if j1_n < dgs1[k] or j1_n > dge1[k]: + continue pr_j1 = k + pr_j2 = 0 for k in range(dgs2.size): - if j2_n < dgs2[k] or j2_n > dge2[k]:continue + if j2_n < dgs2[k] or j2_n > dge2[k]: + continue pr_j2 = k + pr_j3 = 0 for k in range(dgs3.size): - if j3_n < dgs3[k] or j3_n > dge3[k]:continue + if j3_n < dgs3[k] or j3_n > dge3[k]: + continue pr_j3 = k pr_j = pr_j3 + pr_j2 * dgs3.size + pr_j1 * dgs2.size * dgs3.size - j_g = dsh[pr_j] + j3_n - dgs3[pr_j3] + (j2_n - dgs2[pr_j2]) * dnlb3[pr_j] + (j1_n - dgs1[pr_j1]) * dnlb2[pr_j] * dnlb3[pr_j] if nnz_in_row == 0: @@ -205,7 +216,6 @@ def stencil2IJV_3d_C(A:'T[:,:,:,:,:,:]', Ib:'int64[:]', Jb:'int64[:]', Vb:'T[:]' Jb[nnz] = j_g Vb[nnz] = value nnz += 1 - nnz_in_row += 1 if nnz_in_row > 0: From b9a64a07fa657f6413e964210483f80142114841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Thu, 1 Aug 2024 14:20:22 +0200 Subject: [PATCH 81/88] Apply some PEP8 recommendations to topetsc.py --- psydac/linalg/topetsc.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/psydac/linalg/topetsc.py b/psydac/linalg/topetsc.py index 3161d5c41..d31cb5ca8 100644 --- a/psydac/linalg/topetsc.py +++ b/psydac/linalg/topetsc.py @@ -1,18 +1,28 @@ -import numpy as np - -from psydac.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator -from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix -from psydac.linalg.basic import VectorSpace from itertools import product as cartesian_prod -__all__ = ('petsc_local_to_psydac', 'psydac_to_petsc_global', 'get_npts_local', 'get_npts_per_block', 'vec_topetsc', 'mat_topetsc') +import numpy as np -from .kernels.stencil2IJV_kernels import stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C -# Dictionary used to select correct kernel functions based on dimensionality +from psydac.linalg.basic import VectorSpace +from psydac.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator +from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix +from psydac.linalg.kernels.stencil2IJV_kernels import stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C + +__all__ = ( + 'petsc_local_to_psydac', + 'psydac_to_petsc_global', + 'get_npts_local', + 'get_npts_per_block', + 'vec_topetsc', + 'mat_topetsc' +) + +# Dictionary used to select the correct kernel function based on dimensionality kernels = { 'stencil2IJV': {'F': None, 'C': (None, stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C)} } + + def get_index_shift_per_block_per_process(V): npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k @@ -24,6 +34,7 @@ def get_index_shift_per_block_per_process(V): return index_shift_per_block_per_process #Global variable indexed as [b][k] fo block b, process k + def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, dnpts_block, cnpts_block, dshift_block, cshift_block, order='C'): # Extract Cartesian decomposition of the Block where the node is: dspace_block = dspace if isinstance(dspace, StencilVectorSpace) else dspace.spaces[bd] @@ -74,6 +85,7 @@ def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, dnpts_block, return I, J, V, rowmap + def petsc_local_to_psydac( V : VectorSpace, petsc_index : int): @@ -145,6 +157,7 @@ def petsc_local_to_psydac( return (bb,), tuple(ii) + def psydac_to_petsc_global( V : VectorSpace, block_indices, @@ -250,6 +263,7 @@ def psydac_to_petsc_global( return global_index + def get_npts_local(V : VectorSpace) -> list: """ Compute the local number of nodes per dimension owned by the actual process. @@ -282,6 +296,7 @@ def get_npts_local(V : VectorSpace) -> list: return npts_local_per_block + def get_npts_per_block(V : VectorSpace) -> list: """ Compute the number of nodes per block, process and dimension. @@ -316,7 +331,8 @@ def get_npts_per_block(V : VectorSpace) -> list: return npts_local_per_block -def vec_topetsc( vec ): + +def vec_topetsc(vec): """ Convert vector from Psydac format to a PETSc.Vec object. Parameters @@ -415,7 +431,8 @@ def vec_topetsc( vec ): return gvec -def mat_topetsc( mat ): + +def mat_topetsc(mat): """ Convert operator from Psydac format to a PETSc.Mat object. Parameters From f193766db3362a30163043120784d37a7fc1b913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Wed, 31 Jul 2024 15:06:13 +0200 Subject: [PATCH 82/88] Revert "Disable deploy_docs in documentation.yml" This reverts commit 956b23c648208d483e73a2fbed85f7b9ad7d23b1. --- .github/workflows/documentation.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 243e10f92..0598a6849 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -45,14 +45,14 @@ jobs: # with: # path: 'docs/build/html' - # deploy_docs: - # if: github.event_name != 'pull_request' - # needs: build_docs - # runs-on: ubuntu-latest - # environment: - # name: github-pages - # url: ${{ steps.deployment.outputs.page_url }} - # steps: - # - name: Deploy to GitHub Pages - # id: deployment - # uses: actions/deploy-pages@v2 + deploy_docs: + if: github.event_name != 'pull_request' + needs: build_docs + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From f07548f66108b5e5610e2ca1099be12fb3ccdfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Wed, 31 Jul 2024 15:05:35 +0200 Subject: [PATCH 83/88] Revert "CI: Temporarily disabled docs deployment since base repo is a fork" This reverts commit 1e1c96e56d3fb3e51c2ba1c104534171fd8c14b4. --- .github/workflows/documentation.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 0598a6849..88e8065d5 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -36,14 +36,12 @@ jobs: make -C docs clean make -C docs html python docs/update_links.py - - # Disable docs deployment for now as we're in psydac fork repo - # - name: Setup Pages - # uses: actions/configure-pages@v3 - # - name: Upload artifact - # uses: actions/upload-pages-artifact@v1 - # with: - # path: 'docs/build/html' + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'docs/build/html' deploy_docs: if: github.event_name != 'pull_request' From fcd50ca5058ab847ab5744ebd1260ee288697885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Fri, 2 Aug 2024 17:15:18 +0200 Subject: [PATCH 84/88] Require SciPy >= 1.12, hence Python >= 3.9 --- pyproject.toml | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d59494d00..f93d6f363 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "psydac" version = "0.1" description = "Python package for isogeometric analysis (IGA)" readme = "README.md" -requires-python = ">= 3.8, < 3.12" +requires-python = ">= 3.9, < 3.12" license = {file = "LICENSE"} authors = [ {name = "Psydac development team", email = "psydac@googlegroups.com"} @@ -23,7 +23,7 @@ dependencies = [ # Third-party packages from PyPi 'numpy >= 1.16', - 'scipy >= 0.18, < 1.14', + 'scipy >= 1.12', 'sympy >= 1.5', 'matplotlib', 'pytest >= 4.5', diff --git a/requirements.txt b/requirements.txt index 33b497272..a42e47520 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ wheel setuptools >= 61, != 67.2.0 numpy >= 1.16 -scipy >= 1.14 +scipy >= 1.12 Cython >= 0.25, < 3.0 mpi4py >= 3.0.0, < 4 From 123aa00ca8158a32fb7660425311f8b26643a55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Fri, 2 Aug 2024 17:19:21 +0200 Subject: [PATCH 85/88] Do not skip tests with Python 3.9 --- .github/workflows/continuous-integration.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 83dd52cd1..95c074f5f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -20,7 +20,6 @@ jobs: isMerge: - ${{ github.event_name == 'push' && github.ref == 'refs/heads/devel' }} exclude: - - { isMerge: false, python-version: 3.9 } - { isMerge: false, python-version: '3.10' } include: - os: macos-latest From e8d53e01eb3cc72d2534881c54859947b167d708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Fri, 2 Aug 2024 18:29:18 +0200 Subject: [PATCH 86/88] Apply some PEP8 recommendations to linalg/basic.py --- psydac/linalg/basic.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 33cc77d63..33fba4967 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -8,11 +8,11 @@ """ from abc import ABC, abstractmethod +from types import LambdaType +from inspect import signature import numpy as np from scipy.sparse import coo_matrix -from types import LambdaType -from inspect import signature from psydac.utilities.utils import is_real @@ -1219,5 +1219,3 @@ def transpose(self, conjugate=False): new_dot = self._dot_transpose return MatrixFreeLinearOperator(domain=self.codomain, codomain=self.domain, dot=new_dot, dot_transpose=self._dot) - - \ No newline at end of file From f3c67b65c00090a7556b2a88c0e938164587a942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Wed, 31 Jul 2024 15:09:39 +0200 Subject: [PATCH 87/88] Add missing newline at end of file --- psydac/api/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/api/settings.py b/psydac/api/settings.py index 6d0988451..74a48c645 100644 --- a/psydac/api/settings.py +++ b/psydac/api/settings.py @@ -67,4 +67,4 @@ 'pyccel-intel' : PSYDAC_BACKEND_IPYCCEL, 'pyccel-pgi' : PSYDAC_BACKEND_PGPYCCEL, 'pyccel-nvidia': PSYDAC_BACKEND_NVPYCCEL, -} \ No newline at end of file +} From e46742cdfde2e7d43407f6de99efbbe9dd83b334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Wed, 31 Jul 2024 15:10:10 +0200 Subject: [PATCH 88/88] Use master branch of Igakit once again --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f93d6f363..a797463b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,10 +50,7 @@ dependencies = [ 'tblib', # IGAKIT - not on PyPI - - # !! WARNING !! Path to igakit below is from fork pyccel/igakit. This was done to - # quickly fix the numpy 2.0 issue. See https://github.com/dalcinl/igakit/pull/4 - 'igakit @ https://github.com/pyccel/igakit/archive/refs/heads/bugfix-numpy2.0.zip' + 'igakit @ https://github.com/dalcinl/igakit/archive/refs/heads/master.zip' ] [project.urls]