From c3c801dedb6b4bdc4ee4b221b1d4a2f2aa38bdbc Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Sep 2025 15:05:59 +0200 Subject: [PATCH 001/133] poisson can run in parallel --- psydac/feec/polar/__init__.py | 0 psydac/feec/polar/conga_projections.py | 108 +++ psydac/feec/polar/poisson_2d.py | 996 +++++++++++++++++++++++++ 3 files changed, 1104 insertions(+) create mode 100644 psydac/feec/polar/__init__.py create mode 100644 psydac/feec/polar/conga_projections.py create mode 100644 psydac/feec/polar/poisson_2d.py diff --git a/psydac/feec/polar/__init__.py b/psydac/feec/polar/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py new file mode 100644 index 000000000..826c14f96 --- /dev/null +++ b/psydac/feec/polar/conga_projections.py @@ -0,0 +1,108 @@ +import numpy as np + +from scipy.sparse import coo_matrix + +from psydac.linalg.basic import LinearOperator, Vector +from psydac.linalg.stencil import StencilVector +from psydac.fem.tensor import TensorFemSpace + + +class C0PolarProjection_V0(LinearOperator): + """ + CONGA Projector P0 from the full spline space S^{p1, p2} on logical domain + to V0 the pre-polar 0-forms splines. The associate matrix is square as in + the CONGA approach we keep using the tensor B-spline basis, instead of the + polar basis of Toshniwal. P0 enforces coefficient relations to be in V0. + + Parameters: + ----------- + + W0 : TensorFemSpace + The full tensor product spline space S^{p1,p2} + + transposed : Boolean + switch between P0 and P0 transposed (defalut is False) + + hbc : Boolean + switch on and off the imposition of homogeneous Dirichlet boundary + conditions (default is False) + """ + + def __init__(self, W0, *, transposed=False, hbc=False): + assert isinstance(W0, TensorFemSpace) + + self.W0 = W0 + self.transposed = transposed + self.hbc = hbc + + @property + def domain(self): + return self.W0.coeff_space + + @property + def codomain(self): + return self.W0.coeff_space + + @property + def dtype(self): + return float + + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + + [s1, s2] = self.W0.coeff_space.starts + [e1, e2] = self.W0.coeff_space.ends + [n1, n2] = self.W0.coeff_space.npts + rank_at_polar_edge = (s1 == 0) + rank_at_outer_edge = (e1 == n1 - 1) + + if out is None: + y = self.W0.coeff_space.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.W0.coeff_space + y = out + + # Symmetric Matrix -> Same result when transposed + if rank_at_polar_edge: + y[0, s2:e2 + 1] = np.average(x[0, s2:e2 + 1]) # must be parallelized + y[1:, s2:e2 + 1] = x[1:, s2:e2 + 1] + + if self.hbc: + if rank_at_outer_edge: + y[e1, :] = 0. + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + #should just return self since it's symmetric? + return C0PolarProjection_V0(self.W0, transposed=not self.transposed, hbc=self.hbc) + + def tosparse(self): + + [n1, n2] = self.W0.coeff_space.npts + + data = np.tile((1 / n2) * np.ones(n2), n2) + cols = np.repeat(np.arange(n2), n2) + rows = np.tile(np.arange(n2), n2) + if self.hbc: + data = np.concatenate((data, np.ones(n2 * (n1 - 2)))) + cols = np.concatenate((cols, np.arange(n2, (n1 - 1) * n2))) + rows = np.concatenate((rows, np.arange(n2, (n1 - 1) * n2))) + else: + data = np.concatenate((data, np.ones(n2 * (n1 - 1)))) + cols = np.concatenate((cols, np.arange(n2, n1 * n2))) + rows = np.concatenate((rows, np.arange(n2, n1 * n2))) + + P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) + P.eliminate_zeros() + + # P is symmetric so we always return P no matter self.transposed + return P + + def toarray(self): + return self.tosparse().toarray() + diff --git a/psydac/feec/polar/poisson_2d.py b/psydac/feec/polar/poisson_2d.py new file mode 100644 index 000000000..6206cb291 --- /dev/null +++ b/psydac/feec/polar/poisson_2d.py @@ -0,0 +1,996 @@ +# coding: utf-8 +from mpi4py import MPI +from time import time, sleep +# import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable + +import numpy as np +import matplotlib.pyplot as plt +from sympy import sqrt, sin, cos, pi + +from sympde.topology.analytical_mapping import PolarMapping, TargetMapping, CzarnyMapping +from sympde.topology.domain import Square, Domain +from sympde.topology import ScalarFunctionSpace, elements_of +from sympde.expr import BilinearForm, LinearForm, integral, Norm, Functional +from sympde.calculus import dot, grad, laplace +from sympde.topology.mapping import Mapping +from sympde.expr.evaluation import LogicalExpr + +from psydac.api.discretization import discretize +from psydac.linalg.stencil import StencilVector, StencilMatrix +from psydac.linalg.basic import LinearOperator +from psydac.linalg.solvers import inverse +from psydac.fem.splines import SplineSpace +from psydac.fem.tensor import TensorFemSpace +from psydac.fem.basic import FemField +from psydac.mapping.discrete import SplineMapping +from psydac.utilities.utils import refine_array_1d +from psydac.cad.geometry import Geometry +from psydac.ddm.cart import DomainDecomposition + +from psydac.polar.c1_projections import C1Projector + +from psydac.api.settings import PSYDAC_BACKENDS + +from conga_projections import C0PolarProjection_V0 + +# backend = PSYDAC_BACKENDS['numba'] +backend = PSYDAC_BACKENDS['python'] + +# Set to true in order to compute discrete norms on logical domain +trick = False + + +# ============================================================================== +class Laplacian: + + def __init__(self, mapping): + assert isinstance(mapping, Mapping) + + self._eta = mapping.logical_coordinates + self._metric = mapping.metric_expr + self._metric_det = mapping.metric_det_expr + + # ... + def __call__(self, phi): + from sympy import sqrt, Matrix + + u = self._eta + G = self._metric + sqrt_g = sqrt(self._metric_det) + + # Store column vector of partial derivatives of phi w.r.t. uj + dphi_du = Matrix([phi.diff(uj) for uj in u]) + + # Compute gradient of phi in tangent basis: A = G^(-1) dphi_du + A = G.LUsolve(dphi_du) + + # Compute Laplacian of phi using formula for divergence of vector A + lapl = sum((sqrt_g * Ai).diff(ui) for ui, Ai in zip(u, A)) / sqrt_g + + return lapl + + +# ============================= EXACT SOLUTION ================================# +class Poisson2D: + """ + Exact solution to the 2D Poisson equation with Dirichlet boundary + conditions, to be employed for the method of manufactured solutions. + + :code + $(\partial^2_{xx} + \partial^2_{yy}) \phi(x,y) = -\rho(x,y)$ + + """ + + def __init__(self, domain, mapping, phi, rho): + assert isinstance(mapping, Mapping) + + self._domain = domain + self._mapping = mapping + self._phi = phi + self._rho = rho + + from sympy import lambdify + s, t = mapping.logical_coordinates + self._phi_callable = lambdify([s, t], phi) + self._rho_callable = lambdify([s, t], rho) + + # x, y = mapping.coordinates + # self._phi_callable = lambdify([x, y], phi) + # # self._rho_callable = lambdify([x, y], rho) + + # ... + @staticmethod + def disk_domain(R=2.0, polar_mapping=False, shift_D=0.0, use_logical_sol=False): + """ + Solve Poisson's equation on a disk of radius R centered at (x,y) = (0, 0), + with logical coordinates (s, theta): + + - The radial coordinate s belongs to the interval [0, R]; + - The angular coordinate theta belongs to the interval [0, 2 * pi). + + : code + $\phi(x,y) = sin(3.5 \pi (R^2 - x^2 - y^2)/R^2)$. + """ + domain = ((0, R), (0, 2 * np.pi)) + + if polar_mapping: + mapping = PolarMapping('PM', c1=0, c2=0, rmin=0, rmax=1) + else: + # domain = ((0, 1), (0, 2 * np.pi)) + mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) + + lapl = Laplacian(mapping) + # print('vars(mapping): ', vars(mapping)) + x, y = mapping.expressions + # exit() + s, t = mapping.logical_coordinates + + def get_radius_angle(self, x, y): + # from np import sqrt, arctan2 #, sin, cos + r = np.sqrt(x * x + y * y) + alpha = np.arctan2(y, x) + return r, alpha + + if use_logical_sol: + # Manufactured solutions in logical coordinates + # phi = R**2 - s**2 + phi = sin(3.5 * pi * (R ** 2 - s ** 2) / R ** 2) + rho = - lapl(phi) + + else: + # physical field (cf use of physical ref solution in Maxwell case) + phi = sin(3.5 * pi * (R ** 2 - x ** 2 - y ** 2) / R ** 2) + # rho = - lapl(phi) + # phi = R**2 - x**2 - y**2 + rho = - lapl(phi) + + # def Ex_ex(self, t, x, y): + # from np import cos, sin + + return Poisson2D(domain, mapping, phi, rho) + + # ... + @staticmethod + def target_domain(): + """ + Solve Poisson's equation on a polar domain, with logical coordinates (s, theta): + + - The radial coordinate s belongs to the interval [0, 1]; + - The angular coordinate theta belongs to the interval [0, 2 * pi). + + The shape of the domain is set by parameter k in [0, 1): for k = 0 we have + a disk and for k --> 1 the disk is horizontally squeezed. + + The parameter D in (-(1 - k)/2, (1 - k)/2) moves horizontally the pole within + the shape. + + The parameter c1, c2 are just shifts on the plane of the domain. + + : code + $\phi(x,y) = (1 - s^8)\sin(k_x(x - 0.5))\cos(k_y y)$. + + """ + + domain = ((0, 1), (0, 2 * np.pi)) + params = dict(c1=0, c2=0, k=0.3, D=0.2) + mapping = TargetMapping('F', **params) + + from sympy import sin, cos, pi, sqrt + # from sympy.abc import x, y + + lapl = Laplacian(mapping) + s, t = mapping.logical_coordinates + x, y = mapping.expressions + + # Manufactured solution in logical coordinates + k = params['k'] + D = params['D'] + kx = 2 * pi / (1 - k + D) + ky = 2 * pi / (1 + k) + + phi = (1 - s ** 8) * sin(kx * (x - 0.5)) * cos(ky * y) + rho = - lapl(phi) + + # c1 = params['c1'] + # c2 = params['c2'] + # y_tilde = (y - c2)/(1 + k) + # x_tilde = (x - c1)/(1 - k) + # D_tilde = (2 * D)/(1 - k) + # r = (x_tilde**2 + y_tilde**2) + # R = sqrt((2 * r)/(1 - D_tilde * x_tilde + sqrt((1 - D_tilde)**2 - D_tilde**2 * r))) + # phi = (1 - R**8) * sin(kx * (x - 0.5)) * cos(ky * y) + # rho = - laplace(phi) + + return Poisson2D(domain, mapping, phi, rho) + + # ... + @staticmethod + def czarny_domain(): + """ + Solve Poisson's equation on a czarny domain, with logical coordinates (s, theta): + + - The radial coordinate s belongs to the interval [0, 1]; + - The angular coordinate theta belongs to the interval [0, 2 * pi). + + : code + $\phi(x,y) = (1 - s^8)\sin(\pi x)\cos(\pi y)$. + + """ + + domain = ((0, 1), (0, 2 * np.pi)) + params = dict(c1=0, c2=0, eps=0.2, b=1.4) + mapping = CzarnyMapping('F', **params) + + from sympy import sin, cos, pi + + lapl = Laplacian(mapping) + s, t = mapping.logical_coordinates + x, y = mapping.expressions + + # Manufactured solution in logical coordinates + phi = (1 - s ** 8) * sin(pi * x) * cos(pi * y) + rho = - lapl(phi) + + return Poisson2D(domain, mapping, phi, rho) + + # ... + @property + def domain(self): + return self._domain + + @property + def mapping(self): + return self._mapping + + @property + def phi(self): + return self._phi + + @property + def rho(self): + return self._rho + + @property + def phi_callable(self): + return self._phi_callable + + @property + def rho_callable(self): + return self._rho_callable + + +# ====================== CONGA (PENALIZED) POISSON ============================# + +class CongaLaplacian(LinearOperator): + + def __init__(self, S, M, P, alpha): + assert isinstance(S, StencilMatrix) + assert isinstance(M, StencilMatrix) + assert isinstance(P, (C0PolarProjection_V0, )) + + W0 = P.W0.coeff_space + + assert S.domain is S.codomain is W0 + assert M.domain is M.codomain is W0 + + self.S = S + self.M = M + self.P = P + self.alpha = alpha + self.W0 = W0 + + def dot(self, x, out=None): + if out is None: + y = self.M._domain.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.M._domain + y = out + + y1 = self.P.T.dot(self.S.dot(self.P.dot(x))) + y2 = x - self.P.dot(x) + y2 = self.M.dot(y2) + y2 = self.alpha * (y2 - self.P.T.dot(y2)) + # y = y1 + y2 + y1.copy(out=y) + y += y2 + + y.update_ghost_regions() + return y + + def transpose(self): + return CongaLaplacian(self.S.T, self.M.T, self.P.T, self.alpha) + + def tosparse(self): + S = self.S.tosparse() + M = self.M.tosparse() + P = self.P.tosparse() + alpha = self.alpha + n = self.W0.dimension + + from scipy.sparse import eye + I = eye(n) + + A = alpha * (I - P).T @ M @ (I - P) + P.T @ S @ P + + return A + + def toarray(self): + return self.tosparse().toarray() + + @property + def T(self): + return self.transpose() + + @property + def shape(self): + return (self.W0.dimension, self.W0.dimension) + + @property + def domain(self): + return self.W0 + + @property + def codomain(self): + return self.W0 + + @property + def dtype(self): + return float + + +############################################################################### + +def run_poisson_2d(*, test_case, ncells, degree, + shift_D, use_spline_mapping, smooth_method, + cgtol, cgiter, alphaCONGA, study='poisson', verbose=False): + timing = {} + timing['assembly'] = 0.0 + timing['projection'] = 0.0 + timing['solution'] = 0.0 + timing['diagnostics'] = 0.0 + timing['export'] = 0.0 + + assert study == 'poisson' # for now + + # Method of manufactured solution + if test_case == 'disk': + model = Poisson2D.disk_domain(R=1., polar_mapping=False, shift_D=shift_D) + elif test_case == 'target': + model = Poisson2D.target_domain() + elif test_case == 'czarny': + model = Poisson2D.czarny_domain() + else: + raise ValueError("Only available test-cases are 'disk', 'target' and 'czarny'") + + if smooth_method not in ('polar-std', 'polar-spec', 'C0conga', 'C1conga', 'None'): + raise ValueError( + "Only available options for pole smoothness are 'polar-spec', 'polar-std', 'C0conga', 'C1conga', 'None'") + + if smooth_method == 'polar-spec' and (not use_spline_mapping): + print('WARNING: C1 conforming discretization only available for spline mappings') + print('The domain will be approximated in the 0-forms spline space.') + print() + use_spline_mapping = True + + # Communicator, size, rank + mpi_comm = MPI.COMM_WORLD + mpi_size = mpi_comm.Get_size() + mpi_rank = mpi_comm.Get_rank() + + # Number of elements and spline degree + ne1, ne2 = ncells + p1, p2 = degree + + # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# + + if use_spline_mapping: + # Create uniform grid + grid_1 = np.linspace(*model.domain[0], num=ne1 + 1) + grid_2 = np.linspace(*model.domain[1], num=ne2 + 1) + + # Create 1D finite element spaces + V1 = SplineSpace(p1, grid=grid_1, periodic=False) + V2 = SplineSpace(p2, grid=grid_2, periodic=True) + + # Create 2D tensor product finite element space + domain_decomposition = DomainDecomposition(ncells, [False, True]) # , comm = mpi_comm) + V = TensorFemSpace(domain_decomposition, V1, V2) + + s1, s2 = V.coeff_space.starts + e1, e2 = V.coeff_space.ends + + # ==================== MAPPING & PHYSICAL DOMAIN ==============================# + logical_domain = Square('Omega', bounds1=model.domain[0], + bounds2=model.domain[1]) + if use_spline_mapping: + # Create spline mapping by interpolation of analytical mapping + map_analytic = model.mapping.get_callable_mapping() + map_discrete = SplineMapping.from_mapping(V, map_analytic) + # Create symbolic mapping with callable mapping as spline + mapping = Mapping('M', dim=2) + mapping.set_callable_mapping(map_discrete) + # In order to create a sympde.Domain object from this mapping we have + # to create first a HDF5 file and then load as sympde.Domain.fromfile + t0 = time() + geometry = Geometry.from_discrete_mapping(map_discrete) # , comm=mpi_comm) + geometry.export(f'geo_{mpi_rank}.h5') + mpi_comm.Barrier() + t1 = time() + timing['export'] += t1 - t0 + domain = Domain.from_file(f'geo_{mpi_rank}.h5') + + #check_regular_ring_map(map_discrete) + + else: + # Only symbolic mapping is necessary + mapping = model.mapping + domain = mapping(logical_domain) + + rp_str = f'{ncells[0]}_{ncells[1]}_p={degree[0]}_t={test_case}_D{shift_D}_m={smooth_method}' + if use_spline_mapping: + rp_str += '_sm' + else: + rp_str += '_pm' # WARNING: check that polar_mapping == True ? + + # ========================== SYMBOLIC DEFINITION ==============================# + + # Equations + V0 = ScalarFunctionSpace('V0', domain) + u0, v0 = elements_of(V0, names='u0, v0') + aM = BilinearForm((u0, v0), integral(domain, u0 * v0)) + aS = BilinearForm((u0, v0), integral(domain, dot(grad(u0), grad(v0)))) + # model.rho is in logical coordinates instead of physical but it works anyways + # but needs to comment "Check linearity" in LinearForm._init_ + # rhs = LinearForm(v0, integral(domain, model.rho * v0)) + + from sympy.abc import x, y # try + + f = 2 * 7 * pi * cos(7 * pi / 2 * (1 - x ** 2 - y ** 2)) + (7 * pi) ** 2 * (x ** 2 + y ** 2) * sin( + 7 * pi / 2 * (1 - x ** 2 - y ** 2)) + # f = x+y + rhs = LinearForm(v0, integral(domain, f * v0)) + + err_diff = model.phi - u0 + + # trick should be removed in the final version. The norms should be computed + # on the physical domain. + # NOTE (mcp, dec 2024): norms and errors now computed only for discrete solutions (using M and S matrices) + if trick: + errL2 = Functional(err_diff ** 2 * mapping.jacobian.det(), + logical_domain, evaluate=True) + errH1 = Functional(dot(mapping.jacobian.T ** (-1) * grad(err_diff), + mapping.jacobian.T ** (-1) * grad(err_diff)) * + mapping.jacobian.det(), logical_domain, evaluate=True) + u0L2norm = Functional(u0 ** 2 * mapping.jacobian.det(), + logical_domain, evaluate=True) + u0H1norm = Functional(dot(mapping.jacobian.T ** (-1) * grad(u0), + mapping.jacobian.T ** (-1) * grad(u0)) * + mapping.jacobian.det(), logical_domain, evaluate=True) + else: + errL2 = Norm(err_diff, domain, kind='L2') + errH1 = Norm(err_diff, domain, kind='H1') + + u0L2norm = Norm(u0, domain, kind='L2') + u0H1norm = Norm(u0, domain, kind='H1') + + # L2norm = Norm(err_diff*sqrt(mapping.jacobian.det()), logical_domain, kind = 'L2') + # H1norm = Norm(mapping.jacobian.T**(-1) * grad(err_diff) + # * sqrt(mapping.jacobian.det()), logical_domain, kind = 'L2') + + # ============================= DISCRETIZATION ================================# + if use_spline_mapping: + domain_h = discretize(domain, filename=f'geo_{mpi_rank}.h5') # , comm = mpi_comm) + V0_h = discretize(V0, domain_h) + F = list(domain_h.mappings.values()).pop() + else: + domain_h = discretize(domain, ncells=ncells, periodic=[False, True]) # , comm = mpi_comm) + V0_h = discretize(V0, domain_h, degree=degree) + F = mapping.get_callable_mapping() + + aM_h = discretize(aM, domain_h, (V0_h, V0_h), backend=backend) + aS_h = discretize(aS, domain_h, (V0_h, V0_h), backend=backend) + rhs_h = discretize(rhs, domain_h, V0_h, backend=backend) + + # trick should be removed in the final version. The norms should be computed + # on the physical domain. + if trick: + log_domain_h = discretize(logical_domain, ncells=ncells, periodic=[False, True]) # , + # comm = mpi_comm) + log_V0_h = discretize(V0, log_domain_h, degree=degree) + # log_domain_h.mappings['Omega'] = domain_h.mappings['mapping_0(Omega)'] # If spline mapping + + errL2_h = discretize(errL2, log_domain_h, log_V0_h, backend=backend) + errH1_h = discretize(errH1, log_domain_h, log_V0_h, backend=backend) + + u0L2norm_h = discretize(u0L2norm, log_domain_h, log_V0_h, backend=backend) + u0H1norm_h = discretize(u0H1norm, log_domain_h, log_V0_h, backend=backend) + + else: + + errL2_h = discretize(errL2, domain_h, V0_h, backend=backend) + errH1_h = discretize(errH1, domain_h, V0_h, backend=backend) + + u0L2norm_h = discretize(u0L2norm, domain_h, V0_h, backend=backend) + u0H1norm_h = discretize(u0H1norm, domain_h, V0_h, backend=backend) + + M = aM_h.assemble() + S = aS_h.assemble() + b = rhs_h.assemble() + + S.update_ghost_regions() + b.update_ghost_regions() + M.update_ghost_regions() + + # ========================= HANDLIG THE SINGULARITY ===========================# + + # If required by user, create C1 projector and then restrict + # stiffness/mass matrices and right-hand-side vector to C1 space + t0 = time() + if smooth_method == 'polar-spec': + proj = C1Projector(F) + Sp = proj.change_matrix_basis(S) + bp = proj.change_rhs_basis(b) + alpha = 'None' + if smooth_method == 'polar-std': + # Build standard polar map from control points of standard polar map + n1, n2 = [W.nbasis for W in V0_h.spaces] + rho = np.array([i1 / (n1 - 1) for i1 in range(n1)]) + theta = np.array([i2 * 2 * np.pi / n2 for i2 in range(n2)]) + sin_theta = np.sin(theta) + cos_theta = np.cos(theta) + cp = np.zeros((n1, n2, 2)) + for i1 in range(n1): + for i2 in range(n2): + cp[i1, i2, 0] = rho[i1] * cos_theta[i2] + cp[i1, i2, 1] = rho[i1] * sin_theta[i2] + F_std = SplineMapping.from_control_points(V0_h, cp) + proj = C1Projector(F_std) + Sp = proj.change_matrix_basis(S) + bp = proj.change_rhs_basis(b) + alpha = 'None' + elif smooth_method == 'C1conga': + raise ValueError("Only C0conga is implemented for now!") + # gamma = 1.0 # any value would be ok. + # alpha = alphaCONGA + # P0 = C1CongaProjector0(V0_h, gamma=gamma, hbc=True) # hbc imposes the boundary conditions + # Sc = CongaLaplacian(S, M, P0, alpha) + # A = Sc.tosparse() + # bc = P0.T.dot(b) + elif smooth_method == 'C0conga': + alpha = alphaCONGA + P0 = C0PolarProjection_V0(V0_h, hbc=True) # hbc imposes the boundary conditions + Sc = CongaLaplacian(S, M, P0, alpha) + bc = P0.T.dot(b) + elif smooth_method == 'None': + alpha = 'None' + t1 = time() + timing['projection'] = t1 - t0 + + # Apply homogeneous Dirichlet boundary conditions for the conforming + # smooth_method case 'polar' and non-conforming case 'None' + # NOTE: this does not effect ghost regions + S_nobc = S.copy() + e1 = V0_h.coeff_space.ends[0] + if e1 == V0_h.coeff_space.npts[0] - 1: + if smooth_method in ('polar-std', 'polar-spec'): + last = bp[1].space.npts[0] - 1 + Sp[1, 1][last, :, :, :] = 0. + Sp[1, 1][last, :, 0, 0] = 1. + bp[1][last, :] = 0. + elif smooth_method == 'None': + S[e1, :, :, :] = 0. + S[e1, :, 0, 0] = 1. + b[e1, :] = 0. + + # ====================== SOLVE GALERKIN SYSTEM WITH CG ========================# + + # Solve linear system + t0 = time() + if smooth_method in ('polar-std', 'polar-spec'): + # Sp_inv = inverse(Sp, 'cg', tol = cgtol, maxiter = cgiter, verbose = True) + # xp = Sp_inv.dot(bp) + # xsol = proj.convert_to_tensor_basis(xp) + # info = Sp_inv.get_info() + from psydac.linalg.utilities import array_to_psydac + import scipy + L = proj.L[:, :, p2: -p2].reshape(3, 2 * ne2) + E = np.block([[L, np.zeros((3, (ne1 + p1 - 2) * ne2))], + [np.zeros(((ne1 + p1 - 2) * ne2, 2 * ne2)), np.eye((ne1 + p1 - 2) * ne2)]]) + xparray = scipy.linalg.solve(Sp.toarray(), bp.toarray()) + xarray = E.T @ xparray + xsol = array_to_psydac(xarray, V0_h.coeff_space) + elif smooth_method == 'C1conga': + Sc_inv = inverse(Sc, 'cg', tol=cgtol, maxiter=cgiter, verbose=verbose) + xsol = Sc_inv.dot(bc) + info = Sc_inv.get_info() + elif smooth_method == 'C0conga': + Sc_inv = inverse(Sc, 'cg', tol=cgtol, maxiter=cgiter, verbose=verbose) + xsol = Sc_inv.dot(bc) + info = Sc_inv.get_info() + elif smooth_method == 'None': + S_inv = inverse(S, 'pcg', pc='jacobi', tol=cgtol, maxiter=cgiter, verbose=verbose) + xsol = S_inv.dot(b) + info = S_inv.get_info() + t1 = time() + timing['solution'] = t1 - t0 + + # ========================= APPROXIMATION ERROR ===============================# + + # Create potential field for discrete solution + phi = FemField(V0_h, coeffs=xsol) + phi.coeffs.update_ghost_regions() + + # ref solution: projected exact solution + from psydac.feec.global_projectors import Projector_H1 + from psydac.feec.pull_push import pull_2d_h1 + from sympy import lambdify + + Pi0 = Projector_H1(V0_h) + # phi_ref = Pi0(model.phi_callable) + + # def P0_phys(f_phys, P0, domain, mappings_list): + # phi = lambdify(domain.coordinates, f_phys) + # P0(f_log) + + phi_symref = sin(7 * pi / 2 * (1 - x ** 2 - y ** 2)) + phi_calref = lambdify(domain.coordinates, phi_symref) + phi_callog = pull_2d_h1(phi_calref, F) + phi_ref = Pi0(phi_callog) + phi_ref.coeffs.update_ghost_regions() + + # L2 and H1 norms + ref_u0L2_2 = phi_ref.coeffs.inner(M.dot(phi_ref.coeffs)) # l2 norm of ref solution + ref_u0H1_semi2 = phi_ref.coeffs.inner(S.dot(phi_ref.coeffs)) + ref_u0L2 = np.sqrt(ref_u0L2_2) + ref_u0H1 = np.sqrt(ref_u0H1_semi2 + ref_u0L2_2) # H1 norm of ref solution + + # L2 and H1 errors + t0 = time() + phi_diff = phi_ref.coeffs - phi.coeffs + + err_l2_2 = phi_diff.inner(M.dot(phi_diff)) + err_h1_semi2 = phi_diff.inner(S.dot(phi_diff)) + + err_l2 = np.sqrt(err_l2_2) + err_h1 = np.sqrt(err_h1_semi2 + err_l2_2) + + rel_err_l2 = err_l2 / ref_u0L2 + rel_err_h1 = err_h1 / ref_u0H1 + + # previous option: ok but H1 norm has problems ? + # ref_u0L2 = u0L2norm_h.assemble(u0 = phi_ref) + # err2 = L2norm_h.assemble(u0 = phi_ref - phi) + # err2 = L2norm_h.assemble(u0 = phi) + + # when trick is used the norm is a functional, that is, an integral. We have + # to take the square root explicitely to have the norm. + # if trick: + # print('WARNING -- do not use the trick !!!!') + # raise NotImplementedError + # err2 = np.sqrt(err2) + # u0L2 = np.sqrt(u0L2) + + # assert np.allclose(err2, err2_matrix, rtol = 1e-7, atol = 1e-7) + + # and H1 error + # t0 = time() + # errh1_semi = H1norm_h.assemble(u0 = phi_ref - phi) + # errh1_semi = errH1_h.assemble(u0 = phi) # BUG ?? + # u0H1_semi = u0H1norm_h.assemble(u0 = phi) + # if trick: + # errh1_semi = np.sqrt(errh1_semi) + # u0H1_semi = np.sqrt(u0H1_semi) + # errh1 = np.sqrt(errh1_semi**2 + err2**2) + # u0H1 = np.sqrt(u0H1_semi**2 + u0L2**2) + # errh1 = np.sqrt(errh1_semi**2 + err2**2) + # errh1_semi_sq = phi_diff.dot(S_nobc.dot(phi_diff)) + # errh1_matrix = np.sqrt(err2_sq + errh1_semi_sq) + + t1 = time() + timing['diagnostics'] = t1 - t0 + + # assert np.allclose(errh1, errh1_matrix, rtol = 1e-7, atol = 1e-7) + + # Write solution to HDF5 file + t0 = time() + V0_h.export_fields(f'fields_{mpi_rank}.h5', phi=phi) + t1 = time() + timing['export'] += t1 - t0 + + # =============================== PRINTING INFO ===============================# + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Print some information to terminal + for i in range(mpi_size): + if i == mpi_rank: + print('--------------------------------------------------') + print(' RANK = {}'.format(mpi_rank)) + print('--------------------------------------------------') + print('> Grid :: [{ne1},{ne2}]'.format(ne1=ne1, ne2=ne2)) + print('> Degree :: [{p1},{p2}]'.format(p1=p1, p2=p2)) + print('> Penalization alpha :: {alpha} '.format(alpha=alpha)) + # print( '> CG info :: ',info ) + print('> L2 norm solution :: {:.2e}'.format(ref_u0L2)) + print('> H1 norm solution :: {:.2e}'.format(ref_u0H1)) + print('> L2 error (relative) :: {:.2e}'.format(rel_err_l2)) + print('> H1 error (relative) :: {:.2e}'.format(rel_err_h1)) + print('') + print('> Assembly time :: {:.2e}'.format(timing['assembly'])) + if smooth_method: + print('> Project. time :: {:.2e}'.format(timing['projection'])) + print('> Solution time :: {:.2e}'.format(timing['solution'])) + print('> Evaluat. time :: {:.2e}'.format(timing['diagnostics'])) + print('> Export time :: {:.2e}'.format(timing['export'])) + print('', flush=True) + sleep(0.001) + mpi_comm.Barrier() + + # =============================== VISUALIZATION ===============================# + + N = 10 + + # Create new serial FEM space and mapping (if needed) + if use_spline_mapping: + geometry = Geometry(filename=f'geo_{mpi_rank}.h5', comm=MPI.COMM_SELF) + map_discrete = [*geometry.mappings.values()].pop() + V = map_discrete.space + mapping = map_discrete + else: + V = TensorFemSpace(domain_decomposition, V1, V2) + + # Import solution vector into new serial field + phi, = V.import_fields(f'fields_{mpi_rank}.h5', 'phi') + + # Callable exact solution (used for plots) + # phi_e = model.phi_callable + phi_e = phi_callog + + # Compute numerical solution (and error) on refined logical grid + [sk1, sk2], [ek1, ek2] = V.local_domain + + eta1 = refine_array_1d(V1.breaks[sk1:ek1 + 2], N) + eta2 = refine_array_1d(V2.breaks[sk2:ek2 + 2], N) + num = np.array([[phi(e1, e2) for e2 in eta2] for e1 in eta1]) + ex = np.array([[phi_e(e1, e2) for e2 in eta2] for e1 in eta1]) + err = num - ex + print('num[0,0] = ', num[0, 0]) + print('ex[0,0] = ', ex[0, 0]) + + # Compute physical coordinates of logical grid + pcoords = np.array([[F(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] + + plot_only_sol = True + + if plot_only_sol: + + # plot only numerical solution on mapped domain (analytical or spline) + fig, axes = plt.subplots(1, 1, figsize=(4.8, 4.8)) + + def add_colorbar(im, ax): + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.2) + cbar = ax.get_figure().colorbar(im, cax=cax) + return cbar + + # # Plot exact solution + # ax = axes[0] + # im = ax.contourf( xx, yy, ex, 40, cmap='jet' ) + # add_colorbar( im, ax ) + # ax.set_xlabel( r'$x$', rotation='horizontal' ) + # ax.set_ylabel( r'$y$', rotation='horizontal' ) + # ax.set_title ( r'$\phi_{ex}(x,y)$' ) + # ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) + # ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) + # ax.set_aspect('equal') + + if use_spline_mapping: + # Recompute physical coordinates of logical grid using spline mapping + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] + + # Plot numerical solution + ax = axes # [0] + im = ax.contourf(xx, yy, num, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi(x,y)$') + ax.plot(xx[:, ::N], yy[:, ::N], 'k') + ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.set_aspect('equal') + + # fig.savefig(f'plots/phi_{rp_str}.png') + fig.show() + # plt.cla() + + # Plot numerical error + fig, axes = plt.subplots(1, 1, figsize=(4.8, 4.8)) + ax = axes # [2] + im = ax.contourf(xx, yy, err, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') + # ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) + # ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) + ax.set_aspect('equal') + + # fig.savefig(f'plots/err_{rp_str}.png') + fig.show() + # plt.clf() + # fig.clf() + + + else: + + # Create figure with 3 subplots: + # 1. exact solution on exact domain + # 2. numerical solution on mapped domain (analytical or spline) + # 3. numerical error on mapped domain (analytical or spline) + fig, axes = plt.subplots(1, 3, figsize=(12.8, 4.8)) + + def add_colorbar(im, ax): + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.2) + cbar = ax.get_figure().colorbar(im, cax=cax) + return cbar + + # Plot exact solution + ax = axes[0] + im = ax.contourf(xx, yy, ex, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi_{ex}(x,y)$') + ax.plot(xx[:, ::N], yy[:, ::N], 'k') + ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.set_aspect('equal') + + if use_spline_mapping: + # Recompute physical coordinates of logical grid using spline mapping + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] + + # Plot numerical solution + ax = axes[1] + im = ax.contourf(xx, yy, num, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi(x,y)$') + ax.plot(xx[:, ::N], yy[:, ::N], 'k') + ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.set_aspect('equal') + + # Plot numerical error + ax = axes[2] + im = ax.contourf(xx, yy, err, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') + # ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) + # ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) + ax.set_aspect('equal') + + # Show figure + fig.savefig(f'plots/phi_and_err_{rp_str}.png') + fig.show() + + return locals() + + +# ============================================================================== +# Parser +# ============================================================================== +def parse_input_arguments(): + import argparse + + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Solve Poisson's equation on a 2D polar domain." + ) + + parser.add_argument('-t', + type=str, + choices=('disk', 'target', 'czarny'), + default='disk', + dest='test_case', + help='Test case' + ) + + parser.add_argument('-D', + type=float, + default=0, + dest='shift_D', + help='Shafranov shift for parametrization of Disk' + ) + + parser.add_argument('-d', + type=int, + nargs=2, + default=[2, 2], + metavar=('P1', 'P2'), + dest='degree', + help='Spline degree along each dimension' + ) + + parser.add_argument('-n', '--ncells', + type=int, + nargs=2, + default=[10, 20], + metavar=('N1', 'N2'), + dest='ncells', + help='Number of grid cells (elements) along each dimension' + ) + + parser.add_argument('-S', + action='store_true', + dest='use_spline_mapping', + help='Use spline mapping in finite element calculations' + ) + + parser.add_argument('-m', + choices=('polar-spec', 'polar-std', 'C0conga', 'C1conga', 'None'), + default='C1conga', + dest='smooth_method', + help='Apply smoothing method at pole either C1-conforming geometry specific / C1-conforming standardized / C1-CONGA / C0-CONGA' + ) + + parser.add_argument('-v', + type=bool, + default=False, + dest='verbose', + help='See CG iterations and L2 norm of the residuals' + ) + + parser.add_argument('-a', + type=float, + default=1000, + dest='alphaCONGA', + help='Penalization constant for CONGA methods' + ) + + parser.add_argument('--cgtol', + type=float, + default=1e-10, + dest='cgtol', + help='absolute tol for the residual error to stop CG' + ) + + parser.add_argument('--maxiter', + type=int, + default=100000, + dest='cgiter', + help='max number of iterations for CG' + ) + + return parser.parse_args() + + +# ============================================================================== +# Script functionality +# ============================================================================== +if __name__ == '__main__': + + args = parse_input_arguments() + namespace = run_poisson_2d(**vars(args)) + + import __main__ + + if hasattr(__main__, '__file__'): + try: + __IPYTHON__ + except NameError: + import matplotlib.pyplot as plt + + plt.show() + +## example of run: +# mpirun -n 2 python poisson_2d.py -S -n 2 3 -d 2 2 -t disk -D 0.2 -m 'C0conga' From ad8adad607f4513980c0bf3c9b8ffa7636ea799d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Sep 2025 17:42:42 +0200 Subject: [PATCH 002/133] reorganize --- psydac/feec/polar/examples/__init__.py | 0 psydac/feec/polar/{ => examples}/poisson_2d.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 psydac/feec/polar/examples/__init__.py rename psydac/feec/polar/{ => examples}/poisson_2d.py (100%) diff --git a/psydac/feec/polar/examples/__init__.py b/psydac/feec/polar/examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/psydac/feec/polar/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py similarity index 100% rename from psydac/feec/polar/poisson_2d.py rename to psydac/feec/polar/examples/poisson_2d.py From 364d64c5b126a20da686c5bec8b6c00bb31bc652 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 17 Sep 2025 08:53:36 +0200 Subject: [PATCH 003/133] only one geometry file is created --- psydac/feec/polar/examples/poisson_2d.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 6206cb291..4c63ee2c5 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -32,7 +32,7 @@ from psydac.api.settings import PSYDAC_BACKENDS -from conga_projections import C0PolarProjection_V0 +from psydac.feec.polar.conga_projections import C0PolarProjection_V0 # backend = PSYDAC_BACKENDS['numba'] backend = PSYDAC_BACKENDS['python'] @@ -415,11 +415,12 @@ def run_poisson_2d(*, test_case, ncells, degree, # to create first a HDF5 file and then load as sympde.Domain.fromfile t0 = time() geometry = Geometry.from_discrete_mapping(map_discrete) # , comm=mpi_comm) - geometry.export(f'geo_{mpi_rank}.h5') + if mpi_rank == 0: + geometry.export('geo.h5') mpi_comm.Barrier() t1 = time() timing['export'] += t1 - t0 - domain = Domain.from_file(f'geo_{mpi_rank}.h5') + domain = Domain.from_file('geo.h5') #check_regular_ring_map(map_discrete) @@ -481,7 +482,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # ============================= DISCRETIZATION ================================# if use_spline_mapping: - domain_h = discretize(domain, filename=f'geo_{mpi_rank}.h5') # , comm = mpi_comm) + domain_h = discretize(domain, filename='geo.h5') # , comm = mpi_comm) V0_h = discretize(V0, domain_h) F = list(domain_h.mappings.values()).pop() else: @@ -695,7 +696,8 @@ def run_poisson_2d(*, test_case, ncells, degree, # Write solution to HDF5 file t0 = time() - V0_h.export_fields(f'fields_{mpi_rank}.h5', phi=phi) + if mpi_rank == 0: + V0_h.export_fields('fields.h5', phi=phi) t1 = time() timing['export'] += t1 - t0 @@ -733,7 +735,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # Create new serial FEM space and mapping (if needed) if use_spline_mapping: - geometry = Geometry(filename=f'geo_{mpi_rank}.h5', comm=MPI.COMM_SELF) + geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) map_discrete = [*geometry.mappings.values()].pop() V = map_discrete.space mapping = map_discrete @@ -741,7 +743,7 @@ def run_poisson_2d(*, test_case, ncells, degree, V = TensorFemSpace(domain_decomposition, V1, V2) # Import solution vector into new serial field - phi, = V.import_fields(f'fields_{mpi_rank}.h5', 'phi') + phi, = V.import_fields('fields.h5', 'phi') # Callable exact solution (used for plots) # phi_e = model.phi_callable From 04b6e8c5faf34369b851cd5cf1e799d30b87a95d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 19 Sep 2025 07:08:22 +0200 Subject: [PATCH 004/133] minor changes --- psydac/feec/polar/examples/poisson_2d.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 4c63ee2c5..5748b3903 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -482,11 +482,15 @@ def run_poisson_2d(*, test_case, ncells, degree, # ============================= DISCRETIZATION ================================# if use_spline_mapping: - domain_h = discretize(domain, filename='geo.h5') # , comm = mpi_comm) + domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm, mpi_dims_mask=[False, True]) + print("Discretized domain") V0_h = discretize(V0, domain_h) + print(f"starts: {V0_h.coeff_space.starts}") + print(f"ends: {V0_h.coeff_space.ends}") + print(f"npts: {V0_h.coeff_space.npts}") F = list(domain_h.mappings.values()).pop() else: - domain_h = discretize(domain, ncells=ncells, periodic=[False, True]) # , comm = mpi_comm) + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], mpi_dims_mask=[False, True], comm = mpi_comm) # , comm = mpi_comm) V0_h = discretize(V0, domain_h, degree=degree) F = mapping.get_callable_mapping() @@ -634,7 +638,6 @@ def run_poisson_2d(*, test_case, ncells, degree, # def P0_phys(f_phys, P0, domain, mappings_list): # phi = lambdify(domain.coordinates, f_phys) # P0(f_log) - phi_symref = sin(7 * pi / 2 * (1 - x ** 2 - y ** 2)) phi_calref = lambdify(domain.coordinates, phi_symref) phi_callog = pull_2d_h1(phi_calref, F) @@ -693,14 +696,12 @@ def run_poisson_2d(*, test_case, ncells, degree, timing['diagnostics'] = t1 - t0 # assert np.allclose(errh1, errh1_matrix, rtol = 1e-7, atol = 1e-7) - # Write solution to HDF5 file t0 = time() if mpi_rank == 0: V0_h.export_fields('fields.h5', phi=phi) t1 = time() timing['export'] += t1 - t0 - # =============================== PRINTING INFO ===============================# # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From 50128563f1dffdd334bdb6d4f9a09ebe18aa028b Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 24 Sep 2025 14:35:41 +0200 Subject: [PATCH 005/133] update names in examples/poisson_2d_mapping.py --- examples/poisson_2d_mapping.py | 4 ++-- psydac/feec/polar/examples/poisson_2d.py | 24 ++++++++---------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/examples/poisson_2d_mapping.py b/examples/poisson_2d_mapping.py index 602b9f34c..47955da62 100644 --- a/examples/poisson_2d_mapping.py +++ b/examples/poisson_2d_mapping.py @@ -401,7 +401,7 @@ def assemble_matrices(V, mapping, kernel, *, nquads): [p1, p2] = V.coeff_space.pads # Quadrature data - quad_grids = V.get_quadrature_grids(*nquads) + quad_grids = V.get_assembly_grids(*nquads) [ nk1, nk2] = [g.num_elements for g in quad_grids] [ nq1, nq2] = [g.num_quad_pts for g in quad_grids] [ spans_1, spans_2] = [g.spans for g in quad_grids] @@ -483,7 +483,7 @@ def assemble_rhs(V, mapping, f, *, nquads): [p1, p2] = V.coeff_space.pads # Quadrature data - quad_grids = V.get_quadrature_grids(*nquads) + quad_grids = V.get_assembly_grids(*nquads) [ nk1, nk2] = [g.num_elements for g in quad_grids] [ nq1, nq2] = [g.num_quad_pts for g in quad_grids] [ spans_1, spans_2] = [g.spans for g in quad_grids] diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 5748b3903..3010791d7 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -395,7 +395,7 @@ def run_poisson_2d(*, test_case, ncells, degree, V2 = SplineSpace(p2, grid=grid_2, periodic=True) # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, [False, True]) # , comm = mpi_comm) + domain_decomposition = DomainDecomposition(ncells, [False, True] , comm = mpi_comm) V = TensorFemSpace(domain_decomposition, V1, V2) s1, s2 = V.coeff_space.starts @@ -414,10 +414,8 @@ def run_poisson_2d(*, test_case, ncells, degree, # In order to create a sympde.Domain object from this mapping we have # to create first a HDF5 file and then load as sympde.Domain.fromfile t0 = time() - geometry = Geometry.from_discrete_mapping(map_discrete) # , comm=mpi_comm) - if mpi_rank == 0: - geometry.export('geo.h5') - mpi_comm.Barrier() + geometry = Geometry.from_discrete_mapping(map_discrete, comm=mpi_comm) + geometry.export('geo.h5') t1 = time() timing['export'] += t1 - t0 domain = Domain.from_file('geo.h5') @@ -482,15 +480,11 @@ def run_poisson_2d(*, test_case, ncells, degree, # ============================= DISCRETIZATION ================================# if use_spline_mapping: - domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm, mpi_dims_mask=[False, True]) - print("Discretized domain") + domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) V0_h = discretize(V0, domain_h) - print(f"starts: {V0_h.coeff_space.starts}") - print(f"ends: {V0_h.coeff_space.ends}") - print(f"npts: {V0_h.coeff_space.npts}") F = list(domain_h.mappings.values()).pop() else: - domain_h = discretize(domain, ncells=ncells, periodic=[False, True], mpi_dims_mask=[False, True], comm = mpi_comm) # , comm = mpi_comm) + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) V0_h = discretize(V0, domain_h, degree=degree) F = mapping.get_callable_mapping() @@ -501,8 +495,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # trick should be removed in the final version. The norms should be computed # on the physical domain. if trick: - log_domain_h = discretize(logical_domain, ncells=ncells, periodic=[False, True]) # , - # comm = mpi_comm) + log_domain_h = discretize(logical_domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) log_V0_h = discretize(V0, log_domain_h, degree=degree) # log_domain_h.mappings['Omega'] = domain_h.mappings['mapping_0(Omega)'] # If spline mapping @@ -698,8 +691,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # assert np.allclose(errh1, errh1_matrix, rtol = 1e-7, atol = 1e-7) # Write solution to HDF5 file t0 = time() - if mpi_rank == 0: - V0_h.export_fields('fields.h5', phi=phi) + V0_h.export_fields('fields.h5', phi=phi) t1 = time() timing['export'] += t1 - t0 # =============================== PRINTING INFO ===============================# @@ -996,4 +988,4 @@ def parse_input_arguments(): plt.show() ## example of run: -# mpirun -n 2 python poisson_2d.py -S -n 2 3 -d 2 2 -t disk -D 0.2 -m 'C0conga' +# mpirun -n 2 python poisson_2d.py -S -n 2 3 -d 2 2 -t disk -D 0.2 -m 'C0conga' \ No newline at end of file From 51d059289f79fdfd30b1b412af788a9e773b50e5 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Oct 2025 15:07:03 +0200 Subject: [PATCH 006/133] error in plot_2d_decomposition --- examples/poisson_2d_mapping.py | 35 +++++++++++++----------- psydac/feec/polar/examples/poisson_2d.py | 8 ++++-- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/examples/poisson_2d_mapping.py b/examples/poisson_2d_mapping.py index 47955da62..307e00394 100644 --- a/examples/poisson_2d_mapping.py +++ b/examples/poisson_2d_mapping.py @@ -781,30 +781,33 @@ def main(*, test_case, ncells, degree, nquads, ########## # Plot domain decomposition (master only) + print(model.mapping) V.plot_2d_decomposition(model.mapping, refine=N) # Perform other visualization using master or all processes - if not distribute_viz: + #if not distribute_viz: # Non-master processes stop here - if mpi_rank != 0: - return - - # Create new serial FEM space and mapping (if needed) - if use_spline_mapping: - geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) - map_discrete = [*geometry.mappings.values()].pop() - Vnew = map_discrete.space - mapping = map_discrete - else: - dd = DomainDecomposition(ncells, model.periodic, comm=MPI.COMM_SELF) - Vnew = TensorFemSpace(dd, V1, V2) - - # Import solution vector into new serial field - phi, = Vnew.import_fields( 'fields.h5', 'phi' ) + #if mpi_rank != 0: + #return + + # Create new serial FEM space and mapping (if needed) + if use_spline_mapping: + geometry = Geometry(filename='geo.h5', comm=mpi_comm) + map_discrete = [*geometry.mappings.values()].pop() + Vnew = map_discrete.space + mapping = map_discrete + else: + dd = DomainDecomposition(ncells, model.periodic, comm=MPI.COMM_SELF) + Vnew = TensorFemSpace(dd, V1, V2) + + # Import solution vector into new serial field + phi, = Vnew.import_fields( 'fields.h5', 'phi' ) # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = Vnew.local_domain + print(mpi_rank) + print([sk1, sk2], [ek1, ek2]) eta1 = refine_array_1d(V1.breaks[sk1:ek1+2], N) eta2 = refine_array_1d(V2.breaks[sk2:ek2+2], N) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 3010791d7..58b32c572 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -725,10 +725,13 @@ def run_poisson_2d(*, test_case, ncells, degree, # =============================== VISUALIZATION ===============================# N = 10 - + print(mapping.get_callable_mapping()) + V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) + # if mpi_rank != 0: + # return # Create new serial FEM space and mapping (if needed) if use_spline_mapping: - geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) + geometry = Geometry(filename='geo.h5', comm=mpi_comm) map_discrete = [*geometry.mappings.values()].pop() V = map_discrete.space mapping = map_discrete @@ -744,6 +747,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = V.local_domain + print([sk1, sk2], [ek1, ek2]) eta1 = refine_array_1d(V1.breaks[sk1:ek1 + 2], N) eta2 = refine_array_1d(V2.breaks[sk2:ek2 + 2], N) From 7354e9766721a0f392cd8c7628a63328d97fc6aa Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 14 Oct 2025 08:52:26 +0200 Subject: [PATCH 007/133] fix an error after merging --- psydac/feec/polar/examples/poisson_2d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 58b32c572..eb3e2b8b4 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -621,11 +621,11 @@ def run_poisson_2d(*, test_case, ncells, degree, phi.coeffs.update_ghost_regions() # ref solution: projected exact solution - from psydac.feec.global_projectors import Projector_H1 + from psydac.feec.global_geometric_projectors import GlobalGeometricProjectorH1 from psydac.feec.pull_push import pull_2d_h1 from sympy import lambdify - Pi0 = Projector_H1(V0_h) + Pi0 = GlobalGeometricProjectorH1(V0_h) # phi_ref = Pi0(model.phi_callable) # def P0_phys(f_phys, P0, domain, mappings_list): From 2c2846294740de2e115630071ec8894f7b1818d1 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 14 Oct 2025 11:20:05 +0200 Subject: [PATCH 008/133] error in plot_2d_decomposition --- psydac/feec/polar/examples/poisson_2d.py | 37 +++++++++++++++--------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index eb3e2b8b4..ca9baf4ad 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -725,21 +725,31 @@ def run_poisson_2d(*, test_case, ncells, degree, # =============================== VISUALIZATION ===============================# N = 10 - print(mapping.get_callable_mapping()) V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) - # if mpi_rank != 0: - # return - # Create new serial FEM space and mapping (if needed) - if use_spline_mapping: - geometry = Geometry(filename='geo.h5', comm=mpi_comm) - map_discrete = [*geometry.mappings.values()].pop() - V = map_discrete.space - mapping = map_discrete + + # plot only with the root process + distribute_viz = False + if not distribute_viz: + # Non-master processes stop here + if mpi_rank != 0: + return + if use_spline_mapping: + geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) + map_discrete = [*geometry.mappings.values()].pop() + Vnew = map_discrete.space + mapping = map_discrete + else: + dd = DomainDecomposition(ncells, model.periodic, comm=MPI.COMM_SELF) + Vnew = TensorFemSpace(dd, V1, V2) + + # Import solution vector into new serial field + phi, = Vnew.import_fields( 'fields.h5', 'phi' ) + else: - V = TensorFemSpace(domain_decomposition, V1, V2) + Vnew = V # Import solution vector into new serial field - phi, = V.import_fields('fields.h5', 'phi') + phi, = Vnew.import_fields('fields.h5', 'phi') # Callable exact solution (used for plots) # phi_e = model.phi_callable @@ -762,7 +772,7 @@ def run_poisson_2d(*, test_case, ncells, degree, xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] - plot_only_sol = True + plot_only_sol = False if plot_only_sol: @@ -879,7 +889,8 @@ def add_colorbar(im, ax): ax.set_aspect('equal') # Show figure - fig.savefig(f'plots/phi_and_err_{rp_str}.png') + #fig.savefig(f'plots/phi_and_err_{rp_str}.png') + fig.suptitle(f'Rank {mpi_rank}') fig.show() return locals() From a86faf1a289dfc56779d9b70376b0ac7b84648e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 14 Oct 2025 14:55:36 +0200 Subject: [PATCH 009/133] Use tight layout --- psydac/feec/polar/examples/poisson_2d.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index ca9baf4ad..7ae92fd05 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -841,7 +841,7 @@ def add_colorbar(im, ax): # 1. exact solution on exact domain # 2. numerical solution on mapped domain (analytical or spline) # 3. numerical error on mapped domain (analytical or spline) - fig, axes = plt.subplots(1, 3, figsize=(12.8, 4.8)) + fig, axes = plt.subplots(1, 3, figsize=(15, 4.8)) def add_colorbar(im, ax): divider = make_axes_locatable(ax) @@ -891,6 +891,7 @@ def add_colorbar(im, ax): # Show figure #fig.savefig(f'plots/phi_and_err_{rp_str}.png') fig.suptitle(f'Rank {mpi_rank}') + fig.tight_layout() fig.show() return locals() @@ -1003,4 +1004,4 @@ def parse_input_arguments(): plt.show() ## example of run: -# mpirun -n 2 python poisson_2d.py -S -n 2 3 -d 2 2 -t disk -D 0.2 -m 'C0conga' \ No newline at end of file +# mpirun -n 2 python poisson_2d.py -S -n 2 3 -d 2 2 -t disk -D 0.2 -m 'C0conga' From 6d1304f7c314bf38104c77d8a7ab50cb95bfce9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 14 Oct 2025 14:56:56 +0200 Subject: [PATCH 010/133] Avoid string warnings --- psydac/feec/polar/examples/poisson_2d.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 7ae92fd05..f86b3e7ce 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -73,7 +73,7 @@ def __call__(self, phi): # ============================= EXACT SOLUTION ================================# class Poisson2D: - """ + r""" Exact solution to the 2D Poisson equation with Dirichlet boundary conditions, to be employed for the method of manufactured solutions. @@ -102,7 +102,7 @@ def __init__(self, domain, mapping, phi, rho): # ... @staticmethod def disk_domain(R=2.0, polar_mapping=False, shift_D=0.0, use_logical_sol=False): - """ + r""" Solve Poisson's equation on a disk of radius R centered at (x,y) = (0, 0), with logical coordinates (s, theta): @@ -153,7 +153,7 @@ def get_radius_angle(self, x, y): # ... @staticmethod def target_domain(): - """ + r""" Solve Poisson's equation on a polar domain, with logical coordinates (s, theta): - The radial coordinate s belongs to the interval [0, 1]; @@ -207,7 +207,7 @@ def target_domain(): # ... @staticmethod def czarny_domain(): - """ + r""" Solve Poisson's equation on a czarny domain, with logical coordinates (s, theta): - The radial coordinate s belongs to the interval [0, 1]; From 39d14f46346ea56b85bd6385735faed7924e7cf0 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 14 Oct 2025 15:46:10 +0200 Subject: [PATCH 011/133] some changes to visualization --- examples/poisson_2d_mapping.py | 11 ++--- psydac/feec/polar/examples/poisson_2d.py | 55 +++++++++--------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/examples/poisson_2d_mapping.py b/examples/poisson_2d_mapping.py index 0c1e87677..fe5a70c37 100644 --- a/examples/poisson_2d_mapping.py +++ b/examples/poisson_2d_mapping.py @@ -832,6 +832,12 @@ def add_colorbar(im, ax): cbar = ax.get_figure().colorbar(im, cax=cax) return cbar + if use_spline_mapping: + # Recompute physical coordinates of logical grid using spline mapping + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] + # Plot exact solution ax = axes[0] im = ax.contourf(xx, yy, ex, 40, cmap='jet') @@ -843,11 +849,6 @@ def add_colorbar(im, ax): ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') ax.set_aspect('equal') - if use_spline_mapping: - # Recompute physical coordinates of logical grid using spline mapping - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) - xx = pcoords[:, :, 0] - yy = pcoords[:, :, 1] # Plot numerical solution ax = axes[1] diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index ca9baf4ad..afc205ee7 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -725,7 +725,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # =============================== VISUALIZATION ===============================# N = 10 - V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) + V.plot_2d_decomposition(model.mapping.get_callable_mapping(), refine=N) # plot only with the root process distribute_viz = False @@ -739,11 +739,10 @@ def run_poisson_2d(*, test_case, ncells, degree, Vnew = map_discrete.space mapping = map_discrete else: - dd = DomainDecomposition(ncells, model.periodic, comm=MPI.COMM_SELF) + dd = DomainDecomposition(ncells, [False, True], comm=MPI.COMM_SELF) Vnew = TensorFemSpace(dd, V1, V2) # Import solution vector into new serial field - phi, = Vnew.import_fields( 'fields.h5', 'phi' ) else: Vnew = V @@ -752,11 +751,11 @@ def run_poisson_2d(*, test_case, ncells, degree, phi, = Vnew.import_fields('fields.h5', 'phi') # Callable exact solution (used for plots) - # phi_e = model.phi_callable - phi_e = phi_callog + phi_e = model.phi_callable + #phi_e = phi_callog # Compute numerical solution (and error) on refined logical grid - [sk1, sk2], [ek1, ek2] = V.local_domain + [sk1, sk2], [ek1, ek2] = Vnew.local_domain print([sk1, sk2], [ek1, ek2]) eta1 = refine_array_1d(V1.breaks[sk1:ek1 + 2], N) @@ -768,33 +767,24 @@ def run_poisson_2d(*, test_case, ncells, degree, print('ex[0,0] = ', ex[0, 0]) # Compute physical coordinates of logical grid - pcoords = np.array([[F(e1, e2) for e2 in eta2] for e1 in eta1]) + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] plot_only_sol = False + def add_colorbar(im, ax): + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.2) + cbar = ax.get_figure().colorbar(im, cax=cax) + return cbar + + if plot_only_sol: # plot only numerical solution on mapped domain (analytical or spline) fig, axes = plt.subplots(1, 1, figsize=(4.8, 4.8)) - def add_colorbar(im, ax): - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size=0.2, pad=0.2) - cbar = ax.get_figure().colorbar(im, cax=cax) - return cbar - - # # Plot exact solution - # ax = axes[0] - # im = ax.contourf( xx, yy, ex, 40, cmap='jet' ) - # add_colorbar( im, ax ) - # ax.set_xlabel( r'$x$', rotation='horizontal' ) - # ax.set_ylabel( r'$y$', rotation='horizontal' ) - # ax.set_title ( r'$\phi_{ex}(x,y)$' ) - # ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) - # ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) - # ax.set_aspect('equal') if use_spline_mapping: # Recompute physical coordinates of logical grid using spline mapping @@ -843,11 +833,11 @@ def add_colorbar(im, ax): # 3. numerical error on mapped domain (analytical or spline) fig, axes = plt.subplots(1, 3, figsize=(12.8, 4.8)) - def add_colorbar(im, ax): - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size=0.2, pad=0.2) - cbar = ax.get_figure().colorbar(im, cax=cax) - return cbar + if use_spline_mapping: + # Recompute physical coordinates of logical grid using spline mapping + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] # Plot exact solution ax = axes[0] @@ -860,11 +850,6 @@ def add_colorbar(im, ax): ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') ax.set_aspect('equal') - if use_spline_mapping: - # Recompute physical coordinates of logical grid using spline mapping - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) - xx = pcoords[:, :, 0] - yy = pcoords[:, :, 1] # Plot numerical solution ax = axes[1] @@ -884,8 +869,8 @@ def add_colorbar(im, ax): ax.set_xlabel(r'$x$', rotation='horizontal') ax.set_ylabel(r'$y$', rotation='horizontal') ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') - # ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) - # ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) + ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) + ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) ax.set_aspect('equal') # Show figure From 9d2a618a491cb02aada3313614ffcf2a5344a9d7 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 20 Oct 2025 08:59:55 +0200 Subject: [PATCH 012/133] computing average in parallel --- psydac/feec/polar/conga_projections.py | 21 ++++++++++++++++++--- psydac/feec/polar/examples/poisson_2d.py | 1 - 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 826c14f96..6134aa86f 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -35,6 +35,11 @@ def __init__(self, W0, *, transposed=False, hbc=False): self.transposed = transposed self.hbc = hbc + # Radial and angle sub-communicators (1D) + self._cart = W0.coeff_space.cart + self._radial_comm = self._cart.subcomm[0] + self._angle_comm = self._cart.subcomm[1] + @property def domain(self): return self.W0.coeff_space @@ -65,10 +70,20 @@ def dot(self, x, out=None): assert out.space is self.W0.coeff_space y = out - # Symmetric Matrix -> Same result when transposed if rank_at_polar_edge: - y[0, s2:e2 + 1] = np.average(x[0, s2:e2 + 1]) # must be parallelized - y[1:, s2:e2 + 1] = x[1:, s2:e2 + 1] + local_avg = np.average(x[0, s2:e2 + 1]) + print("local avg:", local_avg, self._angle_comm.rank) + + if self._cart.is_parallel: + from mpi4py import MPI + local_avg = self._angle_comm.allreduce(local_avg, op=MPI.SUM) + print("sum after allreduce:", local_avg, self._angle_comm.rank) + + y[0, s2:e2 + 1] = local_avg / self._angle_comm.size + y[1:e1 + 1, s2:e2 + 1] = x[1:e1 + 1, s2:e2 + 1] + else: + y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] + if self.hbc: if rank_at_outer_edge: diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 4a830f592..28fdc8b32 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -141,7 +141,6 @@ def get_radius_angle(self, x, y): else: # physical field (cf use of physical ref solution in Maxwell case) phi = sin(3.5 * pi * (R ** 2 - x ** 2 - y ** 2) / R ** 2) - # rho = - lapl(phi) # phi = R**2 - x**2 - y**2 rho = - lapl(phi) From 77cd4e4f09e7263719934b8b55e5a768e5b9105d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 21 Oct 2025 21:51:58 +0200 Subject: [PATCH 013/133] fixed errors in parallel average --- psydac/feec/polar/conga_projections.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 6134aa86f..c4d576da9 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -37,8 +37,9 @@ def __init__(self, W0, *, transposed=False, hbc=False): # Radial and angle sub-communicators (1D) self._cart = W0.coeff_space.cart - self._radial_comm = self._cart.subcomm[0] - self._angle_comm = self._cart.subcomm[1] + if self._cart.is_parallel: + self._radial_comm = self._cart.subcomm[0] + self._angle_comm = self._cart.subcomm[1] @property def domain(self): @@ -71,15 +72,14 @@ def dot(self, x, out=None): y = out if rank_at_polar_edge: - local_avg = np.average(x[0, s2:e2 + 1]) - print("local avg:", local_avg, self._angle_comm.rank) + # compute sum of points with s = 0 for the process + local_sum = np.sum(x[0, s2:e2 + 1]) if self._cart.is_parallel: from mpi4py import MPI - local_avg = self._angle_comm.allreduce(local_avg, op=MPI.SUM) - print("sum after allreduce:", local_avg, self._angle_comm.rank) - - y[0, s2:e2 + 1] = local_avg / self._angle_comm.size + local_sum = self._angle_comm.allreduce(local_sum, op=MPI.SUM) + #compute average of all points with s = 0 + y[0, s2:e2 + 1] = local_sum / n2 y[1:e1 + 1, s2:e2 + 1] = x[1:e1 + 1, s2:e2 + 1] else: y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] From dbdf3a16a49679f25bcbdba13917f5b7eacb2313 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 21 Oct 2025 21:59:51 +0200 Subject: [PATCH 014/133] update plot_2d_decomposition --- psydac/feec/polar/examples/poisson_2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 28fdc8b32..f19d3889f 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -724,7 +724,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # =============================== VISUALIZATION ===============================# N = 10 - V.plot_2d_decomposition(model.mapping.get_callable_mapping(), refine=N) + V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) # plot only with the root process distribute_viz = False From 8ce233f8583bfd512812764dfdb8331df82adc16 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 24 Oct 2025 11:05:46 +0200 Subject: [PATCH 015/133] Changes in disk domain - Remove polar_mapping and use_logical_sol options - Change the analytical solution - Avoid Laplacian() for right hand side - Add disk radius as parse argument --- psydac/feec/polar/examples/poisson_2d.py | 60 ++++++++++-------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index f19d3889f..c6705a41c 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -101,7 +101,7 @@ def __init__(self, domain, mapping, phi, rho): # ... @staticmethod - def disk_domain(R=2.0, polar_mapping=False, shift_D=0.0, use_logical_sol=False): + def disk_domain(R, shift_D): r""" Solve Poisson's equation on a disk of radius R centered at (x,y) = (0, 0), with logical coordinates (s, theta): @@ -113,41 +113,22 @@ def disk_domain(R=2.0, polar_mapping=False, shift_D=0.0, use_logical_sol=False): $\phi(x,y) = sin(3.5 \pi (R^2 - x^2 - y^2)/R^2)$. """ domain = ((0, R), (0, 2 * np.pi)) + mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) - if polar_mapping: - mapping = PolarMapping('PM', c1=0, c2=0, rmin=0, rmax=1) - else: - # domain = ((0, 1), (0, 2 * np.pi)) - mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) - - lapl = Laplacian(mapping) - # print('vars(mapping): ', vars(mapping)) - x, y = mapping.expressions - # exit() - s, t = mapping.logical_coordinates - - def get_radius_angle(self, x, y): - # from np import sqrt, arctan2 #, sin, cos - r = np.sqrt(x * x + y * y) - alpha = np.arctan2(y, x) - return r, alpha - - if use_logical_sol: - # Manufactured solutions in logical coordinates - # phi = R**2 - s**2 - phi = sin(3.5 * pi * (R ** 2 - s ** 2) / R ** 2) - rho = - lapl(phi) - - else: - # physical field (cf use of physical ref solution in Maxwell case) - phi = sin(3.5 * pi * (R ** 2 - x ** 2 - y ** 2) / R ** 2) - # phi = R**2 - x**2 - y**2 - rho = - lapl(phi) - - # def Ex_ex(self, t, x, y): - # from np import cos, sin + # physical field (cf use of physical ref solution in Maxwell case) + params = dict(c1=0, c2=0, k=0, D=shift_D) + k = params['k'] + D = params['D'] + kx = 2 * pi / (R * (1 - k + D)) + ky = 2 * pi / (R * (1 + k)) + x, y = sympy.symbols('x, y') + phi = (1 - ((x * x + y * y) / (R * R)) ** 4) * sin(kx * x) * cos(ky * y) + # phi = (1 - ((X * X + Y * Y) / (R * R)) ** 4) + rho = - phi.diff(x, x) - phi.diff(y, y) + obj = Poisson2D(domain, mapping, phi, rho) + obj.coordinates = (x, y) - return Poisson2D(domain, mapping, phi, rho) + return obj # ... @staticmethod @@ -342,7 +323,7 @@ def dtype(self): ############################################################################### def run_poisson_2d(*, test_case, ncells, degree, - shift_D, use_spline_mapping, smooth_method, + shift_D, R, use_spline_mapping, smooth_method, cgtol, cgiter, alphaCONGA, study='poisson', verbose=False): timing = {} timing['assembly'] = 0.0 @@ -355,7 +336,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # Method of manufactured solution if test_case == 'disk': - model = Poisson2D.disk_domain(R=1., polar_mapping=False, shift_D=shift_D) + model = Poisson2D.disk_domain(R=R, shift_D=shift_D) elif test_case == 'target': model = Poisson2D.target_domain() elif test_case == 'czarny': @@ -907,6 +888,13 @@ def parse_input_arguments(): help='Shafranov shift for parametrization of Disk' ) + parser.add_argument('-R', + type=float, + default=1.0, + dest='R', + help='Radius of the disk' + ) + parser.add_argument('-d', type=int, nargs=2, From c4eab7c082cac155660c3958c9ae14c0da6031aa Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 24 Oct 2025 11:24:47 +0200 Subject: [PATCH 016/133] Callable exact solution in logical coordinates for plotting --- psydac/feec/polar/examples/poisson_2d.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index c6705a41c..1b0fc13d0 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -1,4 +1,5 @@ # coding: utf-8 +import sympy from mpi4py import MPI from time import time, sleep # import matplotlib.pyplot as plt @@ -90,14 +91,14 @@ def __init__(self, domain, mapping, phi, rho): self._phi = phi self._rho = rho - from sympy import lambdify + s, t = mapping.logical_coordinates - self._phi_callable = lambdify([s, t], phi) - self._rho_callable = lambdify([s, t], rho) + self._phi_callable = sympy.lambdify([s, t], phi) + self._rho_callable = sympy.lambdify([s, t], rho) # x, y = mapping.coordinates - # self._phi_callable = lambdify([x, y], phi) - # # self._rho_callable = lambdify([x, y], rho) + # self._phi_callable = sympy.lambdify([x, y], phi) + # # self._rho_callable = sympy.lambdify([x, y], rho) # ... @staticmethod @@ -730,9 +731,12 @@ def run_poisson_2d(*, test_case, ncells, degree, # Import solution vector into new serial field phi, = Vnew.import_fields('fields.h5', 'phi') - # Callable exact solution (used for plots) - phi_e = model.phi_callable - #phi_e = phi_callog + # Callable exact solution in logical coordinates + X, Y = model.coordinates + x, y = model.mapping.expressions + expr_phi_e = model.phi.subs({X: x, Y: y}) + x1, x2 = model.mapping.logical_coordinates + phi_e = sympy.lambdify([x1, x2], expr_phi_e) # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = Vnew.local_domain From 1026b3d48b8bafd0547068ae9fe8a5b6da0c6bcb Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 24 Oct 2025 11:25:40 +0200 Subject: [PATCH 017/133] Fix right hand side --- psydac/feec/polar/examples/poisson_2d.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 1b0fc13d0..1b89e3e05 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -427,9 +427,10 @@ def run_poisson_2d(*, test_case, ncells, degree, from sympy.abc import x, y # try - f = 2 * 7 * pi * cos(7 * pi / 2 * (1 - x ** 2 - y ** 2)) + (7 * pi) ** 2 * (x ** 2 + y ** 2) * sin( - 7 * pi / 2 * (1 - x ** 2 - y ** 2)) + # f = 2 * 7 * pi * cos(7 * pi / 2 * (1 - x ** 2 - y ** 2)) + (7 * pi) ** 2 * (x ** 2 + y ** 2) * sin( + # 7 * pi / 2 * (1 - x ** 2 - y ** 2)) # f = x+y + f = model.rho rhs = LinearForm(v0, integral(domain, f * v0)) err_diff = model.phi - u0 From ad0a0dbaf9d6fb132ada42415956fcfd0cab8e08 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 24 Oct 2025 11:27:40 +0200 Subject: [PATCH 018/133] Fix smoothing methods 'None', 'polar-std' and 'polar-spec' --- psydac/feec/polar/examples/poisson_2d.py | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 1b89e3e05..374788c4c 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -569,18 +569,18 @@ def run_poisson_2d(*, test_case, ncells, degree, # Solve linear system t0 = time() if smooth_method in ('polar-std', 'polar-spec'): - # Sp_inv = inverse(Sp, 'cg', tol = cgtol, maxiter = cgiter, verbose = True) - # xp = Sp_inv.dot(bp) - # xsol = proj.convert_to_tensor_basis(xp) - # info = Sp_inv.get_info() - from psydac.linalg.utilities import array_to_psydac - import scipy - L = proj.L[:, :, p2: -p2].reshape(3, 2 * ne2) - E = np.block([[L, np.zeros((3, (ne1 + p1 - 2) * ne2))], - [np.zeros(((ne1 + p1 - 2) * ne2, 2 * ne2)), np.eye((ne1 + p1 - 2) * ne2)]]) - xparray = scipy.linalg.solve(Sp.toarray(), bp.toarray()) - xarray = E.T @ xparray - xsol = array_to_psydac(xarray, V0_h.coeff_space) + Sp_inv = inverse(Sp, 'cg', tol = cgtol, maxiter = cgiter, verbose = True) + xp = Sp_inv.dot(bp) + xsol = proj.convert_to_tensor_basis(xp) + info = Sp_inv.get_info() + # from psydac.linalg.utilities import array_to_psydac + # import scipy + # L = proj.L[:, :, p2: -p2].reshape(3, 2 * ne2) + # E = np.block([[L, np.zeros((3, (ne1 + p1 - 2) * ne2))], + # [np.zeros(((ne1 + p1 - 2) * ne2, 2 * ne2)), np.eye((ne1 + p1 - 2) * ne2)]]) + # xparray = scipy.linalg.solve(Sp.toarray(), bp.toarray()) + # xarray = E.T @ xparray + # xsol = array_to_psydac(xarray, V0_h.coeff_space) elif smooth_method == 'C1conga': Sc_inv = inverse(Sc, 'cg', tol=cgtol, maxiter=cgiter, verbose=verbose) xsol = Sc_inv.dot(bc) @@ -590,7 +590,8 @@ def run_poisson_2d(*, test_case, ncells, degree, xsol = Sc_inv.dot(bc) info = Sc_inv.get_info() elif smooth_method == 'None': - S_inv = inverse(S, 'pcg', pc='jacobi', tol=cgtol, maxiter=cgiter, verbose=verbose) + pc = S.diagonal(inverse=True) + S_inv = inverse(S, 'pcg', pc=pc, tol=cgtol, maxiter=cgiter, verbose=verbose) xsol = S_inv.dot(b) info = S_inv.get_info() t1 = time() From fb2213b014d57f31945f8bbd234137861e181ff9 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 24 Oct 2025 11:28:39 +0200 Subject: [PATCH 019/133] Print info --- psydac/feec/polar/examples/poisson_2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 374788c4c..a574fa2b9 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -689,7 +689,7 @@ def run_poisson_2d(*, test_case, ncells, degree, print('> Grid :: [{ne1},{ne2}]'.format(ne1=ne1, ne2=ne2)) print('> Degree :: [{p1},{p2}]'.format(p1=p1, p2=p2)) print('> Penalization alpha :: {alpha} '.format(alpha=alpha)) - # print( '> CG info :: ',info ) + print( '> CG info :: ',info ) print('> L2 norm solution :: {:.2e}'.format(ref_u0L2)) print('> H1 norm solution :: {:.2e}'.format(ref_u0H1)) print('> L2 error (relative) :: {:.2e}'.format(rel_err_l2)) From 529897bc3e8abe2582c763ac9ac63d622112a0cf Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 28 Oct 2025 12:16:21 +0100 Subject: [PATCH 020/133] remove trick --- psydac/feec/polar/examples/poisson_2d.py | 58 ++++-------------------- 1 file changed, 8 insertions(+), 50 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index a574fa2b9..5fd1d7803 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -38,9 +38,6 @@ # backend = PSYDAC_BACKENDS['numba'] backend = PSYDAC_BACKENDS['python'] -# Set to true in order to compute discrete norms on logical domain -trick = False - # ============================================================================== class Laplacian: @@ -435,26 +432,12 @@ def run_poisson_2d(*, test_case, ncells, degree, err_diff = model.phi - u0 - # trick should be removed in the final version. The norms should be computed - # on the physical domain. # NOTE (mcp, dec 2024): norms and errors now computed only for discrete solutions (using M and S matrices) - if trick: - errL2 = Functional(err_diff ** 2 * mapping.jacobian.det(), - logical_domain, evaluate=True) - errH1 = Functional(dot(mapping.jacobian.T ** (-1) * grad(err_diff), - mapping.jacobian.T ** (-1) * grad(err_diff)) * - mapping.jacobian.det(), logical_domain, evaluate=True) - u0L2norm = Functional(u0 ** 2 * mapping.jacobian.det(), - logical_domain, evaluate=True) - u0H1norm = Functional(dot(mapping.jacobian.T ** (-1) * grad(u0), - mapping.jacobian.T ** (-1) * grad(u0)) * - mapping.jacobian.det(), logical_domain, evaluate=True) - else: - errL2 = Norm(err_diff, domain, kind='L2') - errH1 = Norm(err_diff, domain, kind='H1') + errL2 = Norm(err_diff, domain, kind='L2') + errH1 = Norm(err_diff, domain, kind='H1') - u0L2norm = Norm(u0, domain, kind='L2') - u0H1norm = Norm(u0, domain, kind='H1') + u0L2norm = Norm(u0, domain, kind='L2') + u0H1norm = Norm(u0, domain, kind='H1') # L2norm = Norm(err_diff*sqrt(mapping.jacobian.det()), logical_domain, kind = 'L2') # H1norm = Norm(mapping.jacobian.T**(-1) * grad(err_diff) @@ -474,26 +457,12 @@ def run_poisson_2d(*, test_case, ncells, degree, aS_h = discretize(aS, domain_h, (V0_h, V0_h), backend=backend) rhs_h = discretize(rhs, domain_h, V0_h, backend=backend) - # trick should be removed in the final version. The norms should be computed - # on the physical domain. - if trick: - log_domain_h = discretize(logical_domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) - log_V0_h = discretize(V0, log_domain_h, degree=degree) - # log_domain_h.mappings['Omega'] = domain_h.mappings['mapping_0(Omega)'] # If spline mapping - errL2_h = discretize(errL2, log_domain_h, log_V0_h, backend=backend) - errH1_h = discretize(errH1, log_domain_h, log_V0_h, backend=backend) + errL2_h = discretize(errL2, domain_h, V0_h, backend=backend) + errH1_h = discretize(errH1, domain_h, V0_h, backend=backend) - u0L2norm_h = discretize(u0L2norm, log_domain_h, log_V0_h, backend=backend) - u0H1norm_h = discretize(u0H1norm, log_domain_h, log_V0_h, backend=backend) - - else: - - errL2_h = discretize(errL2, domain_h, V0_h, backend=backend) - errH1_h = discretize(errH1, domain_h, V0_h, backend=backend) - - u0L2norm_h = discretize(u0L2norm, domain_h, V0_h, backend=backend) - u0H1norm_h = discretize(u0H1norm, domain_h, V0_h, backend=backend) + u0L2norm_h = discretize(u0L2norm, domain_h, V0_h, backend=backend) + u0H1norm_h = discretize(u0H1norm, domain_h, V0_h, backend=backend) M = aM_h.assemble() S = aS_h.assemble() @@ -644,14 +613,6 @@ def run_poisson_2d(*, test_case, ncells, degree, # err2 = L2norm_h.assemble(u0 = phi_ref - phi) # err2 = L2norm_h.assemble(u0 = phi) - # when trick is used the norm is a functional, that is, an integral. We have - # to take the square root explicitely to have the norm. - # if trick: - # print('WARNING -- do not use the trick !!!!') - # raise NotImplementedError - # err2 = np.sqrt(err2) - # u0L2 = np.sqrt(u0L2) - # assert np.allclose(err2, err2_matrix, rtol = 1e-7, atol = 1e-7) # and H1 error @@ -659,9 +620,6 @@ def run_poisson_2d(*, test_case, ncells, degree, # errh1_semi = H1norm_h.assemble(u0 = phi_ref - phi) # errh1_semi = errH1_h.assemble(u0 = phi) # BUG ?? # u0H1_semi = u0H1norm_h.assemble(u0 = phi) - # if trick: - # errh1_semi = np.sqrt(errh1_semi) - # u0H1_semi = np.sqrt(u0H1_semi) # errh1 = np.sqrt(errh1_semi**2 + err2**2) # u0H1 = np.sqrt(u0H1_semi**2 + u0L2**2) # errh1 = np.sqrt(errh1_semi**2 + err2**2) From 6feacffdeb8120e4ca2f3b2a11e1010af2e3f079 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 4 Nov 2025 16:22:09 +0100 Subject: [PATCH 021/133] fixed issue with polar-spec and polar-std (cause unknown) --- psydac/feec/polar/examples/poisson_2d.py | 27 ++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 5fd1d7803..ba89f35e5 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -472,7 +472,21 @@ def run_poisson_2d(*, test_case, ncells, degree, b.update_ghost_regions() M.update_ghost_regions() - # ========================= HANDLIG THE SINGULARITY ===========================# + # =================== PROJECT THE EXACT SOLUTION =========================# + + from psydac.feec.pull_push import pull_2d_h1 + from sympy import lambdify + from psydac.feec.global_geometric_projectors import GlobalGeometricProjectorH1 + + Pi0 = GlobalGeometricProjectorH1(V0_h) + phi_symref = model.phi + phi_calref = lambdify(domain.coordinates, phi_symref) + phi_callog = pull_2d_h1(phi_calref, F) + phi_ref = Pi0(phi_callog) + phi_ref.coeffs.update_ghost_regions() + + + # ========================= HANDLING THE SINGULARITY ===========================# # If required by user, create C1 projector and then restrict # stiffness/mass matrices and right-hand-side vector to C1 space @@ -538,7 +552,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # Solve linear system t0 = time() if smooth_method in ('polar-std', 'polar-spec'): - Sp_inv = inverse(Sp, 'cg', tol = cgtol, maxiter = cgiter, verbose = True) + Sp_inv = inverse(Sp, 'cg', tol = cgtol, maxiter = cgiter, verbose=verbose) xp = Sp_inv.dot(bp) xsol = proj.convert_to_tensor_basis(xp) info = Sp_inv.get_info() @@ -573,21 +587,12 @@ def run_poisson_2d(*, test_case, ncells, degree, phi.coeffs.update_ghost_regions() # ref solution: projected exact solution - from psydac.feec.global_geometric_projectors import GlobalGeometricProjectorH1 - from psydac.feec.pull_push import pull_2d_h1 - from sympy import lambdify - Pi0 = GlobalGeometricProjectorH1(V0_h) # phi_ref = Pi0(model.phi_callable) # def P0_phys(f_phys, P0, domain, mappings_list): # phi = lambdify(domain.coordinates, f_phys) # P0(f_log) - phi_symref = sin(7 * pi / 2 * (1 - x ** 2 - y ** 2)) - phi_calref = lambdify(domain.coordinates, phi_symref) - phi_callog = pull_2d_h1(phi_calref, F) - phi_ref = Pi0(phi_callog) - phi_ref.coeffs.update_ghost_regions() # L2 and H1 norms ref_u0L2_2 = phi_ref.coeffs.inner(M.dot(phi_ref.coeffs)) # l2 norm of ref solution From 6e74dd03bc62b3bd3a176f8122c8d221ce5f0bb8 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 5 Nov 2025 08:29:48 +0100 Subject: [PATCH 022/133] fix crash with analytical mapping --- psydac/feec/polar/examples/poisson_2d.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index ba89f35e5..b9c7128d4 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -427,7 +427,13 @@ def run_poisson_2d(*, test_case, ncells, degree, # f = 2 * 7 * pi * cos(7 * pi / 2 * (1 - x ** 2 - y ** 2)) + (7 * pi) ** 2 * (x ** 2 + y ** 2) * sin( # 7 * pi / 2 * (1 - x ** 2 - y ** 2)) # f = x+y - f = model.rho + + # f = model.rho + X, Y = model.coordinates + x, y = model.mapping.expressions + expr_phi_e = model.phi.subs({X: x, Y: y}) + f = model.rho.subs({X: x, Y: y}) + rhs = LinearForm(v0, integral(domain, f * v0)) err_diff = model.phi - u0 From 3682565edd02a0fde47cbf9348c3d6ad2db44cf0 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 5 Nov 2025 13:34:47 +0100 Subject: [PATCH 023/133] it now works with analytical mapping --- psydac/feec/polar/examples/poisson_2d.py | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index b9c7128d4..ae59f025a 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -363,21 +363,20 @@ def run_poisson_2d(*, test_case, ncells, degree, # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - if use_spline_mapping: - # Create uniform grid - grid_1 = np.linspace(*model.domain[0], num=ne1 + 1) - grid_2 = np.linspace(*model.domain[1], num=ne2 + 1) + # Create uniform grid + grid_1 = np.linspace(*model.domain[0], num=ne1 + 1) + grid_2 = np.linspace(*model.domain[1], num=ne2 + 1) - # Create 1D finite element spaces - V1 = SplineSpace(p1, grid=grid_1, periodic=False) - V2 = SplineSpace(p2, grid=grid_2, periodic=True) + # Create 1D finite element spaces + V1 = SplineSpace(p1, grid=grid_1, periodic=False) + V2 = SplineSpace(p2, grid=grid_2, periodic=True) - # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, [False, True] , comm = mpi_comm) - V = TensorFemSpace(domain_decomposition, V1, V2) + # Create 2D tensor product finite element space + domain_decomposition = DomainDecomposition(ncells, [False, True] , comm = mpi_comm) + V = TensorFemSpace(domain_decomposition, V1, V2) - s1, s2 = V.coeff_space.starts - e1, e2 = V.coeff_space.ends + s1, s2 = V.coeff_space.starts + e1, e2 = V.coeff_space.ends # ==================== MAPPING & PHYSICAL DOMAIN ==============================# logical_domain = Square('Omega', bounds1=model.domain[0], @@ -722,7 +721,8 @@ def run_poisson_2d(*, test_case, ncells, degree, print('ex[0,0] = ', ex[0, 0]) # Compute physical coordinates of logical grid - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + map_temp = map_discrete if use_spline_mapping else F + pcoords = np.array([[map_temp(e1, e2) for e2 in eta2] for e1 in eta1]) xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] From 0e7f56e34f2eff47e60391a25d1425fc9dd6556c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 7 Nov 2025 09:08:42 +0100 Subject: [PATCH 024/133] typo --- psydac/feec/polar/examples/poisson_2d.py | 1 - 1 file changed, 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index ae59f025a..6f72bc809 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -430,7 +430,6 @@ def run_poisson_2d(*, test_case, ncells, degree, # f = model.rho X, Y = model.coordinates x, y = model.mapping.expressions - expr_phi_e = model.phi.subs({X: x, Y: y}) f = model.rho.subs({X: x, Y: y}) rhs = LinearForm(v0, integral(domain, f * v0)) From 04e9c4b1c8a80de1c5eafe7d128fa46685be0f39 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 10 Nov 2025 10:43:31 +0100 Subject: [PATCH 025/133] initial commit for maxwell --- psydac/feec/polar/conga_projections.py | 505 ++++++- psydac/feec/polar/examples/analyticalTE.py | 300 ++++ psydac/feec/polar/examples/maxwell_2d.py | 1289 ++++++++++++++++++ psydac/feec/polar/examples/utils_congapol.py | 117 ++ psydac/feec/polar/examples/waveTE.py | 199 +++ 5 files changed, 2409 insertions(+), 1 deletion(-) create mode 100644 psydac/feec/polar/examples/analyticalTE.py create mode 100644 psydac/feec/polar/examples/maxwell_2d.py create mode 100644 psydac/feec/polar/examples/utils_congapol.py create mode 100644 psydac/feec/polar/examples/waveTE.py diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index c4d576da9..127c30004 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -1,10 +1,15 @@ import numpy as np -from scipy.sparse import coo_matrix +from scipy.linalg import toeplitz +from scipy.sparse import coo_matrix, lil_matrix, spmatrix, eye as sp_eye +from scipy.sparse.linalg import inv as sp_inv +from psydac.fem.vector import VectorFemSpace from psydac.linalg.basic import LinearOperator, Vector +from psydac.linalg.block import BlockLinearOperator from psydac.linalg.stencil import StencilVector from psydac.fem.tensor import TensorFemSpace +from psydac.linalg.utilities import array_to_psydac class C0PolarProjection_V0(LinearOperator): @@ -121,3 +126,501 @@ def tosparse(self): def toarray(self): return self.tosparse().toarray() + +# --------- 1-FORMS CONGA PROJECTOR P1 ----------# +# It is a BlockLinearOperator with 4 blocks Upper-Left (0, 0), Upper-Right (0, 1) +# Lower-Left (1, 0) and Lower-Right (1, 1). (0, 1) and (1, 0) are identical. +# +# ______________________ +# | | | +# | (0,0) | (1,0) | +# |___________|__________| +# | | | +# | (1,0) | (1,1) | +# |___________|__________| + +class C0PolarProjection_V1_00(LinearOperator): + """ + Upper Left block of P1. + + Parameters: + ---------- + + W1 : VectorFemSpace + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + """ + + def __init__(self, W1): + # assert isinstance(W1, ProductFemSpace) + assert isinstance(W1, VectorFemSpace) + + self.W1 = W1 + + @property + def domain(self): + return self.W1.coeff_space[0] + + @property + def codomain(self): + return self.W1.coeff_space[0] + + @property + def dtype(self): + return float + + # The upper-left block of P1 is an identity block + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + [s1, s2] = self.W1.coeff_space[0].starts + [e1, e2] = self.W1.coeff_space[0].ends + + if out is None: + y = self.W1.coeff_space[0].zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.W1.coeff_space[0] + y = out + + y[:, s2:e2 + 1] = x[:, s2:e2 + 1] + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return self + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n01, n02] = self.domain.npts + return sp_eye(n01 * n02) + + def toarray(self): + return self.tosparse().toarray() + + + +class C0PolarProjection_V1_10(LinearOperator): + """ + Lower left block of P1. + + Parameters: + ----------- + + W1 : VectorFemSpace (former ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + """ + + def __init__(self, W1, transposed=False): + # assert isinstance(W1, ProductFemSpace) + assert isinstance(W1, VectorFemSpace) + + self.W1 = W1 + self.transposed = transposed + + @property + def domain(self): + idx = 1 if self.transposed else 0 + return self.W1.coeff_space[idx] + + @property + def codomain(self): + idx = 0 if self.transposed else 1 + return self.W1.coeff_space[idx] + + @property + def dtype(self): + return float + + # Warning: this dot method has to be revised for mpi! + # the toeplitz multiplication requires all processes along the theta dir. to communicate. + + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + # The number of radial basis functions is one less than the number on the angular basis functions along dir x1 + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends + [n1, n2] = self.domain.npts + + if out is None: + y = self.codomain.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.codomain + y = out + + if self.transposed: + y[0, s2:e2 + 1] = np.subtract(np.roll(x[1, s2:e2 + 1], 1), x[1, s2:e2 + 1]) + y[1:, s2:e2 + 1] = 0 + else: + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = np.subtract(np.roll(x[0, s2:e2 + 1], -1), x[0, s2:e2 + 1]) + y[2:, s2:e2 + 1] = 0 + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return C0PolarProjection_V1_10(self.W1, transposed=not self.transposed) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + if self.transposed: + domain_P1_10 = self.codomain + codomain_P1_10 = self.domain + else: + domain_P1_10 = self.domain + codomain_P1_10 = self.codomain + + [n01, n02] = domain_P1_10.npts + [n11, n12] = codomain_P1_10.npts + + dtype = domain_P1_10.dtype + + r = np.zeros(n02) + r[:2] = [-1, 1] + c = np.zeros(n02) + c[::n02 - 1] = [-1, 1] + + d_block = toeplitz(c, r) + P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) + P[n12:2 * n12, :n02] = d_block + + # print(f'in C0CongaProjector1_10.tosparse : P.T.shape = {P.T.shape}, P.shape = {P.shape}, self.transposed = {self.transposed}') + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +class C0PolarProjection_V1_11(LinearOperator): + """ + Lower right block of P1. + + Parameters: + ----------- + + W1 : VectorFemSpace (former ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + + hbc : Boolean + Switch on and off the imposition of homogeneous Dirichlet boundary + conditions on the tangential (angular) direction (default is False) + """ + + def __init__(self, W1, transposed=False, hbc=False): + # assert isinstance(W1, ProductFemSpace) + assert isinstance(W1, VectorFemSpace) + + self.W1 = W1 + self.transposed = transposed + self.hbc = hbc + + @property + def domain(self): + return self.W1.coeff_space[1] + + @property + def codomain(self): + return self.W1.coeff_space[1] + + @property + def dtype(self): + return float + + # It is diagonal block with a zero block of size (2n2)x(2n2) and an identity block + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + [s1, s2] = self.codomain.starts + [e1, e2] = self.codomain.ends + [n1, n2] = self.codomain.npts + + if out is None: + y = self.W1.coeff_space[1].zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.codomain + y = out + + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = 0 + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] # Identity block + + if self.hbc: + if e1 == n1 - 1: + y[e1, :] = 0. + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return C0PolarProjection_V1_11(self.W1, transposed=not self.transposed, hbc=self.hbc) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n01, n02] = self.domain.npts + [n11, n12] = self.codomain.npts + dtype = self.domain.dtype + + P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) + P[2 * n12:, 2 * n02:] = sp_eye((n11 - 2) * n12) + if self.hbc: + P[-n12:, -n02:] = 0 + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +class C0PolarProjection_V1(BlockLinearOperator): + """ + CONGA Projector P1 from the full spline space S^{p1-1, p2} x S^{p1, p2-1} + on logical domain to V1 the pre-polar 1-forms splines. The associate matrix + is square as in the CONGA approach we keep using the tensor B-spline basis, + instead of the polar basis of Toshniwal. P1 enforces coefficient relations + to be in V1. + + Parameters: + ----------- + + W1 : VectorFemSpace (former ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + + hbc : Boolean + Switch on and off the imposition of homogeneous Dirichlet boundary + conditions on the tangential (angular) direction (default is False) + """ + + def __init__(self, W1, transposed=False, hbc=False): + assert isinstance(W1, VectorFemSpace) + assert W1.symbolic_space.kind.name == 'hcurl' + + T1 = W1.coeff_space + + super().__init__(T1, T1) + + self[0, 0] = C0PolarProjection_V1_00(W1) + # self[0, 1] = C0CongaProjector1_01(W1, transposed = transposed) + self[1, 0] = C0PolarProjection_V1_10(W1, transposed=transposed) + self[1, 1] = C0PolarProjection_V1_11(W1, transposed=transposed, hbc=hbc) + + self.W1 = W1 + self.transposed = transposed + self.hbc = hbc + + + @property + def T(self): + return self.transpose() + + +# ---------------- 2-FORMS CONGA PROJECTOR P2 ----------------# +class C0PolarProjection_V2(LinearOperator): + """ + CONGA Projector P2 from the full spline space S^{p1-1, p2-1} on logical + domain to V2, the pre-polar 2-forms splines. The associate matrix + is square, as in the CONGA approach we keep using the tensor B-spline basis, + instead of the polar basis of Toshniwal. P2 enforces coefficient relations + to be in V2. + + Parameters: + ----------- + + W2 : TensorFemSpace + Full tensor product spline space of the 2-forms S^{p1-1, p2-1} + + transposed : Boolean + Switch between P2 and P2 transposed (default is False) + """ + + def __init__(self, W2, transposed=False): + assert isinstance(W2, TensorFemSpace) + + self.W2 = W2 + self.transposed = transposed + + @property + def domain(self): + return self.W2.coeff_space + + @property + def codomain(self): + return self.W2.coeff_space + + @property + def dtype(self): + return float + + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + + [s1, s2] = self.W2.coeff_space.starts + [e1, e2] = self.W2.coeff_space.ends + [n1, n2] = self.W2.coeff_space.npts + + if out is None: + y = self.W2.coeff_space.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.W2.coeff_space + y = out + + if self.transposed: + + y[0, s2:e2 + 1] = x[1, s2:e2 + 1] + y[1, s2:e2 + 1] = x[1, s2:e2 + 1] + + else: + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] + + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return C0PolarProjection_V2(self.W2, transposed=not self.transposed) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n1, n2] = self.W2.coeff_space.npts + + data = np.ones(n1 * n2) + + cols = np.arange(n1 * n2) + rows = np.tile(np.arange(n2, 2 * n2), 2) + rows = np.concatenate((rows, np.arange(2 * n2, n1 * n2))) + + P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) + P.eliminate_zeros() + + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +# ------------ strong and weak curl ----------------- + +from scipy.sparse.linalg import spilu, cg +from scipy.sparse.linalg import LinearOperator as SparseLinearOperator + + +class SparseCurlAsOperator(LinearOperator): + + def __init__(self, W1, W2, strong_curl_sp, M1=None, M2=None, strong=False, store_M1inv=False): + assert isinstance(W1, VectorFemSpace) + assert isinstance(W2, TensorFemSpace) + assert isinstance(strong_curl_sp, spmatrix) + + self.W1 = W1 + self.W2 = W2 + + self.strong = strong + self._store_M1inv = store_M1inv + + if strong: + self.curl_sp = strong_curl_sp + + else: + self.curl_sp = strong_curl_sp.T # dual curl (in the dual bases) + assert isinstance(M1, LinearOperator) + assert isinstance(M2, LinearOperator) + self.M1_sp = M1.tosparse() + self.M2_sp = M2.tosparse() + if store_M1inv: + self._M1inv_sp = sp_inv(self.M1_sp) + else: + self._M1_spilu = spilu(self.M1_sp) + self._precond_M = SparseLinearOperator(self.M1_sp.shape, self._M1_spilu.solve) + + @property + def domain(self): + if self.strong: + return self.W1.coeff_space + else: + return self.W2.coeff_space + + @property + def codomain(self): + if self.strong: + return self.W2.coeff_space + else: + return self.W1.coeff_space + + @property + def dtype(self): + return float + + def toarray(self): + # return self + raise NotImplementedError('toarray() is not defined for this class.') + + def tosparse(self): + # return self + raise NotImplementedError('tosparse() is not defined for this class.') + + def transpose(self, conjugate=False): + raise NotImplementedError('transpose() is not defined for this class.') + + # Warning: this dot method has to be revised for mpi! + # the toeplitz multiplication requires all processes along the theta-dir to communicate. + def dot(self, x, out=None): + assert isinstance(x, Vector) + if self.strong: + Cx_arr = self.curl_sp @ x.toarray() + else: + tx_arr = self.M2_sp @ x.toarray() + tCx_arr = self.curl_sp @ tx_arr + if self._store_M1inv: + Cx_arr = self._M1inv_sp.dot(tCx_arr) + else: + # Cx_arr = spsolve(self.M1_sp, tCx_arr) + Cx_arr, exit_code = cg(self.M1_sp, tCx_arr, M=self._precond_M, rtol=1e-7) + # Cx_arr = self._M1_spilu.solve(tCx_arr) + if out is None: + y = self.codomain.zeros() + else: + assert isinstance(out, Vector) + assert out.space is self.codomain + y = out + + y1 = array_to_psydac(Cx_arr, self.codomain) + y1.copy(out=y) + return y + + diff --git a/psydac/feec/polar/examples/analyticalTE.py b/psydac/feec/polar/examples/analyticalTE.py new file mode 100644 index 000000000..72ec3d816 --- /dev/null +++ b/psydac/feec/polar/examples/analyticalTE.py @@ -0,0 +1,300 @@ +from sympde.topology import Square +from sympde.topology.analytical_mapping import PolarMapping +from psydac.feec.pull_push import push_2d_hcurl, push_2d_l2 +from numpy import pi +import numpy as np +import matplotlib.pyplot as plt + + +def add_colorbar(im, ax, **kwargs): + from mpl_toolkits.axes_grid1 import make_axes_locatable + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.3) + cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) + return cbar + + +class CircularCavitySolution: + """ + Time-harmonic solution of Maxwell's equations in a disk-like domain with + perfectly conducting walls. This is a "transverse electric" solution, with + E = (Ex, Ey) and B = Bz. Domain is [0, R] x [0, 2pi]. + + Parameters + ---------- + R : float + domain radius + + c: float + Speed of light in arbitrary units + + (m, n): int + Mode number. Warning: m > 0, n >= 0 + + D: float + shift of logical center (in "Target" mapping with c0=D*R2, c1=0, k=0, D=D) + + scale: float + Rescaling the values by a real factor. Default = 1 + + """ + + def __init__(self, R, c, m, n, D=0, scale=1, variables='log'): + from numpy import pi + from scipy.special import jnp_zeros + pnm = jnp_zeros(n, m)[-1] + kc = pnm / R + omega = c * kc + + phase = pi / 4 + + self.c = c + self.scale = scale + self.n = n + self.kc = kc + self.omega = omega + self.phase = phase + self._R = R + assert 0 <= D < .5 + self._D = D + # assert variables in ['log', 'phys'] + # self._logical = (variables == 'log') + + # Exact solutions for electric and magnetic field with polar parametrization of disk domain + def Es_ex(self, t, s, theta): + from numpy import sin # , cos, sqrt, arctan2 + from scipy.special import jv + + scale = self.scale + n = self.n + kc = self.kc + omega = self.omega + phase = self.phase + c = self.c + + return - scale * c * n / (s * kc + 1e-10) * sin(n * theta) * jv(n, kc * s) * sin(omega * t + phase) + + def Et_ex(self, t, s, theta, s_factor=True): + """ + if s_factor: multiply by s (as in logical field) + """ + from numpy import sin, cos + from scipy.special import jvp + + scale = self.scale + n = self.n + kc = self.kc + omega = self.omega + phase = self.phase + c = self.c + + val = - scale * c * cos(n * theta) * jvp(n, kc * s) * sin(omega * t + phase) + if s_factor: + val *= s + return val + + def B_ex(self, t, s, theta, s_factor=True): + """ + if s_factor: multiply by s (as in logical field) + """ + from numpy import cos + from scipy.special import jv + + scale = self.scale + n = self.n + kc = self.kc + omega = self.omega + phase = self.phase + + val = scale * cos(n * theta) * jv(n, kc * s) * cos(omega * t + phase) + if s_factor: + val *= s + return val + # The magnitude of B is approximately equal to scale / 3 + + def Bt_ex(self, t, s, theta): + ''' + = dB/dt + todo: change the name + ''' + from numpy import cos, sin + from scipy.special import jv + + scale = self.scale + n = self.n + kc = self.kc + omega = self.omega + phase = self.phase + + return scale * omega * s * cos(n * theta) * jv(n, kc * s) * sin(omega * t + phase) + + # physical field + + def get_radius_angle(self, x, y): + from numpy import sqrt, arctan2 # , sin, cos + r = sqrt(x * x + y * y) + alpha = arctan2(y, x) + # print(f'r*np.cos(alpha) - x = {r*np.cos(alpha) - x}') + # print(f'r*np.sin(alpha) - y = {r*np.sin(alpha) - y}') + return r, alpha + + def Ex_ex(self, t, x, y): + from numpy import cos, sin + + r, alpha = self.get_radius_angle(x, y) + return cos(alpha) * self.Es_ex(t, r, alpha) - sin(alpha) * self.Et_ex(t, r, alpha, s_factor=False) + + def Ey_ex(self, t, x, y): + from numpy import cos, sin + + r, alpha = self.get_radius_angle(x, y) + return sin(alpha) * self.Es_ex(t, r, alpha) + cos(alpha) * self.Et_ex(t, r, alpha, s_factor=False) + + def Bz_ex(self, t, x, y): + + r, alpha = self.get_radius_angle(x, y) + return self.B_ex(t, r, alpha, s_factor=False) + + +def main(): + # TODO: update with D_shift + + # Physical domain is rectangle [0, R] x [0, 2pi] + R = 2.0 + + # Speed of light equal c and scaling of the fields by a scale factor + c = 1 + scale = 1 + + # Mode number + # (m, n) = (1, 0) + # (m, n) = (2, 1) + (m, n) = (2, 3) + + # Exact solution + exact_solution = CircularCavitySolution(R=R, c=c, m=m, n=n, scale=scale) + + # Exact fields, as callable functions of (t, s, theta) + Es_ex = exact_solution.Es_ex + Et_ex = exact_solution.Et_ex + B_ex = exact_solution.B_ex + Bt_ex = exact_solution.Bt_ex + + # Logical domain: [0, R] x [0, 2pi] + logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) + + # Physical domain: disk of radius R obtained as image of the logical_domain + # with the analytical mapping of a circle + mapping = PolarMapping('F', c1=0, c2=0, rmin=0, rmax=1) + domain = mapping(logical_domain) + F = mapping.get_callable_mapping() + + # Set time + t = 0 + + Es = lambda x, y: Es_ex(t, x, y) + Et = lambda x, y: Et_ex(t, x, y) + B = lambda x, y: B_ex(t, x, y) + Bt = lambda x, y: Bt_ex(t, x, y) + + # Plot of fields + N = 100 + + rho = np.linspace(1e-20, R, N) + theta = np.linspace(0, 2 * pi, N) + rho, theta = np.meshgrid(rho, theta, indexing='ij') + x, y = F(rho, theta) + + Ex_values = np.empty_like(rho) + Ey_values = np.empty_like(rho) + B_values = np.empty_like(rho) + + valerr = 0 + for i, x1i in enumerate(rho[:, 0]): + for j, x2j in enumerate(theta[0, :]): + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Es, Et, x1i, x2j, F) + + B_values[i, j] = push_2d_l2(B, x1i, x2j, F) + + fig, axs = plt.subplots(2, 2, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_values, 50) + im2 = axs[1, 0].contourf(x, y, np.sqrt(Ex_values ** 2 + Ey_values ** 2), 50) + im3 = axs[1, 1].contourf(x, y, B_values, 50) + axs[0, 0].set_title(r'$E_x$') + axs[0, 1].set_title(r'$E_y$') + axs[1, 0].set_title(r'$||\mathbf{E}||$') + axs[1, 1].set_title('$B$') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[1, 0]) + add_colorbar(im3, axs[1, 1]) + + # Test: curl E = - d_t B_z with finite Differences + # on a tensor grid in the square inscribed the disk + N = 160 + l = R / np.sqrt(2) # edge length + + x, dx = np.linspace(-l, l, N, retstep=True) + y, dy = np.linspace(-l, l, N, retstep=True) + x, y = np.meshgrid(x, y, indexing='ij') + rho = np.sqrt(x ** 2 + y ** 2) + theta = np.arctan2(y, x) % (2 * pi) + + Ex_values = np.empty_like(rho) + Ey_values = np.empty_like(rho) + Bt_values = np.empty_like(rho) + + ni, nj = rho.shape + for i in range(ni): + for j in range(nj): + x1_ij = rho[i, j] + x2_ij = theta[i, j] + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Es, Et, x1_ij, x2_ij, F) + + Bt_values[i, j] = push_2d_l2(Bt, x1_ij, x2_ij, F) + + fig, axs = plt.subplots(2, 3, figsize=(15, 15)) + im1 = axs[0, 0].contourf(x, y, np.sqrt(Ex_values ** 2 + Ey_values ** 2)) + im2 = axs[0, 1].contourf(x, y, -Bt_values) + axs[0, 0].set_title(r'$||\mathbf{E}||$') + axs[0, 1].set_title(r'$-\partial_t B$') + for axi in axs.flat: + axi.set_aspect('equal') + add_colorbar(im1, axs[0, 0]) + add_colorbar(im2, axs[0, 1]) + + curlE_values = np.zeros_like(rho) + valerr = 0 + for i in range(1, ni - 1): + for j in range(1, nj - 1): + curlE_values[i, j] = (Ey_values[i + 1, j] - Ey_values[i - 1, j]) / (2 * dx) \ + - (Ex_values[i, j + 1] - Ex_values[i, j - 1]) / (2 * dy) + + d = np.abs(Bt_values[i, j] + curlE_values[i, j]) + + if valerr < d: + valerr = d + + skip = (slice(None, None, int(N / 20)), slice(None, None, int(N / 20))) + im3 = axs[1, 0].contourf(x, y, curlE_values) + axs[1, 0].quiver(x[skip], y[skip], Ex_values[skip], Ey_values[skip]) + add_colorbar(im3, axs[1, 0]) + axs[1, 0].set_title(r'curl $\mathbf{E}$') + im4 = axs[1, 1].contourf(x, y, -Bt_values) + axs[1, 1].quiver(x[skip], y[skip], Ex_values[skip], Ey_values[skip]) + add_colorbar(im4, axs[1, 1]) + im5 = axs[0, 2].contourf(x, y, Ex_values) + im6 = axs[1, 2].contourf(x, y, Ey_values) + add_colorbar(im5, axs[0, 2]) + add_colorbar(im6, axs[1, 2]) + print('|curlE + d_t B| <= ', valerr) + + +if __name__ == "__main__": + main() + plt.show() + + diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py new file mode 100644 index 000000000..7f1ea8451 --- /dev/null +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -0,0 +1,1289 @@ +# TODO: Test weak divergence of E = 0 and strong divergence of B = 0 +# NB: Use L2 projection for the initial condition of E to test the weak div of E + +""" +Solve the Transverse Electric Time dependent Maxwell Problem +on an analytical disk domain. +""" +import os +import numpy as np + +import matplotlib.pyplot as plt + +from utils_congapol import print_map_polar_coeffs, check_regular_ring_map + + +# from scipy.sparse.linalg import spsolve + +# def visdir_name(study): +# vdn = 'plots_{study}/' +# os.makedirs(vdn, exist_ok=True) +# return vdn + +# ====================== TIME DISCRETIZATION ==================================# + +def step_faraday_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, **kwargs): + """ + Exactly integrate the semi-discrete Faraday equation over one time-step: + + b_new = b - ∆t D1 P1 e + + """ + b -= dt * D1.dot(P1.dot(e)) + + +# e += 0 + +def step_ampere_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, *, pc=None, tol=1e-7, verbose=False): + """ + Exactly integrate the semi-discrete Amperè equation over one time-step: + + e_new = e + ∆t (M1^{-1} P1^T D1^T M2) b + + """ + options = dict(tol=tol, verbose=verbose) + if pc: + from psydac.linalg.iterative_solvers import pcg as isolve + options['pc'] = pc + else: + from psydac.linalg.iterative_solvers import cg as isolve + + # b += 0 + e += dt * isolve(M1, P1_T.dot(D1_T.dot(M2.dot(b))), **options)[0] + + +def compute_stable_dt(cfl, C_m, dC_m, V, tau=None, light_c=1): + """ + compute stable time step for a leap-frog Maxwell solver, + given the (discrete) primal and dual curl operators + + cfl: stability factor (1 to choose the maximum stable dt) + C_m: (primal) curl V -> dV + dC_m: (dual) curl dV -> V + V: FEM space + tau: (optional) time to solve with an integer number of time steps + light_c: Maxwell parameter + + """ + print(f" .. compute stable dt by estimating the operator norm of ") + print(f" .. dual_curl_h @ curl_h: V_h -> V_h ") + print(f" .. with dim(V_h) = {V.coeff_space.dimension} ... ") + + def vect_norm_2(vv): + return np.sqrt(vv.inner(vv)) + + vv = V.coeff_space.zeros() + print(f'type(V.coeff_space) = {type(V.coeff_space)}') + print(f'V.coeff_space.shape = {V.coeff_space.shape}, V.coeff_space.dimension = {V.coeff_space.dimension}') + # print(type(vv.[])) + # vv[:] = np.random.random(V.coeff_space.dimension) + vv[:] = np.random.random(size=V.coeff_space.shape) + norm_vv = vect_norm_2(vv) + max_ncfl = 500 + ncfl = 0 + spectral_rho = 1 + conv = False + CC_m = dC_m @ C_m + # print(f'type(CC_m) = {type(CC_m)}') + # print('going through CC_m.tmp_vectors...') + # for tv in CC_m.tmp_vectors: + # print(f'type(tv) = {type(tv)}') + while not (conv or ncfl > max_ncfl): + # print(f'... ') + # print(f'#1 type(vv) = {type(vv)}') + ncfl += 1 + vv *= (1. / norm_vv) + # print(f'#2 type(vv) = {type(vv)}') + CC_m.dot(vv.copy(), out=vv) + # print(f'#3 type(vv) = {type(vv)}') + norm_vv = vect_norm_2(vv) + old_spectral_rho = spectral_rho + spectral_rho = norm_vv.copy() # copy ?? + conv = abs((spectral_rho - old_spectral_rho) / spectral_rho) < 0.001 + print(f" ... spectral radius iteration: spectral_rho( dC_m @ C_m ) ~= {spectral_rho}") + norm_op = np.sqrt(spectral_rho) + c_dt_max = 2. / norm_op + dt = cfl * c_dt_max / light_c + if tau is not None: + Nt_per_tau = int(np.ceil(tau / dt)) + assert Nt_per_tau >= 1 + dt = tau / Nt_per_tau + else: + Nt_per_tau = 1 + assert light_c * dt <= cfl * c_dt_max + print(f" Time step dt computed for Maxwell solver:") + print(f" with cfl = repr({cfl} we found dt = {dt} -- that is Nt_per_tau = {Nt_per_tau} on tau = {tau}.") + 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 Nt_per_tau, dt, norm_op + + +# =============================================================================# + +# =========================== VISUALIZATION ===================================# + +def add_colorbar(im, ax, **kwargs): + from mpl_toolkits.axes_grid1 import make_axes_locatable + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.3) + cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) + return cbar + + +def plot_field_and_error(name, t, x, y, field_h, field_ex, *gridlines, only_field=True): + # import matplotlib.pyplot as plt + if only_field: + fig, ax0 = plt.subplots(1, 1, figsize=(7, 6)) + axes = [ax0] + else: + fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(15, 6)) + axes = [ax0, ax1] + print(type(x)) + print(type(y)) + im0 = ax0.contourf(x, y, field_h, 50) + ax0.set_title(r'${0}_h$'.format(name)) + if not only_field: + im1 = ax1.contourf(x, y, field_ex - field_h, 50) + ax1.set_title(r'${0} - {0}_h$'.format(name)) + for ax in axes: + # if not only_field: + ax.plot(*gridlines[0], color='k') + ax.plot(*gridlines[1], color='k') + ax.set_xlabel('x', fontsize=14) + ax.set_ylabel('y', fontsize=14, rotation='horizontal') + ax.set_aspect('equal') + add_colorbar(im0, ax0) + if not only_field: + add_colorbar(im1, ax1) + fig.suptitle('Time t = {:10.3e}'.format(t)) + fig.tight_layout() + return fig + + +def update_plot(fig, t, x, y, field_h, field_ex): + ax0, ax1, cax0, cax1 = fig.axes + # fig.cla() + # ax0.collections.clear(); cax0.clear() + # ax1.collections.clear(); cax1.clear() + im0 = ax0.contourf(x, y, field_h, 50) + im1 = ax1.contourf(x, y, field_ex - field_h, 50) + fig.colorbar(im0, cax=cax0) + fig.colorbar(im1, cax=cax1) + fig.suptitle('Time t = {:10.3e}'.format(t)) + fig.canvas.draw() + + +def plot_curve_along_s(name, s_str, time_str, theta0, + s, curve_h, curve_ref=None, + left_s=None, left_curve_h=None, left_curve_ref=None, ): + # import matplotlib.pyplot as plt + + # t = np.arange(0.0, 2.0, 0.01) + # s = 1 + np.sin(2 * np.pi * t) + + fig, ax = plt.subplots() + ax.plot(s, curve_h, label=f'{name}_h') + ax.plot(s, curve_ref, label=f'{name}_ref') + + if left_s is not None: + ax.plot(left_s, left_curve_h, label=f'{name}_h (left)') + if left_curve_ref is not None: + ax.plot(left_s, left_curve_ref, label=f'{name}_ref (left)') + + ax.set(xlabel=s_str, title=f'field {name} along {s_str} for theta={theta0}, at {time_str}') + ax.legend() + # ax.grid() + return fig + + +# =============================================================================# + +def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, + splitting_order, shift_D, use_spline_mapping, plot_time, tol, + cfl=0.9, show_figs=False, plot_final=True, + study='maxwell_bessel', use_scipy=True, verbose=False): + import numpy as np + from numpy import pi + # import matplotlib.pyplot as plt + + from sympy import cos, sin, Tuple, exp, sqrt, atan2 + + from sympde.topology import Square, Domain + from sympde.topology.analytical_mapping import PolarMapping, TargetMapping + from sympde.topology import Derham + from sympde.topology import elements_of, element_of + from sympde.topology import NormalVector + from sympde.topology.mapping import Mapping + from sympde.calculus import dot, cross + from sympde.expr import integral + from sympde.expr import BilinearForm, LinearForm + + from psydac.api.discretization import discretize + from psydac.feec.pull_push import push_2d_hcurl, push_2d_l2 + from psydac.utilities.utils import refine_array_1d + from psydac.linalg.solvers import inverse + from psydac.linalg.basic import IdentityOperator + from psydac.linalg.block import BlockLinearOperator + from psydac.mapping.discrete import SplineMapping + from psydac.fem.splines import SplineSpace + from psydac.fem.tensor import TensorFemSpace + from psydac.cad.geometry import Geometry + from psydac.ddm.cart import DomainDecomposition + + assert splitting_order in [2, 4] + + from psydac.feec.polar.conga_projections import C0PolarProjection_V1, C0PolarProjection_V2, SparseCurlAsOperator + + from analyticalTE import CircularCavitySolution # , constant_field + from waveTE import GaussianSolution + + # Radius physical domain + R = 1. # 2.0 + + # Speed of light and scaling + c = 1.0 + scale = 1.0 + + # Mode number + (m, n) = (2, 3) + + # Courant parameter on uniform grid + Cp = 0.125 + + # Exact/initial solution + assert study in ['L2_proj', 'maxwell_bessel', 'maxwell_wave'] + study_L2_proj = (study == 'L2_proj') + study_maxwell = not study_L2_proj + + visdir = f'plots_{study}' + os.makedirs(visdir, exist_ok=True) + + if study == 'maxwell_wave': + exact_solution = GaussianSolution(sigma=1e-1, x0=0, y0=0, scale=scale) + else: + exact_solution = CircularCavitySolution(R=R, c=c, m=m, n=n, scale=scale) + + use_logical_sol = (shift_D == 0) + + use_logical_sol = False ## TODO: USE AS DEFAULT ! + print(f'[use_logical_sol = {use_logical_sol}]') + + # Exact fields, as callable functions of (t, s, theta) or (t, x, y) + if use_logical_sol: + Es_ex_t = exact_solution.Es_ex + Et_ex_t = exact_solution.Et_ex + B_log_ex_t = exact_solution.B_ex + else: + Ex_ex_t = exact_solution.Ex_ex + Ey_ex_t = exact_solution.Ey_ex + Bz_ex_t = exact_solution.Bz_ex + + # Logical domain: [0, R] x [0, 2pi] + logical_bounds = [[0, R], [0, 2 * pi]] + logical_domain = Square('Omega', bounds1=logical_bounds[0], bounds2=logical_bounds[1]) + + # Physical domain: disk of radius R obtained as image of the logical_domain + # with the analytical mapping of a circle + polar_mapping = False + if polar_mapping: + mapping = PolarMapping('PM', c1=0, c2=0, rmin=0, rmax=1) + else: + # domain = ((0, 1), (0, 2 * np.pi)) + mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) + + # use_spline_mapping = False #True + # run parameters string + rp_str = f'{ncells[0]}_{ncells[1]}_p{degree[0]}_D{shift_D}_s{smooth}' + if use_spline_mapping: + rp_str += '_sm' + else: + rp_str += '_pm' # WARNING: check that polar_mapping == True ? + + # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# + + if use_spline_mapping: + + # Number of elements and spline degree + ne1, ne2 = ncells + p1, p2 = degree + + # Create uniform grid + grid_1 = np.linspace(*logical_bounds[0], num=ne1 + 1) + grid_2 = np.linspace(*logical_bounds[1], num=ne2 + 1) + + # Create 1D finite element spaces + V1 = SplineSpace(p1, grid=grid_1, periodic=False) + V2 = SplineSpace(p2, grid=grid_2, periodic=True) + + # Create 2D tensor product finite element space + domain_decomposition = DomainDecomposition(ncells, [False, True]) # , comm = mpi_comm) + V = TensorFemSpace(domain_decomposition, V1, V2) + + s1, s2 = V.coeff_space.starts + e1, e2 = V.coeff_space.ends + + # ==================== MAPPING & PHYSICAL DOMAIN ==============================# + + # Create spline mapping by interpolation of analytical mapping + map_analytic = mapping.get_callable_mapping() + map_discrete = SplineMapping.from_mapping(V, map_analytic) + + check_regular_ring_map(map_discrete) + + # STOP: CHECK that mapping is consistent with paper ? + + # Create symbolic mapping with callable mapping as spline + mapping = Mapping('M', dim=2) + mapping.set_callable_mapping(map_discrete) + # In order to create a sympde.Domain object from this mapping we have + # to create first a HDF5 file and then load as sympde.Domain.fromfile + # t0 = time() + geometry = Geometry.from_discrete_mapping(map_discrete) # , comm=mpi_comm) + geometry.export('geo.h5') + # t1 = time() + # timing['export'] += t1 - t0 + domain = Domain.from_file('geo.h5') + + # TODO (MCP 07.2024): check that mapping = domain.mapping ?? + + else: + # Only symbolic mapping is necessary + # mapping = model.mapping + domain = mapping(logical_domain) + + # F = mapping.get_callable_mapping() + + # domain = mapping(logical_domain) + + # DeRham sequence + derham = Derham(domain, sequence=['h1', 'hcurl', 'l2']) + + # Trial and test functions + u1, v1 = elements_of(derham.V1, names='u1, v1') # electric field E = (Ex, Ey) + u2, v2 = elements_of(derham.V2, names='u2, v2') # magnetic field Bz + + # Bilinear forms that correspond to mass matrices for spaces V1 and V2 + a1 = BilinearForm((u1, v1), integral(domain, dot(u1, v1))) + a2 = BilinearForm((u2, v2), integral(domain, u2 * v2)) + + # Discrete physical domain and discrete DeRham sequence + if use_spline_mapping: + domain_h = discretize(domain, filename='geo.h5') # , comm = mpi_comm) + # V0_h = discretize(V0, domain_h) + derham_h = discretize(derham, domain_h) # , degree = degree) #, quad_order = [4, 4]) + # F = list(domain_h.mappings.values()).pop() + else: + domain_h = discretize(domain, ncells=ncells, periodic=[False, True]) # , comm = mpi_comm) + derham_h = discretize(derham, domain_h, degree=degree) # , quad_order = [4, 4]) + # V0_h = discretize(V0, domain_h, degree = degree) + # F = mapping.get_callable_mapping() + F = mapping.get_callable_mapping() + + def phys_domain_integral(f_log): + """ + Compute the integral of f over the physical domain. + Interface needed since FEM space `integral` function is over the symbolic domain... + """ + f_with_det = lambda eta1, eta2: f_log(eta1, eta2) * np.sqrt(F.metric_det(eta1, eta2)) + return derham_h.V0.integral(f_with_det) + + def l2_norm_of(f_log): + """ + Compute the l2 norm of f over the physical domain. + Interface needed since FEM space `integral` function is over the symbolic domain... + """ + f2_with_det = lambda eta1, eta2: f_log(eta1, eta2) ** 2 * np.sqrt(F.metric_det(eta1, eta2)) + return np.sqrt(derham_h.V0.integral(f2_with_det)) + + def study_L2_proj(): + omega = 4 + print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') + x, y = domain.coordinates + r = sqrt(x * x + y * y) + + # f in H_0(curl;Omega) + f_x = -x + sin(omega * (y + 2 * x * x)) * (r - R) + f_y = -y + cos(omega * (2 * x - y * y)) * (r - R) + f_phys = Tuple(f_x, f_y) + + from sympy import lambdify + fx_call = lambdify([x, y], f_x) + fy_call = lambdify([x, y], f_y) + + print('# compute tilde_f') + # tilde_f = derham_h.get_dual_dofs(space='V1', f=f_ex) + v = element_of(derham.V1, name='u') + + l = LinearForm(v, integral(domain, dot(f_phys, v))) + lh = discretize(l, domain_h, V1) + tilde_f = lh.assemble() + # exit() + + # create fields and point to coeffs + + fh = Pi1((fx_call, fy_call)) + fh_filter = Pi1((fx_call, fy_call)) + + fh_c = fh.coeffs + fh_filter_c = fh_filter.coeffs + + M1_inv = inverse(M1, 'cg', verbose=verbose, tol=tol) + print("using standard L2 projection") + M1_inv.dot(tilde_f, out=fh_c) + P1.dot(fh_c.copy(), out=fh_c) + print('fh_c:') + print(fh_c.toarray()[:]) + + print("using filtered L2 projection") + PTtilde_f = P1.T @ tilde_f + M1_inv.dot(PTtilde_f, out=fh_filter_c) + P1.dot(fh_filter_c.copy(), out=fh_filter_c) + + # compute error and exit + + errx = lambda x1, x2: push_2d_hcurl(fh.fields[0], fh.fields[1], x1, x2, F)[0] - fx_call(F(x1, x2)[0], + F(x1, x2)[1]) + erry = lambda x1, x2: push_2d_hcurl(fh.fields[0], fh.fields[1], x1, x2, F)[1] - fy_call(F(x1, x2)[0], + F(x1, x2)[1]) + errx_filter = lambda x1, x2: push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1, x2, F)[ + 0] - fx_call(F(x1, x2)[0], F(x1, x2)[1]) + erry_filter = lambda x1, x2: push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1, x2, F)[ + 1] - fy_call(F(x1, x2)[0], F(x1, x2)[1]) + + error_l2_fx = l2_norm_of(errx) + error_l2_fy = l2_norm_of(erry) + error_l2_fx_filter = l2_norm_of(errx_filter) + error_l2_fy_filter = l2_norm_of(erry_filter) + print('L2 norm of projection error on fx: {:.2e}'.format(error_l2_fx)) + print('L2 norm of projection error on fy: {:.2e}'.format(error_l2_fy)) + print('L2 norm of projection error on fx (filtered): {:.2e}'.format(error_l2_fx_filter)) + print('L2 norm of projection error on fy (filtered): {:.2e}'.format(error_l2_fy_filter)) + + cst_wo_det = lambda x1, x2: 1 + cst_wi_det = lambda x1, x2: 1 * np.sqrt(F.metric_det(x1,x2)) + mydet = lambda x1, x2: x1**2 + + for x1 in [0.1, 0.01, 0.001]: + for x2 in [0.1, 0.01, 0.001]: + print(f'x1 = {x1}, x2 = {x2}, det_err = {F.metric_det(x1,x2)-mydet(x1,x2)}') + # print(f'a = {F.metric_det(.1,.1), cst_wo_det(.1,.1), cst_wi_det(.1,.1)}') + # print(f'b = {F.metric_det(.01,.01), cst_wo_det(.01,.01), cst_wi_det(.01,.01)}') + # print(f'c = {F.metric_det(.001,.001), cst_wo_det(.001,.001), cst_wi_det(.001,.001)}') + int_wo_det = derham_h.V0.integral(cst_wo_det) + int_wi_det = derham_h.V0.integral(cst_wi_det) + print('V0 - integral of 1 (no det): {:.2e}'.format(int_wo_det)) + print('V0 - integral of 1 (with det): {:.2e}'.format(int_wi_det)) + print('radius of disk: {:.2e}'.format(R)) + print('area of log domain: {:.2e}'.format(2*pi*R)) + print('area of disk: {:.2e}'.format(pi*R*R)) + + int_wo_det = derham_h.V1.spaces[0].integral(cst_wo_det) + int_wi_det = derham_h.V1.spaces[0].integral(cst_wi_det) + print('V1.x - integral of 1 (no det): {:.2e}'.format(int_wo_det)) + print('V1.x - integral of 1 (with det): {:.2e}'.format(int_wi_det)) + + # plot + + # fx_values = np.empty_like(x1) + # fy_values = np.empty_like(x1) + # fx_filter_values = np.empty_like(x1) + # fy_filter_values = np.empty_like(x1) + # + # for i, x1i in enumerate(x1[:, 0]): + # for j, x2j in enumerate(x2[0, :]): + # + # fx_ex_values = np.empty_like(x1) + # fy_ex_values = np.empty_like(x1) + # xij, yij = F(x1i, x2j) + # fx_values[i, j], fy_values[i, j] = \ + # push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) + # fx_filter_values[i, j], fy_filter_values[i, j] = \ + # push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) + # fx_ex_values[i, j], fy_ex_values[i, j] = \ + # fx_call(xij, yij), fy_call(xij, yij) + # fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) + # fig2.savefig(f'{visdir}/fx_{rp_str}.png') + # + # fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) + # fig3.savefig(f'{visdir}/fy_{rp_str}.png') + # + # print('done: showing fh') + # + # fig2.clf() + # fig2 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) + # fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') + # + # fig3.clf() + # fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) + # fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') + # + # print('done: showing fh_filter') + + return locals() + + return locals() + + # Differential operators + D0, D1 = derham_h.derivatives(kind='linop') + D1_T = D1.T + + # Extract spaces + V0, V1, V2 = derham_h.spaces + + I1 = BlockLinearOperator(V1.coeff_space, V1.coeff_space) + I1[0, 0] = IdentityOperator(V1.coeff_space[0]) + I1[1, 1] = IdentityOperator(V1.coeff_space[1]) + + I2 = IdentityOperator(V2.coeff_space) + + # Conga projectors + if smooth == 0: + P1 = C0PolarProjection_V1(V1, hbc=True) + print("P1:") + print(P1) + P2 = C0PolarProjection_V2(V2) + #else: + #P1 = C1PolarProjection_V1(V1, hbc=True) + #P2 = C1PolarProjection_V1(V2) + P1_T = P1.T + P2_T = P2.T + + # Discrete bilinear forms + a1_h = discretize(a1, domain_h, (derham_h.V1, derham_h.V1)) + a2_h = discretize(a2, domain_h, (derham_h.V2, derham_h.V2)) + + hs = R / ncells[0] + htheta = 2 * pi / ncells[1] + + # Mass matrices (StencilMatrix objects) + basic_fix = False + if basic_fix: + M1 = a1_h.assemble() + M2 = a2_h.assemble() + + M1[1, 1][0, :, :, :] = 0 + M_temp = M1[1, 1].transpose() + M_temp[0, :, :, :] = 0 + M1[1, 1] = M_temp.transpose() + M1[1, 1][0, :, 0, :] = 1e20 + + M2[0, :, :, :] = 0 + M_temp = M2.transpose() + M_temp[0, :, :, :] = 0 + M2 = M_temp.transpose() + M2[0, :, 0, :] = 1e20 + + else: + # raw Mass matrices in W1 and W2 have unbounded values, this seems to be fine for the assembly but inverting these matrices leads to virtual nonsense... + # so they need to be regularized below + M1_raw = a1_h.assemble() + M2_raw = a2_h.assemble() + + # regularization of M1 and M2 so that they are bounded and invertible: TODO try various factors (htheta*hs), this one probably too small but Maxwell simlulation was good already :) + M1 = (htheta * hs) * (I1 - P1.T) @ (I1 - P1) + P1.T @ M1_raw @ P1 + M2 = (htheta * hs) * (I2 - P2.T) @ (I2 - P2) + P2.T @ M2_raw @ P2 + + Pi0, Pi1, Pi2 = derham_h.projectors(nquads=[degree[0] + 10, degree[1] + 10]) + + # Geometric Projectors + # if use_logical_sol: + # As the analytical solution is already expressed in the logical domain we + # do not perform the pullback in Pi. To do that we cancel the mapping from + # derham_h. + # raise ValueError('discard this case, it should not be used anymore') + # derham_h._mapping = None + + if plot_time <= 0: + return locals() + + # Time integration setup + # -------------------------------------------------------------------------- + + t = 0 + + if study_maxwell: + # Callable exact fields + if use_logical_sol: + Es_ex = lambda t: (lambda eta1, eta2, t0=t: Es_ex_t(t0, eta1, eta2)) + Et_ex = lambda t: (lambda eta1, eta2, t0=t: Et_ex_t(t0, eta1, eta2)) + B_log_ex = lambda t: (lambda eta1, eta2, t0=t: B_log_ex_t(t0, eta1, eta2)) + + # Initial conditions, discrete fields -- here no pull-back because mapping removed by hand... + E_log = Pi1((Es_ex(t), Et_ex(t))) + B_log = Pi2(B_log_ex(t)) + + else: + Ex_ex = lambda t: (lambda x, y, t0=t: Ex_ex_t(t0, x, y)) + Ey_ex = lambda t: (lambda x, y, t0=t: Ey_ex_t(t0, x, y)) + Bz_ex = lambda t: (lambda x, y, t0=t: Bz_ex_t(t0, x, y)) + + # Initial conditions, discrete fields -- here with a pull-back in the projections + E_log = Pi1((Ex_ex(t), Ey_ex(t))) + B_log = Pi2(Bz_ex(t)) + + # Initial conditions, spline coefficients + e = E_log.coeffs + b = B_log.coeffs + + if study == 'maxwell_wave': + D1.dot(e, out=b) + + # Conga Projection + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) + + if use_scipy: + + print(" -------------- SCIPY operators ------------ ") + + # M1_sp = M1.tosparse() + # M2_sp = M2.tosparse() + conga_curl_sp = (D1 @ P1).tosparse() + step_faraday_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, strong=True, + store_M1inv=False) + step_ampere_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, M1=M1, M2=M2, + strong=False) + # M1_inv = spsolve(M1) + + else: + M1_inv = inverse(M1, 'cg', verbose=verbose, tol=tol) + step_ampere_2d = M1_inv @ P1_T @ D1_T @ M2 + step_faraday_2d = D1 @ P1 + + # Time step size + # dx_min_1 = np.sqrt(np.diff(grid_x, axis=0)**2 + np.diff(grid_y, axis=0)**2).min() + # dx_min_2 = np.sqrt(np.diff(grid_x, axis=1)**2 + np.diff(grid_y, axis=1)**2).min() + # dx_min = min(dx_min_1, dx_min_2) + # dt = Cp * dx_min / c + # only use radial grid-step because angular goes to zero near pole + # dt = Cp * dx_min_1 / c + # print(f'dt = {dt}') + + Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2, tau=tend, light_c=1) + + if plot_time > 0: + plot_interval = max(int(plot_time / dt), 1) + else: + plot_interval = 0 + print(f'plot_interval = {plot_interval}, corresponding to a time = {plot_interval * dt}') + + # If final time is given, recompute number of time steps + if tend is None: + tend = nsteps * dt + print(f'final time (re)computed: {tend}') + else: + nsteps = Nt + print(f'nsteps recomputed: {nsteps}') + + # Visualization setup + # -------------------------------------------------------------------------- + + # Logical and physical grids + grid_x1 = derham_h.V0.breaks[0] + grid_x2 = derham_h.V0.breaks[1] + # Fix division by zero without taking care of the limit as s --> 0 + grid_x1[0] = 1e-20 + + # Very fine grids for evaluation of solution + N = 5 + x1 = refine_array_1d(grid_x1, N) + x2 = refine_array_1d(grid_x2, N) + + x1, x2 = np.meshgrid(x1, x2, indexing='ij') + x = np.empty_like(x1) + y = np.empty_like(x1) + print(x1.shape) + print(x2.shape) + for i in range(x1.shape[0]): + for j in range(x1.shape[1]): + # print(f'i = {i}') + # print(f'x1i = {x1i}') + # print(f'x2i = {x2i}') + # for x1i, x2i in zip(x1, x2): + x[i, j], y[i, j] = F(x1[i, j], x2[i, j]) + + gridlines_x1 = (x[:, ::N], y[:, ::N]) + gridlines_x2 = (x[::N, :].T, y[::N, :].T) + gridlines = (gridlines_x1, gridlines_x2) + + Ex_ex_values = np.empty_like(x1) + Ey_ex_values = np.empty_like(x1) + Bz_ex_values = np.empty_like(x1) + + Ex_values = np.empty_like(x1) + Ey_values = np.empty_like(x1) + Bz_values = np.empty_like(x1) + + if study_L2_proj: + study_L2_proj() + plot_interval = 0 + + # print( x2) + + def plot_fields_along_s(tstr): # , j0=0, j1=0): + + # if j1 is None: + j0 = 0 + j1 = x2.shape[1] // 2 + theta0 = x2[0, j0] + theta1 = x2[0, j1] + name = 'Ex' + fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + x1[:, j0], Ex_values[:, j0], Ex_ex_values[:, j0], + -x1[:, j1], Ex_values[:, j1], Ex_ex_values[:, j1]) + # fig_line = plot_curve_along_s(name, 's', tstr, -x1[:,j1], f'{theta1}', Ex_values[:,j1], Ex_ex_values[:,j1]) + fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + # plt.close(fig_line) + fig_line.clf() + name = 'Ey' + fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + x1[:, j0], Ey_values[:, j0], Ey_ex_values[:, j0], + -x1[:, j1], Ey_values[:, j1], Ey_ex_values[:, j1]) + fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + fig_line.clf() + name = 'Bz' + fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + x1[:, j0], Bz_values[:, j0], Bz_ex_values[:, j0], + -x1[:, j1], Bz_values[:, j1], Bz_ex_values[:, j1]) + fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + plt.close(fig_line) + + # Prepare plots + if plot_interval: + + # Plot physical grid and mapping's metric determinant + # fig1, ax1 = plt.subplots(1, 1, figsize=(8, 6)) + # im = ax1.contourf(x, y, np.sqrt(F.metric_det(x1, x2))) + # add_colorbar(im, ax1, label=r'Metric determinant $\sqrt{g}$ of mapping $F$') + # ax1.plot(*gridlines_x1, color='k') + # ax1.plot(*gridlines_x2, color='k') + # ax1.set_title('Mapped grid of {} x {} cells'.format(ncells, ncells)) + # ax1.set_xlabel('x', fontsize=14) + # ax1.set_ylabel('y', fontsize=14) + # ax1.set_aspect('equal') + # fig1.tight_layout() + # fig1.show() + + # ... + # Plot initial conditions + # TODO: improve + if not study_L2_proj: + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + + Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + + if use_logical_sol: + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + + Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + else: + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + + # fields along s for fixed theta + plot_fields_along_s(tstr='t0') # , j0=0, j1=ncells[1]//2) + + # Electric field, x component + fig = plot_field_and_error(r'E^x', 0, x, y, Ex_values, Ex_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ex_t0_{rp_str}.png') + plt.close(fig) + # fig.clf() + + # Electric field, y component + fig = plot_field_and_error(r'E^y', 0, x, y, Ey_values, Ey_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ey_t0_{rp_str}.png') + plt.close(fig) + # fig.clf() + + # fig3.show() + + # Magnetic field, z component + fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Bz_t0_{rp_str}.png') + plt.close(fig) + + if show_figs: + # Plot exact and approximate solutions at t = 0 + fig, axs = plt.subplots(3, 3, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) + im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) + im3 = axs[1, 0].contourf(x, y, Ex_values, 50) + im4 = axs[1, 1].contourf(x, y, Ey_values, 50) + im5 = axs[1, 2].contourf(x, y, Bz_values, 50) + im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) + im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) + im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) + axs[0, 0].set_title(r'$E^x$ at t = 0') + axs[0, 1].set_title(r'$E^y$ at t = 0') + axs[0, 2].set_title(r'$B^z$ at t = 0') + axs[1, 0].set_title(r'$E_h^x$ at t = 0') + axs[1, 1].set_title(r'$E_h^y$ at t = 0') + axs[1, 2].set_title(r'$B_h^z$ at t = 0') + axs[2, 0].set_title(r'$E^x - E_h^x$ at t = 0') + axs[2, 1].set_title(r'$E^y - E_h^y$ at t = 0') + axs[2, 2].set_title(r'$B^z - B_h^z$ at t = 0') + for i in range(3): + for j in range(3): + axs[i, j].plot(*gridlines[0], color='k') + axs[i, j].plot(*gridlines[1], color='k') + axs[i, j].set_xlabel('x', fontsize=14) + axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') + axs[i, j].set_aspect('equal') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[0, 2]) + add_colorbar(im3, axs[1, 0]) + add_colorbar(im4, axs[1, 1]) + add_colorbar(im5, axs[1, 2]) + add_colorbar(im6, axs[2, 0]) + add_colorbar(im7, axs[2, 1]) + add_colorbar(im8, axs[2, 2]) + fig.suptitle('Compare Exact Solution and Approximate solution at initial time') + fig.tight_layout() + + # Need a small pause to show the plot of the initial condition + plt.pause(.1) + + # L2 norms (of ref solution) + normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) + normy = lambda x1, x2: Ey_ex_t(t, *F(x1, x2)) + normz = lambda x1, x2: Bz_ex_t(t, *F(x1, x2)) + + norm_l2_Ex = l2_norm_of(normx) + norm_l2_Ey = l2_norm_of(normy) + norm_l2_Bz = l2_norm_of(normz) + + # L2 errors + errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) + erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) + errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) + + error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex + error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey + error_l2_Bz = l2_norm_of(errz) / norm_l2_Bz + + print('L2 norm of rel. error on Ex(t,x,y) at initial time: {:.2e}'.format(error_l2_Ex)) + print('L2 norm of rel. error on Ey(t,x,y) at initial time: {:.2e}'.format(error_l2_Ey)) + print('L2 norm of rel. error on Bz(t,x,y) at initial time: {:.2e}'.format(error_l2_Bz)) + + # input('\nSimulation setup done... press any key to start') + + # Solution + # -------------------------------------------------------------------------- + + de = derham_h.V1.coeff_space.zeros() + db = derham_h.V2.coeff_space.zeros() + + def Strang_update(dtau): + # Strang splitting, 2nd order + + # b := b - dt/2 * curl e + step_faraday_2d.dot(e, out=db) + b.mul_iadd(- dtau / 2, db) + + # e := e + dt * curl b + step_ampere_2d.dot(b, out=de) + e.mul_iadd(dtau, de) + + # b := b - dt/2 * curl e + step_faraday_2d.dot(e, out=db) + b.mul_iadd(- dtau / 2, db) + + # weights for Suzuki-Yoshida composition (4th-order splitting) + + gamma_1 = 1 / (2 - 2 ** (1 / 3)) + gamma_2 = 1 - 2 * gamma_1 + + # My temporary fix + if study_maxwell: + # Time loop + for ts in range(1, nsteps + 1): + + print(f'step = {ts}/{nsteps}') + # TODO: allow for high-order splitting + + if splitting_order == 2: + + Strang_update(dt) + + # # Strang splitting, 2nd order + # # b := b - dt/2 * curl e + # step_faraday_2d.dot(e, out = db) + # b.mul_iadd(- dt/2, db) + + # # e := e + dt * curl b + # step_ampere_2d.dot(b, out = de) + # e.mul_iadd(dt, de) + + # # b := b - dt/2 * curl e + # step_faraday_2d.dot(e, out = db) + # b.mul_iadd(- dt/2, db) + + elif splitting_order == 4: + + Strang_update(dt * gamma_1) + Strang_update(dt * gamma_2) + Strang_update(dt * gamma_1) + + else: + raise NotImplementedError('splitting_order must be 2 or 4') + + t += dt + + # diag + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) + + # for i, x1i in enumerate(x1[:, 0]): + # for j, x2j in enumerate(x2[0, :]): + + # Ex_values[i, j], Ey_values[i, j] = \ + # push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + + # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + # # Bz_values[i, j] = B(x1i, x2j) + + # if use_logical_sol: + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + + # Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + # else: + # xij, yij = F(x1i, x2j) + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + + # ... + # Animation and diags + if plot_interval and (ts % plot_interval == 0 or ts == nsteps): + + # project to conforming space to apply posh-forwards + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) # TO TEST: is this necessary? try to comment + # ... + # TODO: improve + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + + Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + # Bz_values[i, j] = B(x1i, x2j) + + if use_logical_sol: + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + + Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + + else: + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + # ... + + # max norm + max_Ex = abs(Ex_values).max() + max_Ey = abs(Ey_values).max() + max_Bz = abs(Bz_values).max() + print() + print('Max-norm of Ex(t,x): {:.2e}'.format(max_Ex)) + print('Max-norm of Ey(t,x): {:.2e}'.format(max_Ey)) + print('Max-norm of Bz(t,x): {:.2e}'.format(max_Bz)) + + if show_figs: + # Update plot + update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) + update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) + update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) + plt.pause(0.1) + else: + fig = plot_field_and_error(r'E^x', t, x, y, Ex_values, Ex_ex_values, *gridlines) + fig.savefig(f'{visdir}/Ex_{ts}_{rp_str}.png') + # fig.clf() + plt.close(fig) + + fig = plot_field_and_error(r'E^y', t, x, y, Ey_values, Ey_ex_values, *gridlines) + fig.savefig(f'{visdir}/Ey_{ts}_{rp_str}.png') + # fig.clf() + plt.close(fig) + + fig = plot_field_and_error(r'B^z', t, x, y, Bz_values, Bz_ex_values, *gridlines) + fig.savefig(f'{visdir}/Bz_{ts}_{rp_str}.png') + plt.close(fig) + + print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) + + # My temporary fix because some variables are referenced before assignment in study_L2_proj: + if study_maxwell: + if not plot_interval: + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + + Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + # Bz_values[i, j] = B(x1i, x2j) + + if use_logical_sol: + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + + Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + + else: + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + + # ... + + # Error at final time + error_Ex = abs(Ex_ex_values - Ex_values).max() + error_Ey = abs(Ey_ex_values - Ey_values).max() + error_Bz = abs(Bz_ex_values - Bz_values).max() + print() + print('Max-norm of error on Ex(t,x) at final time: {:.2e}'.format(error_Ex)) + print('Max-norm of error on Ey(t,x) at final time: {:.2e}'.format(error_Ey)) + print('Max-norm of error on Bz(t,x) at final time: {:.2e}'.format(error_Bz)) + + # L2 norms (of ref solution) + normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) + normy = lambda x1, x2: Ey_ex_t(t, *F(x1, x2)) + normz = lambda x1, x2: Bz_ex_t(t, *F(x1, x2)) + + norm_l2_Ex = l2_norm_of(normx) + norm_l2_Ey = l2_norm_of(normy) + norm_l2_Bz = l2_norm_of(normz) + + # L2 errors + errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) + erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) + errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) + + error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex + error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey + error_l2_Bz = l2_norm_of(errz) / norm_l2_Bz + + print() + print('L2 norm of rel. error on Ex(t,x,y) at final time: {:.2e}'.format(error_l2_Ex)) + print('L2 norm of rel. error on Ey(t,x,y) at final time: {:.2e}'.format(error_l2_Ey)) + print('L2 norm of rel. error on Bz(t,x,y) at final time: {:.2e}'.format(error_l2_Bz)) + + if plot_final: + # Plot exact and approximate solution at final time + fig1, axs = plt.subplots(3, 3, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) + im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) + im3 = axs[1, 0].contourf(x, y, Ex_values, 50) + im4 = axs[1, 1].contourf(x, y, Ey_values, 50) + im5 = axs[1, 2].contourf(x, y, Bz_values, 50) + im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) + im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) + im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) + axs[0, 0].set_title(r'$E^x$ at t = {:10.3e}'.format(t)) + axs[0, 1].set_title(r'$E^y$ at t = {:10.3e}'.format(t)) + axs[0, 2].set_title(r'$B$ at t = {:10.3e}'.format(t)) + axs[1, 0].set_title(r'$E_h^x$ at t = {:10.3e}'.format(t)) + axs[1, 1].set_title(r'$E_h^y$ at t = {:10.3e}'.format(t)) + axs[1, 2].set_title(r'$B_h$ at t = {:10.3e}'.format(t)) + axs[2, 0].set_title(r'$E^x - E^x_h$ at t = {:10.3e}'.format(t)) + axs[2, 1].set_title(r'$E^y - E^y_h$ at t = {:10.3e}'.format(t)) + axs[2, 2].set_title(r'$B - B_h$ at t = {:10.3e}'.format(t)) + for i in range(3): + for j in range(3): + axs[i, j].plot(*gridlines[0], color='k') + axs[i, j].plot(*gridlines[1], color='k') + axs[i, j].set_xlabel('x', fontsize=14) + axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') + axs[i, j].set_aspect('equal') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[0, 2]) + add_colorbar(im3, axs[1, 0]) + add_colorbar(im4, axs[1, 1]) + add_colorbar(im5, axs[1, 2]) + add_colorbar(im6, axs[2, 0]) + add_colorbar(im7, axs[2, 1]) + add_colorbar(im8, axs[2, 2]) + fig1.suptitle('Compare Exact Solution and Approximate solution at final time') + fig1.tight_layout() + + # fields along s, final time + plot_fields_along_s(tstr='T') # , j0=0, j1=ncells[1]//2) + + # Electric field, x component + fig = plot_field_and_error(r'E^x', tend, x, y, Ex_values, Ex_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ex_T_{rp_str}.png') + plt.close(fig) # fig.clf() + + # Electric field, y component + fig = plot_field_and_error(r'E^y', tend, x, y, Ey_values, Ey_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ey_T_{rp_str}.png') + plt.close(fig) # fig.clf() + + # Magnetic field, z component + fig = plot_field_and_error(r'B^z', tend, x, y, Bz_values, Bz_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Bz_T_{rp_str}.png') + plt.close(fig) + + return locals() + + +# ============================================================================== +# SCRIPT CAPABILITIES +# ============================================================================== +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Solve Transverse Time Harmonic Maxwell system on analytical disk with CONGA polar spline method." + ) + + parser.add_argument('--study', + choices=('L2_proj', 'maxwell_bessel', 'maxwell_wave'), + default='maxwell_bessel', + dest='study', + help='Study to be performed' + ) + + parser.add_argument('-S', + action='store_true', + dest='use_spline_mapping', + help='Use spline mapping in finite element calculations' + ) + + parser.add_argument('-D', + type=float, + default=0, + dest='shift_D', + help='Shafranov shift for parametrization of Disk' + ) + + parser.add_argument('-n', '--ncells', + nargs=2, + type=int, + default=[10, 20], + dest='ncells', + help='Number of grid cells (elements) along each dimension' + ) + + parser.add_argument('-s', '--smoothness', + type=int, + default=0, + dest='smooth', + help='Smoothness at the pole. Only C0 and C1 possible. C0 as default.' + ) + + parser.add_argument('-d', '--degree', + nargs=2, + type=int, + default=[3, 3], + dest='degree', + help='Polynomial spline degrees' + ) + + parser.add_argument('-o', '--splitting_order', + type=int, + default=2, + choices=[2, 4, 6], + help='Order of accuracy of operator splitting' + ) + + # ... + time_opts = parser.add_mutually_exclusive_group() + time_opts.add_argument('-t', + type=int, + default=1, + dest='nsteps', + metavar='NSTEPS', + help='Number of time-steps to be taken' + ) + time_opts.add_argument('-T', + type=float, + dest='tend', + metavar='END_TIME', + help='Run simulation until given final time' + ) + # ... + + parser.add_argument('-p', + type=float, + default=1., + metavar='PLOT_TIME', + dest='plot_time', + help='Approx time between successive plots of solution, if I=0 no plots are made' + ) + + parser.add_argument('--tol', + type=float, + default=1e-7, + help='Tolerance for iterative solver (L2-norm of residual)' + ) + + parser.add_argument('--scipy', + action='store_true', + dest='use_scipy', + help='use scipy matrices and direct inverses' + ) + + parser.add_argument('-v', '--verbose', + action='store_true', + help='Print convergence information of iterative solver' + ) + + # Read input arguments + args = parser.parse_args() + + print(f'running maxwell_2d_TE with args:') + print(f'{args}') + # Run simulation + namespace = run_maxwell_2d_TE(**vars(args)) + + # Keep matplotlib windows open + # import matplotlib.pyplot as plt + plt.show() + +## example of run: + +## python conga_polar_maxwell_2d.py -S -n 16 32 -d 3 3 -T 1 -D 0.2 -s 1 -p 100 \ No newline at end of file diff --git a/psydac/feec/polar/examples/utils_congapol.py b/psydac/feec/polar/examples/utils_congapol.py new file mode 100644 index 000000000..22a681431 --- /dev/null +++ b/psydac/feec/polar/examples/utils_congapol.py @@ -0,0 +1,117 @@ +import numpy as np + + +def print_map_polar_coeffs(map_discrete): + # Print spline mapping + print('Spline mapping:') + # print(map_discrete) + print('vars(map_discrete):') + print(vars(map_discrete)) + print() + # print(vars(map_discrete._control_points)) + # print(vars(map_discrete._control_points._mapping)) + # print(vars(map_discrete._control_points._mapping._control_points)) + # print(map_discrete._control_points._mapping._fields) + print('vars(map_discrete._fields[0]) :') + print(vars(map_discrete._fields[0])) + + print() + print('vars(map_discrete._fields[0]._space._spaces[0]) :') + # print(vars(map_discrete._fields[0]._space)) + print(vars(map_discrete._fields[0]._space._spaces[0])) + print('vars(map_discrete._fields[0]._space._spaces[1]) :') + print(vars(map_discrete._fields[0]._space._spaces[1])) + n_s = map_discrete._fields[0]._space._spaces[0]._nbasis + n_theta = map_discrete._fields[0]._space._spaces[1]._nbasis + assert n_s == map_discrete._fields[1]._space._spaces[0]._nbasis + assert n_theta == map_discrete._fields[1]._space._spaces[1]._nbasis + # deg_s = degree[0] + # print('ncells_s = ', n_s-deg_s, ' = ', ncells[0]) + print('n_s = ', n_s) + print('n_theta = ', n_theta) + print() + + # print('map_discrete._fields[0]._coeffs :') + # print(map_discrete._fields[0]._coeffs) + # print() + + map_0_c = map_discrete._fields[0]._coeffs.toarray() + map_1_c = map_discrete._fields[1]._coeffs.toarray() + + print('pole: ', map_0_c[0], map_1_c[0]) + map_0_c -= map_0_c[0] + map_1_c -= map_1_c[0] + + # print(map_0_c**2 + map_1_c**2) + radius_pole = np.sqrt(map_0_c[:n_theta] ** 2 + map_1_c[:n_theta] ** 2) + radius_first_ring = np.sqrt( + map_0_c[n_theta:2 * n_theta] ** 2 + map_1_c[n_theta:2 * n_theta] ** 2 + ) + rho_1 = radius_first_ring[0] + + cs = map_0_c[n_theta:2 * n_theta] / rho_1 + sn = map_1_c[n_theta:2 * n_theta] / rho_1 + theta = np.arctan2(sn, cs) + # print('cos theta_j ?', map_0_c[4:8]/rho_1) + # print('sin theta_j ?', map_1_c[4:8]/rho_1) + + # print('theta_j:', theta) + print('radius_pole:', radius_pole) + print('radius first ring:', radius_first_ring) + print('D theta_j:', np.mod(theta[1:] - theta[:-1], 2 * np.pi)) + # print('theta_j ?', np.arcsin(map_1_c[4:8]/rho_1)) + # exit() + + # angle2 = + # print(vars(map_discrete._control_points._mapping._fields[0]._coeffs)) + # exit() + + +def check_regular_ring_map(map_discrete, verbose=False): + n_s = map_discrete._fields[0]._space._spaces[0]._nbasis + n_theta = map_discrete._fields[0]._space._spaces[1]._nbasis + assert n_s == map_discrete._fields[1]._space._spaces[0]._nbasis + assert n_theta == map_discrete._fields[1]._space._spaces[1]._nbasis + # deg_s = degree[0] + # print('ncells_s = ', n_s-deg_s, ' = ', ncells[0]) + if verbose: + print('n_s = ', n_s) + print('n_theta = ', n_theta) + print() + + map_0_c = map_discrete._fields[0]._coeffs.toarray() + map_1_c = map_discrete._fields[1]._coeffs.toarray() + + # print('pole: ', map_0_c[0], map_1_c[0]) + map_0_c -= map_0_c[0] + map_1_c -= map_1_c[0] + + # print(map_0_c**2 + map_1_c**2) + radius_pole = np.sqrt(map_0_c[:n_theta] ** 2 + map_1_c[:n_theta] ** 2) + radius_first_ring = np.sqrt( + map_0_c[n_theta:2 * n_theta] ** 2 + map_1_c[n_theta:2 * n_theta] ** 2 + ) + rho_1 = radius_first_ring[0] + + cs = map_0_c[n_theta:2 * n_theta] / rho_1 + sn = map_1_c[n_theta:2 * n_theta] / rho_1 + theta = np.arctan2(sn, cs) + # print('cos theta_j ?', map_0_c[4:8]/rho_1) + # print('sin theta_j ?', map_1_c[4:8]/rho_1) + + delta_theta = np.mod(theta[1:] - theta[:-1], 2 * np.pi) + if verbose: + print('radius_pole:', radius_pole) + print('radius first ring:', radius_first_ring) + print('D theta_j:', delta_theta) + print() + + circle_error = np.linalg.norm(radius_pole[1:] - radius_pole[:-1]) + regularity_error = np.linalg.norm(delta_theta[1:] - delta_theta[:-1]) + regularity_check = abs(circle_error) + abs(regularity_error) < 1e-12 + + print('CHECK: spline mapping is regular: ', regularity_check) + if not regularity_check: + print(' - circle error on 1st ring:', circle_error) + print(' - regularity error on 1st ring:', regularity_error) + print() diff --git a/psydac/feec/polar/examples/waveTE.py b/psydac/feec/polar/examples/waveTE.py new file mode 100644 index 000000000..2e3d58917 --- /dev/null +++ b/psydac/feec/polar/examples/waveTE.py @@ -0,0 +1,199 @@ +from sympde.topology import Square +from sympde.topology.analytical_mapping import PolarMapping +from psydac.feec.pull_push import push_2d_hcurl, push_2d_l2 +from numpy import pi +import numpy as np +import matplotlib.pyplot as plt + + +def add_colorbar(im, ax, **kwargs): + from mpl_toolkits.axes_grid1 import make_axes_locatable + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.3) + cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) + return cbar + + +class GaussianSolution: + """ + just a Gaussian field with B = curl E + """ + + def __init__(self, sigma, x0, y0, scale=1): + from numpy import pi + from scipy.special import jnp_zeros + + self.x0 = x0 + self.y0 = y0 + self.sigma = sigma + self.scale = scale + + # self._logical = (variables == 'log') + + # def get_radius_angle(self, x, y): + # from numpy import sqrt, arctan2 #, sin, cos + # r = sqrt(x*x + y*y) + # alpha = arctan2(y, x) + # return r, alpha + + def Ex_ex(self, t, x, y): + from numpy import exp, cos, sqrt + sig2 = self.sigma ** 2 + r2 = (x - self.x0) ** 2 + (y - self.y0) ** 2 + + return (y - self.y0) / sig2 * exp(-r2 / (2 * sig2)) + + def Ey_ex(self, t, x, y): + from numpy import exp, cos, sqrt + + sig2 = self.sigma ** 2 + r2 = (x - self.x0) ** 2 + (y - self.y0) ** 2 + + return -(x - self.x0) / sig2 * exp(-r2 / (2 * sig2)) + + def Bz_ex(self, t, x, y): + """ + Bz = curl E (?) + """ + from numpy import exp + + return 0 # -2*(y-self.y0)/self.sigma**2 * exp(-((x-self.x0)**2 + (y-self.y0)**2)/self.sigma**2) + + +def main(): + # TODO: update with D_shift + + # Physical domain is rectangle [0, R] x [0, 2pi] + R = 2.0 + + # Speed of light equal c and scaling of the fields by a scale factor + sigma = 0.1 + scale = 1 + + # Exact solution + exact_solution = GaussianSolution(sigma=sigma, x0=0, y0=0, scale=scale) + + # Exact fields, as callable functions of (t, s, theta) + Es_ex = exact_solution.Es_ex + Et_ex = exact_solution.Et_ex + B_ex = exact_solution.B_ex + Bt_ex = exact_solution.Bt_ex + + # Logical domain: [0, R] x [0, 2pi] + logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) + + # Physical domain: disk of radius R obtained as image of the logical_domain + # with the analytical mapping of a circle + mapping = PolarMapping('F', c1=0, c2=0, rmin=0, rmax=1) + domain = mapping(logical_domain) + F = mapping.get_callable_mapping() + + # Set time + t = 0 + + Es = lambda x, y: Es_ex(t, x, y) + Et = lambda x, y: Et_ex(t, x, y) + B = lambda x, y: B_ex(t, x, y) + Bt = lambda x, y: Bt_ex(t, x, y) + + # Plot of fields + N = 100 + + rho = np.linspace(1e-20, R, N) + theta = np.linspace(0, 2 * pi, N) + rho, theta = np.meshgrid(rho, theta, indexing='ij') + x, y = F(rho, theta) + + Ex_values = np.empty_like(rho) + Ey_values = np.empty_like(rho) + B_values = np.empty_like(rho) + + valerr = 0 + for i, x1i in enumerate(rho[:, 0]): + for j, x2j in enumerate(theta[0, :]): + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Es, Et, x1i, x2j, F) + + B_values[i, j] = push_2d_l2(B, x1i, x2j, F) + + fig, axs = plt.subplots(2, 2, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_values, 50) + im2 = axs[1, 0].contourf(x, y, np.sqrt(Ex_values ** 2 + Ey_values ** 2), 50) + im3 = axs[1, 1].contourf(x, y, B_values, 50) + axs[0, 0].set_title(r'$E_x$') + axs[0, 1].set_title(r'$E_y$') + axs[1, 0].set_title(r'$||\mathbf{E}||$') + axs[1, 1].set_title('$B$') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[1, 0]) + add_colorbar(im3, axs[1, 1]) + + # Test: curl E = - d_t B_z with finite Differences + # on a tensor grid in the square inscribed the disk + N = 160 + l = R / np.sqrt(2) # edge length + + x, dx = np.linspace(-l, l, N, retstep=True) + y, dy = np.linspace(-l, l, N, retstep=True) + x, y = np.meshgrid(x, y, indexing='ij') + rho = np.sqrt(x ** 2 + y ** 2) + theta = np.arctan2(y, x) % (2 * pi) + + Ex_values = np.empty_like(rho) + Ey_values = np.empty_like(rho) + Bt_values = np.empty_like(rho) + + ni, nj = rho.shape + for i in range(ni): + for j in range(nj): + x1_ij = rho[i, j] + x2_ij = theta[i, j] + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Es, Et, x1_ij, x2_ij, F) + + Bt_values[i, j] = push_2d_l2(Bt, x1_ij, x2_ij, F) + + fig, axs = plt.subplots(2, 3, figsize=(15, 15)) + im1 = axs[0, 0].contourf(x, y, np.sqrt(Ex_values ** 2 + Ey_values ** 2)) + im2 = axs[0, 1].contourf(x, y, -Bt_values) + axs[0, 0].set_title(r'$||\mathbf{E}||$') + axs[0, 1].set_title(r'$-\partial_t B$') + for axi in axs.flat: + axi.set_aspect('equal') + add_colorbar(im1, axs[0, 0]) + add_colorbar(im2, axs[0, 1]) + + curlE_values = np.zeros_like(rho) + valerr = 0 + for i in range(1, ni - 1): + for j in range(1, nj - 1): + curlE_values[i, j] = (Ey_values[i + 1, j] - Ey_values[i - 1, j]) / (2 * dx) \ + - (Ex_values[i, j + 1] - Ex_values[i, j - 1]) / (2 * dy) + + d = np.abs(Bt_values[i, j] + curlE_values[i, j]) + + if valerr < d: + valerr = d + + skip = (slice(None, None, int(N / 20)), slice(None, None, int(N / 20))) + im3 = axs[1, 0].contourf(x, y, curlE_values) + axs[1, 0].quiver(x[skip], y[skip], Ex_values[skip], Ey_values[skip]) + add_colorbar(im3, axs[1, 0]) + axs[1, 0].set_title(r'curl $\mathbf{E}$') + im4 = axs[1, 1].contourf(x, y, -Bt_values) + axs[1, 1].quiver(x[skip], y[skip], Ex_values[skip], Ey_values[skip]) + add_colorbar(im4, axs[1, 1]) + im5 = axs[0, 2].contourf(x, y, Ex_values) + im6 = axs[1, 2].contourf(x, y, Ey_values) + add_colorbar(im5, axs[0, 2]) + add_colorbar(im6, axs[1, 2]) + print('|curlE + d_t B| <= ', valerr) + + +if __name__ == "__main__": + main() + plt.show() + + From bd3e1d565567eef485365c771f180ba9cf858c87 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 13 Nov 2025 10:47:42 +0100 Subject: [PATCH 026/133] fixed some errors --- psydac/feec/polar/examples/maxwell_2d.py | 305 ++++++++++++----------- psydac/feec/polar/examples/waveTE.py | 2 +- 2 files changed, 154 insertions(+), 153 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 7f1ea8451..f99b3b494 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -201,7 +201,7 @@ def plot_curve_along_s(name, s_str, time_str, theta0, def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, splitting_order, shift_D, use_spline_mapping, plot_time, tol, - cfl=0.9, show_figs=False, plot_final=True, + cfl=0.9, show_figs=True, plot_final=True, study='maxwell_bessel', use_scipy=True, verbose=False): import numpy as np from numpy import pi @@ -396,7 +396,7 @@ def l2_norm_of(f_log): f2_with_det = lambda eta1, eta2: f_log(eta1, eta2) ** 2 * np.sqrt(F.metric_det(eta1, eta2)) return np.sqrt(derham_h.V0.integral(f2_with_det)) - def study_L2_proj(): + def run_study_L2_proj(): omega = 4 print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') x, y = domain.coordinates @@ -464,9 +464,9 @@ def study_L2_proj(): cst_wi_det = lambda x1, x2: 1 * np.sqrt(F.metric_det(x1,x2)) mydet = lambda x1, x2: x1**2 - for x1 in [0.1, 0.01, 0.001]: - for x2 in [0.1, 0.01, 0.001]: - print(f'x1 = {x1}, x2 = {x2}, det_err = {F.metric_det(x1,x2)-mydet(x1,x2)}') + # for point1 in [0.1, 0.01, 0.001]: + # for point2 in [0.1, 0.01, 0.001]: + # print(f'x1 = {x1}, x2 = {x2}, det_err = {F.metric_det(x1,x2)-mydet(x1,x2)}') # print(f'a = {F.metric_det(.1,.1), cst_wo_det(.1,.1), cst_wi_det(.1,.1)}') # print(f'b = {F.metric_det(.01,.01), cst_wo_det(.01,.01), cst_wi_det(.01,.01)}') # print(f'c = {F.metric_det(.001,.001), cst_wo_det(.001,.001), cst_wi_det(.001,.001)}') @@ -485,42 +485,47 @@ def study_L2_proj(): # plot - # fx_values = np.empty_like(x1) - # fy_values = np.empty_like(x1) - # fx_filter_values = np.empty_like(x1) - # fy_filter_values = np.empty_like(x1) - # - # for i, x1i in enumerate(x1[:, 0]): - # for j, x2j in enumerate(x2[0, :]): - # - # fx_ex_values = np.empty_like(x1) - # fy_ex_values = np.empty_like(x1) - # xij, yij = F(x1i, x2j) - # fx_values[i, j], fy_values[i, j] = \ - # push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) - # fx_filter_values[i, j], fy_filter_values[i, j] = \ - # push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) - # fx_ex_values[i, j], fy_ex_values[i, j] = \ - # fx_call(xij, yij), fy_call(xij, yij) - # fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) - # fig2.savefig(f'{visdir}/fx_{rp_str}.png') - # - # fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) - # fig3.savefig(f'{visdir}/fy_{rp_str}.png') - # - # print('done: showing fh') - # - # fig2.clf() - # fig2 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) - # fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') - # - # fig3.clf() - # fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) - # fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') - # - # print('done: showing fh_filter') + fx_values = np.empty_like(x1) + fy_values = np.empty_like(x1) + fx_filter_values = np.empty_like(x1) + fy_filter_values = np.empty_like(x1) - return locals() + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + fx_ex_values = np.empty_like(x1) + fy_ex_values = np.empty_like(x1) + xij, yij = F(x1i, x2j) + fx_values[i, j], fy_values[i, j] = \ + push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) + fx_filter_values[i, j], fy_filter_values[i, j] = \ + push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) + fx_ex_values[i, j], fy_ex_values[i, j] = \ + fx_call(xij, yij), fy_call(xij, yij) + fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) + fig2.savefig(f'{visdir}/fx_{rp_str}.png') + + fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) + fig3.savefig(f'{visdir}/fy_{rp_str}.png') + + print('done: showing fh') + + fig2.clf() + fig2 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) + fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') + + fig3.clf() + fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) + fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') + + if show_figs: + # Update plot + update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) + update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) + # update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) + plt.pause(0.1) + + print('done: showing fh_filter') return locals() @@ -543,7 +548,7 @@ def study_L2_proj(): print("P1:") print(P1) P2 = C0PolarProjection_V2(V2) - #else: + #else: TODO #P1 = C1PolarProjection_V1(V1, hbc=True) #P2 = C1PolarProjection_V1(V2) P1_T = P1.T @@ -716,7 +721,7 @@ def study_L2_proj(): Bz_values = np.empty_like(x1) if study_L2_proj: - study_L2_proj() + run_study_L2_proj() plot_interval = 0 # print( x2) @@ -913,136 +918,74 @@ def Strang_update(dtau): gamma_1 = 1 / (2 - 2 ** (1 / 3)) gamma_2 = 1 - 2 * gamma_1 - # My temporary fix - if study_maxwell: - # Time loop - for ts in range(1, nsteps + 1): + # Time loop + for ts in range(1, nsteps + 1): - print(f'step = {ts}/{nsteps}') - # TODO: allow for high-order splitting + print(f'step = {ts}/{nsteps}') + # TODO: allow for high-order splitting - if splitting_order == 2: + if splitting_order == 2: - Strang_update(dt) + Strang_update(dt) - # # Strang splitting, 2nd order - # # b := b - dt/2 * curl e - # step_faraday_2d.dot(e, out = db) - # b.mul_iadd(- dt/2, db) + # # Strang splitting, 2nd order + # # b := b - dt/2 * curl e + # step_faraday_2d.dot(e, out = db) + # b.mul_iadd(- dt/2, db) - # # e := e + dt * curl b - # step_ampere_2d.dot(b, out = de) - # e.mul_iadd(dt, de) + # # e := e + dt * curl b + # step_ampere_2d.dot(b, out = de) + # e.mul_iadd(dt, de) - # # b := b - dt/2 * curl e - # step_faraday_2d.dot(e, out = db) - # b.mul_iadd(- dt/2, db) + # # b := b - dt/2 * curl e + # step_faraday_2d.dot(e, out = db) + # b.mul_iadd(- dt/2, db) - elif splitting_order == 4: + elif splitting_order == 4: - Strang_update(dt * gamma_1) - Strang_update(dt * gamma_2) - Strang_update(dt * gamma_1) + Strang_update(dt * gamma_1) + Strang_update(dt * gamma_2) + Strang_update(dt * gamma_1) - else: - raise NotImplementedError('splitting_order must be 2 or 4') - - t += dt - - # diag - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + else: + raise NotImplementedError('splitting_order must be 2 or 4') - # for i, x1i in enumerate(x1[:, 0]): - # for j, x2j in enumerate(x2[0, :]): + t += dt - # Ex_values[i, j], Ey_values[i, j] = \ - # push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + # diag + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) - # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - # # Bz_values[i, j] = B(x1i, x2j) + # for i, x1i in enumerate(x1[:, 0]): + # for j, x2j in enumerate(x2[0, :]): - # if use_logical_sol: - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + # Ex_values[i, j], Ey_values[i, j] = \ + # push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) - # Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - # else: - # xij, yij = F(x1i, x2j) - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + # # Bz_values[i, j] = B(x1i, x2j) - # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + # if use_logical_sol: + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) - # ... - # Animation and diags - if plot_interval and (ts % plot_interval == 0 or ts == nsteps): - - # project to conforming space to apply posh-forwards - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) # TO TEST: is this necessary? try to comment - # ... - # TODO: improve - for i, x1i in enumerate(x1[:, 0]): - for j, x2j in enumerate(x2[0, :]): - - Ex_values[i, j], Ey_values[i, j] = \ - push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) - - Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - # Bz_values[i, j] = B(x1i, x2j) - - if use_logical_sol: - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) - - Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - - else: - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - # ... - - # max norm - max_Ex = abs(Ex_values).max() - max_Ey = abs(Ey_values).max() - max_Bz = abs(Bz_values).max() - print() - print('Max-norm of Ex(t,x): {:.2e}'.format(max_Ex)) - print('Max-norm of Ey(t,x): {:.2e}'.format(max_Ey)) - print('Max-norm of Bz(t,x): {:.2e}'.format(max_Bz)) - - if show_figs: - # Update plot - update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) - update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) - update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) - plt.pause(0.1) - else: - fig = plot_field_and_error(r'E^x', t, x, y, Ex_values, Ex_ex_values, *gridlines) - fig.savefig(f'{visdir}/Ex_{ts}_{rp_str}.png') - # fig.clf() - plt.close(fig) + # Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + # else: + # xij, yij = F(x1i, x2j) + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - fig = plot_field_and_error(r'E^y', t, x, y, Ey_values, Ey_ex_values, *gridlines) - fig.savefig(f'{visdir}/Ey_{ts}_{rp_str}.png') - # fig.clf() - plt.close(fig) + # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - fig = plot_field_and_error(r'B^z', t, x, y, Bz_values, Bz_ex_values, *gridlines) - fig.savefig(f'{visdir}/Bz_{ts}_{rp_str}.png') - plt.close(fig) - - print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) + # ... + # Animation and diags + if plot_interval and (ts % plot_interval == 0 or ts == nsteps): - # My temporary fix because some variables are referenced before assignment in study_L2_proj: - if study_maxwell: - if not plot_interval: + # project to conforming space to apply posh-forwards P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + P2.dot(b.copy(), out=b) # TO TEST: is this necessary? try to comment + # ... + # TODO: improve for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): @@ -1064,6 +1007,64 @@ def Strang_update(dtau): Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + # ... + + # max norm + max_Ex = abs(Ex_values).max() + max_Ey = abs(Ey_values).max() + max_Bz = abs(Bz_values).max() + print() + print('Max-norm of Ex(t,x): {:.2e}'.format(max_Ex)) + print('Max-norm of Ey(t,x): {:.2e}'.format(max_Ey)) + print('Max-norm of Bz(t,x): {:.2e}'.format(max_Bz)) + + # if show_figs: + # # Update plot + # update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) + # update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) + # update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) + # plt.pause(0.1) + if not show_figs: + fig = plot_field_and_error(r'E^x', t, x, y, Ex_values, Ex_ex_values, *gridlines) + fig.savefig(f'{visdir}/Ex_{ts}_{rp_str}.png') + # fig.clf() + plt.close(fig) + + fig = plot_field_and_error(r'E^y', t, x, y, Ey_values, Ey_ex_values, *gridlines) + fig.savefig(f'{visdir}/Ey_{ts}_{rp_str}.png') + # fig.clf() + plt.close(fig) + + fig = plot_field_and_error(r'B^z', t, x, y, Bz_values, Bz_ex_values, *gridlines) + fig.savefig(f'{visdir}/Bz_{ts}_{rp_str}.png') + plt.close(fig) + + print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) + + if not plot_interval: + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + + Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + # Bz_values[i, j] = B(x1i, x2j) + + if use_logical_sol: + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + + Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + + else: + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # ... diff --git a/psydac/feec/polar/examples/waveTE.py b/psydac/feec/polar/examples/waveTE.py index 2e3d58917..6a7b82495 100644 --- a/psydac/feec/polar/examples/waveTE.py +++ b/psydac/feec/polar/examples/waveTE.py @@ -57,7 +57,7 @@ def Bz_ex(self, t, x, y): """ from numpy import exp - return 0 # -2*(y-self.y0)/self.sigma**2 * exp(-((x-self.x0)**2 + (y-self.y0)**2)/self.sigma**2) + return -2*(y-self.y0)/self.sigma**2 * exp(-((x-self.x0)**2 + (y-self.y0)**2)/self.sigma**2) def main(): From 850100a569cf85ac485a3f1eb78185f1531e37b2 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 13 Nov 2025 14:29:55 +0100 Subject: [PATCH 027/133] parallelization, fixed mapping --- psydac/feec/polar/examples/maxwell_2d.py | 43 +++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index f99b3b494..b49053e08 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -7,6 +7,7 @@ """ import os import numpy as np +from mpi4py import MPI import matplotlib.pyplot as plt @@ -300,6 +301,14 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, else: rp_str += '_pm' # WARNING: check that polar_mapping == True ? + # Communicator, size, rank + mpi_comm = MPI.COMM_WORLD + mpi_size = mpi_comm.Get_size() + mpi_rank = mpi_comm.Get_rank() + if mpi_rank != 0: + show_figs = False + + # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# if use_spline_mapping: @@ -317,7 +326,7 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, V2 = SplineSpace(p2, grid=grid_2, periodic=True) # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, [False, True]) # , comm = mpi_comm) + domain_decomposition = DomainDecomposition(ncells, [False, True], comm = mpi_comm) V = TensorFemSpace(domain_decomposition, V1, V2) s1, s2 = V.coeff_space.starts @@ -339,7 +348,7 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # In order to create a sympde.Domain object from this mapping we have # to create first a HDF5 file and then load as sympde.Domain.fromfile # t0 = time() - geometry = Geometry.from_discrete_mapping(map_discrete) # , comm=mpi_comm) + geometry = Geometry.from_discrete_mapping(map_discrete, comm=mpi_comm) geometry.export('geo.h5') # t1 = time() # timing['export'] += t1 - t0 @@ -369,16 +378,16 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # Discrete physical domain and discrete DeRham sequence if use_spline_mapping: - domain_h = discretize(domain, filename='geo.h5') # , comm = mpi_comm) + domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) # V0_h = discretize(V0, domain_h) derham_h = discretize(derham, domain_h) # , degree = degree) #, quad_order = [4, 4]) - # F = list(domain_h.mappings.values()).pop() + F = map_analytic else: - domain_h = discretize(domain, ncells=ncells, periodic=[False, True]) # , comm = mpi_comm) + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) derham_h = discretize(derham, domain_h, degree=degree) # , quad_order = [4, 4]) # V0_h = discretize(V0, domain_h, degree = degree) - # F = mapping.get_callable_mapping() - F = mapping.get_callable_mapping() + F = mapping.get_callable_mapping() + # F = mapping.get_callable_mapping() def phys_domain_integral(f_log): """ @@ -529,6 +538,10 @@ def run_study_L2_proj(): return locals() + # ============================================================================== + # DISCRETIZATION + # ============================================================================== + # Differential operators D0, D1 = derham_h.derivatives(kind='linop') D1_T = D1.T @@ -681,8 +694,9 @@ def run_study_L2_proj(): nsteps = Nt print(f'nsteps recomputed: {nsteps}') - # Visualization setup - # -------------------------------------------------------------------------- + # ============================================================================== + # VISUALIZATION SETUP + # ============================================================================== # Logical and physical grids grid_x1 = derham_h.V0.breaks[0] @@ -700,6 +714,7 @@ def run_study_L2_proj(): y = np.empty_like(x1) print(x1.shape) print(x2.shape) + for i in range(x1.shape[0]): for j in range(x1.shape[1]): # print(f'i = {i}') @@ -722,7 +737,7 @@ def run_study_L2_proj(): if study_L2_proj: run_study_L2_proj() - plot_interval = 0 + #plot_interval = 0 # print( x2) @@ -892,8 +907,9 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): # input('\nSimulation setup done... press any key to start') - # Solution - # -------------------------------------------------------------------------- + # ============================================================================== + # SOLUTION + # ============================================================================== de = derham_h.V1.coeff_space.zeros() db = derham_h.V2.coeff_space.zeros() @@ -1041,6 +1057,9 @@ def Strang_update(dtau): print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) + N = 10 + V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) + if not plot_interval: P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) From 1a722f2aa89cf3dde7a1c7467f81a3e777538251 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 18 Nov 2025 14:52:41 +0100 Subject: [PATCH 028/133] fixed an error with study_L2proj --- psydac/feec/polar/examples/maxwell_2d.py | 50 +++++++++++------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index b49053e08..17b8a6be9 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -408,17 +408,17 @@ def l2_norm_of(f_log): def run_study_L2_proj(): omega = 4 print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') - x, y = domain.coordinates - r = sqrt(x * x + y * y) + xs, ys = domain.coordinates + r = sqrt(xs * xs + ys * ys) # f in H_0(curl;Omega) - f_x = -x + sin(omega * (y + 2 * x * x)) * (r - R) - f_y = -y + cos(omega * (2 * x - y * y)) * (r - R) + f_x = -xs + sin(omega * (ys + 2 * xs * xs)) * (r - R) + f_y = -ys + cos(omega * (2 * xs - ys * ys)) * (r - R) f_phys = Tuple(f_x, f_y) from sympy import lambdify - fx_call = lambdify([x, y], f_x) - fy_call = lambdify([x, y], f_y) + fx_call = lambdify([xs, ys], f_x) + fy_call = lambdify([xs, ys], f_y) print('# compute tilde_f') # tilde_f = derham_h.get_dual_dofs(space='V1', f=f_ex) @@ -492,6 +492,9 @@ def run_study_L2_proj(): print('V1.x - integral of 1 (no det): {:.2e}'.format(int_wo_det)) print('V1.x - integral of 1 (with det): {:.2e}'.format(int_wi_det)) + if plot_time <= 0: + return locals() + # plot fx_values = np.empty_like(x1) @@ -499,11 +502,13 @@ def run_study_L2_proj(): fx_filter_values = np.empty_like(x1) fy_filter_values = np.empty_like(x1) + fx_ex_values = np.empty_like(x1) + fy_ex_values = np.empty_like(x1) + for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): - fx_ex_values = np.empty_like(x1) - fy_ex_values = np.empty_like(x1) + xij, yij = F(x1i, x2j) fx_values[i, j], fy_values[i, j] = \ push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) @@ -511,8 +516,9 @@ def run_study_L2_proj(): push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) fx_ex_values[i, j], fy_ex_values[i, j] = \ fx_call(xij, yij), fy_call(xij, yij) - fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) - fig2.savefig(f'{visdir}/fx_{rp_str}.png') + + fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) + fig2.savefig(f'{visdir}/fx_{rp_str}.png') fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) fig3.savefig(f'{visdir}/fy_{rp_str}.png') @@ -527,12 +533,6 @@ def run_study_L2_proj(): fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') - if show_figs: - # Update plot - update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) - update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) - # update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) - plt.pause(0.1) print('done: showing fh_filter') @@ -737,7 +737,7 @@ def run_study_L2_proj(): if study_L2_proj: run_study_L2_proj() - #plot_interval = 0 + return locals() # print( x2) @@ -788,7 +788,7 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): # ... # Plot initial conditions # TODO: improve - if not study_L2_proj: + if study_maxwell: for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): @@ -797,17 +797,11 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - if use_logical_sol: - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) - - Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - else: - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # fields along s for fixed theta plot_fields_along_s(tstr='t0') # , j0=0, j1=ncells[1]//2) From 5d3477f44f171d3a5051fdb8b02e9f253c17865a Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 20 Nov 2025 11:21:19 +0100 Subject: [PATCH 029/133] fix plots at final time --- psydac/feec/polar/examples/maxwell_2d.py | 191 +++++++++++------------ 1 file changed, 94 insertions(+), 97 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 17b8a6be9..307d0066d 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -612,9 +612,6 @@ def run_study_L2_proj(): # raise ValueError('discard this case, it should not be used anymore') # derham_h._mapping = None - if plot_time <= 0: - return locals() - # Time integration setup # -------------------------------------------------------------------------- @@ -1082,103 +1079,103 @@ def Strang_update(dtau): # ... # Error at final time - error_Ex = abs(Ex_ex_values - Ex_values).max() - error_Ey = abs(Ey_ex_values - Ey_values).max() - error_Bz = abs(Bz_ex_values - Bz_values).max() - print() - print('Max-norm of error on Ex(t,x) at final time: {:.2e}'.format(error_Ex)) - print('Max-norm of error on Ey(t,x) at final time: {:.2e}'.format(error_Ey)) - print('Max-norm of error on Bz(t,x) at final time: {:.2e}'.format(error_Bz)) - - # L2 norms (of ref solution) - normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) - normy = lambda x1, x2: Ey_ex_t(t, *F(x1, x2)) - normz = lambda x1, x2: Bz_ex_t(t, *F(x1, x2)) + error_Ex = abs(Ex_ex_values - Ex_values).max() + error_Ey = abs(Ey_ex_values - Ey_values).max() + error_Bz = abs(Bz_ex_values - Bz_values).max() + print() + print('Max-norm of error on Ex(t,x) at final time: {:.2e}'.format(error_Ex)) + print('Max-norm of error on Ey(t,x) at final time: {:.2e}'.format(error_Ey)) + print('Max-norm of error on Bz(t,x) at final time: {:.2e}'.format(error_Bz)) + + # L2 norms (of ref solution) + normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) + normy = lambda x1, x2: Ey_ex_t(t, *F(x1, x2)) + normz = lambda x1, x2: Bz_ex_t(t, *F(x1, x2)) + + norm_l2_Ex = l2_norm_of(normx) + norm_l2_Ey = l2_norm_of(normy) + norm_l2_Bz = l2_norm_of(normz) + + # L2 errors + errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) + erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) + errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) + + error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex + error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey + error_l2_Bz = l2_norm_of(errz) / norm_l2_Bz + + print() + print('L2 norm of rel. error on Ex(t,x,y) at final time: {:.2e}'.format(error_l2_Ex)) + print('L2 norm of rel. error on Ey(t,x,y) at final time: {:.2e}'.format(error_l2_Ey)) + print('L2 norm of rel. error on Bz(t,x,y) at final time: {:.2e}'.format(error_l2_Bz)) + + if plot_final: + # Plot exact and approximate solution at final time + fig1, axs = plt.subplots(3, 3, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) + im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) + im3 = axs[1, 0].contourf(x, y, Ex_values, 50) + im4 = axs[1, 1].contourf(x, y, Ey_values, 50) + im5 = axs[1, 2].contourf(x, y, Bz_values, 50) + im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) + im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) + im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) + axs[0, 0].set_title(r'$E^x$ at t = {:10.3e}'.format(t)) + axs[0, 1].set_title(r'$E^y$ at t = {:10.3e}'.format(t)) + axs[0, 2].set_title(r'$B$ at t = {:10.3e}'.format(t)) + axs[1, 0].set_title(r'$E_h^x$ at t = {:10.3e}'.format(t)) + axs[1, 1].set_title(r'$E_h^y$ at t = {:10.3e}'.format(t)) + axs[1, 2].set_title(r'$B_h$ at t = {:10.3e}'.format(t)) + axs[2, 0].set_title(r'$E^x - E^x_h$ at t = {:10.3e}'.format(t)) + axs[2, 1].set_title(r'$E^y - E^y_h$ at t = {:10.3e}'.format(t)) + axs[2, 2].set_title(r'$B - B_h$ at t = {:10.3e}'.format(t)) + for i in range(3): + for j in range(3): + axs[i, j].plot(*gridlines[0], color='k') + axs[i, j].plot(*gridlines[1], color='k') + axs[i, j].set_xlabel('x', fontsize=14) + axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') + axs[i, j].set_aspect('equal') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[0, 2]) + add_colorbar(im3, axs[1, 0]) + add_colorbar(im4, axs[1, 1]) + add_colorbar(im5, axs[1, 2]) + add_colorbar(im6, axs[2, 0]) + add_colorbar(im7, axs[2, 1]) + add_colorbar(im8, axs[2, 2]) + fig1.suptitle('Compare Exact Solution and Approximate solution at final time') + fig1.tight_layout() + + # fields along s, final time + plot_fields_along_s(tstr='T') # , j0=0, j1=ncells[1]//2) - norm_l2_Ex = l2_norm_of(normx) - norm_l2_Ey = l2_norm_of(normy) - norm_l2_Bz = l2_norm_of(normz) - - # L2 errors - errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) - erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) - errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) - - error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex - error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey - error_l2_Bz = l2_norm_of(errz) / norm_l2_Bz + # Electric field, x component + fig = plot_field_and_error(r'E^x', tend, x, y, Ex_values, Ex_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ex_T_{rp_str}.png') + plt.close(fig) # fig.clf() - print() - print('L2 norm of rel. error on Ex(t,x,y) at final time: {:.2e}'.format(error_l2_Ex)) - print('L2 norm of rel. error on Ey(t,x,y) at final time: {:.2e}'.format(error_l2_Ey)) - print('L2 norm of rel. error on Bz(t,x,y) at final time: {:.2e}'.format(error_l2_Bz)) + # Electric field, y component + fig = plot_field_and_error(r'E^y', tend, x, y, Ey_values, Ey_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ey_T_{rp_str}.png') + plt.close(fig) # fig.clf() - if plot_final: - # Plot exact and approximate solution at final time - fig1, axs = plt.subplots(3, 3, figsize=(12, 12)) - im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) - im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) - im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) - im3 = axs[1, 0].contourf(x, y, Ex_values, 50) - im4 = axs[1, 1].contourf(x, y, Ey_values, 50) - im5 = axs[1, 2].contourf(x, y, Bz_values, 50) - im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) - im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) - im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) - axs[0, 0].set_title(r'$E^x$ at t = {:10.3e}'.format(t)) - axs[0, 1].set_title(r'$E^y$ at t = {:10.3e}'.format(t)) - axs[0, 2].set_title(r'$B$ at t = {:10.3e}'.format(t)) - axs[1, 0].set_title(r'$E_h^x$ at t = {:10.3e}'.format(t)) - axs[1, 1].set_title(r'$E_h^y$ at t = {:10.3e}'.format(t)) - axs[1, 2].set_title(r'$B_h$ at t = {:10.3e}'.format(t)) - axs[2, 0].set_title(r'$E^x - E^x_h$ at t = {:10.3e}'.format(t)) - axs[2, 1].set_title(r'$E^y - E^y_h$ at t = {:10.3e}'.format(t)) - axs[2, 2].set_title(r'$B - B_h$ at t = {:10.3e}'.format(t)) - for i in range(3): - for j in range(3): - axs[i, j].plot(*gridlines[0], color='k') - axs[i, j].plot(*gridlines[1], color='k') - axs[i, j].set_xlabel('x', fontsize=14) - axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') - axs[i, j].set_aspect('equal') - add_colorbar(im0, axs[0, 0]) - add_colorbar(im1, axs[0, 1]) - add_colorbar(im2, axs[0, 2]) - add_colorbar(im3, axs[1, 0]) - add_colorbar(im4, axs[1, 1]) - add_colorbar(im5, axs[1, 2]) - add_colorbar(im6, axs[2, 0]) - add_colorbar(im7, axs[2, 1]) - add_colorbar(im8, axs[2, 2]) - fig1.suptitle('Compare Exact Solution and Approximate solution at final time') - fig1.tight_layout() - - # fields along s, final time - plot_fields_along_s(tstr='T') # , j0=0, j1=ncells[1]//2) - - # Electric field, x component - fig = plot_field_and_error(r'E^x', tend, x, y, Ex_values, Ex_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ex_T_{rp_str}.png') - plt.close(fig) # fig.clf() - - # Electric field, y component - fig = plot_field_and_error(r'E^y', tend, x, y, Ey_values, Ey_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ey_T_{rp_str}.png') - plt.close(fig) # fig.clf() - - # Magnetic field, z component - fig = plot_field_and_error(r'B^z', tend, x, y, Bz_values, Bz_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Bz_T_{rp_str}.png') - plt.close(fig) + # Magnetic field, z component + fig = plot_field_and_error(r'B^z', tend, x, y, Bz_values, Bz_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Bz_T_{rp_str}.png') + plt.close(fig) return locals() From cc500f8c43ac81f6758b0141afd99f9ba492fd9d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 24 Nov 2025 14:30:25 +0100 Subject: [PATCH 030/133] Plotting only for root process - Export Ex, Ey, Bz at initial and final times - Import fields for plotting with the root process (initial and final times) - Comment out plotting at intermediate time steps (animation) --- psydac/feec/polar/examples/maxwell_2d.py | 341 +++++++++++++---------- 1 file changed, 189 insertions(+), 152 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 307d0066d..6b20734ed 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -11,6 +11,7 @@ import matplotlib.pyplot as plt +from psydac.fem.basic import FemField from utils_congapol import print_map_polar_coeffs, check_regular_ring_map @@ -641,6 +642,15 @@ def run_study_L2_proj(): e = E_log.coeffs b = B_log.coeffs + V1x, V1y = V1.spaces + Ex_field = FemField(V1x, coeffs=e[0]) + Ey_field = FemField(V1y, coeffs=e[1]) + B_field = FemField(V2, coeffs=b) + V1x.export_fields('Ex.h5', Ex_field=Ex_field) + V1y.export_fields('Ey.h5', Ey_field=Ey_field) + V2.export_fields('B.h5', B_field=B_field) + + if study == 'maxwell_wave': D1.dot(e, out=b) @@ -785,94 +795,107 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): # ... # Plot initial conditions # TODO: improve - if study_maxwell: - for i, x1i in enumerate(x1[:, 0]): - for j, x2j in enumerate(x2[0, :]): - Ex_values[i, j], Ey_values[i, j] = \ - push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + if mpi_rank == 0: + if use_spline_mapping: + geometry = Geometry(filename='geo.h5') + domain_h_serial = discretize(domain, filename='geo.h5') + F_serial = [*domain_h_serial.mappings.values()].pop() + derham_h_serial = discretize(derham, domain_h_serial, degree=degree) + V0_s, V1_s, V2_s = derham_h_serial.spaces + V1_sx, V1_sy = V1_s.spaces + print(V1_sx.coeff_space.parallel) + Ex_serial, = V1_sx.import_fields('Ex.h5', 'Ex_field') + Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') + B_serial, = V2_s.import_fields('B.h5', 'B_field') + if study_maxwell: + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) + + Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) - Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - # fields along s for fixed theta - plot_fields_along_s(tstr='t0') # , j0=0, j1=ncells[1]//2) + # fields along s for fixed theta + #plot_fields_along_s(tstr='t0') # , j0=0, j1=ncells[1]//2) - # Electric field, x component - fig = plot_field_and_error(r'E^x', 0, x, y, Ex_values, Ex_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ex_t0_{rp_str}.png') - plt.close(fig) - # fig.clf() + # Electric field, x component + fig = plot_field_and_error(r'E^x', 0, x, y, Ex_values, Ex_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ex_t0_{rp_str}.png') + plt.close(fig) + # fig.clf() - # Electric field, y component - fig = plot_field_and_error(r'E^y', 0, x, y, Ey_values, Ey_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ey_t0_{rp_str}.png') - plt.close(fig) - # fig.clf() + # Electric field, y component + fig = plot_field_and_error(r'E^y', 0, x, y, Ey_values, Ey_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Ey_t0_{rp_str}.png') + plt.close(fig) + # fig.clf() - # fig3.show() + # fig3.show() - # Magnetic field, z component - fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Bz_t0_{rp_str}.png') - plt.close(fig) + # Magnetic field, z component + fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) + if show_figs: + fig.show() + else: + fig.savefig(f'{visdir}/Bz_t0_{rp_str}.png') + plt.close(fig) - if show_figs: - # Plot exact and approximate solutions at t = 0 - fig, axs = plt.subplots(3, 3, figsize=(12, 12)) - im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) - im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) - im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) - im3 = axs[1, 0].contourf(x, y, Ex_values, 50) - im4 = axs[1, 1].contourf(x, y, Ey_values, 50) - im5 = axs[1, 2].contourf(x, y, Bz_values, 50) - im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) - im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) - im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) - axs[0, 0].set_title(r'$E^x$ at t = 0') - axs[0, 1].set_title(r'$E^y$ at t = 0') - axs[0, 2].set_title(r'$B^z$ at t = 0') - axs[1, 0].set_title(r'$E_h^x$ at t = 0') - axs[1, 1].set_title(r'$E_h^y$ at t = 0') - axs[1, 2].set_title(r'$B_h^z$ at t = 0') - axs[2, 0].set_title(r'$E^x - E_h^x$ at t = 0') - axs[2, 1].set_title(r'$E^y - E_h^y$ at t = 0') - axs[2, 2].set_title(r'$B^z - B_h^z$ at t = 0') - for i in range(3): - for j in range(3): - axs[i, j].plot(*gridlines[0], color='k') - axs[i, j].plot(*gridlines[1], color='k') - axs[i, j].set_xlabel('x', fontsize=14) - axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') - axs[i, j].set_aspect('equal') - add_colorbar(im0, axs[0, 0]) - add_colorbar(im1, axs[0, 1]) - add_colorbar(im2, axs[0, 2]) - add_colorbar(im3, axs[1, 0]) - add_colorbar(im4, axs[1, 1]) - add_colorbar(im5, axs[1, 2]) - add_colorbar(im6, axs[2, 0]) - add_colorbar(im7, axs[2, 1]) - add_colorbar(im8, axs[2, 2]) - fig.suptitle('Compare Exact Solution and Approximate solution at initial time') - fig.tight_layout() - - # Need a small pause to show the plot of the initial condition - plt.pause(.1) + if show_figs: + # Plot exact and approximate solutions at t = 0 + fig, axs = plt.subplots(3, 3, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) + im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) + im3 = axs[1, 0].contourf(x, y, Ex_values, 50) + im4 = axs[1, 1].contourf(x, y, Ey_values, 50) + im5 = axs[1, 2].contourf(x, y, Bz_values, 50) + im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) + im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) + im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) + axs[0, 0].set_title(r'$E^x$ at t = 0') + axs[0, 1].set_title(r'$E^y$ at t = 0') + axs[0, 2].set_title(r'$B^z$ at t = 0') + axs[1, 0].set_title(r'$E_h^x$ at t = 0') + axs[1, 1].set_title(r'$E_h^y$ at t = 0') + axs[1, 2].set_title(r'$B_h^z$ at t = 0') + axs[2, 0].set_title(r'$E^x - E_h^x$ at t = 0') + axs[2, 1].set_title(r'$E^y - E_h^y$ at t = 0') + axs[2, 2].set_title(r'$B^z - B_h^z$ at t = 0') + for i in range(3): + for j in range(3): + axs[i, j].plot(*gridlines[0], color='k') + axs[i, j].plot(*gridlines[1], color='k') + axs[i, j].set_xlabel('x', fontsize=14) + axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') + axs[i, j].set_aspect('equal') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[0, 2]) + add_colorbar(im3, axs[1, 0]) + add_colorbar(im4, axs[1, 1]) + add_colorbar(im5, axs[1, 2]) + add_colorbar(im6, axs[2, 0]) + add_colorbar(im7, axs[2, 1]) + add_colorbar(im8, axs[2, 2]) + fig.suptitle('Compare Exact Solution and Approximate solution at initial time') + fig.tight_layout() + + # Need a small pause to show the plot of the initial condition + plt.pause(.1) # L2 norms (of ref solution) normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) @@ -986,81 +1009,95 @@ def Strang_update(dtau): # ... # Animation and diags - if plot_interval and (ts % plot_interval == 0 or ts == nsteps): - - # project to conforming space to apply posh-forwards - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) # TO TEST: is this necessary? try to comment - # ... - # TODO: improve - for i, x1i in enumerate(x1[:, 0]): - for j, x2j in enumerate(x2[0, :]): - - Ex_values[i, j], Ey_values[i, j] = \ - push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) - - Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - # Bz_values[i, j] = B(x1i, x2j) - - if use_logical_sol: - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) - - Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - - else: - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - # ... - - # max norm - max_Ex = abs(Ex_values).max() - max_Ey = abs(Ey_values).max() - max_Bz = abs(Bz_values).max() - print() - print('Max-norm of Ex(t,x): {:.2e}'.format(max_Ex)) - print('Max-norm of Ey(t,x): {:.2e}'.format(max_Ey)) - print('Max-norm of Bz(t,x): {:.2e}'.format(max_Bz)) - - # if show_figs: - # # Update plot - # update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) - # update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) - # update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) - # plt.pause(0.1) - if not show_figs: - fig = plot_field_and_error(r'E^x', t, x, y, Ex_values, Ex_ex_values, *gridlines) - fig.savefig(f'{visdir}/Ex_{ts}_{rp_str}.png') - # fig.clf() - plt.close(fig) - - fig = plot_field_and_error(r'E^y', t, x, y, Ey_values, Ey_ex_values, *gridlines) - fig.savefig(f'{visdir}/Ey_{ts}_{rp_str}.png') - # fig.clf() - plt.close(fig) - - fig = plot_field_and_error(r'B^z', t, x, y, Bz_values, Bz_ex_values, *gridlines) - fig.savefig(f'{visdir}/Bz_{ts}_{rp_str}.png') - plt.close(fig) + # if plot_interval and (ts % plot_interval == 0 or ts == nsteps): + # # project to conforming space to apply posh-forwards + # P1.dot(e.copy(), out=e) + # P2.dot(b.copy(), out=b) # TO TEST: is this necessary? try to comment + # # ... + # # TODO: improve + # for i, x1i in enumerate(x1[:, 0]): + # for j, x2j in enumerate(x2[0, :]): + # + # Ex_values[i, j], Ey_values[i, j] = \ + # push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + # + # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + # # Bz_values[i, j] = B(x1i, x2j) + # + # if use_logical_sol: + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + # + # Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) + # + # else: + # xij, yij = F(x1i, x2j) + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + # + # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + # # ... + # + # # max norm + # max_Ex = abs(Ex_values).max() + # max_Ey = abs(Ey_values).max() + # max_Bz = abs(Bz_values).max() + # print() + # print('Max-norm of Ex(t,x): {:.2e}'.format(max_Ex)) + # print('Max-norm of Ey(t,x): {:.2e}'.format(max_Ey)) + # print('Max-norm of Bz(t,x): {:.2e}'.format(max_Bz)) + # + # # if show_figs: + # # # Update plot + # # update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) + # # update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) + # # update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) + # # plt.pause(0.1) + # if not show_figs: + # fig = plot_field_and_error(r'E^x', t, x, y, Ex_values, Ex_ex_values, *gridlines) + # fig.savefig(f'{visdir}/Ex_{ts}_{rp_str}.png') + # # fig.clf() + # plt.close(fig) + # + # fig = plot_field_and_error(r'E^y', t, x, y, Ey_values, Ey_ex_values, *gridlines) + # fig.savefig(f'{visdir}/Ey_{ts}_{rp_str}.png') + # # fig.clf() + # plt.close(fig) + # + # fig = plot_field_and_error(r'B^z', t, x, y, Bz_values, Bz_ex_values, *gridlines) + # fig.savefig(f'{visdir}/Bz_{ts}_{rp_str}.png') + # plt.close(fig) print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) N = 10 V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) - if not plot_interval: - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + # if not plot_interval: + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) + + Ex_field = FemField(V1x, coeffs=e[0]) + Ey_field = FemField(V1y, coeffs=e[1]) + B_field = FemField(V2, coeffs=b) + V1x.export_fields('Ex_final.h5', Ex_field=Ex_field) + V1y.export_fields('Ey_final.h5', Ey_field=Ey_field) + V2.export_fields('B_final.h5', B_field=B_field) + print("exported fields at final time") + + if mpi_rank == 0: + Ex_serial, = V1_sx.import_fields('Ex_final.h5', 'Ex_field') + Ey_serial, = V1_sy.import_fields('Ey_final.h5', 'Ey_field') + B_serial, = V2_s.import_fields('B_final.h5', 'B_field') + print("imported fields at final time") + for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): Ex_values[i, j], Ey_values[i, j] = \ - push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) + push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) - Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) + Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) # Bz_values[i, j] = B(x1i, x2j) if use_logical_sol: @@ -1079,13 +1116,13 @@ def Strang_update(dtau): # ... # Error at final time - error_Ex = abs(Ex_ex_values - Ex_values).max() - error_Ey = abs(Ey_ex_values - Ey_values).max() - error_Bz = abs(Bz_ex_values - Bz_values).max() - print() - print('Max-norm of error on Ex(t,x) at final time: {:.2e}'.format(error_Ex)) - print('Max-norm of error on Ey(t,x) at final time: {:.2e}'.format(error_Ey)) - print('Max-norm of error on Bz(t,x) at final time: {:.2e}'.format(error_Bz)) + error_Ex = abs(Ex_ex_values - Ex_values).max() + error_Ey = abs(Ey_ex_values - Ey_values).max() + error_Bz = abs(Bz_ex_values - Bz_values).max() + print() + print('Max-norm of error on Ex(t,x) at final time: {:.2e}'.format(error_Ex)) + print('Max-norm of error on Ey(t,x) at final time: {:.2e}'.format(error_Ey)) + print('Max-norm of error on Bz(t,x) at final time: {:.2e}'.format(error_Bz)) # L2 norms (of ref solution) normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) @@ -1110,7 +1147,7 @@ def Strang_update(dtau): print('L2 norm of rel. error on Ey(t,x,y) at final time: {:.2e}'.format(error_l2_Ey)) print('L2 norm of rel. error on Bz(t,x,y) at final time: {:.2e}'.format(error_l2_Bz)) - if plot_final: + if plot_final and mpi_rank == 0: # Plot exact and approximate solution at final time fig1, axs = plt.subplots(3, 3, figsize=(12, 12)) im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) @@ -1151,7 +1188,7 @@ def Strang_update(dtau): fig1.tight_layout() # fields along s, final time - plot_fields_along_s(tstr='T') # , j0=0, j1=ncells[1]//2) + # plot_fields_along_s(tstr='T') # , j0=0, j1=ncells[1]//2) # Electric field, x component fig = plot_field_and_error(r'E^x', tend, x, y, Ex_values, Ex_ex_values, *gridlines) From b04892982058cadcd3e530645dc503cd7bb6fd8f Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 25 Nov 2025 14:58:32 +0100 Subject: [PATCH 031/133] remove use_logical_solution --- psydac/feec/polar/examples/maxwell_2d.py | 88 ++++++------------------ 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 6b20734ed..8aed69736 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -266,20 +266,9 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, else: exact_solution = CircularCavitySolution(R=R, c=c, m=m, n=n, scale=scale) - use_logical_sol = (shift_D == 0) - - use_logical_sol = False ## TODO: USE AS DEFAULT ! - print(f'[use_logical_sol = {use_logical_sol}]') - - # Exact fields, as callable functions of (t, s, theta) or (t, x, y) - if use_logical_sol: - Es_ex_t = exact_solution.Es_ex - Et_ex_t = exact_solution.Et_ex - B_log_ex_t = exact_solution.B_ex - else: - Ex_ex_t = exact_solution.Ex_ex - Ey_ex_t = exact_solution.Ey_ex - Bz_ex_t = exact_solution.Bz_ex + Ex_ex_t = exact_solution.Ex_ex + Ey_ex_t = exact_solution.Ey_ex + Bz_ex_t = exact_solution.Bz_ex # Logical domain: [0, R] x [0, 2pi] logical_bounds = [[0, R], [0, 2 * pi]] @@ -606,12 +595,6 @@ def run_study_L2_proj(): Pi0, Pi1, Pi2 = derham_h.projectors(nquads=[degree[0] + 10, degree[1] + 10]) # Geometric Projectors - # if use_logical_sol: - # As the analytical solution is already expressed in the logical domain we - # do not perform the pullback in Pi. To do that we cancel the mapping from - # derham_h. - # raise ValueError('discard this case, it should not be used anymore') - # derham_h._mapping = None # Time integration setup # -------------------------------------------------------------------------- @@ -620,23 +603,14 @@ def run_study_L2_proj(): if study_maxwell: # Callable exact fields - if use_logical_sol: - Es_ex = lambda t: (lambda eta1, eta2, t0=t: Es_ex_t(t0, eta1, eta2)) - Et_ex = lambda t: (lambda eta1, eta2, t0=t: Et_ex_t(t0, eta1, eta2)) - B_log_ex = lambda t: (lambda eta1, eta2, t0=t: B_log_ex_t(t0, eta1, eta2)) - # Initial conditions, discrete fields -- here no pull-back because mapping removed by hand... - E_log = Pi1((Es_ex(t), Et_ex(t))) - B_log = Pi2(B_log_ex(t)) + Ex_ex = lambda t: (lambda x, y, t0=t: Ex_ex_t(t0, x, y)) + Ey_ex = lambda t: (lambda x, y, t0=t: Ey_ex_t(t0, x, y)) + Bz_ex = lambda t: (lambda x, y, t0=t: Bz_ex_t(t0, x, y)) - else: - Ex_ex = lambda t: (lambda x, y, t0=t: Ex_ex_t(t0, x, y)) - Ey_ex = lambda t: (lambda x, y, t0=t: Ey_ex_t(t0, x, y)) - Bz_ex = lambda t: (lambda x, y, t0=t: Bz_ex_t(t0, x, y)) - - # Initial conditions, discrete fields -- here with a pull-back in the projections - E_log = Pi1((Ex_ex(t), Ey_ex(t))) - B_log = Pi2(Bz_ex(t)) + # Initial conditions, discrete fields -- here with a pull-back in the projections + E_log = Pi1((Ex_ex(t), Ey_ex(t))) + B_log = Pi2(Bz_ex(t)) # Initial conditions, spline coefficients e = E_log.coeffs @@ -995,17 +969,11 @@ def Strang_update(dtau): # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) # # Bz_values[i, j] = B(x1i, x2j) - # if use_logical_sol: - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) - - # Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - # else: - # xij, yij = F(x1i, x2j) - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + # xij, yij = F(x1i, x2j) + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # ... # Animation and diags @@ -1024,18 +992,11 @@ def Strang_update(dtau): # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) # # Bz_values[i, j] = B(x1i, x2j) # - # if use_logical_sol: - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) + # xij, yij = F(x1i, x2j) + # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) # - # Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - # - # else: - # xij, yij = F(x1i, x2j) - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - # - # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # # ... # # # max norm @@ -1100,18 +1061,11 @@ def Strang_update(dtau): Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) # Bz_values[i, j] = B(x1i, x2j) - if use_logical_sol: - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - push_2d_hcurl(Es_ex(t), Et_ex(t), x1i, x2j, F) - - Bz_ex_values[i, j] = push_2d_l2(B_log_ex(t), x1i, x2j, F) - - else: - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # ... From 1e3e31c6daab6f5f1478c607df0ad655cf9f5232 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 26 Nov 2025 11:56:18 +0100 Subject: [PATCH 032/133] fix error (exporting fields before projecting), remove comments --- psydac/feec/polar/examples/maxwell_2d.py | 106 +++++------------------ 1 file changed, 20 insertions(+), 86 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 8aed69736..bceef0cb7 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -610,12 +610,22 @@ def run_study_L2_proj(): # Initial conditions, discrete fields -- here with a pull-back in the projections E_log = Pi1((Ex_ex(t), Ey_ex(t))) + E_log.coeffs.update_ghost_regions() B_log = Pi2(Bz_ex(t)) + B_log.coeffs.update_ghost_regions() # Initial conditions, spline coefficients e = E_log.coeffs b = B_log.coeffs + + if study == 'maxwell_wave': + D1.dot(e, out=b) + + # Conga Projection + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) + V1x, V1y = V1.spaces Ex_field = FemField(V1x, coeffs=e[0]) Ey_field = FemField(V1y, coeffs=e[1]) @@ -625,13 +635,6 @@ def run_study_L2_proj(): V2.export_fields('B.h5', B_field=B_field) - if study == 'maxwell_wave': - D1.dot(e, out=b) - - # Conga Projection - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) - if use_scipy: print(" -------------- SCIPY operators ------------ ") @@ -782,20 +785,20 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): Ex_serial, = V1_sx.import_fields('Ex.h5', 'Ex_field') Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') B_serial, = V2_s.import_fields('B.h5', 'B_field') - if study_maxwell: - for i, x1i in enumerate(x1[:, 0]): - for j, x2j in enumerate(x2[0, :]): - Ex_values[i, j], Ey_values[i, j] = \ - push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): - Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + xij, yij = F(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # fields along s for fixed theta #plot_fields_along_s(tstr='t0') # , j0=0, j1=ncells[1]//2) @@ -960,75 +963,6 @@ def Strang_update(dtau): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - # for i, x1i in enumerate(x1[:, 0]): - # for j, x2j in enumerate(x2[0, :]): - - # Ex_values[i, j], Ey_values[i, j] = \ - # push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) - - # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - # # Bz_values[i, j] = B(x1i, x2j) - - # xij, yij = F(x1i, x2j) - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - - # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - - # ... - # Animation and diags - # if plot_interval and (ts % plot_interval == 0 or ts == nsteps): - # # project to conforming space to apply posh-forwards - # P1.dot(e.copy(), out=e) - # P2.dot(b.copy(), out=b) # TO TEST: is this necessary? try to comment - # # ... - # # TODO: improve - # for i, x1i in enumerate(x1[:, 0]): - # for j, x2j in enumerate(x2[0, :]): - # - # Ex_values[i, j], Ey_values[i, j] = \ - # push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1i, x2j, F) - # - # Bz_values[i, j] = push_2d_l2(B_log, x1i, x2j, F) - # # Bz_values[i, j] = B(x1i, x2j) - # - # xij, yij = F(x1i, x2j) - # Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - # Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - # - # Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - # # ... - # - # # max norm - # max_Ex = abs(Ex_values).max() - # max_Ey = abs(Ey_values).max() - # max_Bz = abs(Bz_values).max() - # print() - # print('Max-norm of Ex(t,x): {:.2e}'.format(max_Ex)) - # print('Max-norm of Ey(t,x): {:.2e}'.format(max_Ey)) - # print('Max-norm of Bz(t,x): {:.2e}'.format(max_Bz)) - # - # # if show_figs: - # # # Update plot - # # update_plot(fig2, t, x, y, Ex_values, Ex_ex_values) - # # update_plot(fig3, t, x, y, Ey_values, Ey_ex_values) - # # update_plot(fig4, t, x, y, Bz_values, Bz_ex_values) - # # plt.pause(0.1) - # if not show_figs: - # fig = plot_field_and_error(r'E^x', t, x, y, Ex_values, Ex_ex_values, *gridlines) - # fig.savefig(f'{visdir}/Ex_{ts}_{rp_str}.png') - # # fig.clf() - # plt.close(fig) - # - # fig = plot_field_and_error(r'E^y', t, x, y, Ey_values, Ey_ex_values, *gridlines) - # fig.savefig(f'{visdir}/Ey_{ts}_{rp_str}.png') - # # fig.clf() - # plt.close(fig) - # - # fig = plot_field_and_error(r'B^z', t, x, y, Bz_values, Bz_ex_values, *gridlines) - # fig.savefig(f'{visdir}/Bz_{ts}_{rp_str}.png') - # plt.close(fig) - print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) N = 10 From 4687a24e7a13bf519e0bbcd7fc04b55f07c4e5aa Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 1 Dec 2025 14:32:56 +0100 Subject: [PATCH 033/133] create grid only with root process --- psydac/feec/polar/examples/maxwell_2d.py | 76 +++++++++++++----------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index bceef0cb7..03e0ca193 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -682,42 +682,6 @@ def run_study_L2_proj(): # VISUALIZATION SETUP # ============================================================================== - # Logical and physical grids - grid_x1 = derham_h.V0.breaks[0] - grid_x2 = derham_h.V0.breaks[1] - # Fix division by zero without taking care of the limit as s --> 0 - grid_x1[0] = 1e-20 - - # Very fine grids for evaluation of solution - N = 5 - x1 = refine_array_1d(grid_x1, N) - x2 = refine_array_1d(grid_x2, N) - - x1, x2 = np.meshgrid(x1, x2, indexing='ij') - x = np.empty_like(x1) - y = np.empty_like(x1) - print(x1.shape) - print(x2.shape) - - for i in range(x1.shape[0]): - for j in range(x1.shape[1]): - # print(f'i = {i}') - # print(f'x1i = {x1i}') - # print(f'x2i = {x2i}') - # for x1i, x2i in zip(x1, x2): - x[i, j], y[i, j] = F(x1[i, j], x2[i, j]) - - gridlines_x1 = (x[:, ::N], y[:, ::N]) - gridlines_x2 = (x[::N, :].T, y[::N, :].T) - gridlines = (gridlines_x1, gridlines_x2) - - Ex_ex_values = np.empty_like(x1) - Ey_ex_values = np.empty_like(x1) - Bz_ex_values = np.empty_like(x1) - - Ex_values = np.empty_like(x1) - Ey_values = np.empty_like(x1) - Bz_values = np.empty_like(x1) if study_L2_proj: run_study_L2_proj() @@ -786,6 +750,38 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') B_serial, = V2_s.import_fields('B.h5', 'B_field') + grid_x1 = V0_s.breaks[0] + grid_x2 = V0_s.breaks[1] + # Fix division by zero without taking care of the limit as s --> 0 + grid_x1[0] = 1e-15 + + # Very fine grids for evaluation of solution + N = 5 + x1 = refine_array_1d(grid_x1, N) + x2 = refine_array_1d(grid_x2, N) + + x1, x2 = np.meshgrid(x1, x2, indexing='ij') + x = np.empty_like(x1) + y = np.empty_like(x1) + print(x1.shape) + print(x2.shape) + + for i in range(x1.shape[0]): + for j in range(x1.shape[1]): + x[i, j], y[i, j] = F_serial(x1[i, j], x2[i, j]) + + gridlines_x1 = (x[:, ::N], y[:, ::N]) + gridlines_x2 = (x[::N, :].T, y[::N, :].T) + gridlines = (gridlines_x1, gridlines_x2) + + Ex_ex_values = np.empty_like(x1) + Ey_ex_values = np.empty_like(x1) + Bz_ex_values = np.empty_like(x1) + + Ex_values = np.empty_like(x1) + Ey_values = np.empty_like(x1) + Bz_values = np.empty_like(x1) + for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): @@ -960,6 +956,9 @@ def Strang_update(dtau): t += dt # diag + e.update_ghost_regions() + b.update_ghost_regions() + P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) @@ -986,6 +985,9 @@ def Strang_update(dtau): B_serial, = V2_s.import_fields('B_final.h5', 'B_field') print("imported fields at final time") + print("Serial coeff after import:") + print(Ey_serial.coeffs) + for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): @@ -1002,6 +1004,8 @@ def Strang_update(dtau): Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # ... + print("AFTER PUSH_2D_HCURL") + print(Ey_values[0]) # Error at final time error_Ex = abs(Ex_ex_values - Ex_values).max() From 516124eefc06589660ad297dab8123b2c76614c3 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 10 Dec 2025 09:38:23 +0100 Subject: [PATCH 034/133] tests for projection --- psydac/feec/polar/conga_projections.py | 55 ++++++--- psydac/feec/polar/examples/maxwell_2d.py | 142 ++++++++++++++++++++++- 2 files changed, 177 insertions(+), 20 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 127c30004..9831a38f6 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -175,6 +175,9 @@ def dtype(self): def dot(self, x, out=None): assert isinstance(x, StencilVector) + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + [s1, s2] = self.W1.coeff_space[0].starts [e1, e2] = self.W1.coeff_space[0].ends @@ -185,7 +188,7 @@ def dot(self, x, out=None): assert out.space is self.W1.coeff_space[0] y = out - y[:, s2:e2 + 1] = x[:, s2:e2 + 1] + y[s1:e1+1, s2:e2+1] = x[s1:e1+1, s2:e2+1] y.update_ghost_regions() return y @@ -248,10 +251,14 @@ def dtype(self): def dot(self, x, out=None): assert isinstance(x, StencilVector) + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + # The number of radial basis functions is one less than the number on the angular basis functions along dir x1 [s1, s2] = self.domain.starts [e1, e2] = self.domain.ends [n1, n2] = self.domain.npts + rank_at_polar_edge = (s1 == 0) if out is None: y = self.codomain.zeros() @@ -260,13 +267,14 @@ def dot(self, x, out=None): assert out.space is self.codomain y = out + y[s1:e1 + 1, s2:e2 + 1] = 0 + if self.transposed: - y[0, s2:e2 + 1] = np.subtract(np.roll(x[1, s2:e2 + 1], 1), x[1, s2:e2 + 1]) - y[1:, s2:e2 + 1] = 0 + if rank_at_polar_edge: + y[0, s2:e2 + 1] = np.subtract(np.roll(x[1, s2:e2 + 1], 1), x[1, s2:e2 + 1]) else: - y[0, s2:e2 + 1] = 0 - y[1, s2:e2 + 1] = np.subtract(np.roll(x[0, s2:e2 + 1], -1), x[0, s2:e2 + 1]) - y[2:, s2:e2 + 1] = 0 + if rank_at_polar_edge: + y[1, s2:e2 + 1] = np.subtract(np.roll(x[0, s2:e2 + 1], -1), x[0, s2:e2 + 1]) y.update_ghost_regions() return y @@ -350,9 +358,14 @@ def dtype(self): def dot(self, x, out=None): assert isinstance(x, StencilVector) + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + [s1, s2] = self.codomain.starts [e1, e2] = self.codomain.ends [n1, n2] = self.codomain.npts + rank_at_polar_edge = (s1 == 0) + rank_at_outer_edge = (e1 == n1 - 1) if out is None: y = self.W1.coeff_space[1].zeros() @@ -360,13 +373,14 @@ def dot(self, x, out=None): assert isinstance(out, StencilVector) assert out.space is self.codomain y = out + if rank_at_polar_edge: + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = 0 + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] # Identity block + else: + y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] - y[0, s2:e2 + 1] = 0 - y[1, s2:e2 + 1] = 0 - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] # Identity block - - if self.hbc: - if e1 == n1 - 1: + if self.hbc and rank_at_outer_edge: y[e1, :] = 0. y.update_ghost_regions() @@ -486,6 +500,7 @@ def dot(self, x, out=None): [s1, s2] = self.W2.coeff_space.starts [e1, e2] = self.W2.coeff_space.ends [n1, n2] = self.W2.coeff_space.npts + rank_at_polar_edge = (s1 == 0) if out is None: y = self.W2.coeff_space.zeros() @@ -495,15 +510,19 @@ def dot(self, x, out=None): y = out if self.transposed: - - y[0, s2:e2 + 1] = x[1, s2:e2 + 1] - y[1, s2:e2 + 1] = x[1, s2:e2 + 1] + if rank_at_polar_edge: + y[0, s2:e2 + 1] = x[1, s2:e2 + 1] + y[1, s2:e2 + 1] = x[1, s2:e2 + 1] + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] else: - y[0, s2:e2 + 1] = 0 - y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] + if rank_at_polar_edge: + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + else: + y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] y.update_ghost_regions() return y diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 03e0ca193..8f0ff220d 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -395,6 +395,132 @@ def l2_norm_of(f_log): f2_with_det = lambda eta1, eta2: f_log(eta1, eta2) ** 2 * np.sqrt(F.metric_det(eta1, eta2)) return np.sqrt(derham_h.V0.integral(f2_with_det)) + #TODO Remove this! It's only for debugging + def compare_serial_parallel(): + + if mpi_rank == 0: + if use_spline_mapping: + #geometry = Geometry(filename='geo.h5') + domain_h_s = discretize(domain, filename='geo.h5') + #F_serial = [*domain_h_serial.mappings.values()].pop() + derham_h_s = discretize(derham, domain_h_s, degree=degree) + V0_s, V1_s, V2_s = derham_h_s.spaces + V1_sx, V1_sy = V1_s.spaces + ex_field, = V1_sx.import_fields('Ex.h5', 'Ex_field') + ey_field, = V1_sy.import_fields('Ey.h5', 'Ey_field') + ex_data = ex_field.coeffs._data + ey_data = ey_field.coeffs._data + #B_serial, = V2_s.import_fields('B.h5', 'B_field') + + + if mpi_size == 1: + np.save('Ex_coeffs_ser.npy', ex_data) + else: + np.save('Ex_coeffs_par.npy', ex_data) + + if mpi_size == 1: + np.save('Ey_coeffs_ser.npy', ey_data) + else: + np.save('Ey_coeffs_par.npy', ey_data) + + Ex_coeffs_ser = np.load('Ex_coeffs_ser.npy') + Ex_coeffs_par = np.load('Ex_coeffs_par.npy') + Ey_coeffs_ser = np.load('Ey_coeffs_ser.npy') + Ey_coeffs_par = np.load('Ey_coeffs_par.npy') + print(Ex_coeffs_ser - Ex_coeffs_par) + print(np.linalg.norm(Ex_coeffs_ser - Ex_coeffs_par)) + print(Ey_coeffs_ser - Ey_coeffs_par) + print(np.linalg.norm(Ey_coeffs_ser - Ey_coeffs_par)) + a = input() + exit() + + def compare_P(): + + if mpi_rank == 0: + if use_spline_mapping: + #geometry = Geometry(filename='geo.h5') + domain_h_s = discretize(domain, filename='geo.h5') + #F_serial = [*domain_h_serial.mappings.values()].pop() + derham_h_s = discretize(derham, domain_h_s, degree=degree) + V0_s, V1_s, V2_s = derham_h_s.spaces + V1_sx, V1_sy = V1_s.spaces + print("NCELLS") + print(V1_sx.degree, V1_sx.ncells, V1_sx.nbasis) + print(V1_sy.degree, V1_sy.ncells, V1_sy.nbasis) + ex_field, = V1_sx.import_fields('Ex.h5', 'Ex_field') + ey_field, = V1_sy.import_fields('Ey.h5', 'Ey_field') + + ex_fieldP, = V1_sx.import_fields('ExP.h5', 'Ex_field') + ey_fieldP, = V1_sy.import_fields('EyP.h5', 'Ey_field') + + ex_fieldPP, = V1_sx.import_fields('ExPP.h5', 'Ex_field') + ey_fieldPP, = V1_sy.import_fields('EyPP.h5', 'Ey_field') + + ex_data = ex_field.coeffs._data + ey_data = ey_field.coeffs._data + ex_dataP = ex_fieldP.coeffs._data + ey_dataP = ey_fieldP.coeffs._data + ex_dataPP = ex_fieldPP.coeffs._data + ey_dataPP = ey_fieldPP.coeffs._data + #B_serial, = V2_s.import_fields('B.h5', 'B_field') + + + print(ey_data) + print(ey_dataP) + print(ex_data.shape) + print(ey_data.shape) + # print(ex_dataP - ex_dataPP) + # print(np.linalg.norm(ex_dataP - ex_dataPP)) + # print(ey_dataP - ey_dataPP) + # print(np.linalg.norm(ey_dataP - ey_dataPP)) + a = input() + exit() + + def test_P1(V1, P1): + + e = V1.coeff_space.zeros() + Ex, Ey = e[0], e[1] + + [s1, s2] = Ey.starts + [e1, e2] = Ey.ends + + # for i in range(s1, e1 + 1): + # for j in range(s2, e2 + 1): + # Ex[i, j] = 0.1 * i + 0.01 * j + # Ey[i, j] = -0.2 * i + 0.03 * j + print("RANK") + print(mpi_rank, s1, e1, s2, e2) + print(Ey._data) + Ey[0, 0] = 1 + + # e.update_ghost_regions() + Ex_field = FemField(V1x, coeffs=Ex) + Ey_field = FemField(V1y, coeffs=Ey) + V1x.export_fields('Ex.h5', Ex_field=Ex_field) + V1y.export_fields('Ey.h5', Ey_field=Ey_field) + + eP = V1.coeff_space.zeros() + P1.dot(e, out=eP) + ExP, EyP = eP[0], eP[1] + ExP.update_ghost_regions() + EyP.update_ghost_regions() + Ex_field = FemField(V1x, coeffs=ExP) + Ey_field = FemField(V1y, coeffs=EyP) + V1x.export_fields('ExP.h5', Ex_field=Ex_field) + V1y.export_fields('EyP.h5', Ey_field=Ey_field) + + P1.dot(eP, out=eP) + ExP, EyP = eP[0], eP[1] + ExP.update_ghost_regions() + EyP.update_ghost_regions() + Ex_field = FemField(V1x, coeffs=ExP) + Ey_field = FemField(V1y, coeffs=EyP) + V1x.export_fields('ExPP.h5', Ex_field=Ex_field) + V1y.export_fields('EyPP.h5', Ey_field=Ey_field) + compare_P() + + exit() + def run_study_L2_proj(): omega = 4 print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') @@ -626,7 +752,19 @@ def run_study_L2_proj(): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - V1x, V1y = V1.spaces + + V1x, V1y = V1.spaces #call them s, theta + #test_P1(V1, P1) + + # Ex_field = FemField(V1x, coeffs=e[0]) + # Ey_field = FemField(V1y, coeffs=e[1]) + # B_field = FemField(V2, coeffs=b) + # V1x.export_fields('Ex.h5', Ex_field=Ex_field) + # V1y.export_fields('Ey.h5', Ey_field=Ey_field) + # V2.export_fields('B.h5', B_field=B_field) + # compare_serial_parallel() + + V1x, V1y = V1.spaces #call them s, theta Ex_field = FemField(V1x, coeffs=e[0]) Ey_field = FemField(V1y, coeffs=e[1]) B_field = FemField(V2, coeffs=b) @@ -753,7 +891,7 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): grid_x1 = V0_s.breaks[0] grid_x2 = V0_s.breaks[1] # Fix division by zero without taking care of the limit as s --> 0 - grid_x1[0] = 1e-15 + grid_x1[0] = 1e-20 # Very fine grids for evaluation of solution N = 5 From 25efc2edf56974b7b742597f0a2a9c304dab8afa Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 15 Dec 2025 18:34:14 +0100 Subject: [PATCH 035/133] fix P1 for C0 --- psydac/feec/polar/conga_projections.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 9831a38f6..5217d599f 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -271,10 +271,11 @@ def dot(self, x, out=None): if self.transposed: if rank_at_polar_edge: - y[0, s2:e2 + 1] = np.subtract(np.roll(x[1, s2:e2 + 1], 1), x[1, s2:e2 + 1]) + y[0, s2:e2 + 1] = x[1, s2 - 1:e2] - x[1, s2:e2 + 1] else: if rank_at_polar_edge: - y[1, s2:e2 + 1] = np.subtract(np.roll(x[0, s2:e2 + 1], -1), x[0, s2:e2 + 1]) + # the cells are periodic in angular dim, so we use ghost regions + y[1, s2:e2 + 1] = x[0, s2 + 1:e2 + 2] - x[0, s2:e2 + 1] y.update_ghost_regions() return y @@ -381,7 +382,7 @@ def dot(self, x, out=None): y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] if self.hbc and rank_at_outer_edge: - y[e1, :] = 0. + y[e1, :] = 0. y.update_ghost_regions() return y From b50640e3121f4182d859f5428df8b65bbc095589 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 15 Dec 2025 18:35:25 +0100 Subject: [PATCH 036/133] fix P1 for C0 --- psydac/feec/polar/conga_projections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 5217d599f..687488384 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -377,7 +377,7 @@ def dot(self, x, out=None): if rank_at_polar_edge: y[0, s2:e2 + 1] = 0 y[1, s2:e2 + 1] = 0 - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] # Identity block + y[2:e1 + 1, s2:e2 + 1] = x[2:e1 + 1, s2:e2 + 1] # Identity block else: y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] From 858078cc7651f17b4a4cf871faf93944be98a4fb Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 15 Dec 2025 20:05:11 +0100 Subject: [PATCH 037/133] remove prints --- psydac/feec/polar/examples/maxwell_2d.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 8f0ff220d..429df02e5 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -752,16 +752,7 @@ def run_study_L2_proj(): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - - V1x, V1y = V1.spaces #call them s, theta #test_P1(V1, P1) - - # Ex_field = FemField(V1x, coeffs=e[0]) - # Ey_field = FemField(V1y, coeffs=e[1]) - # B_field = FemField(V2, coeffs=b) - # V1x.export_fields('Ex.h5', Ex_field=Ex_field) - # V1y.export_fields('Ey.h5', Ey_field=Ey_field) - # V2.export_fields('B.h5', B_field=B_field) # compare_serial_parallel() V1x, V1y = V1.spaces #call them s, theta @@ -883,7 +874,6 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): derham_h_serial = discretize(derham, domain_h_serial, degree=degree) V0_s, V1_s, V2_s = derham_h_serial.spaces V1_sx, V1_sy = V1_s.spaces - print(V1_sx.coeff_space.parallel) Ex_serial, = V1_sx.import_fields('Ex.h5', 'Ex_field') Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') B_serial, = V2_s.import_fields('B.h5', 'B_field') @@ -901,8 +891,6 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): x1, x2 = np.meshgrid(x1, x2, indexing='ij') x = np.empty_like(x1) y = np.empty_like(x1) - print(x1.shape) - print(x2.shape) for i in range(x1.shape[0]): for j in range(x1.shape[1]): @@ -1115,16 +1103,11 @@ def Strang_update(dtau): V1x.export_fields('Ex_final.h5', Ex_field=Ex_field) V1y.export_fields('Ey_final.h5', Ey_field=Ey_field) V2.export_fields('B_final.h5', B_field=B_field) - print("exported fields at final time") if mpi_rank == 0: Ex_serial, = V1_sx.import_fields('Ex_final.h5', 'Ex_field') Ey_serial, = V1_sy.import_fields('Ey_final.h5', 'Ey_field') B_serial, = V2_s.import_fields('B_final.h5', 'B_field') - print("imported fields at final time") - - print("Serial coeff after import:") - print(Ey_serial.coeffs) for i, x1i in enumerate(x1[:, 0]): for j, x2j in enumerate(x2[0, :]): @@ -1142,8 +1125,6 @@ def Strang_update(dtau): Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # ... - print("AFTER PUSH_2D_HCURL") - print(Ey_values[0]) # Error at final time error_Ex = abs(Ex_ex_values - Ex_values).max() From e7ef3748feabf9230e0c6c6eb64b31cd459d0f5f Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 15 Dec 2025 22:36:31 +0100 Subject: [PATCH 038/133] fix a bug when use_spline_mapping is False --- psydac/feec/polar/examples/maxwell_2d.py | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 429df02e5..fadf62fcd 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -301,28 +301,24 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - if use_spline_mapping: - - # Number of elements and spline degree - ne1, ne2 = ncells - p1, p2 = degree - - # Create uniform grid - grid_1 = np.linspace(*logical_bounds[0], num=ne1 + 1) - grid_2 = np.linspace(*logical_bounds[1], num=ne2 + 1) + # Number of elements and spline degree + ne1, ne2 = ncells + p1, p2 = degree - # Create 1D finite element spaces - V1 = SplineSpace(p1, grid=grid_1, periodic=False) - V2 = SplineSpace(p2, grid=grid_2, periodic=True) + # Create uniform grid + grid_1 = np.linspace(*logical_bounds[0], num=ne1 + 1) + grid_2 = np.linspace(*logical_bounds[1], num=ne2 + 1) - # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, [False, True], comm = mpi_comm) - V = TensorFemSpace(domain_decomposition, V1, V2) + # Create 1D finite element spaces + V1 = SplineSpace(p1, grid=grid_1, periodic=False) + V2 = SplineSpace(p2, grid=grid_2, periodic=True) - s1, s2 = V.coeff_space.starts - e1, e2 = V.coeff_space.ends + # Create 2D tensor product finite element space + domain_decomposition = DomainDecomposition(ncells, [False, True], comm = mpi_comm) + V = TensorFemSpace(domain_decomposition, V1, V2) # ==================== MAPPING & PHYSICAL DOMAIN ==============================# + if use_spline_mapping: # Create spline mapping by interpolation of analytical mapping map_analytic = mapping.get_callable_mapping() @@ -871,6 +867,10 @@ def plot_fields_along_s(tstr): # , j0=0, j1=0): geometry = Geometry(filename='geo.h5') domain_h_serial = discretize(domain, filename='geo.h5') F_serial = [*domain_h_serial.mappings.values()].pop() + else: + domain_h_serial = discretize(domain, ncells=ncells, periodic=[False, True]) + F_serial = F + derham_h_serial = discretize(derham, domain_h_serial, degree=degree) V0_s, V1_s, V2_s = derham_h_serial.spaces V1_sx, V1_sy = V1_s.spaces From 4051e6449aaa4e7a759e0be6565781b1ad6c847b Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Dec 2025 13:11:04 +0100 Subject: [PATCH 039/133] small changes --- psydac/feec/polar/conga_projections.py | 624 ++++++++++++++++++++++- psydac/feec/polar/examples/maxwell_2d.py | 8 +- 2 files changed, 626 insertions(+), 6 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 687488384..7d04c0b92 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -1,6 +1,7 @@ import numpy as np +from numpy import pi -from scipy.linalg import toeplitz +from scipy.linalg import toeplitz, matmul_toeplitz from scipy.sparse import coo_matrix, lil_matrix, spmatrix, eye as sp_eye from scipy.sparse.linalg import inv as sp_inv @@ -514,13 +515,15 @@ def dot(self, x, out=None): if rank_at_polar_edge: y[0, s2:e2 + 1] = x[1, s2:e2 + 1] y[1, s2:e2 + 1] = x[1, s2:e2 + 1] - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + y[2:e1 + 1, s2:e2 + 1] = x[2:e1 + 1, s2:e2 + 1] + else: + y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] else: if rank_at_polar_edge: y[0, s2:e2 + 1] = 0 y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + y[2:e1 + 1, s2:e2 + 1] = x[2:e1 + 1, s2:e2 + 1] else: y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] @@ -644,3 +647,618 @@ def dot(self, x, out=None): return y +# =============================================================================# +# C1 POLAR SPLINE COMPLEX (C1P) # +# =============================================================================# + +# --------- 0-FORMS CONGA PROJECTOR P0 ----------# +class C1CongaProjector0(LinearOperator): + """ + CONGA Projector P0 from the full spline space S^{p1, p2} on logical domain + to U0 the pre-polar 0-forms splines. The associate matrix is square as in + the CONGA approach we keep using the tensor B-spline basis, instead of the + polar basis of Toshniwal. P0 enforces coefficient relations to be in U0. + + Parameters: + ----------- + + W0 : TensorFemSpace + The full tensor product spline space S^{p1,p2} + + gamma : float + free parameter in the entries of P0. Any values provides a valid CONGA + prjector in U0. However, in order to have the commuting property + grad P0 u = P1 grad u + for u in Im(Pi0) and Pi0 the geometric projector on W0, we should set + gamma = 1 (default) + + transposed : Boolean + switch between P0 and P0 transposed (defalut is False) + + hbc : Boolean + switch on and off the imposition of homogeneous Dirichlet boundary + conditions (default is False) + """ + + def __init__(self, W0, *, gamma=1, transposed=False, hbc=False): + assert isinstance(W0, TensorFemSpace) + + self.gamma = gamma + self.W0 = W0 + self.transposed = transposed + self.hbc = hbc + + @property + def domain(self): + return self.W0.coeff_space + + @property + def codomain(self): + return self.W0.coeff_space + + @property + def dtype(self): + return float + + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + + [s1, s2] = self.W0.coeff_space.starts + [e1, e2] = self.W0.coeff_space.ends + [n1, n2] = self.W0.coeff_space.npts + theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case + + if out is None: + y = self.W0.coeff_space.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.W0.coeff_space + y = out + + if self.transposed: + x0 = np.average(x[0, s2:e2 + 1]) + x1 = np.average(x[1, s2:e2 + 1]) + y[0, s2:e2 + 1] = self.gamma * x0 + self.gamma * x1 + y[1, s2:e2 + 1] = (1 - self.gamma) * x0 + (1 - self.gamma) * x1 + \ + (2 / n2) * matmul_toeplitz(np.cos(theta[s2:e2 + 1] - theta[0]), x[1, s2:e2 + 1]) + else: + x0 = np.average(x[0, s2:e2 + 1]) + x1 = np.average(x[1, s2:e2 + 1]) + y[0, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 + y[1, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 + \ + (2 / n2) * matmul_toeplitz(np.cos(theta[s2:e2 + 1] - theta[0]), x[1, s2:e2 + 1]) + + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + + if self.hbc: + if e1 == n1 - 1: + y[e1, :] = 0. + + y.update_ghost_regions() + return y + + def transpose(self): + return C1CongaProjector0(self.W0, gamma=self.gamma, transposed=not self.transposed, hbc=self.hbc) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n1, n2] = self.W0.coeff_space.npts + theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case + + d_block1 = (self.gamma / n2) * np.ones(n2) + d_block2 = (1 - self.gamma) / n2 * np.ones(n2) + + data = np.concatenate((d_block1, d_block2)) + data = np.tile(data, n2) + + d_block1 = (self.gamma / n2) * np.ones((n2, n2)) + d_block2 = (1 - self.gamma) / n2 * np.ones((n2, n2)) + \ + 2 / n2 * toeplitz(np.cos(theta - theta[0])) + d_block = np.vstack([d_block1, d_block2]).ravel('F') + + cols = np.tile(np.arange(2 * n2), (2 * n2, 1)) + rows = cols.T + cols = cols.flatten() + rows = rows.flatten() + + if self.hbc: + data = np.concatenate((data, d_block, np.ones(n2 * (n1 - 3)))) + cols = np.concatenate((cols, np.arange(2 * n2, (n1 - 1) * n2))) + rows = np.concatenate((rows, np.arange(2 * n2, (n1 - 1) * n2))) + else: + data = np.concatenate((data, d_block, np.ones(n2 * (n1 - 2)))) + cols = np.concatenate((cols, np.arange(2 * n2, n1 * n2))) + rows = np.concatenate((rows, np.arange(2 * n2, n1 * n2))) + + P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) + P.eliminate_zeros() + print(P) + + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +# --------- 1-FORMS CONGA PROJECTOR P1 ----------# +# It is a BlockLinearOperator with 4 blocks Upper-Left (0, 0), Upper-Right (0, 1) +# Lower-Left (1, 0) and Lower-Right (1, 1). +# +# ______________________ +# | | | +# | (0,0) | (1,0) | +# |___________|__________| +# | | | +# | (1,0) | (1,1) | +# |___________|__________| + +class C1CongaProjector1_00(LinearOperator): + """ + Upper Left block of P1. + + Parameters: + ---------- + + W1 : VectorFemSpace (former ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + """ + + def __init__(self, W1, transposed=False): + # assert isinstance(W1, ProductFemSpace) + assert isinstance(W1, VectorFemSpace) + + self.W1 = W1 + self.transposed = transposed + + @property + def domain(self): + return self.W1.coeff_space[0] + + @property + def codomain(self): + return self.W1.coeff_space[0] + + @property + def dtype(self): + return float + + # Warning: this dot method has to be revised for mpi! + # the toeplitz multiplication requires all processes along the theta-dir to communicate. + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends + [n1, n2] = self.domain.npts + theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning not mpi + + if out is None: + y = self.codomain.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.codomain + y = out + + if self.transposed: + y[0, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), x[0, s2:e2 + 1]) + \ + x[1, s2:e2 + 1] - matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), + x[1, s2:e2 + 1]) + y[1, s2:e2 + 1] = x[1, s2:e2 + 1] + else: + y[0, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), x[0, s2:e2 + 1]) + y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] - y[0, s2:e2 + 1] + + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return C1CongaProjector1_00(self.W1, transposed=not self.transposed) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n01, n02] = self.domain.npts + theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! + + ntot = n01 * n02 + dtype = self.domain.dtype + P = lil_matrix((ntot, ntot), dtype=dtype) + + # c = (2/n02) * (np.cos(np.roll(theta, -1) - theta[0]) - np.cos(theta - theta[0])) + # r = (2/n02) * (np.cos(theta[1] - theta) - np.cos(theta[0] - theta)) + # p_block = 2/n02 * toeplitz(np.cos(theta - theta[0])) + + c = (2 / n02) * np.cos(theta - theta[0]) # first column + r = (2 / n02) * np.cos(theta[0] - theta) # first row + + p_block = toeplitz(c, r) + + P[:n02, :n02] = p_block + P[n02:2 * n02, :n02] += sp_eye(n02) # - p_block + P[n02:2 * n02, :n02] -= p_block + P[n02:, n02:] = sp_eye(ntot - n02) + + # p2 = p_block @ p_block + # print(f'p-p2 = {p_block - p2}') + # Imp = sp_eye(n02) - p_block + + # imp_b = P[n02:2*n02, :n02] + # print(f'check 2 = {Imp - imp_b}') + + # prod_b = Imp @ p_block + p_block @ Imp + # print(f'check 3 = {Imp - prod_b}') + + # print( f" ---- PU1_00 -> to sparse: {self.transposed} ---- ") + + # exit() + + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +class C1CongaProjector1_10(LinearOperator): + """ + Lower left block of P1. + + Parameters: + ----------- + + W1 : VectorFemSpace (former ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + """ + + def __init__(self, W1, transposed=False): + # assert isinstance(W1, ProductFemSpace) + assert isinstance(W1, VectorFemSpace) + + self.W1 = W1 + self.transposed = transposed + + @property + def domain(self): + if self.transposed: + return self.W1.coeff_space[1] + return self.W1.coeff_space[0] + + @property + def codomain(self): + if self.transposed: + return self.W1.coeff_space[0] + return self.W1.coeff_space[1] + + @property + def dtype(self): + return float + + # Warning: this dot method has to be revised for mpi! + # the toeplitz multiplication requires all processes along the theta-dir to communicate. + # We also use np.roll which should be changed with the use of ghost regions + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + # The number of radial basis functions is one less than + # the number on the angular basis functions + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends + [n1, n2] = self.domain.npts + theta = np.linspace(0, 2 * pi, n2, endpoint=False) + + if out is None: + y = self.codomain.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.codomain + y = out + + if self.transposed: + y[0, s2:e2 + 1] = np.subtract(np.roll(x[1, s2:e2 + 1], 1), x[1, s2:e2 + 1]) + y[0, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), y[0, s2:e2 + 1]) + y[1:, s2:e2 + 1] = 0 + else: + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), x[0, s2:e2 + 1]) + y[1, s2:e2 + 1] = np.subtract(np.roll(y[1, s2:e2 + 1], -1), y[1, s2:e2 + 1]) + y[2:, s2:e2 + 1] = 0 + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return C1CongaProjector1_10(self.W1, transposed=not self.transposed) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + if self.transposed: + domain_P1_10 = self.codomain + codomain_P1_10 = self.domain + else: + domain_P1_10 = self.domain + codomain_P1_10 = self.codomain + + [n01, n02] = domain_P1_10.npts # radial grid + [n11, n12] = codomain_P1_10.npts # angular grid + + theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! + + dtype = domain_P1_10.dtype + P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) + + c = (2 / n02) * (np.cos(np.roll(theta, -1) - theta[0]) - np.cos(theta - theta[0])) # correct ? + # c = (2/n02) * (np.cos(np.roll(theta, 1) - theta[0]) - np.cos(theta - theta[0])) + r = (2 / n02) * (np.cos(theta[1] - theta) - np.cos(theta[0] - theta)) + + q_block = toeplitz(c, r) + # print(f'domain V1_s: [n01, n02] = {[n01, n02]}') + # print(f'codomain V1_theta: [n11, n12] = {[n11, n12]}') + + # print(f'n01 * n02 + n12:n01 * n02 + 2 * n12 = {n01 * n02 + n12}:{n01 * n02 + 2 * n12}') + # print(f'P.shape = {P.shape}') + # print(f'q_block.shape = {q_block.shape}') + # print(f'P[n01 * n02 + n12:n01 * n02 + 2 * n12, :n12].shape = {P[n01 * n02 + n12:n01 * n02 + 2 * n12, :n12].shape}') + # P[n01 * n02 + n12:n01 * n02 + 2 * n12, :n12] = q_block # seems wrong + P[n12:2 * n12, :n02] = q_block + + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +class C1CongaProjector1_11(LinearOperator): + """ + Lower right block of P1. + + Parameters: + ----------- + + W1 : VectorFemSpace (former ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + + hbc : Boolean + Switch on and off the imposition of homogeneous Dirichlet boundary + conditions on the tangential (angular) direction (default is False) + """ + + def __init__(self, W1, transposed=False, hbc=False): + # assert isinstance(W1, ProductFemSpace) + assert isinstance(W1, VectorFemSpace) + + self.W1 = W1 + self.transposed = transposed + self.hbc = hbc + + @property + def domain(self): + return self.W1.coeff_space[1] + + @property + def codomain(self): + return self.W1.coeff_space[1] + + @property + def dtype(self): + return float + + # Warning: this dot method has to be revised for mpi! + # the toeplitz multiplication requires all processes along the theta-dir to communicate. + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends + [n1, n2] = self.domain.npts + + if out is None: + y = self.codomain.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.codomain + y = out + + # Both for P1 and P1 transposed + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = 0 + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + + if self.hbc: + if e1 == n1 - 1: + y[e1, :] = 0. + + y.update_ghost_regions() + return y + + def transpose(self, conjugate=False): + return C1CongaProjector1_11(self.W1, transposed=not self.transposed, hbc=self.hbc) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n01, n02] = self.domain.npts + [n11, n12] = self.codomain.npts + dtype = self.domain.dtype + + P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) + P[2 * n12:, 2 * n02:] = sp_eye((n11 - 2) * n12) + if self.hbc: + P[-n12:, -n02:] = 0 + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + +def new_C1CongaProjector1(W1, hbc=False): + assert isinstance(W1, VectorFemSpace) + assert W1.symbolic_space.kind.name == 'hcurl' + + T1 = W1.coeff_space + P1 = BlockLinearOperator(T1, T1) + P1[0, 0] = C1CongaProjector1_00(W1) + P1[1, 0] = C1CongaProjector1_10(W1) + P1[1, 1] = C1CongaProjector1_11(W1, hbc=hbc) + + return P1 + + +class C1CongaProjector1(BlockLinearOperator): + """ + CONGA Projector P1 from the full spline space S^{p1-1, p2} x S^{p1, p2-1} + on logical domain to U1 the pre-polar 1-forms splines. The associate matrix + is square as in the CONGA approach we keep using the tensor B-spline basis, + instead of the polar basis of Toshniwal. P1 enforces coefficient relations + to be in U1. + + Parameters: + ----------- + + W1 : VectorFemSpace (ProductFemSpace) + Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} + + transposed : Boolean + Switch between P1 and P1 transposed (default is False) + + hbc : Boolean + Switch on and off the imposition of homogeneous Dirichlet boundary + conditions on the tangential (angular) direction (default is False) + """ + + def __init__(self, W1, transposed=False, hbc=False): + assert isinstance(W1, VectorFemSpace) + assert W1.symbolic_space.kind.name == 'hcurl' + + T1 = W1.coeff_space + + super().__init__(T1, T1) + + self[0, 0] = C1CongaProjector1_00(W1, transposed=transposed) + self[1, 0] = C1CongaProjector1_10(W1, transposed=transposed) + # self[0, 1] = C1CongaProjector1_01(W1, transposed = transposed) + self[1, 1] = C1CongaProjector1_11(W1, transposed=transposed, hbc=hbc) + + self.W1 = W1 + self.transposed = transposed + self.hbc = hbc + + +# -------------- 2-FORMS CONGA PROJECTOR P2 ----------------# +class C1CongaProjector2(LinearOperator): + """ + CONGA Projector P2 from the full spline space S^{p1-1, p2-1} on logical + domain to U2, the pre-polar 2-forms splines. The associate matrix + is square, as in the CONGA approach we keep using the tensor B-spline basis, + instead of the polar basis of Toshniwal. P2 enforces coefficient relations + to be in U2. + + Parameters: + ----------- + + W2 : TensorFemSpace + Full tensor product spline space of the 2-forms S^{p1-1, p2-1} + + transposed : Boolean + Switch between P2 and P2 transposed (default is False) + """ + + def __init__(self, W2, transposed=False): + assert isinstance(W2, TensorFemSpace) + + self.W2 = W2 + self.transposed = transposed + + @property + def domain(self): + return self.W2.coeff_space + + @property + def codomain(self): + return self.W2.coeff_space + + @property + def dtype(self): + return float + + def dot(self, x, out=None): + assert isinstance(x, StencilVector) + + if not x.ghost_regions_in_sync: + x.update_ghost_regions() + + [s1, s2] = self.W2.coeff_space.starts + [e1, e2] = self.W2.coeff_space.ends + [n1, n2] = self.W2.coeff_space.npts + + if out is None: + y = self.W2.coeff_space.zeros() + else: + assert isinstance(out, StencilVector) + assert out.space is self.W2.coeff_space + y = out + + if self.transposed: + y[0, s2:e2 + 1] = x[1, s2:e2 + 1] + y[1, s2:e2 + 1] = x[1, s2:e2 + 1] + else: + y[0, s2:e2 + 1] = 0 + y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] + + y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + + y.update_ghost_regions() + return y + + def transpose(self): + return C1CongaProjector2(self.W2, transposed=not self.transposed) + + @property + def T(self): + return self.transpose() + + def tosparse(self): + + [n1, n2] = self.W2.coeff_space.npts + + data = np.ones(n1 * n2) + cols = np.arange(n1 * n2) + rows = np.tile(np.arange(n2, 2 * n2), 2) + rows = np.concatenate((rows, np.arange(2 * n2, n1 * n2))) + + P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) + P.eliminate_zeros() + + return P.T if self.transposed else P + + def toarray(self): + return self.tosparse().toarray() + + + diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index fadf62fcd..da2d73843 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -489,7 +489,9 @@ def test_P1(V1, P1): print(Ey._data) Ey[0, 0] = 1 - # e.update_ghost_regions() + e.update_ghost_regions() + print(Ex._data) + Ex_field = FemField(V1x, coeffs=Ex) Ey_field = FemField(V1y, coeffs=Ey) V1x.export_fields('Ex.h5', Ex_field=Ex_field) @@ -505,7 +507,7 @@ def test_P1(V1, P1): V1x.export_fields('ExP.h5', Ex_field=Ex_field) V1y.export_fields('EyP.h5', Ey_field=Ey_field) - P1.dot(eP, out=eP) + P1.dot(eP.copy(), out=eP) ExP, EyP = eP[0], eP[1] ExP.update_ghost_regions() EyP.update_ghost_regions() @@ -748,7 +750,6 @@ def run_study_L2_proj(): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - #test_P1(V1, P1) # compare_serial_parallel() V1x, V1y = V1.spaces #call them s, theta @@ -758,6 +759,7 @@ def run_study_L2_proj(): V1x.export_fields('Ex.h5', Ex_field=Ex_field) V1y.export_fields('Ey.h5', Ey_field=Ey_field) V2.export_fields('B.h5', B_field=B_field) + #test_P1(V1, P1) if use_scipy: From baf1a32d78e1b9b75ea4b8510baea63e965b0bbc Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Dec 2025 14:20:19 +0100 Subject: [PATCH 040/133] C1 projections in serial --- psydac/feec/polar/conga_projections.py | 40 +++++++++--------------- psydac/feec/polar/examples/poisson_2d.py | 17 +++++----- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 7d04c0b92..ac0fda537 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -652,7 +652,7 @@ def dot(self, x, out=None): # =============================================================================# # --------- 0-FORMS CONGA PROJECTOR P0 ----------# -class C1CongaProjector0(LinearOperator): +class C1PolarProjection_V0(LinearOperator): """ CONGA Projector P0 from the full spline space S^{p1, p2} on logical domain to U0 the pre-polar 0-forms splines. The associate matrix is square as in @@ -741,7 +741,7 @@ def dot(self, x, out=None): return y def transpose(self): - return C1CongaProjector0(self.W0, gamma=self.gamma, transposed=not self.transposed, hbc=self.hbc) + return C1PolarProjection_V0(self.W0, gamma=self.gamma, transposed=not self.transposed, hbc=self.hbc) @property def T(self): @@ -799,7 +799,7 @@ def toarray(self): # | (1,0) | (1,1) | # |___________|__________| -class C1CongaProjector1_00(LinearOperator): +class C1PolarProjection_V1_00(LinearOperator): """ Upper Left block of P1. @@ -864,7 +864,7 @@ def dot(self, x, out=None): return y def transpose(self, conjugate=False): - return C1CongaProjector1_00(self.W1, transposed=not self.transposed) + return C1PolarProjection_V1_00(self.W1, transposed=not self.transposed) @property def T(self): @@ -913,7 +913,7 @@ def toarray(self): return self.tosparse().toarray() -class C1CongaProjector1_10(LinearOperator): +class C1PolarProjection_V1_10(LinearOperator): """ Lower left block of P1. @@ -984,7 +984,7 @@ def dot(self, x, out=None): return y def transpose(self, conjugate=False): - return C1CongaProjector1_10(self.W1, transposed=not self.transposed) + return C1PolarProjection_V1_10(self.W1, transposed=not self.transposed) @property def T(self): @@ -1028,7 +1028,7 @@ def toarray(self): return self.tosparse().toarray() -class C1CongaProjector1_11(LinearOperator): +class C1PolarProjection_V1_11(LinearOperator): """ Lower right block of P1. @@ -1095,7 +1095,7 @@ def dot(self, x, out=None): return y def transpose(self, conjugate=False): - return C1CongaProjector1_11(self.W1, transposed=not self.transposed, hbc=self.hbc) + return C1PolarProjection_V1_11(self.W1, transposed=not self.transposed, hbc=self.hbc) @property def T(self): @@ -1117,20 +1117,8 @@ def toarray(self): return self.tosparse().toarray() -def new_C1CongaProjector1(W1, hbc=False): - assert isinstance(W1, VectorFemSpace) - assert W1.symbolic_space.kind.name == 'hcurl' - T1 = W1.coeff_space - P1 = BlockLinearOperator(T1, T1) - P1[0, 0] = C1CongaProjector1_00(W1) - P1[1, 0] = C1CongaProjector1_10(W1) - P1[1, 1] = C1CongaProjector1_11(W1, hbc=hbc) - - return P1 - - -class C1CongaProjector1(BlockLinearOperator): +class C1PolarProjection_V1(BlockLinearOperator): """ CONGA Projector P1 from the full spline space S^{p1-1, p2} x S^{p1, p2-1} on logical domain to U1 the pre-polar 1-forms splines. The associate matrix @@ -1160,10 +1148,10 @@ def __init__(self, W1, transposed=False, hbc=False): super().__init__(T1, T1) - self[0, 0] = C1CongaProjector1_00(W1, transposed=transposed) - self[1, 0] = C1CongaProjector1_10(W1, transposed=transposed) + self[0, 0] = C1PolarProjection_V1_00(W1, transposed=transposed) + self[1, 0] = C1PolarProjection_V1_10(W1, transposed=transposed) # self[0, 1] = C1CongaProjector1_01(W1, transposed = transposed) - self[1, 1] = C1CongaProjector1_11(W1, transposed=transposed, hbc=hbc) + self[1, 1] = C1PolarProjection_V1_11(W1, transposed=transposed, hbc=hbc) self.W1 = W1 self.transposed = transposed @@ -1171,7 +1159,7 @@ def __init__(self, W1, transposed=False, hbc=False): # -------------- 2-FORMS CONGA PROJECTOR P2 ----------------# -class C1CongaProjector2(LinearOperator): +class C1PolarProjection_V2(LinearOperator): """ CONGA Projector P2 from the full spline space S^{p1-1, p2-1} on logical domain to U2, the pre-polar 2-forms splines. The associate matrix @@ -1237,7 +1225,7 @@ def dot(self, x, out=None): return y def transpose(self): - return C1CongaProjector2(self.W2, transposed=not self.transposed) + return C1PolarProjection_V2(self.W2, transposed=not self.transposed) @property def T(self): diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 6f72bc809..41f06caa0 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -33,7 +33,7 @@ from psydac.api.settings import PSYDAC_BACKENDS -from psydac.feec.polar.conga_projections import C0PolarProjection_V0 +from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C1PolarProjection_V0 # backend = PSYDAC_BACKENDS['numba'] backend = PSYDAC_BACKENDS['python'] @@ -245,7 +245,7 @@ class CongaLaplacian(LinearOperator): def __init__(self, S, M, P, alpha): assert isinstance(S, StencilMatrix) assert isinstance(M, StencilMatrix) - assert isinstance(P, (C0PolarProjection_V0, )) + assert isinstance(P, (C0PolarProjection_V0, C1PolarProjection_V0)) W0 = P.W0.coeff_space @@ -518,13 +518,12 @@ def run_poisson_2d(*, test_case, ncells, degree, bp = proj.change_rhs_basis(b) alpha = 'None' elif smooth_method == 'C1conga': - raise ValueError("Only C0conga is implemented for now!") - # gamma = 1.0 # any value would be ok. - # alpha = alphaCONGA - # P0 = C1CongaProjector0(V0_h, gamma=gamma, hbc=True) # hbc imposes the boundary conditions - # Sc = CongaLaplacian(S, M, P0, alpha) - # A = Sc.tosparse() - # bc = P0.T.dot(b) + gamma = 1.0 # any value would be ok. + alpha = alphaCONGA + P0 = C1PolarProjection_V0(V0_h, gamma=gamma, hbc=True) # hbc imposes the boundary conditions + Sc = CongaLaplacian(S, M, P0, alpha) + A = Sc.tosparse() + bc = P0.T.dot(b) elif smooth_method == 'C0conga': alpha = alphaCONGA P0 = C0PolarProjection_V0(V0_h, hbc=True) # hbc imposes the boundary conditions From 6894d32f6efebcf5cc1d0dfc38c208f0c111f162 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 18 Dec 2025 14:30:01 +0100 Subject: [PATCH 041/133] added P0 C1 --- psydac/feec/polar/conga_projections.py | 55 ++++++++++++++++++-------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index ac0fda537..295dbf61e 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -688,6 +688,13 @@ def __init__(self, W0, *, gamma=1, transposed=False, hbc=False): self.transposed = transposed self.hbc = hbc + # Radial and angle sub-communicators (1D) + self._cart = W0.coeff_space.cart + if self._cart.is_parallel: + self._radial_comm = self._cart.subcomm[0] + self._angle_comm = self._cart.subcomm[1] + + @property def domain(self): return self.W0.coeff_space @@ -710,6 +717,8 @@ def dot(self, x, out=None): [e1, e2] = self.W0.coeff_space.ends [n1, n2] = self.W0.coeff_space.npts theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case + rank_at_polar_edge = (s1 == 0) + rank_at_outer_edge = (e1 == n1 - 1) if out is None: y = self.W0.coeff_space.zeros() @@ -718,24 +727,38 @@ def dot(self, x, out=None): assert out.space is self.W0.coeff_space y = out - if self.transposed: - x0 = np.average(x[0, s2:e2 + 1]) - x1 = np.average(x[1, s2:e2 + 1]) - y[0, s2:e2 + 1] = self.gamma * x0 + self.gamma * x1 - y[1, s2:e2 + 1] = (1 - self.gamma) * x0 + (1 - self.gamma) * x1 + \ - (2 / n2) * matmul_toeplitz(np.cos(theta[s2:e2 + 1] - theta[0]), x[1, s2:e2 + 1]) - else: - x0 = np.average(x[0, s2:e2 + 1]) - x1 = np.average(x[1, s2:e2 + 1]) - y[0, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 - y[1, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 + \ - (2 / n2) * matmul_toeplitz(np.cos(theta[s2:e2 + 1] - theta[0]), x[1, s2:e2 + 1]) + if rank_at_polar_edge: + # compute sum of points with s = 0, 1 for the process + local_sum0 = np.sum(x[0, s2:e2 + 1]) + local_sum1 = np.sum(x[1, s2:e2 + 1]) + sum_cos = (2 / n2) * np.dot(np.cos(theta[s2:e2 + 1]), x[1, s2:e2 + 1]) + sum_sin = (2 / n2) * np.dot(np.sin(theta[s2:e2 + 1]), x[1, s2:e2 + 1]) - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + if self._cart.is_parallel: + from mpi4py import MPI + local_sum0 = self._angle_comm.allreduce(local_sum0, op=MPI.SUM) + local_sum1 = self._angle_comm.allreduce(local_sum1, op=MPI.SUM) + sum_cos = self._angle_comm.allreduce(sum_cos, op=MPI.SUM) + sum_sin = self._angle_comm.allreduce(sum_sin, op=MPI.SUM) + + #compute average of all points with s = 0, 1 + x0 = local_sum0 / n2 + x1 = local_sum1 / n2 + + if self.transposed: + y[0, s2:e2 + 1] = self.gamma * x0 + self.gamma * x1 + y[1, s2:e2 + 1] = (1 - self.gamma) * x0 + (1 - self.gamma) * x1 + \ + np.cos(theta[s2:e2 + 1]) * sum_cos + np.sin(theta[s2:e2 + 1]) * sum_sin + else: + y[0, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 + y[1, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 + \ + np.cos(theta[s2:e2 + 1]) * sum_cos + np.sin(theta[s2:e2 + 1]) * sum_sin + y[2:e1 + 1, s2:e2 + 1] = x[2:e1 + 1, s2:e2 + 1] + else: + y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] - if self.hbc: - if e1 == n1 - 1: - y[e1, :] = 0. + if self.hbc and rank_at_outer_edge: + y[e1, :] = 0. y.update_ghost_regions() return y From 1262f6661fa56227a1b5d88ec5d82787e4e4e60a Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Sat, 20 Dec 2025 10:11:01 +0100 Subject: [PATCH 042/133] Parallelize C1PolarProjection_V0 --- psydac/feec/polar/conga_projections.py | 143 ++++++------------------- 1 file changed, 31 insertions(+), 112 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 295dbf61e..2432db87a 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -557,100 +557,29 @@ def toarray(self): return self.tosparse().toarray() -# ------------ strong and weak curl ----------------- - -from scipy.sparse.linalg import spilu, cg -from scipy.sparse.linalg import LinearOperator as SparseLinearOperator - - -class SparseCurlAsOperator(LinearOperator): - - def __init__(self, W1, W2, strong_curl_sp, M1=None, M2=None, strong=False, store_M1inv=False): - assert isinstance(W1, VectorFemSpace) - assert isinstance(W2, TensorFemSpace) - assert isinstance(strong_curl_sp, spmatrix) - - self.W1 = W1 - self.W2 = W2 - - self.strong = strong - self._store_M1inv = store_M1inv - - if strong: - self.curl_sp = strong_curl_sp - - else: - self.curl_sp = strong_curl_sp.T # dual curl (in the dual bases) - assert isinstance(M1, LinearOperator) - assert isinstance(M2, LinearOperator) - self.M1_sp = M1.tosparse() - self.M2_sp = M2.tosparse() - if store_M1inv: - self._M1inv_sp = sp_inv(self.M1_sp) - else: - self._M1_spilu = spilu(self.M1_sp) - self._precond_M = SparseLinearOperator(self.M1_sp.shape, self._M1_spilu.solve) - - @property - def domain(self): - if self.strong: - return self.W1.coeff_space - else: - return self.W2.coeff_space - - @property - def codomain(self): - if self.strong: - return self.W2.coeff_space - else: - return self.W1.coeff_space - - @property - def dtype(self): - return float - - def toarray(self): - # return self - raise NotImplementedError('toarray() is not defined for this class.') - - def tosparse(self): - # return self - raise NotImplementedError('tosparse() is not defined for this class.') - - def transpose(self, conjugate=False): - raise NotImplementedError('transpose() is not defined for this class.') - - # Warning: this dot method has to be revised for mpi! - # the toeplitz multiplication requires all processes along the theta-dir to communicate. - def dot(self, x, out=None): - assert isinstance(x, Vector) - if self.strong: - Cx_arr = self.curl_sp @ x.toarray() - else: - tx_arr = self.M2_sp @ x.toarray() - tCx_arr = self.curl_sp @ tx_arr - if self._store_M1inv: - Cx_arr = self._M1inv_sp.dot(tCx_arr) - else: - # Cx_arr = spsolve(self.M1_sp, tCx_arr) - Cx_arr, exit_code = cg(self.M1_sp, tCx_arr, M=self._precond_M, rtol=1e-7) - # Cx_arr = self._M1_spilu.solve(tCx_arr) - if out is None: - y = self.codomain.zeros() - else: - assert isinstance(out, Vector) - assert out.space is self.codomain - y = out - - y1 = array_to_psydac(Cx_arr, self.codomain) - y1.copy(out=y) - return y # =============================================================================# # C1 POLAR SPLINE COMPLEX (C1P) # # =============================================================================# +def cos_sin_avg(theta, x, angle_comm, s2, e2, n2, i): + """ + Compute weighted averages in parallel: + sum (2/n2) * cos(theta_k) * x_ik + sum (2/n2) * sin(theta_k) * x_ik + over all k belonging to the process, for fixed i (0 or 1) + then sum over all processes + """ + from mpi4py import MPI + sum_cos = (2 / n2) * np.dot(np.cos(theta), x[i, s2:e2 + 1]) + sum_sin = (2 / n2) * np.dot(np.sin(theta), x[i, s2:e2 + 1]) + if angle_comm is not None: + sum_cos = angle_comm.allreduce(sum_cos, op=MPI.SUM) + sum_sin = angle_comm.allreduce(sum_sin, op=MPI.SUM) + return sum_cos, sum_sin + + # --------- 0-FORMS CONGA PROJECTOR P0 ----------# class C1PolarProjection_V0(LinearOperator): """ @@ -688,13 +617,6 @@ def __init__(self, W0, *, gamma=1, transposed=False, hbc=False): self.transposed = transposed self.hbc = hbc - # Radial and angle sub-communicators (1D) - self._cart = W0.coeff_space.cart - if self._cart.is_parallel: - self._radial_comm = self._cart.subcomm[0] - self._angle_comm = self._cart.subcomm[1] - - @property def domain(self): return self.W0.coeff_space @@ -716,7 +638,7 @@ def dot(self, x, out=None): [s1, s2] = self.W0.coeff_space.starts [e1, e2] = self.W0.coeff_space.ends [n1, n2] = self.W0.coeff_space.npts - theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case + theta_local = np.linspace(s2 * 2 * pi / n2, e2 * 2 * pi / n2, e2 - s2 + 1) rank_at_polar_edge = (s1 == 0) rank_at_outer_edge = (e1 == n1 - 1) @@ -728,31 +650,28 @@ def dot(self, x, out=None): y = out if rank_at_polar_edge: - # compute sum of points with s = 0, 1 for the process - local_sum0 = np.sum(x[0, s2:e2 + 1]) - local_sum1 = np.sum(x[1, s2:e2 + 1]) - sum_cos = (2 / n2) * np.dot(np.cos(theta[s2:e2 + 1]), x[1, s2:e2 + 1]) - sum_sin = (2 / n2) * np.dot(np.sin(theta[s2:e2 + 1]), x[1, s2:e2 + 1]) + # compute sums of points with s = 0, 1 for the process + x0 = np.sum(x[0, s2:e2 + 1]) / n2 + x1 = np.sum(x[1, s2:e2 + 1]) / n2 - if self._cart.is_parallel: - from mpi4py import MPI - local_sum0 = self._angle_comm.allreduce(local_sum0, op=MPI.SUM) - local_sum1 = self._angle_comm.allreduce(local_sum1, op=MPI.SUM) - sum_cos = self._angle_comm.allreduce(sum_cos, op=MPI.SUM) - sum_sin = self._angle_comm.allreduce(sum_sin, op=MPI.SUM) + angle_comm = x.space.cart.subcomm[1] if x.space.parallel else None + sum_cos, sum_sin = cos_sin_avg(theta_local, x, angle_comm, s2, e2, n2, 1) + + if x.space.parallel: - #compute average of all points with s = 0, 1 - x0 = local_sum0 / n2 - x1 = local_sum1 / n2 + from mpi4py import MPI + # global averages + x0 = angle_comm.allreduce(x0, op=MPI.SUM) + x1 = angle_comm.allreduce(x1, op=MPI.SUM) if self.transposed: y[0, s2:e2 + 1] = self.gamma * x0 + self.gamma * x1 y[1, s2:e2 + 1] = (1 - self.gamma) * x0 + (1 - self.gamma) * x1 + \ - np.cos(theta[s2:e2 + 1]) * sum_cos + np.sin(theta[s2:e2 + 1]) * sum_sin + np.cos(theta_local) * sum_cos + np.sin(theta_local) * sum_sin else: y[0, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 y[1, s2:e2 + 1] = self.gamma * x0 + (1 - self.gamma) * x1 + \ - np.cos(theta[s2:e2 + 1]) * sum_cos + np.sin(theta[s2:e2 + 1]) * sum_sin + np.cos(theta_local) * sum_cos + np.sin(theta_local) * sum_sin y[2:e1 + 1, s2:e2 + 1] = x[2:e1 + 1, s2:e2 + 1] else: y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] From 450825911bd73e3753cba74c9f6f0cd321bc7109 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Sun, 21 Dec 2025 20:15:35 +0100 Subject: [PATCH 043/133] parallelized C1PolarProjection_V1 --- psydac/feec/polar/conga_projections.py | 278 +++++++++-------------- psydac/feec/polar/examples/maxwell_2d.py | 10 +- 2 files changed, 109 insertions(+), 179 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 2432db87a..1da60a8b7 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -756,7 +756,6 @@ class C1PolarProjection_V1_00(LinearOperator): """ def __init__(self, W1, transposed=False): - # assert isinstance(W1, ProductFemSpace) assert isinstance(W1, VectorFemSpace) self.W1 = W1 @@ -782,7 +781,8 @@ def dot(self, x, out=None): [s1, s2] = self.domain.starts [e1, e2] = self.domain.ends [n1, n2] = self.domain.npts - theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning not mpi + theta_local = np.linspace(s2 * 2 * pi / n2, e2 * 2 * pi / n2, e2 - s2 + 1) + rank_at_polar_edge = (s1 == 0) if out is None: y = self.codomain.zeros() @@ -791,16 +791,22 @@ def dot(self, x, out=None): assert out.space is self.codomain y = out - if self.transposed: - y[0, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), x[0, s2:e2 + 1]) + \ - x[1, s2:e2 + 1] - matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), - x[1, s2:e2 + 1]) - y[1, s2:e2 + 1] = x[1, s2:e2 + 1] - else: - y[0, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), x[0, s2:e2 + 1]) - y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] - y[0, s2:e2 + 1] + if rank_at_polar_edge: + angle_comm = x.space.cart.subcomm[1] if x.space.parallel else None + sum_cos0, sum_sin0 = cos_sin_avg(theta_local, x, angle_comm, s2, e2, n2, 0) + + if self.transposed: + sum_cos1, sum_sin1 = cos_sin_avg(theta_local, x, angle_comm, s2, e2, n2, 1) + y[0, s2:e2 + 1] = sum_cos0 * np.cos(theta_local) + sum_sin0 * np.sin(theta_local) +\ + x[1, s2:e2 + 1] - sum_cos1 * np.cos(theta_local) - sum_sin1 * np.sin(theta_local) + y[1, s2:e2 + 1] = x[1, s2:e2 + 1] + else: + y[0, s2:e2 + 1] = sum_cos0 * np.cos(theta_local) + sum_sin0 * np.sin(theta_local) + y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] - y[0, s2:e2 + 1] - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] + y[2:e1 + 1, s2:e2 + 1] = x[2:e1 + 1, s2:e2 + 1] + else: + y[s1:e1 + 1, s2:e2 + 1] = x[s1:e1 + 1, s2:e2 + 1] y.update_ghost_regions() return y @@ -897,13 +903,16 @@ def dtype(self): # We also use np.roll which should be changed with the use of ghost regions def dot(self, x, out=None): assert isinstance(x, StencilVector) + if not x.ghost_regions_in_sync: + x.update_ghost_regions() # The number of radial basis functions is one less than # the number on the angular basis functions [s1, s2] = self.domain.starts [e1, e2] = self.domain.ends [n1, n2] = self.domain.npts - theta = np.linspace(0, 2 * pi, n2, endpoint=False) + theta_local = np.linspace(s2 * 2 * pi / n2, e2 * 2 * pi / n2, e2 - s2 + 1) + rank_at_polar_edge = (s1 == 0) if out is None: y = self.codomain.zeros() @@ -912,15 +921,24 @@ def dot(self, x, out=None): assert out.space is self.codomain y = out - if self.transposed: - y[0, s2:e2 + 1] = np.subtract(np.roll(x[1, s2:e2 + 1], 1), x[1, s2:e2 + 1]) - y[0, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), y[0, s2:e2 + 1]) - y[1:, s2:e2 + 1] = 0 + if rank_at_polar_edge: + angle_comm = x.space.cart.subcomm[1] if x.space.parallel else None + + if self.transposed: + y[0, s2:e2 + 1] = x[1, s2 - 1:e2] - x[1, s2:e2 + 1] + sum_cos, sum_sin = cos_sin_avg(theta_local, y, angle_comm, s2, e2, n2, 0) + y[0, s2:e2 + 1] = np.cos(theta_local) * sum_cos + np.sin(theta_local) * sum_sin + y[1:e1 + 1, s2:e2 + 1] = 0 + else: + y[0, s2:e2 + 1] = 0 + sum_cos, sum_sin = cos_sin_avg(theta_local, x, angle_comm, s2, e2, n2, 0) + y[1, s2:e2 + 1] = np.cos(theta_local) * sum_cos + np.sin(theta_local) * sum_sin + y.update_ghost_regions() + y[1, s2:e2 + 1] = y[1, s2 + 1:e2 + 2] - y[1, s2:e2 + 1] + y[2:e1 + 1, s2:e2 + 1] = 0 else: - y[0, s2:e2 + 1] = 0 - y[1, s2:e2 + 1] = matmul_toeplitz((2 / n2) * np.cos(theta[s2:e2 + 1] - theta[0]), x[0, s2:e2 + 1]) - y[1, s2:e2 + 1] = np.subtract(np.roll(y[1, s2:e2 + 1], -1), y[1, s2:e2 + 1]) - y[2:, s2:e2 + 1] = 0 + y[s1:e1 + 1, s2:e2 + 1] = 0 + y.update_ghost_regions() return y @@ -970,95 +988,6 @@ def toarray(self): return self.tosparse().toarray() -class C1PolarProjection_V1_11(LinearOperator): - """ - Lower right block of P1. - - Parameters: - ----------- - - W1 : VectorFemSpace (former ProductFemSpace) - Full tensor product spline space of the 1-forms S^{p1-1, p2} x S^{p1, p2-1} - - transposed : Boolean - Switch between P1 and P1 transposed (default is False) - - hbc : Boolean - Switch on and off the imposition of homogeneous Dirichlet boundary - conditions on the tangential (angular) direction (default is False) - """ - - def __init__(self, W1, transposed=False, hbc=False): - # assert isinstance(W1, ProductFemSpace) - assert isinstance(W1, VectorFemSpace) - - self.W1 = W1 - self.transposed = transposed - self.hbc = hbc - - @property - def domain(self): - return self.W1.coeff_space[1] - - @property - def codomain(self): - return self.W1.coeff_space[1] - - @property - def dtype(self): - return float - - # Warning: this dot method has to be revised for mpi! - # the toeplitz multiplication requires all processes along the theta-dir to communicate. - def dot(self, x, out=None): - assert isinstance(x, StencilVector) - - [s1, s2] = self.domain.starts - [e1, e2] = self.domain.ends - [n1, n2] = self.domain.npts - - if out is None: - y = self.codomain.zeros() - else: - assert isinstance(out, StencilVector) - assert out.space is self.codomain - y = out - - # Both for P1 and P1 transposed - y[0, s2:e2 + 1] = 0 - y[1, s2:e2 + 1] = 0 - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] - - if self.hbc: - if e1 == n1 - 1: - y[e1, :] = 0. - - y.update_ghost_regions() - return y - - def transpose(self, conjugate=False): - return C1PolarProjection_V1_11(self.W1, transposed=not self.transposed, hbc=self.hbc) - - @property - def T(self): - return self.transpose() - - def tosparse(self): - - [n01, n02] = self.domain.npts - [n11, n12] = self.codomain.npts - dtype = self.domain.dtype - - P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) - P[2 * n12:, 2 * n02:] = sp_eye((n11 - 2) * n12) - if self.hbc: - P[-n12:, -n02:] = 0 - return P.T if self.transposed else P - - def toarray(self): - return self.tosparse().toarray() - - class C1PolarProjection_V1(BlockLinearOperator): """ @@ -1092,8 +1021,9 @@ def __init__(self, W1, transposed=False, hbc=False): self[0, 0] = C1PolarProjection_V1_00(W1, transposed=transposed) self[1, 0] = C1PolarProjection_V1_10(W1, transposed=transposed) - # self[0, 1] = C1CongaProjector1_01(W1, transposed = transposed) - self[1, 1] = C1PolarProjection_V1_11(W1, transposed=transposed, hbc=hbc) + # self[0, 1] is 0 block + # The lower right block is the same for C0 and C1 projections + self[1, 1] = C0PolarProjection_V1_11(W1, transposed=transposed, hbc=hbc) self.W1 = W1 self.transposed = transposed @@ -1101,94 +1031,96 @@ def __init__(self, W1, transposed=False, hbc=False): # -------------- 2-FORMS CONGA PROJECTOR P2 ----------------# -class C1PolarProjection_V2(LinearOperator): - """ - CONGA Projector P2 from the full spline space S^{p1-1, p2-1} on logical - domain to U2, the pre-polar 2-forms splines. The associate matrix - is square, as in the CONGA approach we keep using the tensor B-spline basis, - instead of the polar basis of Toshniwal. P2 enforces coefficient relations - to be in U2. +# Equals C0PolarProjection_V2 - Parameters: - ----------- +# ------------ strong and weak curl ----------------- - W2 : TensorFemSpace - Full tensor product spline space of the 2-forms S^{p1-1, p2-1} +from scipy.sparse.linalg import spilu, cg +from scipy.sparse.linalg import LinearOperator as SparseLinearOperator - transposed : Boolean - Switch between P2 and P2 transposed (default is False) - """ - def __init__(self, W2, transposed=False): +class SparseCurlAsOperator(LinearOperator): + + def __init__(self, W1, W2, strong_curl_sp, M1=None, M2=None, strong=False, store_M1inv=False): + assert isinstance(W1, VectorFemSpace) assert isinstance(W2, TensorFemSpace) + assert isinstance(strong_curl_sp, spmatrix) + self.W1 = W1 self.W2 = W2 - self.transposed = transposed + + self.strong = strong + self._store_M1inv = store_M1inv + + if strong: + self.curl_sp = strong_curl_sp + + else: + self.curl_sp = strong_curl_sp.T # dual curl (in the dual bases) + assert isinstance(M1, LinearOperator) + assert isinstance(M2, LinearOperator) + self.M1_sp = M1.tosparse() + self.M2_sp = M2.tosparse() + if store_M1inv: + self._M1inv_sp = sp_inv(self.M1_sp) + else: + self._M1_spilu = spilu(self.M1_sp) + self._precond_M = SparseLinearOperator(self.M1_sp.shape, self._M1_spilu.solve) @property def domain(self): - return self.W2.coeff_space + if self.strong: + return self.W1.coeff_space + else: + return self.W2.coeff_space @property def codomain(self): - return self.W2.coeff_space + if self.strong: + return self.W2.coeff_space + else: + return self.W1.coeff_space @property def dtype(self): return float - def dot(self, x, out=None): - assert isinstance(x, StencilVector) + def toarray(self): + # return self + raise NotImplementedError('toarray() is not defined for this class.') - if not x.ghost_regions_in_sync: - x.update_ghost_regions() + def tosparse(self): + # return self + raise NotImplementedError('tosparse() is not defined for this class.') - [s1, s2] = self.W2.coeff_space.starts - [e1, e2] = self.W2.coeff_space.ends - [n1, n2] = self.W2.coeff_space.npts + def transpose(self, conjugate=False): + raise NotImplementedError('transpose() is not defined for this class.') + # Warning: this dot method has to be revised for mpi! + # the toeplitz multiplication requires all processes along the theta-dir to communicate. + def dot(self, x, out=None): + assert isinstance(x, Vector) + if self.strong: + Cx_arr = self.curl_sp @ x.toarray() + else: + tx_arr = self.M2_sp @ x.toarray() + tCx_arr = self.curl_sp @ tx_arr + if self._store_M1inv: + Cx_arr = self._M1inv_sp.dot(tCx_arr) + else: + # Cx_arr = spsolve(self.M1_sp, tCx_arr) + Cx_arr, exit_code = cg(self.M1_sp, tCx_arr, M=self._precond_M, rtol=1e-7) + # Cx_arr = self._M1_spilu.solve(tCx_arr) if out is None: - y = self.W2.coeff_space.zeros() + y = self.codomain.zeros() else: - assert isinstance(out, StencilVector) - assert out.space is self.W2.coeff_space + assert isinstance(out, Vector) + assert out.space is self.codomain y = out - if self.transposed: - y[0, s2:e2 + 1] = x[1, s2:e2 + 1] - y[1, s2:e2 + 1] = x[1, s2:e2 + 1] - else: - y[0, s2:e2 + 1] = 0 - y[1, s2:e2 + 1] = x[0, s2:e2 + 1] + x[1, s2:e2 + 1] - - y[2:, s2:e2 + 1] = x[2:, s2:e2 + 1] - - y.update_ghost_regions() + y1 = array_to_psydac(Cx_arr, self.codomain) + y1.copy(out=y) return y - def transpose(self): - return C1PolarProjection_V2(self.W2, transposed=not self.transposed) - - @property - def T(self): - return self.transpose() - - def tosparse(self): - - [n1, n2] = self.W2.coeff_space.npts - - data = np.ones(n1 * n2) - cols = np.arange(n1 * n2) - rows = np.tile(np.arange(n2, 2 * n2), 2) - rows = np.concatenate((rows, np.arange(2 * n2, n1 * n2))) - - P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) - P.eliminate_zeros() - - return P.T if self.transposed else P - - def toarray(self): - return self.tosparse().toarray() - diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index da2d73843..c6b5fe1c8 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -235,7 +235,7 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, assert splitting_order in [2, 4] - from psydac.feec.polar.conga_projections import C0PolarProjection_V1, C0PolarProjection_V2, SparseCurlAsOperator + from psydac.feec.polar.conga_projections import C0PolarProjection_V1, C0PolarProjection_V2, C1PolarProjection_V1, SparseCurlAsOperator from analyticalTE import CircularCavitySolution # , constant_field from waveTE import GaussianSolution @@ -672,12 +672,10 @@ def run_study_L2_proj(): # Conga projectors if smooth == 0: P1 = C0PolarProjection_V1(V1, hbc=True) - print("P1:") - print(P1) P2 = C0PolarProjection_V2(V2) - #else: TODO - #P1 = C1PolarProjection_V1(V1, hbc=True) - #P2 = C1PolarProjection_V1(V2) + else: + P1 = C1PolarProjection_V1(V1, hbc=True) + P2 = C0PolarProjection_V2(V2) P1_T = P1.T P2_T = P2.T From bedf25d0ef649277bbe58f61669e1a570fb4f4a5 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 22 Dec 2025 10:20:25 +0100 Subject: [PATCH 044/133] use only one allreduce --- psydac/feec/polar/conga_projections.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 1da60a8b7..042e86f68 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -41,12 +41,6 @@ def __init__(self, W0, *, transposed=False, hbc=False): self.transposed = transposed self.hbc = hbc - # Radial and angle sub-communicators (1D) - self._cart = W0.coeff_space.cart - if self._cart.is_parallel: - self._radial_comm = self._cart.subcomm[0] - self._angle_comm = self._cart.subcomm[1] - @property def domain(self): return self.W0.coeff_space @@ -81,9 +75,10 @@ def dot(self, x, out=None): # compute sum of points with s = 0 for the process local_sum = np.sum(x[0, s2:e2 + 1]) - if self._cart.is_parallel: + if x.space.is_parallel: from mpi4py import MPI - local_sum = self._angle_comm.allreduce(local_sum, op=MPI.SUM) + angle_comm = x.space.cart.subcomm[1] + local_sum = angle_comm.allreduce(local_sum, op=MPI.SUM) #compute average of all points with s = 0 y[0, s2:e2 + 1] = local_sum / n2 y[1:e1 + 1, s2:e2 + 1] = x[1:e1 + 1, s2:e2 + 1] @@ -574,10 +569,11 @@ def cos_sin_avg(theta, x, angle_comm, s2, e2, n2, i): from mpi4py import MPI sum_cos = (2 / n2) * np.dot(np.cos(theta), x[i, s2:e2 + 1]) sum_sin = (2 / n2) * np.dot(np.sin(theta), x[i, s2:e2 + 1]) + buf = np.array([sum_cos, sum_sin]) + if angle_comm is not None: - sum_cos = angle_comm.allreduce(sum_cos, op=MPI.SUM) - sum_sin = angle_comm.allreduce(sum_sin, op=MPI.SUM) - return sum_cos, sum_sin + buf = angle_comm.allreduce(buf, op=MPI.SUM) + return buf # --------- 0-FORMS CONGA PROJECTOR P0 ----------# From a37ef70e11003e1a6d12ab2c4cfa8e01d556b800 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 20 Jan 2026 13:25:12 +0100 Subject: [PATCH 045/133] tosparse and unit test for C0 P0 --- psydac/feec/polar/conga_projections.py | 44 ++++++++++----- psydac/feec/polar/tests/__init__.py | 0 .../polar/tests/test_conga_projections.py | 54 +++++++++++++++++++ 3 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 psydac/feec/polar/tests/__init__.py create mode 100644 psydac/feec/polar/tests/test_conga_projections.py diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 042e86f68..d37b99414 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -74,8 +74,7 @@ def dot(self, x, out=None): if rank_at_polar_edge: # compute sum of points with s = 0 for the process local_sum = np.sum(x[0, s2:e2 + 1]) - - if x.space.is_parallel: + if x.space.parallel: from mpi4py import MPI angle_comm = x.space.cart.subcomm[1] local_sum = angle_comm.allreduce(local_sum, op=MPI.SUM) @@ -99,19 +98,38 @@ def transpose(self, conjugate=False): def tosparse(self): + # Matrix size is n1*n2. Columns with numbers i*n2 + j with + # s1 <= i <= e1, s2 <= j <= e2 belong to the process + [n1, n2] = self.W0.coeff_space.npts + [s1, s2] = self.W0.coeff_space.starts + [e1, e2] = self.W0.coeff_space.ends + rank_at_polar_edge = (s1 == 0) + rank_at_outer_edge = (e1 == n1 - 1) + data, cols, rows = [], [], [] - data = np.tile((1 / n2) * np.ones(n2), n2) - cols = np.repeat(np.arange(n2), n2) - rows = np.tile(np.arange(n2), n2) - if self.hbc: - data = np.concatenate((data, np.ones(n2 * (n1 - 2)))) - cols = np.concatenate((cols, np.arange(n2, (n1 - 1) * n2))) - rows = np.concatenate((rows, np.arange(n2, (n1 - 1) * n2))) - else: - data = np.concatenate((data, np.ones(n2 * (n1 - 1)))) - cols = np.concatenate((cols, np.arange(n2, n1 * n2))) - rows = np.concatenate((rows, np.arange(n2, n1 * n2))) + # Assemble the matrix entries which contribute to the polar edge (s1 = 0) + len_theta = e2 - s2 + 1 + if rank_at_polar_edge: + data = np.tile((1 / n2) * np.ones(n2), len_theta) + cols = np.repeat(np.arange(s2, e2 + 1), n2) + rows = np.tile(np.arange(n2), len_theta) + + # Assemble the rest of the matrix (identity block) + start_s = s1 + end_s = e1 + 1 + # We do not need entries with s1 = 0 (already accounted for) + if rank_at_polar_edge: + start_s += 1 + if rank_at_outer_edge and self.hbc: + end_s -= 1 + i = np.arange(start_s, end_s)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + local_cols = (i * n2 + j).ravel() + + data = np.concatenate((data, np.ones(len_theta * (end_s - start_s)))) + cols = np.concatenate((cols, local_cols)) + rows = np.concatenate((rows, local_cols)) P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() diff --git a/psydac/feec/polar/tests/__init__.py b/psydac/feec/polar/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py new file mode 100644 index 000000000..ab13a8468 --- /dev/null +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -0,0 +1,54 @@ +import numpy as np +from numpy import pi + +import pytest +from sympde import ScalarFunctionSpace + +from sympde.topology import Square +from sympde.topology.analytical_mapping import PolarMapping + +from psydac.api.discretization import discretize +from psydac.feec.polar.conga_projections import C0PolarProjection_V0 +from psydac.fem.basic import FemField + + +@pytest.mark.parametrize( 'R', [1]) +@pytest.mark.parametrize( 'ncells', [[2, 4]]) +@pytest.mark.parametrize( 'degree', [[1, 1]]) +@pytest.mark.parallel + +def test_C0PolarProjection_V0(R, ncells, degree): + print('Testing C0PolarProjection_V0...') + + # Build physical domain - disk of radius R + logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) + mapping = PolarMapping('F', c1=0, c2=0, rmin=0, rmax=1) + domain = mapping(logical_domain) + + # Discrete physical domain and discrete space + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=None) + V0 = ScalarFunctionSpace('V0', domain) + V0_h = discretize(V0, domain_h, degree=degree) + + P0 = C0PolarProjection_V0(V0_h, hbc=True) + + [s1, s2] = V0_h.coeff_space.starts + [e1, e2] = V0_h.coeff_space.ends + + phi = FemField(V0_h) + x = phi.coeffs + x[s1:e1 + 1, s2:e2 + 1] = np.random.random([e1 - s1 + 1, e2 - s2 + 1]) + x.update_ghost_regions() + + phiC = FemField(V0_h) + y = phiC.coeffs + P0.dot(x, out=y) + + # Checking projection property P0(P0(phi)) = P0(phi) + assert np.allclose(P0.dot(y)[:,:], y[:,:]) + + # Comparing results of dot and tosparse + sp_P0 = P0.tosparse() + y_sp = sp_P0 @ x.toarray() + print(np.allclose(y_sp, y.toarray())) + assert np.allclose(y_sp, y.toarray()) From 6c7c072e03e1e54fd3bd2a1e86146d2c122c35a8 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 20 Jan 2026 13:54:35 +0100 Subject: [PATCH 046/133] fix a bug in test --- .../feec/polar/tests/test_conga_projections.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index ab13a8468..ada856db6 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -4,6 +4,8 @@ import pytest from sympde import ScalarFunctionSpace +from mpi4py import MPI + from sympde.topology import Square from sympde.topology.analytical_mapping import PolarMapping @@ -13,12 +15,12 @@ @pytest.mark.parametrize( 'R', [1]) -@pytest.mark.parametrize( 'ncells', [[2, 4]]) -@pytest.mark.parametrize( 'degree', [[1, 1]]) +@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) +@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) @pytest.mark.parallel def test_C0PolarProjection_V0(R, ncells, degree): - print('Testing C0PolarProjection_V0...') + mpi_comm = MPI.COMM_WORLD # Build physical domain - disk of radius R logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) @@ -26,7 +28,7 @@ def test_C0PolarProjection_V0(R, ncells, degree): domain = mapping(logical_domain) # Discrete physical domain and discrete space - domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=None) + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) V0 = ScalarFunctionSpace('V0', domain) V0_h = discretize(V0, domain_h, degree=degree) @@ -50,5 +52,8 @@ def test_C0PolarProjection_V0(R, ncells, degree): # Comparing results of dot and tosparse sp_P0 = P0.tosparse() y_sp = sp_P0 @ x.toarray() - print(np.allclose(y_sp, y.toarray())) - assert np.allclose(y_sp, y.toarray()) + y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) + y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) + + print(np.allclose(y_sp, y)) + assert np.allclose(y_sp, y) From 3baed170a6f443196792425e9e499faebb010ce3 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 27 Jan 2026 14:18:27 +0100 Subject: [PATCH 047/133] C0 projection P2 and unit tests --- psydac/feec/polar/conga_projections.py | 29 ++++++-- .../polar/tests/test_conga_projections.py | 74 ++++++++++++++----- 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index d37b99414..f96bdf3e3 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -217,7 +217,18 @@ def T(self): def tosparse(self): [n01, n02] = self.domain.npts - return sp_eye(n01 * n02) + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends + + i = np.arange(s1, e1 + 1)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + local_cols = (i * n02 + j).ravel() + data = np.ones((e1 - s1 + 1) * (e2 - s2 + 1)) + + P = coo_matrix((data, (local_cols, local_cols)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) + P.eliminate_zeros() + + return P def toarray(self): return self.tosparse().toarray() @@ -553,15 +564,21 @@ def T(self): def tosparse(self): + [s1, s2] = self.W2.coeff_space.starts + [e1, e2] = self.W2.coeff_space.ends [n1, n2] = self.W2.coeff_space.npts - data = np.ones(n1 * n2) + data = np.ones((e1 - s1 + 1) * (e2 - s2 + 1)) + + i = np.arange(s1, e1 + 1)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + local_cols = (i * n2 + j).ravel() - cols = np.arange(n1 * n2) - rows = np.tile(np.arange(n2, 2 * n2), 2) - rows = np.concatenate((rows, np.arange(2 * n2, n1 * n2))) + rows_to_repeat = local_cols[(local_cols >= n2) & (local_cols < 2*n2)] + rows = np.tile(rows_to_repeat, 2) + rows = np.concatenate((rows, local_cols[local_cols >= 2*n2])) - P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) + P = coo_matrix((data, (rows, local_cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) P.eliminate_zeros() return P.T if self.transposed else P diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index ada856db6..512d9f71d 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -10,44 +10,53 @@ from sympde.topology.analytical_mapping import PolarMapping from psydac.api.discretization import discretize -from psydac.feec.polar.conga_projections import C0PolarProjection_V0 +from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2 from psydac.fem.basic import FemField +def get_domain(R): + # Build physical domain - disk of radius R + logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) + mapping = PolarMapping('F', c1=0, c2=0, rmin=0, rmax=1) + domain = mapping(logical_domain) + + return domain + +def get_random_vector(space): + [s1, s2] = space.coeff_space.starts + [e1, e2] = space.coeff_space.ends + + phi = FemField(space) + x = phi.coeffs + x[s1:e1 + 1, s2:e2 + 1] = np.random.random([e1 - s1 + 1, e2 - s2 + 1]) + x.update_ghost_regions() + return x + + @pytest.mark.parametrize( 'R', [1]) @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) -@pytest.mark.parallel +@pytest.mark.mpi def test_C0PolarProjection_V0(R, ncells, degree): mpi_comm = MPI.COMM_WORLD - - # Build physical domain - disk of radius R - logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) - mapping = PolarMapping('F', c1=0, c2=0, rmin=0, rmax=1) - domain = mapping(logical_domain) + domain = get_domain(R) # Discrete physical domain and discrete space domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) V0 = ScalarFunctionSpace('V0', domain) V0_h = discretize(V0, domain_h, degree=degree) + print(V0_h.spaces) P0 = C0PolarProjection_V0(V0_h, hbc=True) - [s1, s2] = V0_h.coeff_space.starts - [e1, e2] = V0_h.coeff_space.ends - - phi = FemField(V0_h) - x = phi.coeffs - x[s1:e1 + 1, s2:e2 + 1] = np.random.random([e1 - s1 + 1, e2 - s2 + 1]) - x.update_ghost_regions() - + x = get_random_vector(V0_h) phiC = FemField(V0_h) y = phiC.coeffs P0.dot(x, out=y) # Checking projection property P0(P0(phi)) = P0(phi) - assert np.allclose(P0.dot(y)[:,:], y[:,:]) + assert np.allclose(P0.dot(y)[:, :], y[:, :]) # Comparing results of dot and tosparse sp_P0 = P0.tosparse() @@ -55,5 +64,36 @@ def test_C0PolarProjection_V0(R, ncells, degree): y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) - print(np.allclose(y_sp, y)) assert np.allclose(y_sp, y) + + +@pytest.mark.parametrize( 'R', [1]) +@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) +@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.mpi + +def test_C0PolarProjection_V2(R, ncells, degree): + mpi_comm = MPI.COMM_WORLD + domain = get_domain(R) + + # Discrete physical domain and discrete space + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) + V2 = ScalarFunctionSpace('V2', domain) + V2_h = discretize(V2, domain_h, degree=degree) + + P2 = C0PolarProjection_V2(V2_h) + + x = get_random_vector(V2_h) + phiC = FemField(V2_h) + y = phiC.coeffs + P2.dot(x, out=y) + + # Checking projection property P0(P0(phi)) = P0(phi) + assert np.allclose(P2.dot(y)[:, :], y[:, :]) + + # Comparing results of dot and tosparse + sp_P2 = P2.tosparse() + y_sp = sp_P2 @ x.toarray() + + assert np.allclose(y_sp, y.toarray()) + From bf2b88790259a7575948702d327c071446955f9d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 27 Jan 2026 17:43:23 +0100 Subject: [PATCH 048/133] some unit tests for P1 --- .../polar/tests/test_conga_projections.py | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 512d9f71d..914d4c27a 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -2,7 +2,7 @@ from numpy import pi import pytest -from sympde import ScalarFunctionSpace +from sympde import ScalarFunctionSpace, VectorFunctionSpace from mpi4py import MPI @@ -10,8 +10,9 @@ from sympde.topology.analytical_mapping import PolarMapping from psydac.api.discretization import discretize -from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2 +from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2, C0PolarProjection_V1 from psydac.fem.basic import FemField +from psydac.linalg.block import BlockVector def get_domain(R): @@ -32,6 +33,14 @@ def get_random_vector(space): x.update_ghost_regions() return x +def get_random_block_vector(space): + x = BlockVector(space.coeff_space) + for i in (0, 1): + [s1, s2] = space.coeff_space[i].starts + [e1, e2] = space.coeff_space[i].ends + x[i][s1:e1 + 1, s2:e2 + 1] = np.random.random([e1 - s1 + 1, e2 - s2 + 1]) + return x + @pytest.mark.parametrize( 'R', [1]) @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @@ -46,7 +55,6 @@ def test_C0PolarProjection_V0(R, ncells, degree): domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) V0 = ScalarFunctionSpace('V0', domain) V0_h = discretize(V0, domain_h, degree=degree) - print(V0_h.spaces) P0 = C0PolarProjection_V0(V0_h, hbc=True) @@ -67,6 +75,38 @@ def test_C0PolarProjection_V0(R, ncells, degree): assert np.allclose(y_sp, y) +@pytest.mark.parametrize( 'R', [1]) +@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) +@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.mpi + +def test_C0PolarProjection_V1(R, ncells, degree): + mpi_comm = MPI.COMM_WORLD + domain = get_domain(R) + + # Discrete physical domain and discrete space + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) + V1 = VectorFunctionSpace('V1', domain, kind='hcurl') + V1_h = discretize(V1, domain_h, degree=degree) + + P1 = C0PolarProjection_V1(V1_h, hbc=True) + + x = get_random_block_vector(V1_h) + print(x) + y = BlockVector(V1_h.coeff_space) + P1.dot(x, out=y) + + # Checking projection property P1(P1(phi)) = P1(phi) + assert np.allclose(P1.dot(y)[0][:, :], y[0][:, :]) + assert np.allclose(P1.dot(y)[1][:, :], y[1][:, :]) + + # Comparing results of dot and tosparse + sp_P1 = P1.tosparse() + y_sp = sp_P1 @ x.toarray() + + assert np.allclose(y_sp, y.toarray()) + + @pytest.mark.parametrize( 'R', [1]) @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) From 463f00979a2d86cbad38a8b6141c59d256c60109 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 30 Jan 2026 11:24:22 +0100 Subject: [PATCH 049/133] tosparse for C0 P1 and hbc for tests --- psydac/feec/polar/conga_projections.py | 47 +++++++++++++------ .../polar/tests/test_conga_projections.py | 19 ++++---- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index f96bdf3e3..ce8626f00 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -282,7 +282,6 @@ def dot(self, x, out=None): # The number of radial basis functions is one less than the number on the angular basis functions along dir x1 [s1, s2] = self.domain.starts [e1, e2] = self.domain.ends - [n1, n2] = self.domain.npts rank_at_polar_edge = (s1 == 0) if out is None: @@ -323,19 +322,22 @@ def tosparse(self): [n01, n02] = domain_P1_10.npts [n11, n12] = codomain_P1_10.npts + s1, s2 = domain_P1_10.starts + e1, e2 = domain_P1_10.ends + rank_at_polar_edge = (s1 == 0) - dtype = domain_P1_10.dtype - - r = np.zeros(n02) - r[:2] = [-1, 1] - c = np.zeros(n02) - c[::n02 - 1] = [-1, 1] + data, cols, rows = [], [], [] - d_block = toeplitz(c, r) - P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) - P[n12:2 * n12, :n02] = d_block + if rank_at_polar_edge: + len_theta = e2 - s2 + 1 + data = np.tile([-1, 1], len_theta) + cols = np.repeat(np.arange(s2, e2 + 1), 2) + k = np.arange(s2, e2 + 1) + rows = np.column_stack((k, (k - 1) % n12 )).ravel() + n12 - # print(f'in C0CongaProjector1_10.tosparse : P.T.shape = {P.T.shape}, P.shape = {P.shape}, self.transposed = {self.transposed}') + dtype = domain_P1_10.dtype + P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=dtype) + P.eliminate_zeros() return P.T if self.transposed else P def toarray(self): @@ -421,16 +423,31 @@ def T(self): def tosparse(self): + #size of the block is (n_s*n_t)x(n_s*n_t) + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends [n01, n02] = self.domain.npts [n11, n12] = self.codomain.npts + rank_at_outer_edge = (e1 == n01 - 1) dtype = self.domain.dtype - P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) - P[2 * n12:, 2 * n02:] = sp_eye((n11 - 2) * n12) - if self.hbc: - P[-n12:, -n02:] = 0 + start_s = max(2, s1) + end_s = e1 + 1 + if rank_at_outer_edge and self.hbc: + end_s -= 1 + len_theta = e2 - s2 + 1 + data = np.ones(len_theta * (end_s - start_s)) + + i = np.arange(start_s, end_s)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + local_cols = (i * n02 + j).ravel() + + P = coo_matrix((data, (local_cols, local_cols)), shape=[n11 * n12, n01 * n02], dtype=dtype) + P.eliminate_zeros() + print(P.toarray()) return P.T if self.transposed else P + def toarray(self): return self.tosparse().toarray() diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 914d4c27a..c07805e08 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -45,9 +45,10 @@ def get_random_block_vector(space): @pytest.mark.parametrize( 'R', [1]) @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.parametrize( 'hbc', [True, False]) @pytest.mark.mpi -def test_C0PolarProjection_V0(R, ncells, degree): +def test_C0PolarProjection_V0(R, ncells, degree, hbc): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -56,7 +57,7 @@ def test_C0PolarProjection_V0(R, ncells, degree): V0 = ScalarFunctionSpace('V0', domain) V0_h = discretize(V0, domain_h, degree=degree) - P0 = C0PolarProjection_V0(V0_h, hbc=True) + P0 = C0PolarProjection_V0(V0_h, hbc=hbc) x = get_random_vector(V0_h) phiC = FemField(V0_h) @@ -76,11 +77,12 @@ def test_C0PolarProjection_V0(R, ncells, degree): @pytest.mark.parametrize( 'R', [1]) -@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) -@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.parametrize( 'ncells', [[6, 8], [15, 12], [14, 20]]) +@pytest.mark.parametrize( 'degree', [[2, 2], [2, 3]]) +@pytest.mark.parametrize( 'hbc', [True, False]) @pytest.mark.mpi -def test_C0PolarProjection_V1(R, ncells, degree): +def test_C0PolarProjection_V1(R, ncells, degree, hbc): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -89,10 +91,9 @@ def test_C0PolarProjection_V1(R, ncells, degree): V1 = VectorFunctionSpace('V1', domain, kind='hcurl') V1_h = discretize(V1, domain_h, degree=degree) - P1 = C0PolarProjection_V1(V1_h, hbc=True) + P1 = C0PolarProjection_V1(V1_h, hbc=hbc) x = get_random_block_vector(V1_h) - print(x) y = BlockVector(V1_h.coeff_space) P1.dot(x, out=y) @@ -103,8 +104,10 @@ def test_C0PolarProjection_V1(R, ncells, degree): # Comparing results of dot and tosparse sp_P1 = P1.tosparse() y_sp = sp_P1 @ x.toarray() + y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) + y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) - assert np.allclose(y_sp, y.toarray()) + assert np.allclose(y_sp, y) @pytest.mark.parametrize( 'R', [1]) From bda769b2ff4846a665d0bcf4511771fe050ab920 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 10 Feb 2026 11:53:37 +0100 Subject: [PATCH 050/133] add transposed to tests --- psydac/feec/polar/tests/test_conga_projections.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index c07805e08..158d923f6 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -46,9 +46,10 @@ def get_random_block_vector(space): @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) @pytest.mark.parametrize( 'hbc', [True, False]) +@pytest.mark.parametrize( 'transposed', [True, False]) @pytest.mark.mpi -def test_C0PolarProjection_V0(R, ncells, degree, hbc): +def test_C0PolarProjection_V0(R, ncells, degree, hbc, transposed): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -57,7 +58,7 @@ def test_C0PolarProjection_V0(R, ncells, degree, hbc): V0 = ScalarFunctionSpace('V0', domain) V0_h = discretize(V0, domain_h, degree=degree) - P0 = C0PolarProjection_V0(V0_h, hbc=hbc) + P0 = C0PolarProjection_V0(V0_h, hbc=hbc, transposed=transposed) x = get_random_vector(V0_h) phiC = FemField(V0_h) @@ -113,9 +114,10 @@ def test_C0PolarProjection_V1(R, ncells, degree, hbc): @pytest.mark.parametrize( 'R', [1]) @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.parametrize( 'transposed', [True, False]) @pytest.mark.mpi -def test_C0PolarProjection_V2(R, ncells, degree): +def test_C0PolarProjection_V2(R, ncells, degree, transposed): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -124,7 +126,7 @@ def test_C0PolarProjection_V2(R, ncells, degree): V2 = ScalarFunctionSpace('V2', domain) V2_h = discretize(V2, domain_h, degree=degree) - P2 = C0PolarProjection_V2(V2_h) + P2 = C0PolarProjection_V2(V2_h, transposed=transposed) x = get_random_vector(V2_h) phiC = FemField(V2_h) From fd7b300324da291718722f94f0f4108a3a332b9b Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 12 Feb 2026 10:04:29 +0100 Subject: [PATCH 051/133] fix bug in dot methods --- psydac/feec/polar/conga_projections.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index ce8626f00..c001d5616 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -69,6 +69,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.W0.coeff_space + out *= 0 y = out if rank_at_polar_edge: @@ -200,6 +201,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.W1.coeff_space[0] + out *= 0 y = out y[s1:e1+1, s2:e2+1] = x[s1:e1+1, s2:e2+1] @@ -289,6 +291,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.codomain + out *= 0 y = out y[s1:e1 + 1, s2:e2 + 1] = 0 @@ -400,6 +403,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.codomain + out *= 0 y = out if rank_at_polar_edge: y[0, s2:e2 + 1] = 0 @@ -550,6 +554,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.W2.coeff_space + out *= 0 y = out if self.transposed: @@ -695,6 +700,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.W0.coeff_space + out *= 0 y = out if rank_at_polar_edge: @@ -837,6 +843,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.codomain + out *= 0 y = out if rank_at_polar_edge: @@ -967,6 +974,7 @@ def dot(self, x, out=None): else: assert isinstance(out, StencilVector) assert out.space is self.codomain + out *= 0 y = out if rank_at_polar_edge: From 6e97568f79d85d00806315ff154dfb089a6e136e Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 13 Feb 2026 13:04:49 +0100 Subject: [PATCH 052/133] tosparse and unit test for C1 projection P0 --- psydac/feec/polar/conga_projections.py | 69 ++++++++++++------- .../polar/tests/test_conga_projections.py | 50 ++++++++++---- 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index c001d5616..7aa457703 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -634,6 +634,14 @@ def cos_sin_avg(theta, x, angle_comm, s2, e2, n2, i): # --------- 0-FORMS CONGA PROJECTOR P0 ----------# +def toeplitz_columns_sym(t, s2, e2, n2): + + i = np.arange(n2)[:, None] # (n2, 1) + j = np.arange(s2, e2 + 1)[None, :] # (1, m) + idx = np.abs(i - j).ravel('F') # (n2, m) + return np.cos(t[idx]) + + class C1PolarProjection_V0(LinearOperator): """ CONGA Projector P0 from the full spline space S^{p1, p2} on logical domain @@ -736,7 +744,7 @@ def dot(self, x, out=None): y.update_ghost_regions() return y - def transpose(self): + def transpose(self, conjugate=False): return C1PolarProjection_V0(self.W0, gamma=self.gamma, transposed=not self.transposed, hbc=self.hbc) @property @@ -746,36 +754,49 @@ def T(self): def tosparse(self): [n1, n2] = self.W0.coeff_space.npts - theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case - - d_block1 = (self.gamma / n2) * np.ones(n2) - d_block2 = (1 - self.gamma) / n2 * np.ones(n2) + [s1, s2] = self.W0.coeff_space.starts + [e1, e2] = self.W0.coeff_space.ends + theta = np.linspace(0, 2 * pi, n2, endpoint=False) - data = np.concatenate((d_block1, d_block2)) - data = np.tile(data, n2) + rank_at_polar_edge = (s1 == 0) + rank_at_outer_edge = (e1 == n1 - 1) + data, cols, rows = [], [], [] + np.set_printoptions(precision=3) - d_block1 = (self.gamma / n2) * np.ones((n2, n2)) - d_block2 = (1 - self.gamma) / n2 * np.ones((n2, n2)) + \ - 2 / n2 * toeplitz(np.cos(theta - theta[0])) - d_block = np.vstack([d_block1, d_block2]).ravel('F') + #theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case + if rank_at_polar_edge: + data = (self.gamma / n2) * np.ones(2 * n2) + data = np.tile(data, e2 - s2 + 1) + cols = np.repeat(np.arange(s2, e2 + 1), 2 * n2) + rows = np.tile(np.arange(2 * n2), e2 - s2 + 1) + if e1 > 1 > s1: + d_block2 = (1 - self.gamma) / n2 * np.ones((e2 - s2 + 1) * n2) + data = np.concatenate((data, d_block2)) + cols = np.concatenate((cols, np.repeat(np.arange(n2 + s2, n2 + e2 + 1), n2))) + rows = np.concatenate((rows, np.tile(np.arange(n2), e2 - s2 + 1))) + + d_block2 = (1 - self.gamma) / n2 * np.ones((e2 - s2 + 1) * n2) + \ + 2 / n2 * toeplitz_columns_sym(theta, s2, e2, n2) + data = np.concatenate((data, d_block2)) + cols = np.concatenate((cols, np.repeat(np.arange(n2 + s2, n2 + e2 + 1), n2))) + rows = np.concatenate((rows, np.tile(np.arange(n2, 2 * n2), e2 - s2 + 1))) - cols = np.tile(np.arange(2 * n2), (2 * n2, 1)) - rows = cols.T - cols = cols.flatten() - rows = rows.flatten() + # Assemble the rest of the matrix (identity block) + start_s = max(s1, 2) + end_s = e1 + 1 + if rank_at_outer_edge and self.hbc: + end_s -= 1 + i = np.arange(start_s, end_s)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + local_cols = (i * n2 + j).ravel() - if self.hbc: - data = np.concatenate((data, d_block, np.ones(n2 * (n1 - 3)))) - cols = np.concatenate((cols, np.arange(2 * n2, (n1 - 1) * n2))) - rows = np.concatenate((rows, np.arange(2 * n2, (n1 - 1) * n2))) - else: - data = np.concatenate((data, d_block, np.ones(n2 * (n1 - 2)))) - cols = np.concatenate((cols, np.arange(2 * n2, n1 * n2))) - rows = np.concatenate((rows, np.arange(2 * n2, n1 * n2))) + data = np.concatenate((data, np.ones((e2 - s2 + 1) * (end_s - start_s)))) + cols = np.concatenate((cols, local_cols)) + rows = np.concatenate((rows, local_cols)) P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() - print(P) + print(P.toarray()) return P.T if self.transposed else P diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 158d923f6..561b02e04 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -10,7 +10,8 @@ from sympde.topology.analytical_mapping import PolarMapping from psydac.api.discretization import discretize -from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2, C0PolarProjection_V1 +from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2, C0PolarProjection_V1, \ + C1PolarProjection_V0 from psydac.fem.basic import FemField from psydac.linalg.block import BlockVector @@ -41,6 +42,24 @@ def get_random_block_vector(space): x[i][s1:e1 + 1, s2:e2 + 1] = np.random.random([e1 - s1 + 1, e2 - s2 + 1]) return x +def tests_for_P0_projections(P0, V0_h, mpi_comm): + + x = get_random_vector(V0_h) + phiC = FemField(V0_h) + y = phiC.coeffs + P0.dot(x, out=y) + + # Checking projection property P0(P0(phi)) = P0(phi) + assert np.allclose(P0.dot(y)[:, :], y[:, :]) + + # Comparing results of dot and tosparse + sp_P0 = P0.tosparse() + y_sp = sp_P0 @ x.toarray() + y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) + y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) + + assert np.allclose(y_sp, y) + @pytest.mark.parametrize( 'R', [1]) @pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) @@ -59,22 +78,27 @@ def test_C0PolarProjection_V0(R, ncells, degree, hbc, transposed): V0_h = discretize(V0, domain_h, degree=degree) P0 = C0PolarProjection_V0(V0_h, hbc=hbc, transposed=transposed) + tests_for_P0_projections(P0, V0_h, mpi_comm) - x = get_random_vector(V0_h) - phiC = FemField(V0_h) - y = phiC.coeffs - P0.dot(x, out=y) - # Checking projection property P0(P0(phi)) = P0(phi) - assert np.allclose(P0.dot(y)[:, :], y[:, :]) +@pytest.mark.parametrize( 'R', [1]) +@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) +@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.parametrize( 'hbc', [True, False]) +@pytest.mark.parametrize( 'transposed', [True, False]) +@pytest.mark.mpi - # Comparing results of dot and tosparse - sp_P0 = P0.tosparse() - y_sp = sp_P0 @ x.toarray() - y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) - y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) +def test_C1PolarProjection_V0(R, ncells, degree, hbc, transposed): + mpi_comm = MPI.COMM_WORLD + domain = get_domain(R) - assert np.allclose(y_sp, y) + # Discrete physical domain and discrete space + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) + V0 = ScalarFunctionSpace('V0', domain) + V0_h = discretize(V0, domain_h, degree=degree) + + P0 = C1PolarProjection_V0(V0_h, hbc=hbc, transposed=transposed, gamma=1.0) + tests_for_P0_projections(P0, V0_h, mpi_comm) @pytest.mark.parametrize( 'R', [1]) From 8865845695d30c196995d26b0b9e306e982dea5c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 17 Feb 2026 14:56:28 +0100 Subject: [PATCH 053/133] refactor unit tests --- .../polar/tests/test_conga_projections.py | 83 +++++++------------ 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 561b02e04..8e58193f3 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -11,7 +11,7 @@ from psydac.api.discretization import discretize from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2, C0PolarProjection_V1, \ - C1PolarProjection_V0 + C1PolarProjection_V0, C1PolarProjection_V1 from psydac.fem.basic import FemField from psydac.linalg.block import BlockVector @@ -42,7 +42,25 @@ def get_random_block_vector(space): x[i][s1:e1 + 1, s2:e2 + 1] = np.random.random([e1 - s1 + 1, e2 - s2 + 1]) return x -def tests_for_P0_projections(P0, V0_h, mpi_comm): + +@pytest.mark.parametrize('transposed', [False, True]) +@pytest.mark.parametrize('hbc', [False, True]) +@pytest.mark.parametrize('degree', [[1, 1]]) +@pytest.mark.parametrize('ncells', [[2, 3]]) +@pytest.mark.parametrize('R', [1]) +@pytest.mark.parametrize('Projector', [C0PolarProjection_V0, C1PolarProjection_V0]) +@pytest.mark.mpi + +def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): + mpi_comm = MPI.COMM_WORLD + domain = get_domain(R) + + # Discrete physical domain and discrete space + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) + V0 = ScalarFunctionSpace('V0', domain) + V0_h = discretize(V0, domain_h, degree=degree) + + P0 = Projector(V0_h, hbc=hbc, transposed=transposed) x = get_random_vector(V0_h) phiC = FemField(V0_h) @@ -61,53 +79,16 @@ def tests_for_P0_projections(P0, V0_h, mpi_comm): assert np.allclose(y_sp, y) -@pytest.mark.parametrize( 'R', [1]) -@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) -@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) -@pytest.mark.parametrize( 'hbc', [True, False]) -@pytest.mark.parametrize( 'transposed', [True, False]) -@pytest.mark.mpi - -def test_C0PolarProjection_V0(R, ncells, degree, hbc, transposed): - mpi_comm = MPI.COMM_WORLD - domain = get_domain(R) - - # Discrete physical domain and discrete space - domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) - V0 = ScalarFunctionSpace('V0', domain) - V0_h = discretize(V0, domain_h, degree=degree) - - P0 = C0PolarProjection_V0(V0_h, hbc=hbc, transposed=transposed) - tests_for_P0_projections(P0, V0_h, mpi_comm) - -@pytest.mark.parametrize( 'R', [1]) -@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) -@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) -@pytest.mark.parametrize( 'hbc', [True, False]) -@pytest.mark.parametrize( 'transposed', [True, False]) -@pytest.mark.mpi - -def test_C1PolarProjection_V0(R, ncells, degree, hbc, transposed): - mpi_comm = MPI.COMM_WORLD - domain = get_domain(R) - - # Discrete physical domain and discrete space - domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) - V0 = ScalarFunctionSpace('V0', domain) - V0_h = discretize(V0, domain_h, degree=degree) - - P0 = C1PolarProjection_V0(V0_h, hbc=hbc, transposed=transposed, gamma=1.0) - tests_for_P0_projections(P0, V0_h, mpi_comm) - - -@pytest.mark.parametrize( 'R', [1]) -@pytest.mark.parametrize( 'ncells', [[6, 8], [15, 12], [14, 20]]) -@pytest.mark.parametrize( 'degree', [[2, 2], [2, 3]]) -@pytest.mark.parametrize( 'hbc', [True, False]) +@pytest.mark.parametrize('transposed', [False, True]) +@pytest.mark.parametrize('hbc', [True, False]) +@pytest.mark.parametrize('degree', [[2, 2], [2, 3]]) +@pytest.mark.parametrize('ncells', [[6, 8], [15, 12], [14, 20]]) +@pytest.mark.parametrize('R', [1]) +@pytest.mark.parametrize('Projector', [C0PolarProjection_V1, C1PolarProjection_V1]) @pytest.mark.mpi -def test_C0PolarProjection_V1(R, ncells, degree, hbc): +def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -116,7 +97,7 @@ def test_C0PolarProjection_V1(R, ncells, degree, hbc): V1 = VectorFunctionSpace('V1', domain, kind='hcurl') V1_h = discretize(V1, domain_h, degree=degree) - P1 = C0PolarProjection_V1(V1_h, hbc=hbc) + P1 = Projector(V1_h, hbc=hbc, transposed=transposed) x = get_random_block_vector(V1_h) y = BlockVector(V1_h.coeff_space) @@ -135,13 +116,13 @@ def test_C0PolarProjection_V1(R, ncells, degree, hbc): assert np.allclose(y_sp, y) -@pytest.mark.parametrize( 'R', [1]) -@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) -@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) @pytest.mark.parametrize( 'transposed', [True, False]) +@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) +@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) +@pytest.mark.parametrize( 'R', [1]) @pytest.mark.mpi -def test_C0PolarProjection_V2(R, ncells, degree, transposed): +def test_PolarProjection_V2(R, ncells, degree, transposed): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) From f4293906f5d36eedd5aa6fc981f0bd5d2280c2b5 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 18 Feb 2026 12:32:38 +0100 Subject: [PATCH 054/133] get global vector for sparse operators --- .../polar/tests/test_conga_projections.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 8e58193f3..93e30f81b 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -44,9 +44,9 @@ def get_random_block_vector(space): @pytest.mark.parametrize('transposed', [False, True]) -@pytest.mark.parametrize('hbc', [False, True]) -@pytest.mark.parametrize('degree', [[1, 1]]) -@pytest.mark.parametrize('ncells', [[2, 3]]) +@pytest.mark.parametrize('hbc', [True, False]) +@pytest.mark.parametrize('degree', [[2, 2], [2, 3]]) +@pytest.mark.parametrize('ncells', [[8, 10], [15, 12], [14, 20]]) @pytest.mark.parametrize('R', [1]) @pytest.mark.parametrize('Projector', [C0PolarProjection_V0, C1PolarProjection_V0]) @pytest.mark.mpi @@ -72,7 +72,8 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Comparing results of dot and tosparse sp_P0 = P0.tosparse() - y_sp = sp_P0 @ x.toarray() + x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) + y_sp = sp_P0 @ x_global y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) @@ -97,7 +98,9 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): V1 = VectorFunctionSpace('V1', domain, kind='hcurl') V1_h = discretize(V1, domain_h, degree=degree) - P1 = Projector(V1_h, hbc=hbc, transposed=transposed) + P1 = Projector(V1_h, hbc=hbc) + if transposed: + P1 = P1.T x = get_random_block_vector(V1_h) y = BlockVector(V1_h.coeff_space) @@ -109,7 +112,8 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): # Comparing results of dot and tosparse sp_P1 = P1.tosparse() - y_sp = sp_P1 @ x.toarray() + x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) + y_sp = sp_P1 @ x_global y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) @@ -143,7 +147,8 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): # Comparing results of dot and tosparse sp_P2 = P2.tosparse() - y_sp = sp_P2 @ x.toarray() + x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) + y_sp = sp_P2 @ x_global assert np.allclose(y_sp, y.toarray()) From 758139b1990557e3ecb9a813dcfcb039d74a7643 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 3 Mar 2026 18:28:32 +0100 Subject: [PATCH 055/133] add reference matrices to test the tosparse methods --- psydac/feec/polar/tests/P0.h5 | Bin 0 -> 6682328 bytes psydac/feec/polar/tests/P1.h5 | Bin 0 -> 24768728 bytes psydac/feec/polar/tests/P2.h5 | Bin 0 -> 3342752 bytes .../polar/tests/test_conga_projections.py | 74 +++++++++++++++++- 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 psydac/feec/polar/tests/P0.h5 create mode 100644 psydac/feec/polar/tests/P1.h5 create mode 100644 psydac/feec/polar/tests/P2.h5 diff --git a/psydac/feec/polar/tests/P0.h5 b/psydac/feec/polar/tests/P0.h5 new file mode 100644 index 0000000000000000000000000000000000000000..91949c0ad8956347e48d0d75f49d56f1824bb38d GIT binary patch literal 6682328 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 literal 0 HcmV?d00001 diff --git a/psydac/feec/polar/tests/P1.h5 b/psydac/feec/polar/tests/P1.h5 new file mode 100644 index 0000000000000000000000000000000000000000..03c146d79f72024613c409150b3c5ac3a64286b8 GIT binary patch literal 24768728 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 z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- 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 zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai 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 z�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk z6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C7 z2;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF z5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr z009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo z0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1 znF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+ zim92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&f zGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)d zK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C7 z2oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF z5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$ae zK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I z009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+ zIsya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{ znwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNAp zQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+ zKyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N z0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C7 z2oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya; z5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@ zAV7cs0RjXFg zK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eA zn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp& zBX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5); z7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZ zfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+ z009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs z0RjXFgK!5-N z0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^ z2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah z5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>Tg zBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�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 zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? 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 RLC`^ISr8yVfItlabH7HPRY`yVfhJ(?Hwmu< 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 literal 0 HcmV?d00001 diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 93e30f81b..a81c68fc8 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -1,3 +1,6 @@ +from itertools import product + +import h5py import numpy as np from numpy import pi @@ -43,10 +46,51 @@ def get_random_block_vector(space): return x +def create_reference_file(proj_name, projections_list): + """ + Save global sparse matrices (after checking their correctness by hand) in h5 format for tests + This function is needed only if the parameters for the tests are changed + """ + f = h5py.File(proj_name + '.h5', mode='w') + params = dict( + transposed=[False, True], + hbc=[True, False], + degree=[[2, 2], [2, 3]], + ncells=[[8, 10], [15, 12]], + projector=projections_list, + ) + + for (tr, hbc, deg, nc, proj) in product(params["transposed"], params["hbc"], params["degree"], params["ncells"], + params["projector"]): + + if proj_name == "P2" and hbc is True: + continue + domain = get_domain(1) + domain_h = discretize(domain, ncells=nc, periodic=[False, True]) + if proj_name == 'P1': + V = VectorFunctionSpace('V', domain, kind='hcurl') + else: + V = ScalarFunctionSpace('V', domain) + V_h = discretize(V, domain_h, degree=deg) + + if proj_name == 'P2': P = proj(V_h) + else: P = proj(V_h, hbc=hbc) + if tr: P = P.T + + key = f"{proj.__name__}/n{nc[0]}x{nc[1]}/p{deg[0]}-{deg[1]}/hbc{hbc}/T{tr}" + if key in f: + del f[key] + g = f.require_group(key) + g.create_dataset("P", data=P.toarray()) + print("saved", key) + + f.close() + + @pytest.mark.parametrize('transposed', [False, True]) @pytest.mark.parametrize('hbc', [True, False]) @pytest.mark.parametrize('degree', [[2, 2], [2, 3]]) -@pytest.mark.parametrize('ncells', [[8, 10], [15, 12], [14, 20]]) +@pytest.mark.parametrize('ncells', [[8, 10], [15, 12]]) @pytest.mark.parametrize('R', [1]) @pytest.mark.parametrize('Projector', [C0PolarProjection_V0, C1PolarProjection_V0]) @pytest.mark.mpi @@ -72,6 +116,12 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Comparing results of dot and tosparse sp_P0 = P0.tosparse() + name = Projector.__name__ + with h5py.File('P0.h5', "r") as f: + key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" + sp_P0_ref = f[key][()] + assert np.allclose(sp_P0.toarray(), sp_P0_ref) + x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P0 @ x_global y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) @@ -84,7 +134,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): @pytest.mark.parametrize('transposed', [False, True]) @pytest.mark.parametrize('hbc', [True, False]) @pytest.mark.parametrize('degree', [[2, 2], [2, 3]]) -@pytest.mark.parametrize('ncells', [[6, 8], [15, 12], [14, 20]]) +@pytest.mark.parametrize('ncells', [[8, 10], [15, 12]]) @pytest.mark.parametrize('R', [1]) @pytest.mark.parametrize('Projector', [C0PolarProjection_V1, C1PolarProjection_V1]) @pytest.mark.mpi @@ -112,6 +162,12 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): # Comparing results of dot and tosparse sp_P1 = P1.tosparse() + name = Projector.__name__ + with h5py.File('P1.h5', "r") as f: + key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" + sp_P0_ref = f[key][()] + assert np.allclose(sp_P1.toarray(), sp_P0_ref) + x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P1 @ x_global y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) @@ -121,8 +177,8 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): @pytest.mark.parametrize( 'transposed', [True, False]) -@pytest.mark.parametrize( 'degree', [[1, 1], [2, 2]]) -@pytest.mark.parametrize( 'ncells', [[4, 8], [12, 12]]) +@pytest.mark.parametrize('degree', [[2, 2], [2, 3]]) +@pytest.mark.parametrize('ncells', [[8, 10], [15, 12]]) @pytest.mark.parametrize( 'R', [1]) @pytest.mark.mpi @@ -147,8 +203,18 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): # Comparing results of dot and tosparse sp_P2 = P2.tosparse() + name = 'C0PolarProjection_V2' + with h5py.File('P2.h5', "r") as f: + key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{False}/T{transposed}/P" + sp_P0_ref = f[key][()] + assert np.allclose(sp_P2.toarray(), sp_P0_ref) + x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P2 @ x_global assert np.allclose(y_sp, y.toarray()) +if __name__ == '__main__': + create_reference_file("P0", [C0PolarProjection_V0, C1PolarProjection_V0]) + create_reference_file("P1", [C0PolarProjection_V1, C1PolarProjection_V1]) + create_reference_file("P2", [C0PolarProjection_V2]) From d40e4230675c7daacdd6d0cb794965082103e905 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 4 Mar 2026 11:56:46 +0100 Subject: [PATCH 056/133] make some tests run correctly in parallel --- .../polar/tests/test_conga_projections.py | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index a81c68fc8..a8d25da2d 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -1,4 +1,5 @@ from itertools import product +from pathlib import Path import h5py import numpy as np @@ -114,14 +115,19 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Checking projection property P0(P0(phi)) = P0(phi) assert np.allclose(P0.dot(y)[:, :], y[:, :]) - # Comparing results of dot and tosparse + # Comparing the global sparse matrix to reference file sp_P0 = P0.tosparse() - name = Projector.__name__ - with h5py.File('P0.h5', "r") as f: - key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" - sp_P0_ref = f[key][()] - assert np.allclose(sp_P0.toarray(), sp_P0_ref) + sp_P0_global = mpi_comm.allreduce(sp_P0.toarray(), op=MPI.SUM) + if mpi_comm.rank == 0: + name = Projector.__name__ + h5_path = Path(__file__).absolute().parent / "P0.h5" + + with h5py.File(h5_path, "r") as f: + key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" + sp_P0_ref = f[key][()] + assert np.allclose(sp_P0_global, sp_P0_ref) + # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P0 @ x_global y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) @@ -160,14 +166,19 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): assert np.allclose(P1.dot(y)[0][:, :], y[0][:, :]) assert np.allclose(P1.dot(y)[1][:, :], y[1][:, :]) - # Comparing results of dot and tosparse + # Comparing the global sparse matrix to reference file sp_P1 = P1.tosparse() - name = Projector.__name__ - with h5py.File('P1.h5', "r") as f: - key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" - sp_P0_ref = f[key][()] - assert np.allclose(sp_P1.toarray(), sp_P0_ref) + sp_P1_global = mpi_comm.allreduce(sp_P1.toarray(), op=MPI.SUM) + if mpi_comm.rank == 0: + name = Projector.__name__ + h5_path = (Path(__file__).absolute().parent / "P1.h5") + + with h5py.File(h5_path, "r") as f: + key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" + sp_P1_ref = f[key][()] + assert np.allclose(sp_P1_global, sp_P1_ref) + # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P1 @ x_global y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) @@ -201,14 +212,20 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): # Checking projection property P0(P0(phi)) = P0(phi) assert np.allclose(P2.dot(y)[:, :], y[:, :]) - # Comparing results of dot and tosparse + # Comparing the global sparse matrix to reference file sp_P2 = P2.tosparse() - name = 'C0PolarProjection_V2' - with h5py.File('P2.h5', "r") as f: - key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{False}/T{transposed}/P" - sp_P0_ref = f[key][()] - assert np.allclose(sp_P2.toarray(), sp_P0_ref) + sp_P2_global = mpi_comm.allreduce(sp_P2.toarray(), op=MPI.SUM) + if mpi_comm.rank == 0: + + name = 'C0PolarProjection_V2' + h5_path = (Path(__file__).absolute().parent / "P2.h5") + with h5py.File(h5_path, "r") as f: + key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{False}/T{transposed}/P" + sp_P1_ref = f[key][()] + assert np.allclose(sp_P2_global, sp_P1_ref) + + # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P2 @ x_global From 2bf379f954110fe129afe0d4c35137fc5f0fabae Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 5 Mar 2026 13:19:50 +0100 Subject: [PATCH 057/133] tosparse methods for C1 P1 --- psydac/feec/polar/conga_projections.py | 111 +++++++++++++------------ 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 7aa457703..e8856722d 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -448,7 +448,6 @@ def tosparse(self): P = coo_matrix((data, (local_cols, local_cols)), shape=[n11 * n12, n01 * n02], dtype=dtype) P.eliminate_zeros() - print(P.toarray()) return P.T if self.transposed else P @@ -634,6 +633,8 @@ def cos_sin_avg(theta, x, angle_comm, s2, e2, n2, i): # --------- 0-FORMS CONGA PROJECTOR P0 ----------# + +# matrix p in the paper notation def toeplitz_columns_sym(t, s2, e2, n2): i = np.arange(n2)[:, None] # (n2, 1) @@ -796,7 +797,6 @@ def tosparse(self): P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() - print(P.toarray()) return P.T if self.transposed else P @@ -848,8 +848,6 @@ def codomain(self): def dtype(self): return float - # Warning: this dot method has to be revised for mpi! - # the toeplitz multiplication requires all processes along the theta-dir to communicate. def dot(self, x, out=None): assert isinstance(x, StencilVector) @@ -898,38 +896,49 @@ def tosparse(self): [n01, n02] = self.domain.npts theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! + [s1, s2] = self.domain.starts + [e1, e2] = self.domain.ends + print(n01, n02) - ntot = n01 * n02 - dtype = self.domain.dtype - P = lil_matrix((ntot, ntot), dtype=dtype) - - # c = (2/n02) * (np.cos(np.roll(theta, -1) - theta[0]) - np.cos(theta - theta[0])) - # r = (2/n02) * (np.cos(theta[1] - theta) - np.cos(theta[0] - theta)) - # p_block = 2/n02 * toeplitz(np.cos(theta - theta[0])) - - c = (2 / n02) * np.cos(theta - theta[0]) # first column - r = (2 / n02) * np.cos(theta[0] - theta) # first row - - p_block = toeplitz(c, r) - - P[:n02, :n02] = p_block - P[n02:2 * n02, :n02] += sp_eye(n02) # - p_block - P[n02:2 * n02, :n02] -= p_block - P[n02:, n02:] = sp_eye(ntot - n02) - - # p2 = p_block @ p_block - # print(f'p-p2 = {p_block - p2}') - # Imp = sp_eye(n02) - p_block + data, cols, rows = [], [], [] + rank_at_polar_edge = (s1 == 0) - # imp_b = P[n02:2*n02, :n02] - # print(f'check 2 = {Imp - imp_b}') + if rank_at_polar_edge: + #block p + data = (2 / n02) * toeplitz_columns_sym(theta, s2, e2, n02) + # block I - p + i_minus_p = -1 * data.copy() + diag_js = np.arange(s2, e2 + 1) + diag_pos = (diag_js - s2) * n02 + diag_js + i_minus_p[diag_pos] += 1.0 + + data = np.concatenate((data, i_minus_p)) + + rows_theta = np.tile(np.arange(n02), e2 - s2 + 1) + cols_theta = np.repeat(np.arange(s2, e2 + 1), n02) + rows = np.concatenate((rows, rows_theta)) + cols = np.concatenate((cols, cols_theta)) + rows = np.concatenate((rows, rows_theta + 1 * n02)) + cols = np.concatenate((cols, cols_theta)) + + print(data) + print(cols) + print(rows) - # prod_b = Imp @ p_block + p_block @ Imp - # print(f'check 3 = {Imp - prod_b}') + # Assemble the rest of the matrix (identity block) + start_s = max(s1, 1) + end_s = e1 + 1 + i = np.arange(start_s, end_s)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + local_cols = (i * n02 + j).ravel() - # print( f" ---- PU1_00 -> to sparse: {self.transposed} ---- ") + data = np.concatenate((data, np.ones((e2 - s2 + 1) * (end_s - start_s)))) + cols = np.concatenate((cols, local_cols)) + rows = np.concatenate((rows, local_cols)) - # exit() + P = coo_matrix((data, (rows, cols)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) + P.eliminate_zeros() + np.set_printoptions(precision=3) return P.T if self.transposed else P @@ -952,7 +961,6 @@ class C1PolarProjection_V1_10(LinearOperator): """ def __init__(self, W1, transposed=False): - # assert isinstance(W1, ProductFemSpace) assert isinstance(W1, VectorFemSpace) self.W1 = W1 @@ -974,9 +982,6 @@ def codomain(self): def dtype(self): return float - # Warning: this dot method has to be revised for mpi! - # the toeplitz multiplication requires all processes along the theta-dir to communicate. - # We also use np.roll which should be changed with the use of ghost regions def dot(self, x, out=None): assert isinstance(x, StencilVector) if not x.ghost_regions_in_sync: @@ -1038,26 +1043,26 @@ def tosparse(self): [n01, n02] = domain_P1_10.npts # radial grid [n11, n12] = codomain_P1_10.npts # angular grid + [s1, s2] = domain_P1_10.starts + [e1, e2] = domain_P1_10.ends - theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! + rank_at_polar_edge = (s1 == 0) + data, cols, rows = [], [], [] + if rank_at_polar_edge: + theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! + cols = np.repeat(np.arange(s2, e2 + 1), n02) + rows = np.tile(np.arange(n02, 2*n02), e2 - s2 + 1) + i = np.arange(n02)[:, None] + j = np.arange(s2, e2 + 1)[None, :] + idx = np.abs(i - j) + p_cols = np.cos(theta[idx]) - dtype = domain_P1_10.dtype - P = lil_matrix((n11 * n12, n01 * n02), dtype=dtype) - - c = (2 / n02) * (np.cos(np.roll(theta, -1) - theta[0]) - np.cos(theta - theta[0])) # correct ? - # c = (2/n02) * (np.cos(np.roll(theta, 1) - theta[0]) - np.cos(theta - theta[0])) - r = (2 / n02) * (np.cos(theta[1] - theta) - np.cos(theta[0] - theta)) - - q_block = toeplitz(c, r) - # print(f'domain V1_s: [n01, n02] = {[n01, n02]}') - # print(f'codomain V1_theta: [n11, n12] = {[n11, n12]}') - - # print(f'n01 * n02 + n12:n01 * n02 + 2 * n12 = {n01 * n02 + n12}:{n01 * n02 + 2 * n12}') - # print(f'P.shape = {P.shape}') - # print(f'q_block.shape = {q_block.shape}') - # print(f'P[n01 * n02 + n12:n01 * n02 + 2 * n12, :n12].shape = {P[n01 * n02 + n12:n01 * n02 + 2 * n12, :n12].shape}') - # P[n01 * n02 + n12:n01 * n02 + 2 * n12, :n12] = q_block # seems wrong - P[n12:2 * n12, :n02] = q_block + p_next = np.roll(p_cols, shift=-1, axis=0) + data = (2.0 / n02) * (p_next - p_cols).ravel('F') + + + P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=self.domain.dtype) + print(P.toarray()) return P.T if self.transposed else P From d5f180cb90da9193465a1866e228bca87099283c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 9 Mar 2026 21:13:54 +0100 Subject: [PATCH 058/133] fixed deadlock in P1 C1 --- psydac/feec/polar/conga_projections.py | 31 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index e8856722d..b6a669292 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -921,10 +921,6 @@ def tosparse(self): rows = np.concatenate((rows, rows_theta + 1 * n02)) cols = np.concatenate((cols, cols_theta)) - print(data) - print(cols) - print(rows) - # Assemble the rest of the matrix (identity block) start_s = max(s1, 1) end_s = e1 + 1 @@ -982,6 +978,28 @@ def codomain(self): def dtype(self): return float + def forward_theta_diff_local(self, z, angle_comm): + + # Compute result[j] = z[j+1] - z[j] for distributed array z (periodic) + result = np.empty_like(z) + + if angle_comm is None or angle_comm.size == 1: + result[:] = np.roll(z, -1) - z + return result + + result[:-1] = z[1:] - z[:-1] + + rank = angle_comm.rank + size = angle_comm.size + right_rank = (rank + 1) % size + left_rank = (rank - 1) % size + + recvbuf = np.empty(1, dtype=z.dtype) + angle_comm.Sendrecv(sendbuf=z[0], dest=left_rank, recvbuf=recvbuf, source=right_rank) + + result[-1] = recvbuf[0] - z[-1] + return result + def dot(self, x, out=None): assert isinstance(x, StencilVector) if not x.ghost_regions_in_sync: @@ -1014,9 +1032,8 @@ def dot(self, x, out=None): else: y[0, s2:e2 + 1] = 0 sum_cos, sum_sin = cos_sin_avg(theta_local, x, angle_comm, s2, e2, n2, 0) - y[1, s2:e2 + 1] = np.cos(theta_local) * sum_cos + np.sin(theta_local) * sum_sin - y.update_ghost_regions() - y[1, s2:e2 + 1] = y[1, s2 + 1:e2 + 2] - y[1, s2:e2 + 1] + row = np.cos(theta_local) * sum_cos + np.sin(theta_local) * sum_sin + y[1, s2:e2 + 1] = self.forward_theta_diff_local(row, angle_comm) y[2:e1 + 1, s2:e2 + 1] = 0 else: y[s1:e1 + 1, s2:e2 + 1] = 0 From b37c41a0b176346bad289bd186817e8c242ced91 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 9 Mar 2026 21:20:54 +0100 Subject: [PATCH 059/133] small fix --- psydac/feec/polar/tests/P2.h5 | Bin 3342752 -> 1675552 bytes .../polar/tests/test_conga_projections.py | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/tests/P2.h5 b/psydac/feec/polar/tests/P2.h5 index 3c8416fff05057c1d673dbed77a702e6964c38d0..fa0246d19b3b2e993aabe89eb9c81def625c4d45 100644 GIT binary patch delta 726 zcmaKo&ui0Q7{`6zcTLhJ&Bj{Sv~_E$wHse(*t?4`XGW%i_WlPIxU(kbXG_lBqYd6Q-uerz5o>n!STIzY)=%1i1azR z_nvdl_kBM1Mn3tL5(`x#-c}|$A{4B`@c4Bh5Ls{-Xa5|Fa-JOVCD=j~kT`j}e4T-5QvL~-;O0?+^k z-~hBKqsQ2ff-Q2%30Bv#1-a4$`#B9xcp^vd<89=o_3`Bto>#JOvH~iLOi;Ocnyu5j zPA=F9o~(y({_IK!%qoIeid=B^-HSx&>rb5_hwTO6eKM-^3aL%y1Gd_6eLPse^U9yg zPB13*nG=l3HAo%9b#Y%5=aw2;T?vdiRlHVn0$cz$zyt6q*J{3noo;#6hojq-+YX^^ zG&6nOSDxE`M5+%V!Lvfv;;TMs?qg?+Uh`EJ{j{`^XI;!7O#Yr{UrX1olPIIU%giD> z<0nPU-bHvBajHJ9_eYIpVp2_?6rN-+FwKTfiH7OptkM^zhyUg|qkdB5?#bRG4mR2f1?(BA_Vx-j!o$)SC)2aFHy-eDTs&#W(EHfMDOr6bc zj_AzmQ3iiYmQCm30lUu4gE73GAHX`bMy2bwNV`RD9!*OJzi|G#mB#-%51uHee?&>% z`Ygf>fCexC2duTda>i@vX6#QrO}gFirCu|3I0c+;NgG*9di^(e!h zK{Lx{&*lyG$ny!jequP*YJ>cvJZXdY!~Fki1Ixj|GV@d9U>P!Vg+Qwfn&fa%uBial z`Asok>WohXh)z|HGIUz9Y&x66uPs}~a{IkB Date: Tue, 10 Mar 2026 11:43:14 +0100 Subject: [PATCH 060/133] fix variable referenced before assignment --- psydac/feec/polar/examples/poisson_2d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 41f06caa0..6059eb21c 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -495,6 +495,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # If required by user, create C1 projector and then restrict # stiffness/mass matrices and right-hand-side vector to C1 space t0 = time() + bc = None if smooth_method == 'polar-spec': proj = C1Projector(F) Sp = proj.change_matrix_basis(S) @@ -949,4 +950,4 @@ def parse_input_arguments(): plt.show() ## example of run: -# mpirun -n 2 python poisson_2d.py -S -n 2 3 -d 2 2 -t disk -D 0.2 -m 'C0conga' +# mpirun -n 2 python poisson_2d.py -S -n 8 10 -d 2 2 -t disk -D 0.2 -m 'C0conga' From 2a925692af1a262d8fe837b65edaf71ee77d16d6 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 10 Mar 2026 11:57:23 +0100 Subject: [PATCH 061/133] small fix --- psydac/feec/polar/examples/poisson_2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 6059eb21c..9f643ea47 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -277,7 +277,7 @@ def dot(self, x, out=None): y.update_ghost_regions() return y - def transpose(self): + def transpose(self, conjugate=False): return CongaLaplacian(self.S.T, self.M.T, self.P.T, self.alpha) def tosparse(self): From 4aa259111e7af25b082c397ca6058bf8ab77d487 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 10 Mar 2026 12:14:26 +0100 Subject: [PATCH 062/133] changes in maxwell example --- psydac/feec/polar/examples/maxwell_2d.py | 258 ++++++----------------- 1 file changed, 63 insertions(+), 195 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index c6b5fe1c8..94552145e 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -391,134 +391,6 @@ def l2_norm_of(f_log): f2_with_det = lambda eta1, eta2: f_log(eta1, eta2) ** 2 * np.sqrt(F.metric_det(eta1, eta2)) return np.sqrt(derham_h.V0.integral(f2_with_det)) - #TODO Remove this! It's only for debugging - def compare_serial_parallel(): - - if mpi_rank == 0: - if use_spline_mapping: - #geometry = Geometry(filename='geo.h5') - domain_h_s = discretize(domain, filename='geo.h5') - #F_serial = [*domain_h_serial.mappings.values()].pop() - derham_h_s = discretize(derham, domain_h_s, degree=degree) - V0_s, V1_s, V2_s = derham_h_s.spaces - V1_sx, V1_sy = V1_s.spaces - ex_field, = V1_sx.import_fields('Ex.h5', 'Ex_field') - ey_field, = V1_sy.import_fields('Ey.h5', 'Ey_field') - ex_data = ex_field.coeffs._data - ey_data = ey_field.coeffs._data - #B_serial, = V2_s.import_fields('B.h5', 'B_field') - - - if mpi_size == 1: - np.save('Ex_coeffs_ser.npy', ex_data) - else: - np.save('Ex_coeffs_par.npy', ex_data) - - if mpi_size == 1: - np.save('Ey_coeffs_ser.npy', ey_data) - else: - np.save('Ey_coeffs_par.npy', ey_data) - - Ex_coeffs_ser = np.load('Ex_coeffs_ser.npy') - Ex_coeffs_par = np.load('Ex_coeffs_par.npy') - Ey_coeffs_ser = np.load('Ey_coeffs_ser.npy') - Ey_coeffs_par = np.load('Ey_coeffs_par.npy') - print(Ex_coeffs_ser - Ex_coeffs_par) - print(np.linalg.norm(Ex_coeffs_ser - Ex_coeffs_par)) - print(Ey_coeffs_ser - Ey_coeffs_par) - print(np.linalg.norm(Ey_coeffs_ser - Ey_coeffs_par)) - a = input() - exit() - - def compare_P(): - - if mpi_rank == 0: - if use_spline_mapping: - #geometry = Geometry(filename='geo.h5') - domain_h_s = discretize(domain, filename='geo.h5') - #F_serial = [*domain_h_serial.mappings.values()].pop() - derham_h_s = discretize(derham, domain_h_s, degree=degree) - V0_s, V1_s, V2_s = derham_h_s.spaces - V1_sx, V1_sy = V1_s.spaces - print("NCELLS") - print(V1_sx.degree, V1_sx.ncells, V1_sx.nbasis) - print(V1_sy.degree, V1_sy.ncells, V1_sy.nbasis) - ex_field, = V1_sx.import_fields('Ex.h5', 'Ex_field') - ey_field, = V1_sy.import_fields('Ey.h5', 'Ey_field') - - ex_fieldP, = V1_sx.import_fields('ExP.h5', 'Ex_field') - ey_fieldP, = V1_sy.import_fields('EyP.h5', 'Ey_field') - - ex_fieldPP, = V1_sx.import_fields('ExPP.h5', 'Ex_field') - ey_fieldPP, = V1_sy.import_fields('EyPP.h5', 'Ey_field') - - ex_data = ex_field.coeffs._data - ey_data = ey_field.coeffs._data - ex_dataP = ex_fieldP.coeffs._data - ey_dataP = ey_fieldP.coeffs._data - ex_dataPP = ex_fieldPP.coeffs._data - ey_dataPP = ey_fieldPP.coeffs._data - #B_serial, = V2_s.import_fields('B.h5', 'B_field') - - - print(ey_data) - print(ey_dataP) - print(ex_data.shape) - print(ey_data.shape) - # print(ex_dataP - ex_dataPP) - # print(np.linalg.norm(ex_dataP - ex_dataPP)) - # print(ey_dataP - ey_dataPP) - # print(np.linalg.norm(ey_dataP - ey_dataPP)) - a = input() - exit() - - def test_P1(V1, P1): - - e = V1.coeff_space.zeros() - Ex, Ey = e[0], e[1] - - [s1, s2] = Ey.starts - [e1, e2] = Ey.ends - - # for i in range(s1, e1 + 1): - # for j in range(s2, e2 + 1): - # Ex[i, j] = 0.1 * i + 0.01 * j - # Ey[i, j] = -0.2 * i + 0.03 * j - print("RANK") - print(mpi_rank, s1, e1, s2, e2) - print(Ey._data) - Ey[0, 0] = 1 - - e.update_ghost_regions() - print(Ex._data) - - Ex_field = FemField(V1x, coeffs=Ex) - Ey_field = FemField(V1y, coeffs=Ey) - V1x.export_fields('Ex.h5', Ex_field=Ex_field) - V1y.export_fields('Ey.h5', Ey_field=Ey_field) - - eP = V1.coeff_space.zeros() - P1.dot(e, out=eP) - ExP, EyP = eP[0], eP[1] - ExP.update_ghost_regions() - EyP.update_ghost_regions() - Ex_field = FemField(V1x, coeffs=ExP) - Ey_field = FemField(V1y, coeffs=EyP) - V1x.export_fields('ExP.h5', Ex_field=Ex_field) - V1y.export_fields('EyP.h5', Ey_field=Ey_field) - - P1.dot(eP.copy(), out=eP) - ExP, EyP = eP[0], eP[1] - ExP.update_ghost_regions() - EyP.update_ghost_regions() - Ex_field = FemField(V1x, coeffs=ExP) - Ey_field = FemField(V1y, coeffs=EyP) - V1x.export_fields('ExPP.h5', Ex_field=Ex_field) - V1y.export_fields('EyPP.h5', Ey_field=Ey_field) - compare_P() - - exit() - def run_study_L2_proj(): omega = 4 print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') @@ -611,41 +483,41 @@ def run_study_L2_proj(): # plot - fx_values = np.empty_like(x1) - fy_values = np.empty_like(x1) - fx_filter_values = np.empty_like(x1) - fy_filter_values = np.empty_like(x1) - - fx_ex_values = np.empty_like(x1) - fy_ex_values = np.empty_like(x1) - - for i, x1i in enumerate(x1[:, 0]): - for j, x2j in enumerate(x2[0, :]): - - - xij, yij = F(x1i, x2j) - fx_values[i, j], fy_values[i, j] = \ - push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) - fx_filter_values[i, j], fy_filter_values[i, j] = \ - push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) - fx_ex_values[i, j], fy_ex_values[i, j] = \ - fx_call(xij, yij), fy_call(xij, yij) - - fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) - fig2.savefig(f'{visdir}/fx_{rp_str}.png') - - fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) - fig3.savefig(f'{visdir}/fy_{rp_str}.png') - - print('done: showing fh') - - fig2.clf() - fig2 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) - fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') - - fig3.clf() - fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) - fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') + # fx_values = np.empty_like(x1) + # fy_values = np.empty_like(x1) + # fx_filter_values = np.empty_like(x1) + # fy_filter_values = np.empty_like(x1) + # + # fx_ex_values = np.empty_like(x1) + # fy_ex_values = np.empty_like(x1) + # + # for i, x1i in enumerate(x1[:, 0]): + # for j, x2j in enumerate(x2[0, :]): + # + # + # xij, yij = F(x1i, x2j) + # fx_values[i, j], fy_values[i, j] = \ + # push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) + # fx_filter_values[i, j], fy_filter_values[i, j] = \ + # push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) + # fx_ex_values[i, j], fy_ex_values[i, j] = \ + # fx_call(xij, yij), fy_call(xij, yij) + + # fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) + # fig2.savefig(f'{visdir}/fx_{rp_str}.png') + # + # fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) + # fig3.savefig(f'{visdir}/fy_{rp_str}.png') + # + # print('done: showing fh') + # + # fig2.clf() + # fig2 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) + # fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') + # + # fig3.clf() + # fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) + # fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') print('done: showing fh_filter') @@ -748,8 +620,6 @@ def run_study_L2_proj(): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - # compare_serial_parallel() - V1x, V1y = V1.spaces #call them s, theta Ex_field = FemField(V1x, coeffs=e[0]) Ey_field = FemField(V1y, coeffs=e[1]) @@ -757,8 +627,6 @@ def run_study_L2_proj(): V1x.export_fields('Ex.h5', Ex_field=Ex_field) V1y.export_fields('Ey.h5', Ey_field=Ey_field) V2.export_fields('B.h5', B_field=B_field) - #test_P1(V1, P1) - if use_scipy: @@ -814,33 +682,33 @@ def run_study_L2_proj(): # print( x2) - def plot_fields_along_s(tstr): # , j0=0, j1=0): - - # if j1 is None: - j0 = 0 - j1 = x2.shape[1] // 2 - theta0 = x2[0, j0] - theta1 = x2[0, j1] - name = 'Ex' - fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', - x1[:, j0], Ex_values[:, j0], Ex_ex_values[:, j0], - -x1[:, j1], Ex_values[:, j1], Ex_ex_values[:, j1]) - # fig_line = plot_curve_along_s(name, 's', tstr, -x1[:,j1], f'{theta1}', Ex_values[:,j1], Ex_ex_values[:,j1]) - fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - # plt.close(fig_line) - fig_line.clf() - name = 'Ey' - fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', - x1[:, j0], Ey_values[:, j0], Ey_ex_values[:, j0], - -x1[:, j1], Ey_values[:, j1], Ey_ex_values[:, j1]) - fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - fig_line.clf() - name = 'Bz' - fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', - x1[:, j0], Bz_values[:, j0], Bz_ex_values[:, j0], - -x1[:, j1], Bz_values[:, j1], Bz_ex_values[:, j1]) - fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - plt.close(fig_line) + # def plot_fields_along_s(tstr): # , j0=0, j1=0): + # + # # if j1 is None: + # j0 = 0 + # j1 = x2.shape[1] // 2 + # theta0 = x2[0, j0] + # theta1 = x2[0, j1] + # name = 'Ex' + # fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + # x1[:, j0], Ex_values[:, j0], Ex_ex_values[:, j0], + # -x1[:, j1], Ex_values[:, j1], Ex_ex_values[:, j1]) + # # fig_line = plot_curve_along_s(name, 's', tstr, -x1[:,j1], f'{theta1}', Ex_values[:,j1], Ex_ex_values[:,j1]) + # fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + # # plt.close(fig_line) + # fig_line.clf() + # name = 'Ey' + # fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + # x1[:, j0], Ey_values[:, j0], Ey_ex_values[:, j0], + # -x1[:, j1], Ey_values[:, j1], Ey_ex_values[:, j1]) + # fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + # fig_line.clf() + # name = 'Bz' + # fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + # x1[:, j0], Bz_values[:, j0], Bz_ex_values[:, j0], + # -x1[:, j1], Bz_values[:, j1], Bz_ex_values[:, j1]) + # fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + # plt.close(fig_line) # Prepare plots if plot_interval: @@ -1345,4 +1213,4 @@ def Strang_update(dtau): ## example of run: -## python conga_polar_maxwell_2d.py -S -n 16 32 -d 3 3 -T 1 -D 0.2 -s 1 -p 100 \ No newline at end of file +## python maxwell_2d.py -S -n 16 32 -d 3 3 -T 1 -D 0.2 -s 1 -p 100 \ No newline at end of file From 48b49e2bb8275872352966a965c4871935fee46e Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 12 Mar 2026 10:29:11 +0100 Subject: [PATCH 063/133] atol rtol in unit tests --- .../polar/tests/test_conga_projections.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index d0f3eca84..85981afaa 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -113,7 +113,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): P0.dot(x, out=y) # Checking projection property P0(P0(phi)) = P0(phi) - assert np.allclose(P0.dot(y)[:, :], y[:, :]) + assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) # Comparing the global sparse matrix to reference file sp_P0 = P0.tosparse() @@ -125,7 +125,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): with h5py.File(h5_path, "r") as f: key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" sp_P0_ref = f[key][()] - assert np.allclose(sp_P0_global, sp_P0_ref) + assert np.allclose(sp_P0_global, sp_P0_ref, atol=1e-12, rtol=1e-12) # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) @@ -133,7 +133,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) - assert np.allclose(y_sp, y) + assert np.allclose(y_sp, y, atol=1e-12, rtol=1e-12) @@ -163,8 +163,8 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): P1.dot(x, out=y) z = P1.dot(y) # Checking projection property P1(P1(phi)) = P1(phi) - assert np.allclose(z[0][:, :], y[0][:, :]) - assert np.allclose(z[1][:, :], y[1][:, :]) + assert np.allclose(z[0][:, :], y[0][:, :], atol=1e-12, rtol=1e-12) + assert np.allclose(z[1][:, :], y[1][:, :], atol=1e-12, rtol=1e-12) # Comparing the global sparse matrix to reference file sp_P1 = P1.tosparse() @@ -176,7 +176,7 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): with h5py.File(h5_path, "r") as f: key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" sp_P1_ref = f[key][()] - assert np.allclose(sp_P1_global, sp_P1_ref) + assert np.allclose(sp_P1_global, sp_P1_ref, atol=1e-12, rtol=1e-12) # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) @@ -184,7 +184,7 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) - assert np.allclose(y_sp, y) + assert np.allclose(y_sp, y, atol=1e-12, rtol=1e-12) @pytest.mark.parametrize( 'transposed', [True, False]) @@ -210,7 +210,7 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): P2.dot(x, out=y) # Checking projection property P0(P0(phi)) = P0(phi) - assert np.allclose(P2.dot(y)[:, :], y[:, :]) + assert np.allclose(P2.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) # Comparing the global sparse matrix to reference file sp_P2 = P2.tosparse() @@ -223,13 +223,13 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): with h5py.File(h5_path, "r") as f: key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{False}/T{transposed}/P" sp_P1_ref = f[key][()] - assert np.allclose(sp_P2_global, sp_P1_ref) + assert np.allclose(sp_P2_global, sp_P1_ref, atol=1e-12, rtol=1e-12) # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P2 @ x_global - assert np.allclose(y_sp, y.toarray()) + assert np.allclose(y_sp, y.toarray(), atol=1e-12, rtol=1e-12) if __name__ == '__main__': create_reference_file("P0", [C0PolarProjection_V0, C1PolarProjection_V0]) From 4b194335962b14e4b398a320ccdbfe58ffba0fd4 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 13 Mar 2026 00:10:18 +0100 Subject: [PATCH 064/133] test checking entries of sparse matrices for P0 --- .../polar/tests/test_conga_projections.py | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 85981afaa..fb750da1a 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -115,8 +115,46 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Checking projection property P0(P0(phi)) = P0(phi) assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) - # Comparing the global sparse matrix to reference file + # gather sparse matrix entries (rows, columns, data) on root process sp_P0 = P0.tosparse() + payload = (sp_P0.row, sp_P0.col, sp_P0.data) + parts = mpi_comm.gather(payload, root=0) + + if mpi_comm.rank == 0: + + # Concatenate the result of gather (equivalent to summation of local matrices) + rows = np.concatenate([p[0] for p in parts]) + cols = np.concatenate([p[1] for p in parts]) + data = np.concatenate([p[2] for p in parts]) + + [n1, n2] = ncells + + # Check the number of non-zero entries in the sparse matrix + if Projector == C0PolarProjection_V0: + if not hbc: assert len(data) == n2 * n2 + n2 * (n1 + degree[0] - 1) + else: assert len(data) == n2 * n2 + n2 * (n1 + degree[0] - 2) + else: + if not hbc: assert len(data) == 3 * n2 * n2 + n2 * (n1 + degree[0] - 2) + else: assert len(data) == 3 * n2 * n2 + n2 * (n1 + degree[0] - 3) + + if transposed: + rows, cols = cols, rows + + # Check all non-zero entries + for i, j, v in zip(rows, cols, data): + if i < n2 and j < n2: + assert np.allclose(v, 1 / n2, atol=1e-12, rtol=1e-12) + elif Projector == C1PolarProjection_V0 and j < n2 <= i < 2 * n2: + assert np.allclose(v, 1 / n2, atol=1e-12, rtol=1e-12) + elif Projector == C1PolarProjection_V0 and n2 <= i < 2 * n2 and n2 <= j < 2 * n2: + assert np.allclose(v, 2 / n2 * np.cos( (i - j) * 2 * np.pi / n2), atol=1e-12, rtol=1e-12) + else: + # diagonal + assert i == j + assert v == 1.0 + + + # Comparing the global sparse matrix to reference file sp_P0_global = mpi_comm.allreduce(sp_P0.toarray(), op=MPI.SUM) if mpi_comm.rank == 0: name = Projector.__name__ @@ -232,6 +270,4 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): assert np.allclose(y_sp, y.toarray(), atol=1e-12, rtol=1e-12) if __name__ == '__main__': - create_reference_file("P0", [C0PolarProjection_V0, C1PolarProjection_V0]) - create_reference_file("P1", [C0PolarProjection_V1, C1PolarProjection_V1]) - create_reference_file("P2", [C0PolarProjection_V2]) + test_PolarProjection_V0(C0PolarProjection_V0, 1, [8, 10], [2, 2], False, False) \ No newline at end of file From 067ddf82c9261f6b5981f6d91272deff6bc8a185 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 13 Mar 2026 09:31:24 +0100 Subject: [PATCH 065/133] change allclose to isclose --- .../feec/polar/tests/test_conga_projections.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index fb750da1a..8f483dece 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -131,11 +131,9 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Check the number of non-zero entries in the sparse matrix if Projector == C0PolarProjection_V0: - if not hbc: assert len(data) == n2 * n2 + n2 * (n1 + degree[0] - 1) - else: assert len(data) == n2 * n2 + n2 * (n1 + degree[0] - 2) + assert len(data) == n2 * n2 + n2 * (n1 + degree[0] - (2 if hbc else 1)) else: - if not hbc: assert len(data) == 3 * n2 * n2 + n2 * (n1 + degree[0] - 2) - else: assert len(data) == 3 * n2 * n2 + n2 * (n1 + degree[0] - 3) + assert len(data) == 3 * n2 * n2 + n2 * (n1 + degree[0] - (3 if hbc else 2)) if transposed: rows, cols = cols, rows @@ -143,15 +141,15 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Check all non-zero entries for i, j, v in zip(rows, cols, data): if i < n2 and j < n2: - assert np.allclose(v, 1 / n2, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1 / n2, atol=1e-12, rtol=1e-12) elif Projector == C1PolarProjection_V0 and j < n2 <= i < 2 * n2: - assert np.allclose(v, 1 / n2, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1 / n2, atol=1e-12, rtol=1e-12) elif Projector == C1PolarProjection_V0 and n2 <= i < 2 * n2 and n2 <= j < 2 * n2: - assert np.allclose(v, 2 / n2 * np.cos( (i - j) * 2 * np.pi / n2), atol=1e-12, rtol=1e-12) + assert np.isclose(v, 2 / n2 * np.cos( (i - j) * 2 * np.pi / n2), atol=1e-12, rtol=1e-12) else: - # diagonal + # identity assert i == j - assert v == 1.0 + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) # Comparing the global sparse matrix to reference file From 054900f083076e4ceb3fbb5c3d48918abb78779c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 13 Mar 2026 15:22:09 +0100 Subject: [PATCH 066/133] test checking entries of sparse matrices for P2 --- .../polar/tests/test_conga_projections.py | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 8f483dece..a0521ee01 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -115,8 +115,12 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Checking projection property P0(P0(phi)) = P0(phi) assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) - # gather sparse matrix entries (rows, columns, data) on root process sp_P0 = P0.tosparse() + + [n1, n2] = V0_h.coeff_space.npts + assert sp_P0.shape == (n1 * n2, n1 * n2) + + # gather sparse matrix entries (rows, columns, data) on root process payload = (sp_P0.row, sp_P0.col, sp_P0.data) parts = mpi_comm.gather(payload, root=0) @@ -127,13 +131,11 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): cols = np.concatenate([p[1] for p in parts]) data = np.concatenate([p[2] for p in parts]) - [n1, n2] = ncells - # Check the number of non-zero entries in the sparse matrix if Projector == C0PolarProjection_V0: - assert len(data) == n2 * n2 + n2 * (n1 + degree[0] - (2 if hbc else 1)) + assert len(data) == n2 * n2 + n2 * (n1 - (2 if hbc else 1)) else: - assert len(data) == 3 * n2 * n2 + n2 * (n1 + degree[0] - (3 if hbc else 2)) + assert len(data) == 3 * n2 * n2 + n2 * (n1 - (3 if hbc else 2)) if transposed: rows, cols = cols, rows @@ -250,6 +252,35 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): # Comparing the global sparse matrix to reference file sp_P2 = P2.tosparse() + print(mpi_comm.rank, sp_P2.shape, sp_P2.nnz, V2_h.coeff_space.npts) + + payload = (sp_P2.row, sp_P2.col, sp_P2.data) + parts = mpi_comm.gather(payload, root=0) + + [n1, n2] = V2_h.coeff_space.npts + assert sp_P2.shape == (n1 * n2, n1 * n2) + + if mpi_comm.rank == 0: + + # Concatenate the result of gather (equivalent to summation of local matrices) + rows = np.concatenate([p[0] for p in parts]) + cols = np.concatenate([p[1] for p in parts]) + data = np.concatenate([p[2] for p in parts]) + + # Check the number of non-zero entries in the sparse matrix + assert len(data) == n1 * n2 + + if transposed: + rows, cols = cols, rows + + # Check all non-zero entries + for i, j, v in zip(rows, cols, data): + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + if j < n2: + assert i == j + n2 + else: + assert i == j + sp_P2_global = mpi_comm.allreduce(sp_P2.toarray(), op=MPI.SUM) if mpi_comm.rank == 0: @@ -268,4 +299,4 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): assert np.allclose(y_sp, y.toarray(), atol=1e-12, rtol=1e-12) if __name__ == '__main__': - test_PolarProjection_V0(C0PolarProjection_V0, 1, [8, 10], [2, 2], False, False) \ No newline at end of file + test_PolarProjection_V2(1, [8, 10], [2, 2], False) \ No newline at end of file From 099e58633f94ed3544d2a429f33f20ea65c64963 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 16 Mar 2026 11:47:48 +0100 Subject: [PATCH 067/133] test checking entries of sparse matrices for P1 --- .../polar/tests/test_conga_projections.py | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index a0521ee01..2bd75defd 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -204,8 +204,87 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): assert np.allclose(z[0][:, :], y[0][:, :], atol=1e-12, rtol=1e-12) assert np.allclose(z[1][:, :], y[1][:, :], atol=1e-12, rtol=1e-12) - # Comparing the global sparse matrix to reference file sp_P1 = P1.tosparse() + print(sp_P1.toarray()) + + [n01, n02] = V1_h.coeff_space[0].npts + [n11, n12] = V1_h.coeff_space[1].npts + + assert sp_P1.shape == (n01 * n02 + n11 * n12, n01 * n02 + n11 * n12) + + # gather sparse matrix entries (rows, columns, data) on root process + payload = (sp_P1.row, sp_P1.col, sp_P1.data) + parts = mpi_comm.gather(payload, root=0) + + if mpi_comm.rank == 0: + + # Concatenate the result of gather (equivalent to summation of local matrices) + rows = np.concatenate([p[0] for p in parts]) + cols = np.concatenate([p[1] for p in parts]) + data = np.concatenate([p[2] for p in parts]) + + # Check the number of non-zero entries in the sparse matrix + if Projector == C0PolarProjection_V1: + assert len(data) == n01 * n02 + 2 * n02 + (n11 - (3 if hbc else 2)) * n12 + else: + print(len(data)) + assert len(data) == 3 * n02 * n02 + n02 * (n01 - 1) + (n11 - (3 if hbc else 2)) * n12 + + if transposed: + rows, cols = cols, rows + + # Check all non-zero entries + for i, j, v in zip(rows, cols, data): + if Projector == C0PolarProjection_V1: + # Block P1_00 + if i < n01 * n02: + # identity + assert i == j + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + # Block P1_10 + elif n01 * n02 + n02 <= i < n01 * n02 + 2 * n02: + # d matrix + if j == i - n02 * (n01 + 1): + assert np.isclose(v, -1.0, atol=1e-12, rtol=1e-12) + else: + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert j == (i - n02 * (n01 + 1) + 1) % n02 + # Block P1_11 + else: + assert i >= n01 * n02 + 2 * n02 + assert j == i + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + + if Projector == C1PolarProjection_V1: + p_ij = 2 / n02 * np.cos((i - j) * 2 * np.pi / n02) + # Block P1_00 + if i < n02: + # matrix p + assert j < n02 + assert np.isclose(v, p_ij, atol=1e-12, rtol=1e-12) + elif 2 * n02 > i >= n02 > j: + # matrix I - p + if i == j + n02: + assert np.isclose(v, 1 - p_ij, atol=1e-12, rtol=1e-12) + else: + assert np.isclose(v, - p_ij, atol=1e-12, rtol=1e-12) + elif n02 <= i < n01 * n02: + assert j == i + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + + # Block P1_10 + elif j < n02: + # matrix q + q_ij = (2 / n02 * np.cos((i + 1 - j) * 2 * np.pi / n02) - p_ij) + assert np.isclose(v, q_ij, atol=1e-12, rtol=1e-12) + + # Block P1_11 + else: + assert i == j + assert i >= n01 * n02 + 2 * n02 + assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + + # Comparing the global sparse matrix to reference file sp_P1_global = mpi_comm.allreduce(sp_P1.toarray(), op=MPI.SUM) if mpi_comm.rank == 0: name = Projector.__name__ From f7555b420cbc183473c47aba8660b0559e7adac1 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 18 Mar 2026 09:20:29 +0100 Subject: [PATCH 068/133] change tosparse of C0 to distribution by row --- psydac/feec/polar/conga_projections.py | 89 ++++++++++++-------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index b6a669292..3215b2157 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -94,13 +94,12 @@ def dot(self, x, out=None): return y def transpose(self, conjugate=False): - #should just return self since it's symmetric? return C0PolarProjection_V0(self.W0, transposed=not self.transposed, hbc=self.hbc) def tosparse(self): - # Matrix size is n1*n2. Columns with numbers i*n2 + j with - # s1 <= i <= e1, s2 <= j <= e2 belong to the process + # Matrix size is n1*n2. Rows with global indices i*n2 + j (C-style flattening) with + # s1 <= i <= e1, s2 <= j <= e2 are filled for the current process [n1, n2] = self.W0.coeff_space.npts [s1, s2] = self.W0.coeff_space.starts @@ -113,24 +112,22 @@ def tosparse(self): len_theta = e2 - s2 + 1 if rank_at_polar_edge: data = np.tile((1 / n2) * np.ones(n2), len_theta) - cols = np.repeat(np.arange(s2, e2 + 1), n2) - rows = np.tile(np.arange(n2), len_theta) + rows = np.repeat(np.arange(s2, e2 + 1), n2) + cols = np.tile(np.arange(n2), len_theta) # Assemble the rest of the matrix (identity block) - start_s = s1 - end_s = e1 + 1 # We do not need entries with s1 = 0 (already accounted for) - if rank_at_polar_edge: - start_s += 1 - if rank_at_outer_edge and self.hbc: - end_s -= 1 + start_s = s1 + 1 if rank_at_polar_edge else s1 + end_s = e1 if (rank_at_outer_edge and self.hbc) else e1 + 1 + i = np.arange(start_s, end_s)[:, None] j = np.arange(s2, e2 + 1)[None, :] - local_cols = (i * n2 + j).ravel() + # rows of identity owned by the process + local_rows = (i * n2 + j).ravel() data = np.concatenate((data, np.ones(len_theta * (end_s - start_s)))) - cols = np.concatenate((cols, local_cols)) - rows = np.concatenate((rows, local_cols)) + cols = np.concatenate((cols, local_rows)) + rows = np.concatenate((rows, local_rows)) P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() @@ -224,10 +221,10 @@ def tosparse(self): i = np.arange(s1, e1 + 1)[:, None] j = np.arange(s2, e2 + 1)[None, :] - local_cols = (i * n02 + j).ravel() + local_rows = (i * n02 + j).ravel() data = np.ones((e1 - s1 + 1) * (e2 - s2 + 1)) - P = coo_matrix((data, (local_cols, local_cols)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) + P = coo_matrix((data, (local_rows, local_rows)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) P.eliminate_zeros() return P @@ -272,9 +269,6 @@ def codomain(self): def dtype(self): return float - # Warning: this dot method has to be revised for mpi! - # the toeplitz multiplication requires all processes along the theta dir. to communicate. - def dot(self, x, out=None): assert isinstance(x, StencilVector) @@ -316,32 +310,32 @@ def T(self): def tosparse(self): - if self.transposed: - domain_P1_10 = self.codomain - codomain_P1_10 = self.domain - else: - domain_P1_10 = self.domain - codomain_P1_10 = self.codomain - - [n01, n02] = domain_P1_10.npts - [n11, n12] = codomain_P1_10.npts - s1, s2 = domain_P1_10.starts - e1, e2 = domain_P1_10.ends + [n01, n02] = self.domain.npts + [n11, n12] = self.codomain.npts + s1, s2 = self.domain.starts + e1, e2 = self.codomain.ends rank_at_polar_edge = (s1 == 0) data, cols, rows = [], [], [] + # matrix d (derivatives of periodic splines) + # for example: n_theta = 3, single rank at polar edge, not transposed. Then: + # data = [-1 1 -1 1 -1 1], rows = [3 3 4 4 5 5], cols = [0 1 1 2 2 0] if rank_at_polar_edge: len_theta = e2 - s2 + 1 data = np.tile([-1, 1], len_theta) - cols = np.repeat(np.arange(s2, e2 + 1), 2) k = np.arange(s2, e2 + 1) - rows = np.column_stack((k, (k - 1) % n12 )).ravel() + n12 + if self.transposed: + rows = np.repeat(np.arange(s2, e2 + 1), 2) + cols = np.column_stack((k, (k - 1) % n12)).ravel() + n12 + else: + rows = np.repeat(np.arange(s2, e2 + 1), 2) + n12 + cols = np.column_stack((k, (k + 1) % n12 )).ravel() - dtype = domain_P1_10.dtype + dtype = self.domain.dtype P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=dtype) P.eliminate_zeros() - return P.T if self.transposed else P + return P def toarray(self): return self.tosparse().toarray() @@ -427,28 +421,26 @@ def T(self): def tosparse(self): - #size of the block is (n_s*n_t)x(n_s*n_t) - [s1, s2] = self.domain.starts - [e1, e2] = self.domain.ends + [s1, s2] = self.codomain.starts + [e1, e2] = self.codomain.ends [n01, n02] = self.domain.npts [n11, n12] = self.codomain.npts rank_at_outer_edge = (e1 == n01 - 1) dtype = self.domain.dtype + # identity block of size n12(n12 - 2) start_s = max(2, s1) - end_s = e1 + 1 - if rank_at_outer_edge and self.hbc: - end_s -= 1 + end_s = e1 if (rank_at_outer_edge and self.hbc) else e1 + 1 len_theta = e2 - s2 + 1 data = np.ones(len_theta * (end_s - start_s)) i = np.arange(start_s, end_s)[:, None] j = np.arange(s2, e2 + 1)[None, :] - local_cols = (i * n02 + j).ravel() + local_rows = (i * n02 + j).ravel() - P = coo_matrix((data, (local_cols, local_cols)), shape=[n11 * n12, n01 * n02], dtype=dtype) + P = coo_matrix((data, (local_rows, local_rows)), shape=[n11 * n12, n01 * n02], dtype=dtype) P.eliminate_zeros() - return P.T if self.transposed else P + return P def toarray(self): @@ -593,13 +585,15 @@ def tosparse(self): i = np.arange(s1, e1 + 1)[:, None] j = np.arange(s2, e2 + 1)[None, :] - local_cols = (i * n2 + j).ravel() + local_rows = (i * n2 + j).ravel() + rows_to_repeat = local_rows[(local_rows >= n2) & (local_rows < 2 * n2)] - rows_to_repeat = local_cols[(local_cols >= n2) & (local_cols < 2*n2)] rows = np.tile(rows_to_repeat, 2) - rows = np.concatenate((rows, local_cols[local_cols >= 2*n2])) + cols = rows_to_repeat - n2 + rows = np.concatenate((rows, local_rows[local_rows >= 2 * n2])) + cols = np.concatenate((cols, local_rows[local_rows >= n2])) - P = coo_matrix((data, (rows, local_cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) + P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) P.eliminate_zeros() return P.T if self.transposed else P @@ -764,7 +758,6 @@ def tosparse(self): data, cols, rows = [], [], [] np.set_printoptions(precision=3) - #theta = np.linspace(0, 2 * pi, n2, endpoint=False) # Warning parallel case if rank_at_polar_edge: data = (self.gamma / n2) * np.ones(2 * n2) data = np.tile(data, e2 - s2 + 1) From e2a0a7182b2a3de6b834f3ad236731a0199d69d6 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 18 Mar 2026 10:17:43 +0100 Subject: [PATCH 069/133] transpose for P2 --- psydac/feec/polar/conga_projections.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 3215b2157..53aaa6b1a 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -586,17 +586,20 @@ def tosparse(self): i = np.arange(s1, e1 + 1)[:, None] j = np.arange(s2, e2 + 1)[None, :] local_rows = (i * n2 + j).ravel() - rows_to_repeat = local_rows[(local_rows >= n2) & (local_rows < 2 * n2)] - - rows = np.tile(rows_to_repeat, 2) - cols = rows_to_repeat - n2 - rows = np.concatenate((rows, local_rows[local_rows >= 2 * n2])) + if self.transposed: + rows = local_rows + cols = local_rows[local_rows < n2] + n2 + else: + rows_to_repeat = local_rows[(local_rows >= n2) & (local_rows < 2 * n2)] + rows = np.tile(rows_to_repeat, 2) + cols = rows_to_repeat - n2 + rows = np.concatenate((rows, local_rows[local_rows >= 2 * n2])) cols = np.concatenate((cols, local_rows[local_rows >= n2])) P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) P.eliminate_zeros() - return P.T if self.transposed else P + return P def toarray(self): return self.tosparse().toarray() From 3525acc4ed9067906773835910c9d2f7e5872da7 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 18 Mar 2026 12:52:54 +0100 Subject: [PATCH 070/133] tosparse methods for C1 distributed by row --- psydac/feec/polar/conga_projections.py | 59 ++++++++++++++------------ 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 53aaa6b1a..ac6a2ce5f 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -762,34 +762,38 @@ def tosparse(self): np.set_printoptions(precision=3) if rank_at_polar_edge: - data = (self.gamma / n2) * np.ones(2 * n2) - data = np.tile(data, e2 - s2 + 1) - cols = np.repeat(np.arange(s2, e2 + 1), 2 * n2) - rows = np.tile(np.arange(2 * n2), e2 - s2 + 1) + # matrix of size n2*n2 with all entries equal to gamma / n2 + data = (self.gamma / n2) * np.ones(n2 * (e2 - s2 + 1)) + # matrix of size n2*n2 with all entries equal to (1 - gamma) / n2 + data = np.concatenate((data, (1 - self.gamma) / n2 * np.ones((e2 - s2 + 1) * n2))) + rows = np.tile(np.repeat(np.arange(s2, e2 + 1), n2), 2) + cols = np.tile(np.arange(n2), e2 - s2 + 1) + cols = np.concatenate((cols, np.tile(np.arange(n2, 2 * n2), e2 - s2 + 1))) + print(rows, cols) if e1 > 1 > s1: - d_block2 = (1 - self.gamma) / n2 * np.ones((e2 - s2 + 1) * n2) + # matrix of size n2*n2 with all entries equal to gamma / n2 + d_block2 = (self.gamma / n2) * np.ones((e2 - s2 + 1) * n2) data = np.concatenate((data, d_block2)) - cols = np.concatenate((cols, np.repeat(np.arange(n2 + s2, n2 + e2 + 1), n2))) - rows = np.concatenate((rows, np.tile(np.arange(n2), e2 - s2 + 1))) + rows = np.concatenate((rows, np.repeat(np.arange(n2 + s2, n2 + e2 + 1), n2))) + cols = np.concatenate((cols, np.tile(np.arange(n2), e2 - s2 + 1))) + # matrix p d_block2 = (1 - self.gamma) / n2 * np.ones((e2 - s2 + 1) * n2) + \ 2 / n2 * toeplitz_columns_sym(theta, s2, e2, n2) data = np.concatenate((data, d_block2)) - cols = np.concatenate((cols, np.repeat(np.arange(n2 + s2, n2 + e2 + 1), n2))) - rows = np.concatenate((rows, np.tile(np.arange(n2, 2 * n2), e2 - s2 + 1))) + rows = np.concatenate((rows, np.repeat(np.arange(n2 + s2, n2 + e2 + 1), n2))) + cols = np.concatenate((cols, np.tile(np.arange(n2, 2 * n2), e2 - s2 + 1))) # Assemble the rest of the matrix (identity block) start_s = max(s1, 2) - end_s = e1 + 1 - if rank_at_outer_edge and self.hbc: - end_s -= 1 + end_s = e1 if (rank_at_outer_edge and self.hbc) else e1 + 1 i = np.arange(start_s, end_s)[:, None] j = np.arange(s2, e2 + 1)[None, :] - local_cols = (i * n2 + j).ravel() + local_rows = (i * n2 + j).ravel() data = np.concatenate((data, np.ones((e2 - s2 + 1) * (end_s - start_s)))) - cols = np.concatenate((cols, local_cols)) - rows = np.concatenate((rows, local_cols)) + cols = np.concatenate((cols, local_rows)) + rows = np.concatenate((rows, local_rows)) P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() @@ -894,7 +898,6 @@ def tosparse(self): theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! [s1, s2] = self.domain.starts [e1, e2] = self.domain.ends - print(n01, n02) data, cols, rows = [], [], [] rank_at_polar_edge = (s1 == 0) @@ -910,8 +913,8 @@ def tosparse(self): data = np.concatenate((data, i_minus_p)) - rows_theta = np.tile(np.arange(n02), e2 - s2 + 1) - cols_theta = np.repeat(np.arange(s2, e2 + 1), n02) + cols_theta = np.tile(np.arange(n02), e2 - s2 + 1) + rows_theta = np.repeat(np.arange(s2, e2 + 1), n02) rows = np.concatenate((rows, rows_theta)) cols = np.concatenate((cols, cols_theta)) rows = np.concatenate((rows, rows_theta + 1 * n02)) @@ -922,15 +925,16 @@ def tosparse(self): end_s = e1 + 1 i = np.arange(start_s, end_s)[:, None] j = np.arange(s2, e2 + 1)[None, :] - local_cols = (i * n02 + j).ravel() + local_rows = (i * n02 + j).ravel() data = np.concatenate((data, np.ones((e2 - s2 + 1) * (end_s - start_s)))) - cols = np.concatenate((cols, local_cols)) - rows = np.concatenate((rows, local_cols)) + cols = np.concatenate((cols, local_rows)) + rows = np.concatenate((rows, local_rows)) P = coo_matrix((data, (rows, cols)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) P.eliminate_zeros() - np.set_printoptions(precision=3) + np.set_printoptions(precision=3, suppress=True) + print(P.toarray()) return P.T if self.transposed else P @@ -1062,16 +1066,15 @@ def tosparse(self): rank_at_polar_edge = (s1 == 0) data, cols, rows = [], [], [] if rank_at_polar_edge: - theta = np.linspace(0, 2 * pi, n02, endpoint=False) # Warning not mpi! - cols = np.repeat(np.arange(s2, e2 + 1), n02) - rows = np.tile(np.arange(n02, 2*n02), e2 - s2 + 1) + theta = np.linspace(0, 2 * pi, n02, endpoint=False) + cols = np.tile(np.arange(n02), e2 - s2 + 1) + rows = np.repeat(np.arange(s2, e2 + 1), n02) + n02 i = np.arange(n02)[:, None] j = np.arange(s2, e2 + 1)[None, :] - idx = np.abs(i - j) - p_cols = np.cos(theta[idx]) + p_cols = np.cos(theta[np.abs(i - j)]) p_next = np.roll(p_cols, shift=-1, axis=0) - data = (2.0 / n02) * (p_next - p_cols).ravel('F') + data = (2.0 / n02) * (p_next - p_cols).ravel('C') P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=self.domain.dtype) From 7d919555232921b4e9bcc659423198959faac437 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 19 Mar 2026 11:39:25 +0100 Subject: [PATCH 071/133] fix failing test --- psydac/feec/polar/conga_projections.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index ac6a2ce5f..34a499f13 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -759,7 +759,6 @@ def tosparse(self): rank_at_polar_edge = (s1 == 0) rank_at_outer_edge = (e1 == n1 - 1) data, cols, rows = [], [], [] - np.set_printoptions(precision=3) if rank_at_polar_edge: # matrix of size n2*n2 with all entries equal to gamma / n2 @@ -769,7 +768,6 @@ def tosparse(self): rows = np.tile(np.repeat(np.arange(s2, e2 + 1), n2), 2) cols = np.tile(np.arange(n2), e2 - s2 + 1) cols = np.concatenate((cols, np.tile(np.arange(n2, 2 * n2), e2 - s2 + 1))) - print(rows, cols) if e1 > 1 > s1: # matrix of size n2*n2 with all entries equal to gamma / n2 d_block2 = (self.gamma / n2) * np.ones((e2 - s2 + 1) * n2) @@ -933,8 +931,6 @@ def tosparse(self): P = coo_matrix((data, (rows, cols)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) P.eliminate_zeros() - np.set_printoptions(precision=3, suppress=True) - print(P.toarray()) return P.T if self.transposed else P @@ -1066,16 +1062,21 @@ def tosparse(self): rank_at_polar_edge = (s1 == 0) data, cols, rows = [], [], [] if rank_at_polar_edge: - theta = np.linspace(0, 2 * pi, n02, endpoint=False) - cols = np.tile(np.arange(n02), e2 - s2 + 1) - rows = np.repeat(np.arange(s2, e2 + 1), n02) + n02 - i = np.arange(n02)[:, None] - j = np.arange(s2, e2 + 1)[None, :] - p_cols = np.cos(theta[np.abs(i - j)]) + local_j = np.arange(s2, e2 + 1) # rows + all_k = np.arange(n02) # cols + + rows = np.repeat(local_j + n02, n02) + cols = np.tile(all_k, len(local_j)) + + theta = 2.0 * pi * np.arange(n02) / n02 - p_next = np.roll(p_cols, shift=-1, axis=0) - data = (2.0 / n02) * (p_next - p_cols).ravel('C') + j = local_j[:, None] + i = all_k[None, :] + p = np.cos(theta[j - i]) + p_next = np.cos(theta[(j + 1) % n02 - i]) + q = (2 / n02) * (p_next - p) + data = q.ravel(order='C') P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=self.domain.dtype) print(P.toarray()) From 79942621843d52d06052adfa114dda171b038adf Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 19 Mar 2026 11:48:26 +0100 Subject: [PATCH 072/133] revert changes in unrelated file --- examples/old_examples/poisson_2d_mapping.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/old_examples/poisson_2d_mapping.py b/examples/old_examples/poisson_2d_mapping.py index 652f93ae0..36408b934 100644 --- a/examples/old_examples/poisson_2d_mapping.py +++ b/examples/old_examples/poisson_2d_mapping.py @@ -838,12 +838,6 @@ def add_colorbar(im, ax): cbar = ax.get_figure().colorbar(im, cax=cax) return cbar - if use_spline_mapping: - # Recompute physical coordinates of logical grid using spline mapping - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) - xx = pcoords[:, :, 0] - yy = pcoords[:, :, 1] - # Plot exact solution ax = axes[0] im = ax.contourf(xx, yy, ex, 40, cmap='jet') @@ -855,6 +849,11 @@ def add_colorbar(im, ax): ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') ax.set_aspect('equal') + if use_spline_mapping: + # Recompute physical coordinates of logical grid using spline mapping + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] # Plot numerical solution ax = axes[1] From 23525d0940fe6c961edcaf0e0cd9e03efae90f42 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 19 Mar 2026 12:01:14 +0100 Subject: [PATCH 073/133] remove unnecessary tests --- psydac/feec/polar/tests/P0.h5 | Bin 6682328 -> 0 bytes psydac/feec/polar/tests/P1.h5 | Bin 24768728 -> 0 bytes psydac/feec/polar/tests/P2.h5 | Bin 1675552 -> 0 bytes .../polar/tests/test_conga_projections.py | 76 ------------------ 4 files changed, 76 deletions(-) delete mode 100644 psydac/feec/polar/tests/P0.h5 delete mode 100644 psydac/feec/polar/tests/P1.h5 delete mode 100644 psydac/feec/polar/tests/P2.h5 diff --git a/psydac/feec/polar/tests/P0.h5 b/psydac/feec/polar/tests/P0.h5 deleted file mode 100644 index 91949c0ad8956347e48d0d75f49d56f1824bb38d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6682328 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 diff --git a/psydac/feec/polar/tests/P1.h5 b/psydac/feec/polar/tests/P1.h5 deleted file mode 100644 index 03c146d79f72024613c409150b3c5ac3a64286b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24768728 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 z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u* z0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie z*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?- zJ2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pk zu?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+ z0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZ zfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF z5FkK+0D%?-J2oNAZfB*pku?6Ie*!@u*0RjXF5FkK+0D%?-J z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0!d(J+Wa$3_bxA-o9;Qhx^>|@4j=FNzWH(QQgIUHNm%`t*Xw zp#-j6e)#Ijr~B#t<^D++Pe1ta2RGLTj}L7gH}~G=^3xBu61_wTFA*M0r_ z^X}iPe|}zfcKZ^%qXU7X_wV`TOt+T%Cmr#|XP?}A=DF+B!Q-P(JoW7L^m@9FFQykV zA0PkZV~^}ir>1n@y|~=BX}_cQ@jRWU>D8b5i-%t!t$Sm6{??20>zQA^n`Ot%fnWc` z{C)k#59ZB*YdW$)&n^l2^v+wC=gINHcYSnTy8Qe3cRYK)pUn5w&f7oFy!pd(mG5^Qt$)2w zAAWpw_d6bX>GCgs@waz>3A)0RjXF5FkK+0D*jg^yju~<&6AFtRz5y009C72oNAZpml-t|AVTP zGg|jiKLG*+2oNAZfB*pk`2y+xGg~WXqJf8TbkoYA_E`UwyqK!5-N z0t5&U$QSt7LoZ$anm)}>lyZrZ$A6RnA{*qhvXP;ZUtvjio009C7 z2oNAZfB=EJ3#438D`&j30y*QI9-a{(K!5-N0t5&U=u05|_ljCMs+BYDtRrXK*~e!I5FkK+009C72(%@T{(MEPoROy?XXH6qMSuVS z0t5&UAV46tK>BmrwQ|OGrRj8PrDHyK!5-N0t5&UC@YZuzU^8$qinTlH#s9o zcqBl8009C72oN9;N#J7-y>$7PzxdnxwQ@$?v&tEDeeF(w009C72oNAZU_=2qW5ljI zGXVkw2oNAZfB=EI0&+%OU%L|^K!5-N0t5&U7*RmZ7_sZlOn?9Z0t5&UAV8q5fSgg+ z*X{%e5FkK+009C7Mih`UM(nyX6Cgl<009C72oR_%AZOI|wL1X<1PBlyK!5;&5e4Lo z5xefp1PBlyK!5-N0tD&`$QgBg?M{FI0RjXF5FkKcL;*Qt#I8Ft0RjXF5FkK+0D-y! zaz}qoyY9>c2oNAZfB*pk z1nLUN8FhW_PJjRb0t5&UAV6S50XbvDt~)aU0t5&UAV7csfw}^6MqOXK6Cgl<009C7 z2oM-iK+YJk>&{Go009C72oNAZpss+NQPI%pib$#tlfB*pk z1PBlyKwv}xIb+1GJ2L?S1PBlyK!5;&x&m@WU0=HsAV7cs0RjXF5ExNF&KR-l&P;#+ z0RjXF5FkLHu7I3T*Vpa@2oNAZfB*pk1V$8)Ge+#XGZP>{fB*pk1PBnQD}qoyY9>c2oNAZfB*pk z1nLUN8FhW_PJjRb0t5&UAV6S50XbvDt~)aU0t5&UAV7csfw}^6MqOXK6Cgl<009C7 z2oM-iK+YJk>&{Go009C72oNAZpss+NQPI%pib$#tlfB*pk z1PBlyKwv}xIb+1GJ2L?S1PBlyK!5;&x&m@WU0=HsAV7cs0RjXF5ExNF&KR-l&P;#+ z0RjXF5FkLHu7I3T*Vpa@2oNAZfB*pk1V$8)Ge+#XGZP>{fB*pk1PBnQD}qoyY9>c2oNAZfB*pk z1nLUN8FhW_PJjRb0t5&UAV6S50XbvDt~)aU0t5&UAV7csfw}^6MqOXK6Cgl<009C7 z2oM-iK+YJk>&{Go009C72oNAZpss+NQPI%pib$#tlfB*pk z1PBlyKwv}xIb+1GJ2L?S1PBlyK!5;&x&m@WU0=HsAV7cs0RjXF5ExNF&KR-l&P;#+ z0RjXF5FkLHu7I3T*Vpa@2oNAZfB*pk1V$8)Ge+#XGZP>{fB*pk1PBnQD}qoyY9>c2oNAZfB*pk z1nLUN8FhW_PJjRb0t5&UAV6S50XbvDt~)aU0t5&UAV7csfw}^6MqOXK6Cgl<009C7 z2oM-iK+YJk>&{Go009C72oNAZpss+NQPI%pib$#tlfB*pk z1PBlyKwv}xIb+1GJ2L?S1PBlyK!5;&x&m@WU0=HsAV7cs0RjXF5ExNF&KR-l&P;#+ z0RjXF5FkLHu7I3T*Vpa@2oNAZfB*pk1V$8)Ge+#XGZP>{fB*pk1PBnQD}qoyY9>c2oNAZfB*pk z1nLUN8FhW_PJjRb0t5&UAV6S50XbvDt~)aU0t5&UAV7csfw}^6MqOXK6Cgl<009C7 z2oM-iK+YJk>&{Go009C72oNAZpss+NQPI%pib$#tlfB*pk z1PBlyKwv}xIb+1GJ2L?S1PBlyK!5;&x&m@WU0=HsAV7cs0RjXF5ExNF&KR-l&P;#+ z0RjXF5FkLHu7I3T*Vpa@2oNAZfB*pk1V$8)Ge+#XGZP>{fB*pk1PBnQD&pR3rfl{3m$Y)1kF2oNAZ zfB*pk1fmMOzWh1uS~(-?xz$C0009C72oNAZfIxYH<^Kb0RjXF5FkK+ z009DP0Xbu>;hF#e0t5&UAV7csff538MhQiG5FkK+009C72oNB!7LYU68mE2oNAZfB*pkYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7dk`Q% zfB*pk1PBlyuojRr)*7w}5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu>;hF#e0t5&UAV7cs zff538MhQiG5FkK+009C72oNB!7LYU68mE2oNAZ zfB*pkYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7dk`Q%fB*pk1PBlyuojRr)*7w}5FkK+ z009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu>;hF#e0t5&UAV7csff538MhQiG5FkK+009C72oNB! z7LYU68mE2oNAZfB*pkYXLc9t>Kyg0RjXF5FkK+ z0D%$$az+V7dk`Q%fB*pk1PBlyuojRr)*7w}5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu> z;hF#e0t5&UAV7csff538MhQiG5FkK+009C72oNB!7LYU68mE2oNAZfB*pkYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7dk`Q%fB*pk1PBly zuojRr)*7w}5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu>;hF#e0t5&UAV7csff538MhQiG z5FkK+009C72oNB!7LYU68mE2oNAZfB*pkYXLc9 zt>Kyg0RjXF5FkK+0D%$$az+V7dk`Q%fB*pk1PBlyuojRr)*7w}5FkK+009C72oNYC zAZL_Nv0RjXF z5FkK+009DP0Xbu>;hF#e0t5&UAV7csff538MhQiG5FkK+009C72oNB!7LYU68mE2oNAZfB*pkYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7 zdk`Q%fB*pk1PBlyuojRr)*7w}5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu>;hF#e0t5&U zAV7csff538MhQiG5FkK+009C72oNB!7LYU68mE z2oNAZfB*pkYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7dk`Q%fB*pk1PBlyuojRr)*7w} z5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu>;hF#e0t5&UAV7csff538MhQiG5FkK+009C7 z2oNB!7LYU68mE2oNAZfB*pkYXLc9t>Kyg0RjXF z5FkK+0D%$$az+V7dk`Q%fB*pk1PBlyuojRr)*7w}5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP z0Xbu>;hF#e0t5&UAV7csff538MhQiG5FkK+009C72oNB!7LYU68mE2oNAZfB*pkYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7dk`Q%fB*pk z1PBlyuojRr)*7w}5FkK+009C72oNYCAZL_Nv0RjXF5FkK+009DP0Xbu>;hF#e0t5&UAV7csff538 zMhQiG5FkK+009C72oNB!7LYU68mE2oNAZfB*pk zYXLc9t>Kyg0RjXF5FkK+0D%$$az+V7dk`Q%fB*pk1PBlyuojRr)*7w}5FkK+009C7 z2oNYCAZL_Nv z0RjXF5FkK+009DP0Xbu>;hF#e0t5&UAV7csff538MhQiG5FkK+009C72oNB!7LYU6 z8mE2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5;&4@zKX+Wa$3_bvsTo9;Qhy7}YZbNKk$ zQ}g5Ar!HTf4p!bi^0B;adg}}Gy0hs8k3$Jux%}|e&4I5ir#`g_)U(&q>zjLU^Ko&0$o}_n_wQ%^)_gy{u-un-yML#jnAgvDfA*KRuoHo! z_ivhqH<$Y-9dmEFYxfTC;>|>yKQ5&OTRuMi$;TeqnNBTJ_h1hH-+ywr!`u5V&p-3l z{Cb+N7nl1xtvhAt(P+_!1}qxW&D>z*I{ox`t~)?HklU)!BuKVdn| zzHMhZGxuZx*_rN}eNN3Ud}TS=agV1DekQzGdG7q~H231tbm1e@h1;j+H=a3ob@TaC zr{)dMEs*L&XeQ(jSnqfV}5x49naqHC-Z%^vz%p`t~@ta`F_{Y$Je{H+s8Sa zf&c*m1o8yT{X8eD2oNC9i-5V`ODCO!009Dd0_J|6lT`!=5a>m~-0!86&Ov|xfjj|o zKhMc30t5*3B4FE@&wHNJSVFN5FpTtfVtmGC!K=;0Rnjf=6;@& zRRjnS=taQX@1>K@L4W{(JOOh*&&etR1PJsZVD9(QN#`IyfIyyrxu54`6#)VSdJ!=9 zd+DTe5FkJxPr%&IbFzv60Rp`UnESnS(m4naAdn|u?&mpKMSuW-UIfhjUOMR<1PBnw z6EOGloU9^1fIu$-=6)}obPfUp2;>Qv`*}`Q5gmrgnd0RjZ_1kC+BC#wh$ zAkd3|x!+4Cor3@Y0(k=Fex8$61PBo5MZnzerIXG7;WIAV45bz}(MsvWfr!0=)>B`@MA1IS3FSkSAd7 z=Q&wLfB=DB1kC+jI_Vq)2oT56tks|XMv(2Ib%-%BT*g8%^nc>?Bso|9Ds2oUH+z})Yplg>ea z0D(LKb3f0?Dgp!u^deyH_tHt{AV7dXo`AWZ=VTQD0t9*yF!y`uq;n7;Kp;=R+|P5e ziU0uuy$G25y>!w!2oNBUCt&X9Iax)30D)cv%>7m~-0!86&Ov|xfjj|oKhMc30t5*3B4FE@&wHN zJSVFN5FpTtfVtmGC!K=;0Rnjf=6;@&RRjnS=taQX@1>K@L4W{(JOOh*&&etR1PJsZ zVD9(QN#`IyfIyyrxu54`6#)VSdJ!=9d+DTe5FkJxPr%&IbFzv60RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0;34r+MiGA&!dmhapxpJU~~aey1fWYVi=Kkn+fb$a|Fq(k5 zKU&9~mjHp$1j+ z_2;b8+;7!E-2?~_AV7csfl>mcetuP|9{Ug=K!8A90dv2uuiXg{AP`N!+>h1~^$;LH zpss+qU)R^}1PBm_CSdMI>xg;?5Fk)jz}&CvYj*+!2t*Sw_oH=0Jp>34s4HOZ*Y&kK z0RjY~37GrQI-(u|1PIg>F!$^F+MNIa0?`D_{b(Ig4*>!M>I#_qb$#tlfB=DL0_J|S zj;MzK0RnXe%>BB)b|*l9Kr{h!KUzoBLx2E*x&r2YU0=HsAV46RfVm&7BkCbQfIwXV zbHA>y-3bsN5KX|`kJb_O5FkLHu7J5;*Vpa@2oQ)SVD3ljhq8(FDx>XdO`x z0RjZ-3YhzKeeF(w0D))%=6icxgV_~>LEaYKwSZIzpk&{2@oI-yTA009Eg1kC+t9Z?Sf0tD&`nEQ2o?M{FIfoKBeezcCLhX4Trbp_1*y1sTN zK!89r0dqfEN7O@r0D-y!=6+pYyAvQlAew-=AFU(mAwYmYT>*2yuCLt*5Fijuz}%14 z5%mxtK%lOGxnI}U?gR)Bh$dj}N9%}s2oNApSHRq_>uYxc1PDYEF!!T%L_Guu5U49) z?$`CTI{^X&q6wJ$(K@0Y0t5)u6)^Yf`r4fU0Rqtk%>8H`Q4awE1nLTy`*nToPJjS` zXaeSbw2r8U009DZ1b3a-~)I)#(fw}_beqCR?6Cgk!nt-_h1~^$;LHpss+qU)R^}1PBm_CSdMI>xg;?5Fk)jz}&Cv zYj*+!2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7csf!+mnrp-UobnkM|bJIPCS8x9ALx+!Ne{X)=`_$#j)4|HyM?RLf zO_%=rvOc}waVUW+mmj{mIqc{<(r?2~)XJa>Kb_~;W)J$pU9zPSfC z9~b9`?0+A3|Gu((-LL((y!-dcAI$6KyFdHOTiA)f(fc>e!#9`vCmnNdxoh_h@8Zow zoIft51zSEo{>jH4*_lo)Q}^Ke=W{=MxI^0ah2{CpADdr4yX^nMa(}0FNALG7%V>W2 zadY6+-=Dv)Uw<=i4&3~o^Sb2RJd6WuC2({Oq#qA%E%#5_?C=M>gD2_x?@Ic_2_MrO zNcY`~%YB>nKYAahx?bP?ox`t~);;~X|8@BMt>yI-mecIpcBV7`pS}AF)hw;@1N@yT zd#K<|Hwx2qBB_9c9ug8W_JxoMU$t0G51FXgWzx)KCjOxr!44QjXm3J$6AUqvP6DE4 zLQIqhlMCa8;3N}IN5+u3^Cn;d0k6avB(wp+xk=}ov-j@aRdkhmpI&dd*84ss^taEc zz292vS)Z%wSAD8$n#l}ua(Hy|xiWp?TXSc3d%b%0E8+7yAG&@#OtZLgcQ;)ydq;;qbzrOjX|R zI;#J8U;M)Pw?8`G`pqYP^-sU?hsQ_W`GX&S{VQKP{^Im{`qcFK)A_#B`M%S)%-{d! z`TqI7V|5Sp6CgmKSAk{ENA3Myo%BV30DjY zB0zvZb%Evg=cD$1br1CuAV8p3f#u)BM(zDxo%BV30DL);eK(7MJ z*HuRC{a&5)MSuW->H^Ex&qwY3>K^JRK!8B60?VJ1irV|VI_Zl50Rq(prr)>!i0s009DV0?WULjoSPBDzo?ZdAXYa0Rop7n1A2?7`68=SBbrUIX6!c zAV6Td!18sKQG0*;kNjG9+WW7i;&TKD5Qq?1zJ5Mx??=>hIN19!j!q#!fWWH)^Y7cA zM(zDqPmZ{<_ahXYL4W{(Is*27omr`r009CK0``7{qB95(AW%oZ-mfz&l@cI8AVR?2 zk5F_50RjZ-2-y2|W~EXB1PDY3*!vNR&LBX5Kpg>lzs{^wN`L@?2myOPLeUun2oR_v zVDHzNl}ZT^AP^y7??)&)g8%^nbp-7FIAew|sVlmGz&5d!vpgrYMD z5Fk)Tz}~MjE0q!;Kp;ZE-j7gp1_1&D>Im5Tb!Mef0t5&|2-y1(iq0TFfIuAqd%w=C zR7!vVfd~P6KSI$N1PBnQBVg~>nUzWj5FijCVDCpLI)eZK0(AuJ{W`N!DFFfmA_VOH z2t{WQAV8pwfW2R5Rw^YxfIx(Ry&s|I3<3lQ)Df`v>&!}}1PBm_5U}?n6rDkU0D(FJ z_I{mNsgwW#0uciCeuSbk2oNApN5I~%Gb@!6AV45Oz}}BgbOr$e1nLOb`*mieQUU}B zL?X0tD&^*!y*6rBVU}2t)|j`w@!HAV7dX9RYj4&a6~QfB=CA0ee3}(HR5?5U3+y z@7I}?N(m4k5FudiM<_aj009DZ1nm7fvr;Jm0t6xi?EMHuXAmGjppJmOUuRY-B|v~c zgn+#tq38?(1PIg-u=nfCN~Ht{5Qq@4_ahXYL4W{(Is*27omr`r009CK0``7{qB95( zAW%oZ-mfz&l@cI8AVR?2k5F_50RjZ-2-y2|W~EXB1PDY3*!vNR&LBX5009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7dX4+4+;_}3o)^zVH6bbdX4@eAkQe*gIJJAd%wuYcwAm5<*t-FIB>|NL~{>3rYm z@6I3hV4?>C1PJsfFz@$e-!HGP?D@TWzegiI5gW|DHB#@Aqh=CjtZr^dK;Qoz3!fH^pbK!8Ax0?VJHirV`< z8tI7u0RjXF5FoH$z~8IB-^>#P2oR_&VDDF+p4tfzAh2J+-rsNL2?7KNR2H!JD^E}D z1PBn=FJSNQH}eDm0t6}x*!z{Ir*;Aa2<#WI_xGE5f&c*ml?Ckm%F|Oj0RjZ}3)uVn z%{)PX0D;N^_I~B*sht1;0{aE*{rzU1AV7dXWdVD?^7PbBfB=F00`~rXGfxm8K%laK zy=&^2_nUcw z009D(1?>IG(^ESE0tEI8*!%m`vvU%{brsZK!8AH0eipl z^wds(0D=7i_Wph|PY@tLpt69yUwL|JCqRI}egS)bznLcp5Fk)lz}~MsJ+%`cKw!Us zy}#eg69fnls4QUbSDv2Q2@oK#U%=kqZ{`UC1PD|Xu=guZPwfN<5ZEtZ@9#JB1OWmB zDht^Am8Yk60t5)`7qIvDn|Xo&0Roi;?ET8qQ#%0y1ojKq`}@s2L4W{($^!O&<>{%N z009E~1?>I(W}YBGfIwvdd%yDZ)J}i^f&BvZ{(du05FkLHvVgr`d3tImK!Cu00egSH znI{MkAW&Jr-mg48wG$vfV84L9zu(Lg1PBnQEMV_fo}StX5FoH$z~0|)<_Q7>2vioZ z_bX3N?F0xA*e_u3?>F-V0RjXn3)uUWr>AxT1PJUGu=n?yd4d1|0+j{q{mRo*I{^X& z_6yki`^`K-fB=EY0``98>8YIn0RsC4?EU>_o*+PgKxF}Yzw-3dPJjS`{Q~y>elt%H zAV8q9fW2RNdTJ*?fWUqMdw;)~CkPNAP+7p#P2oR_&VDDF+ zp4tfzAh2J+-rsNL2?7KNR2H!JD^E}D1PBn=FJSNQH}eDm0t6}x*!z{Ir*;Aa2oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7csfuFy?$>IEehr`2jDGwbUytsPX``>Z#`rf}Yz217?&6|g_J72!z+x*z!GrvCF zcWwEE*P#TSy!rTh&l`Sv?)v)t`my=_Gk*Jq%=KS@E4?g>eXO_>OzX#91 zP4A-VddolW_Wpfo{&63i-|LF-8aGj0ScI+_?vqpAX(Kzkim)E`G8* z`(XM1-;>LCT=I3<1IzpFyYu^YIseZ0@lw~vZv4-SA91A&%li;sW!(H~v1lG=}* z93GfvGJ~8P9-VxyOrQAd{Q0}RUOoGj@cAtdT|XYCS==~0_Qu0wFJGNp9yt5#`S)MB zG97qr{+099lec|n+8ocn^-a^Q^FN=v?W6ns!Sud5nR_`LzW4X0D(`n4ef)U-`$m1t zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai zQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2T znWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp z$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7 zG6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs z0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+ z009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&U zAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV z5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@ z0t5&UAV7cs0Rnvp$QgaiQ5OUV5FkK+009C7G6~2TnWmu@0t5&UAV7cs0Rnvp$Qgai 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 z�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk z6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C7 z2;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF z5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr z009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo z0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1 znF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+ zim92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&f zGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)d zK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C7 z2oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF z5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$ae zK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I z009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+ zIsya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{ znwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNAp zQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+ zKyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N z0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C7 z2oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya; z5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@ zAV7cs0RjXFg zK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eA zn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp& zBX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�K1nF$aeK!5-N0t5); z7SJ$u^2oNAZfB*pk6$SK+im92I009C72oNAZ zfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah5FkK+009C72;>&fGjiu+Isya;5FkK+ z009CO1@w%HshODo0RjXF5FkK+KyCp&BX>TgBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs z0RjXFgK!5-N z0t5&UAW%_2�K1nF$aeK!5-N0t5);7SJ$u^ z2oNAZfB*pk6$SK+im92I009C72oNAZfIw~mJtKEMrXxUr009C72oNApQ9#eAn3|ah z5FkK+009C72;>&fGjiu+Isya;5FkK+009CO1@w%HshODo0RjXF5FkK+KyCp&BX>Tg zBS3%v0RjXF5Fk)dK+mX{nwbd@AV7cs0RjXFgK!5-N0t5&UAW%_2�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 zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? zi~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+ z0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&U zAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF z5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u z0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR z0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_ zsEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4T zj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7 zaz^a_sEz;u0t5&UAV7e?i~@4Tj9pVR0RjXF5FkK+0D;&7az^a_sEz;u0t5&UAV7e? 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 RLC`^ISr8yVfItlabH7HPRY`yVfhJ(?Hwmu< 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 Z009C7ng!%cGob= n01 * n02 + 2 * n02 assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) - # Comparing the global sparse matrix to reference file - sp_P1_global = mpi_comm.allreduce(sp_P1.toarray(), op=MPI.SUM) - if mpi_comm.rank == 0: - name = Projector.__name__ - h5_path = (Path(__file__).absolute().parent / "P1.h5") - - with h5py.File(h5_path, "r") as f: - key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{hbc}/T{transposed}/P" - sp_P1_ref = f[key][()] - assert np.allclose(sp_P1_global, sp_P1_ref, atol=1e-12, rtol=1e-12) # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) @@ -360,16 +294,6 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): else: assert i == j - sp_P2_global = mpi_comm.allreduce(sp_P2.toarray(), op=MPI.SUM) - if mpi_comm.rank == 0: - - name = 'C0PolarProjection_V2' - h5_path = (Path(__file__).absolute().parent / "P2.h5") - - with h5py.File(h5_path, "r") as f: - key = f"{name}/n{ncells[0]}x{ncells[1]}/p{degree[0]}-{degree[1]}/hbc{False}/T{transposed}/P" - sp_P1_ref = f[key][()] - assert np.allclose(sp_P2_global, sp_P1_ref, atol=1e-12, rtol=1e-12) # Comparing results of dot and tosparse x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) From 59b84c3a29ef15c8b40c04bac7cae0a0076b35ff Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Sun, 29 Mar 2026 23:24:56 +0200 Subject: [PATCH 074/133] added conversion to coo matrix --- psydac/feec/polar/tests/test_conga_projections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index f146e696c..fa8867a07 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -70,7 +70,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Checking projection property P0(P0(phi)) = P0(phi) assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) - sp_P0 = P0.tosparse() + sp_P0 = P0.tosparse().tocoo() [n1, n2] = V0_h.coeff_space.npts assert sp_P0.shape == (n1 * n2, n1 * n2) @@ -148,7 +148,7 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): assert np.allclose(z[0][:, :], y[0][:, :], atol=1e-12, rtol=1e-12) assert np.allclose(z[1][:, :], y[1][:, :], atol=1e-12, rtol=1e-12) - sp_P1 = P1.tosparse() + sp_P1 = P1.tosparse().tocoo() print(sp_P1.toarray()) [n01, n02] = V1_h.coeff_space[0].npts @@ -264,7 +264,7 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): assert np.allclose(P2.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) # Comparing the global sparse matrix to reference file - sp_P2 = P2.tosparse() + sp_P2 = P2.tosparse().tocoo() print(mpi_comm.rank, sp_P2.shape, sp_P2.nnz, V2_h.coeff_space.npts) payload = (sp_P2.row, sp_P2.col, sp_P2.data) From 551323ce9940506a388f150c8cbd103599ab4a20 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 30 Mar 2026 00:08:31 +0200 Subject: [PATCH 075/133] changed atol and rtol to constants --- .../polar/tests/test_conga_projections.py | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index fa8867a07..51cf8850b 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -15,6 +15,9 @@ from psydac.fem.basic import FemField from psydac.linalg.block import BlockVector +ATOL = 1e-12 +RTOL = 1e-12 + def get_domain(R): # Build physical domain - disk of radius R @@ -68,7 +71,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): P0.dot(x, out=y) # Checking projection property P0(P0(phi)) = P0(phi) - assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) + assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=ATOL, rtol=RTOL) sp_P0 = P0.tosparse().tocoo() @@ -98,15 +101,15 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): # Check all non-zero entries for i, j, v in zip(rows, cols, data): if i < n2 and j < n2: - assert np.isclose(v, 1 / n2, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1 / n2, atol=ATOL, rtol=RTOL) elif Projector == C1PolarProjection_V0 and j < n2 <= i < 2 * n2: - assert np.isclose(v, 1 / n2, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1 / n2, atol=ATOL, rtol=RTOL) elif Projector == C1PolarProjection_V0 and n2 <= i < 2 * n2 and n2 <= j < 2 * n2: - assert np.isclose(v, 2 / n2 * np.cos( (i - j) * 2 * np.pi / n2), atol=1e-12, rtol=1e-12) + assert np.isclose(v, 2 / n2 * np.cos( (i - j) * 2 * np.pi / n2), atol=ATOL, rtol=RTOL) else: # identity assert i == j - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) # Comparing results of dot and tosparse @@ -115,7 +118,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) - assert np.allclose(y_sp, y, atol=1e-12, rtol=1e-12) + assert np.allclose(y_sp, y, atol=ATOL, rtol=RTOL) @@ -145,8 +148,8 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): P1.dot(x, out=y) z = P1.dot(y) # Checking projection property P1(P1(phi)) = P1(phi) - assert np.allclose(z[0][:, :], y[0][:, :], atol=1e-12, rtol=1e-12) - assert np.allclose(z[1][:, :], y[1][:, :], atol=1e-12, rtol=1e-12) + assert np.allclose(z[0][:, :], y[0][:, :], atol=ATOL, rtol=RTOL) + assert np.allclose(z[1][:, :], y[1][:, :], atol=ATOL, rtol=RTOL) sp_P1 = P1.tosparse().tocoo() print(sp_P1.toarray()) @@ -184,20 +187,20 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): if i < n01 * n02: # identity assert i == j - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) # Block P1_10 elif n01 * n02 + n02 <= i < n01 * n02 + 2 * n02: # d matrix if j == i - n02 * (n01 + 1): - assert np.isclose(v, -1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, -1.0, atol=ATOL, rtol=RTOL) else: - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) assert j == (i - n02 * (n01 + 1) + 1) % n02 # Block P1_11 else: assert i >= n01 * n02 + 2 * n02 assert j == i - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) if Projector == C1PolarProjection_V1: p_ij = 2 / n02 * np.cos((i - j) * 2 * np.pi / n02) @@ -205,28 +208,28 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): if i < n02: # matrix p assert j < n02 - assert np.isclose(v, p_ij, atol=1e-12, rtol=1e-12) + assert np.isclose(v, p_ij, atol=ATOL, rtol=RTOL) elif 2 * n02 > i >= n02 > j: # matrix I - p if i == j + n02: - assert np.isclose(v, 1 - p_ij, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1 - p_ij, atol=ATOL, rtol=RTOL) else: - assert np.isclose(v, - p_ij, atol=1e-12, rtol=1e-12) + assert np.isclose(v, - p_ij, atol=ATOL, rtol=RTOL) elif n02 <= i < n01 * n02: assert j == i - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) # Block P1_10 elif j < n02: # matrix q q_ij = (2 / n02 * np.cos((i + 1 - j) * 2 * np.pi / n02) - p_ij) - assert np.isclose(v, q_ij, atol=1e-12, rtol=1e-12) + assert np.isclose(v, q_ij, atol=ATOL, rtol=RTOL) # Block P1_11 else: assert i == j assert i >= n01 * n02 + 2 * n02 - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) # Comparing results of dot and tosparse @@ -235,7 +238,7 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): y = mpi_comm.allreduce(y.toarray(), op=MPI.SUM) y_sp = mpi_comm.allreduce(y_sp, op=MPI.SUM) - assert np.allclose(y_sp, y, atol=1e-12, rtol=1e-12) + assert np.allclose(y_sp, y, atol=ATOL, rtol=RTOL) @pytest.mark.parametrize( 'transposed', [True, False]) @@ -261,7 +264,7 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): P2.dot(x, out=y) # Checking projection property P0(P0(phi)) = P0(phi) - assert np.allclose(P2.dot(y)[:, :], y[:, :], atol=1e-12, rtol=1e-12) + assert np.allclose(P2.dot(y)[:, :], y[:, :], atol=ATOL, rtol=RTOL) # Comparing the global sparse matrix to reference file sp_P2 = P2.tosparse().tocoo() @@ -288,7 +291,7 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): # Check all non-zero entries for i, j, v in zip(rows, cols, data): - assert np.isclose(v, 1.0, atol=1e-12, rtol=1e-12) + assert np.isclose(v, 1.0, atol=ATOL, rtol=RTOL) if j < n2: assert i == j + n2 else: @@ -299,7 +302,7 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): x_global = mpi_comm.allreduce(x.toarray(), op=MPI.SUM) y_sp = sp_P2 @ x_global - assert np.allclose(y_sp, y.toarray(), atol=1e-12, rtol=1e-12) + assert np.allclose(y_sp, y.toarray(), atol=ATOL, rtol=RTOL) if __name__ == '__main__': test_PolarProjection_V2(1, [8, 10], [2, 2], False) \ No newline at end of file From b75b380c5a6a0b82dcd0604e00d1d98764d935b6 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 30 Mar 2026 00:10:54 +0200 Subject: [PATCH 076/133] whitespaces --- examples/old_examples/poisson_2d_mapping.py | 2 +- psydac/feec/polar/tests/test_conga_projections.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/old_examples/poisson_2d_mapping.py b/examples/old_examples/poisson_2d_mapping.py index 36408b934..47444fcff 100644 --- a/examples/old_examples/poisson_2d_mapping.py +++ b/examples/old_examples/poisson_2d_mapping.py @@ -963,4 +963,4 @@ def parse_input_arguments(): __IPYTHON__ except NameError: import matplotlib.pyplot as plt - plt.show() \ No newline at end of file + plt.show() diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 51cf8850b..cb855070a 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -305,4 +305,4 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): assert np.allclose(y_sp, y.toarray(), atol=ATOL, rtol=RTOL) if __name__ == '__main__': - test_PolarProjection_V2(1, [8, 10], [2, 2], False) \ No newline at end of file + test_PolarProjection_V2(1, [8, 10], [2, 2], False) From e91cbac82db2b8cf2fb3ee358917a3c2f626415c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 1 Apr 2026 09:47:04 +0200 Subject: [PATCH 077/133] added verbose flag for unit tests --- .../polar/tests/test_conga_projections.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index cb855070a..6548164b8 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -54,7 +54,7 @@ def get_random_block_vector(space): @pytest.mark.parametrize('Projector', [C0PolarProjection_V0, C1PolarProjection_V0]) @pytest.mark.mpi -def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): +def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed, verbose=False): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -74,6 +74,10 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): assert np.allclose(P0.dot(y)[:, :], y[:, :], atol=ATOL, rtol=RTOL) sp_P0 = P0.tosparse().tocoo() + if verbose: + print("Projection P0 in matrix form on rank", mpi_comm.rank) + print(sp_P0.toarray()) + [n1, n2] = V0_h.coeff_space.npts assert sp_P0.shape == (n1 * n2, n1 * n2) @@ -130,7 +134,7 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed): @pytest.mark.parametrize('Projector', [C0PolarProjection_V1, C1PolarProjection_V1]) @pytest.mark.mpi -def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): +def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed, verbose=False): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -152,7 +156,9 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): assert np.allclose(z[1][:, :], y[1][:, :], atol=ATOL, rtol=RTOL) sp_P1 = P1.tosparse().tocoo() - print(sp_P1.toarray()) + if verbose: + print("Projection P1 in matrix form on rank", mpi_comm.rank) + print(sp_P1.toarray()) [n01, n02] = V1_h.coeff_space[0].npts [n11, n12] = V1_h.coeff_space[1].npts @@ -174,7 +180,6 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): if Projector == C0PolarProjection_V1: assert len(data) == n01 * n02 + 2 * n02 + (n11 - (3 if hbc else 2)) * n12 else: - print(len(data)) assert len(data) == 3 * n02 * n02 + n02 * (n01 - 1) + (n11 - (3 if hbc else 2)) * n12 if transposed: @@ -247,7 +252,7 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed): @pytest.mark.parametrize( 'R', [1]) @pytest.mark.mpi -def test_PolarProjection_V2(R, ncells, degree, transposed): +def test_PolarProjection_V2(R, ncells, degree, transposed, verbose=False): mpi_comm = MPI.COMM_WORLD domain = get_domain(R) @@ -268,7 +273,9 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): # Comparing the global sparse matrix to reference file sp_P2 = P2.tosparse().tocoo() - print(mpi_comm.rank, sp_P2.shape, sp_P2.nnz, V2_h.coeff_space.npts) + if verbose: + print("Projection P2 in matrix form on rank", mpi_comm.rank) + print(sp_P2.toarray()) payload = (sp_P2.row, sp_P2.col, sp_P2.data) parts = mpi_comm.gather(payload, root=0) @@ -305,4 +312,6 @@ def test_PolarProjection_V2(R, ncells, degree, transposed): assert np.allclose(y_sp, y.toarray(), atol=ATOL, rtol=RTOL) if __name__ == '__main__': - test_PolarProjection_V2(1, [8, 10], [2, 2], False) + test_PolarProjection_V0(C0PolarProjection_V0, 1, [3, 3], [1, 1], False, False, verbose=False) + test_PolarProjection_V1(C0PolarProjection_V1, 1, [2, 3], [1, 1], False, False, verbose=True) + test_PolarProjection_V2(1, [8, 10], [2, 2], False, verbose=True) From 84d28488a60a2ad966e1d0856bb7ff16951c6acf Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 1 Apr 2026 17:09:47 +0200 Subject: [PATCH 078/133] utility function for gathering arrays of variable len --- psydac/feec/polar/utils.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 psydac/feec/polar/utils.py diff --git a/psydac/feec/polar/utils.py b/psydac/feec/polar/utils.py new file mode 100644 index 000000000..e1909efe4 --- /dev/null +++ b/psydac/feec/polar/utils.py @@ -0,0 +1,34 @@ +import numpy as np +from mpi4py.util.dtlib import from_numpy_dtype + +def gather_vlen_array(v, mpi_comm): + + """ Gather 1D arrays of possibly different lengths onto root process + Other processes return None + Local arrays must be of the same type + """ + dtype = v.dtype + + v = np.ascontiguousarray(v, dtype=dtype) + local_len = np.array(v.size, dtype=np.int64) + + # Gather local array sizes on root process + recvcounts = np.empty(mpi_comm.size, dtype=np.int64) if mpi_comm.rank == 0 else None + mpi_comm.Gather(local_len, recvcounts, root=0) + + if mpi_comm.rank == 0: + displs = np.empty(mpi_comm.size, dtype=np.int64) + displs[0] = 0 + displs[1:] = np.cumsum(recvcounts[:-1]) + + total_len = int(np.sum(recvcounts)) + global_array = np.empty(total_len, dtype=dtype) + + else: + displs = None + global_array = None + mpi_type = from_numpy_dtype(dtype) + + mpi_comm.Gatherv(v, [global_array, recvcounts, displs, mpi_type], root=0) + + return global_array \ No newline at end of file From 80a0fbd1b934323c15b2ff017eab9934ec5087bf Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 6 Apr 2026 22:37:45 +0200 Subject: [PATCH 079/133] specify datatype for cols and rows in tosparse --- psydac/feec/polar/conga_projections.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 34a499f13..88885b428 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -129,6 +129,9 @@ def tosparse(self): cols = np.concatenate((cols, local_rows)) rows = np.concatenate((rows, local_rows)) + rows = np.asarray(rows, dtype=np.int64) + cols = np.asarray(cols, dtype=np.int64) + P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() @@ -224,6 +227,8 @@ def tosparse(self): local_rows = (i * n02 + j).ravel() data = np.ones((e1 - s1 + 1) * (e2 - s2 + 1)) + local_rows = np.asarray(local_rows, dtype=np.int64) + P = coo_matrix((data, (local_rows, local_rows)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) P.eliminate_zeros() @@ -333,6 +338,9 @@ def tosparse(self): cols = np.column_stack((k, (k + 1) % n12 )).ravel() dtype = self.domain.dtype + rows = np.asarray(rows, dtype=np.int64) + cols = np.asarray(cols, dtype=np.int64) + P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=dtype) P.eliminate_zeros() return P @@ -437,6 +445,7 @@ def tosparse(self): i = np.arange(start_s, end_s)[:, None] j = np.arange(s2, e2 + 1)[None, :] local_rows = (i * n02 + j).ravel() + local_rows = np.asarray(local_rows, dtype=np.int64) P = coo_matrix((data, (local_rows, local_rows)), shape=[n11 * n12, n01 * n02], dtype=dtype) P.eliminate_zeros() @@ -596,6 +605,9 @@ def tosparse(self): rows = np.concatenate((rows, local_rows[local_rows >= 2 * n2])) cols = np.concatenate((cols, local_rows[local_rows >= n2])) + rows = np.asarray(rows, dtype=np.int64) + cols = np.asarray(cols, dtype=np.int64) + P = coo_matrix((data, (rows, cols)), shape=(n1 * n2, n1 * n2), dtype=self.W2.coeff_space.dtype) P.eliminate_zeros() @@ -793,6 +805,9 @@ def tosparse(self): cols = np.concatenate((cols, local_rows)) rows = np.concatenate((rows, local_rows)) + rows = np.asarray(rows, dtype=np.int64) + cols = np.asarray(cols, dtype=np.int64) + P = coo_matrix((data, (rows, cols)), shape=[n1 * n2, n1 * n2], dtype=self.W0.coeff_space.dtype) P.eliminate_zeros() @@ -929,6 +944,9 @@ def tosparse(self): cols = np.concatenate((cols, local_rows)) rows = np.concatenate((rows, local_rows)) + rows = np.asarray(rows, dtype=np.int64) + cols = np.asarray(cols, dtype=np.int64) + P = coo_matrix((data, (rows, cols)), shape=[n01 * n02, n01 * n02], dtype=self.domain.dtype) P.eliminate_zeros() @@ -1078,8 +1096,10 @@ def tosparse(self): q = (2 / n02) * (p_next - p) data = q.ravel(order='C') + rows = np.asarray(rows, dtype=np.int64) + cols = np.asarray(cols, dtype=np.int64) + P = coo_matrix((data, (rows, cols)), shape=[n11 * n12, n01 * n02], dtype=self.domain.dtype) - print(P.toarray()) return P.T if self.transposed else P From bdb796e7adc5026b276222cb5971ac5cf1e416ef Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 6 Apr 2026 22:41:04 +0200 Subject: [PATCH 080/133] use Gatherv in the tests --- .../polar/tests/test_conga_projections.py | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 6548164b8..12ba9934d 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -12,6 +12,7 @@ from psydac.api.discretization import discretize from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2, C0PolarProjection_V1, \ C1PolarProjection_V0, C1PolarProjection_V1 +from psydac.feec.polar.utils import gather_vlen_array from psydac.fem.basic import FemField from psydac.linalg.block import BlockVector @@ -83,16 +84,12 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed, verbo assert sp_P0.shape == (n1 * n2, n1 * n2) # gather sparse matrix entries (rows, columns, data) on root process - payload = (sp_P0.row, sp_P0.col, sp_P0.data) - parts = mpi_comm.gather(payload, root=0) + data = gather_vlen_array(sp_P0.data, mpi_comm) + cols = gather_vlen_array(sp_P0.col, mpi_comm) + rows = gather_vlen_array(sp_P0.row, mpi_comm) if mpi_comm.rank == 0: - # Concatenate the result of gather (equivalent to summation of local matrices) - rows = np.concatenate([p[0] for p in parts]) - cols = np.concatenate([p[1] for p in parts]) - data = np.concatenate([p[2] for p in parts]) - # Check the number of non-zero entries in the sparse matrix if Projector == C0PolarProjection_V0: assert len(data) == n2 * n2 + n2 * (n1 - (2 if hbc else 1)) @@ -166,16 +163,12 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed, verbo assert sp_P1.shape == (n01 * n02 + n11 * n12, n01 * n02 + n11 * n12) # gather sparse matrix entries (rows, columns, data) on root process - payload = (sp_P1.row, sp_P1.col, sp_P1.data) - parts = mpi_comm.gather(payload, root=0) + data = gather_vlen_array(sp_P1.data, mpi_comm) + cols = gather_vlen_array(sp_P1.col, mpi_comm) + rows = gather_vlen_array(sp_P1.row, mpi_comm) if mpi_comm.rank == 0: - # Concatenate the result of gather (equivalent to summation of local matrices) - rows = np.concatenate([p[0] for p in parts]) - cols = np.concatenate([p[1] for p in parts]) - data = np.concatenate([p[2] for p in parts]) - # Check the number of non-zero entries in the sparse matrix if Projector == C0PolarProjection_V1: assert len(data) == n01 * n02 + 2 * n02 + (n11 - (3 if hbc else 2)) * n12 @@ -277,18 +270,14 @@ def test_PolarProjection_V2(R, ncells, degree, transposed, verbose=False): print("Projection P2 in matrix form on rank", mpi_comm.rank) print(sp_P2.toarray()) - payload = (sp_P2.row, sp_P2.col, sp_P2.data) - parts = mpi_comm.gather(payload, root=0) - [n1, n2] = V2_h.coeff_space.npts assert sp_P2.shape == (n1 * n2, n1 * n2) - if mpi_comm.rank == 0: + data = gather_vlen_array(sp_P2.data, mpi_comm) + cols = gather_vlen_array(sp_P2.col, mpi_comm) + rows = gather_vlen_array(sp_P2.row, mpi_comm) - # Concatenate the result of gather (equivalent to summation of local matrices) - rows = np.concatenate([p[0] for p in parts]) - cols = np.concatenate([p[1] for p in parts]) - data = np.concatenate([p[2] for p in parts]) + if mpi_comm.rank == 0: # Check the number of non-zero entries in the sparse matrix assert len(data) == n1 * n2 @@ -312,6 +301,6 @@ def test_PolarProjection_V2(R, ncells, degree, transposed, verbose=False): assert np.allclose(y_sp, y.toarray(), atol=ATOL, rtol=RTOL) if __name__ == '__main__': - test_PolarProjection_V0(C0PolarProjection_V0, 1, [3, 3], [1, 1], False, False, verbose=False) + test_PolarProjection_V0(C0PolarProjection_V0, 1, [3, 3], [1, 1], False, False, verbose=True) test_PolarProjection_V1(C0PolarProjection_V1, 1, [2, 3], [1, 1], False, False, verbose=True) test_PolarProjection_V2(1, [8, 10], [2, 2], False, verbose=True) From 7ae350783b4ba43cb948710b5a156863fc1adf1a Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 11:27:08 +0200 Subject: [PATCH 081/133] added parameter mpi_root to utility function --- psydac/feec/polar/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/psydac/feec/polar/utils.py b/psydac/feec/polar/utils.py index e1909efe4..882b3c335 100644 --- a/psydac/feec/polar/utils.py +++ b/psydac/feec/polar/utils.py @@ -1,7 +1,7 @@ import numpy as np from mpi4py.util.dtlib import from_numpy_dtype -def gather_vlen_array(v, mpi_comm): +def gather_vlen_array(v, mpi_comm, mpi_root=0): """ Gather 1D arrays of possibly different lengths onto root process Other processes return None @@ -13,10 +13,10 @@ def gather_vlen_array(v, mpi_comm): local_len = np.array(v.size, dtype=np.int64) # Gather local array sizes on root process - recvcounts = np.empty(mpi_comm.size, dtype=np.int64) if mpi_comm.rank == 0 else None - mpi_comm.Gather(local_len, recvcounts, root=0) + recvcounts = np.empty(mpi_comm.size, dtype=np.int64) if mpi_comm.rank == mpi_root else None + mpi_comm.Gather(local_len, recvcounts, root=mpi_root) - if mpi_comm.rank == 0: + if mpi_comm.rank == mpi_root: displs = np.empty(mpi_comm.size, dtype=np.int64) displs[0] = 0 displs[1:] = np.cumsum(recvcounts[:-1]) @@ -29,6 +29,6 @@ def gather_vlen_array(v, mpi_comm): global_array = None mpi_type = from_numpy_dtype(dtype) - mpi_comm.Gatherv(v, [global_array, recvcounts, displs, mpi_type], root=0) + mpi_comm.Gatherv(v, [global_array, recvcounts, displs, mpi_type], root=mpi_root) return global_array \ No newline at end of file From 2b8cb33b2cf71705a7ecd474a151410959abffa1 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 14:34:57 +0200 Subject: [PATCH 082/133] multiple numpy arrays can be passed to gather_vlen_arrays --- .../polar/tests/test_conga_projections.py | 20 +++-- psydac/feec/polar/utils.py | 34 -------- .../utilities/gather_variable_len_arrays.py | 78 +++++++++++++++++++ 3 files changed, 91 insertions(+), 41 deletions(-) delete mode 100644 psydac/feec/polar/utils.py create mode 100644 psydac/utilities/gather_variable_len_arrays.py diff --git a/psydac/feec/polar/tests/test_conga_projections.py b/psydac/feec/polar/tests/test_conga_projections.py index 12ba9934d..0b41d64a6 100644 --- a/psydac/feec/polar/tests/test_conga_projections.py +++ b/psydac/feec/polar/tests/test_conga_projections.py @@ -12,7 +12,7 @@ from psydac.api.discretization import discretize from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C0PolarProjection_V2, C0PolarProjection_V1, \ C1PolarProjection_V0, C1PolarProjection_V1 -from psydac.feec.polar.utils import gather_vlen_array +from psydac.utilities.gather_variable_len_arrays import gather_vlen_array, gather_vlen_arrays from psydac.fem.basic import FemField from psydac.linalg.block import BlockVector @@ -85,11 +85,13 @@ def test_PolarProjection_V0(Projector, R, ncells, degree, hbc, transposed, verbo # gather sparse matrix entries (rows, columns, data) on root process data = gather_vlen_array(sp_P0.data, mpi_comm) - cols = gather_vlen_array(sp_P0.col, mpi_comm) - rows = gather_vlen_array(sp_P0.row, mpi_comm) + cols_rows = gather_vlen_arrays((sp_P0.col, sp_P0.row), mpi_comm) if mpi_comm.rank == 0: + cols = cols_rows[0] + rows = cols_rows[1] + # Check the number of non-zero entries in the sparse matrix if Projector == C0PolarProjection_V0: assert len(data) == n2 * n2 + n2 * (n1 - (2 if hbc else 1)) @@ -164,11 +166,13 @@ def test_PolarProjection_V1(Projector, R, ncells, degree, hbc, transposed, verbo # gather sparse matrix entries (rows, columns, data) on root process data = gather_vlen_array(sp_P1.data, mpi_comm) - cols = gather_vlen_array(sp_P1.col, mpi_comm) - rows = gather_vlen_array(sp_P1.row, mpi_comm) + cols_rows = gather_vlen_arrays((sp_P1.col, sp_P1.row), mpi_comm) if mpi_comm.rank == 0: + cols = cols_rows[0] + rows = cols_rows[1] + # Check the number of non-zero entries in the sparse matrix if Projector == C0PolarProjection_V1: assert len(data) == n01 * n02 + 2 * n02 + (n11 - (3 if hbc else 2)) * n12 @@ -274,11 +278,13 @@ def test_PolarProjection_V2(R, ncells, degree, transposed, verbose=False): assert sp_P2.shape == (n1 * n2, n1 * n2) data = gather_vlen_array(sp_P2.data, mpi_comm) - cols = gather_vlen_array(sp_P2.col, mpi_comm) - rows = gather_vlen_array(sp_P2.row, mpi_comm) + cols_rows = gather_vlen_arrays((sp_P2.col, sp_P2.row), mpi_comm) if mpi_comm.rank == 0: + cols = cols_rows[0] + rows = cols_rows[1] + # Check the number of non-zero entries in the sparse matrix assert len(data) == n1 * n2 diff --git a/psydac/feec/polar/utils.py b/psydac/feec/polar/utils.py deleted file mode 100644 index 882b3c335..000000000 --- a/psydac/feec/polar/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -import numpy as np -from mpi4py.util.dtlib import from_numpy_dtype - -def gather_vlen_array(v, mpi_comm, mpi_root=0): - - """ Gather 1D arrays of possibly different lengths onto root process - Other processes return None - Local arrays must be of the same type - """ - dtype = v.dtype - - v = np.ascontiguousarray(v, dtype=dtype) - local_len = np.array(v.size, dtype=np.int64) - - # Gather local array sizes on root process - recvcounts = np.empty(mpi_comm.size, dtype=np.int64) if mpi_comm.rank == mpi_root else None - mpi_comm.Gather(local_len, recvcounts, root=mpi_root) - - if mpi_comm.rank == mpi_root: - displs = np.empty(mpi_comm.size, dtype=np.int64) - displs[0] = 0 - displs[1:] = np.cumsum(recvcounts[:-1]) - - total_len = int(np.sum(recvcounts)) - global_array = np.empty(total_len, dtype=dtype) - - else: - displs = None - global_array = None - mpi_type = from_numpy_dtype(dtype) - - mpi_comm.Gatherv(v, [global_array, recvcounts, displs, mpi_type], root=mpi_root) - - return global_array \ No newline at end of file diff --git a/psydac/utilities/gather_variable_len_arrays.py b/psydac/utilities/gather_variable_len_arrays.py new file mode 100644 index 000000000..1f2e2635d --- /dev/null +++ b/psydac/utilities/gather_variable_len_arrays.py @@ -0,0 +1,78 @@ +import numpy as np +from mpi4py.util.dtlib import from_numpy_dtype + +def gather_vlen_arrays(arrays, mpi_comm, mpi_root=0): + + """ + Gather several 1D NumPy arrays of the same local length onto root process. Length may vary between processes. + + Parameters + ---------- + arrays : iterable of 1D arrays + All local arrays must have the same length and dtype + All ranks must pass the same number of arrays + mpi_comm : MPI communicator + mpi_root : int, default=0 + + Returns + ------- + On root: + tuple of gathered 1D arrays + On other ranks: + None + """ + + rank = mpi_comm.rank + arrays = tuple(np.asarray(a) for a in arrays) + dtype = arrays[0].dtype + arr_len = arrays[0].size + + for a in arrays: + if a.dtype != dtype: + raise TypeError("all arrays must have same dtype") + if a.size != arr_len: + raise ValueError("all arrays must have same size") + + # reshape as (arr_len, num of arrays) and flatten + arrays_flat = np.ascontiguousarray(np.column_stack(arrays)).ravel(order="C") + + local_len = np.array(arrays_flat.size, dtype=np.int64) + + # Gather local flattened array sizes on root process + recvcounts = np.empty(mpi_comm.size, dtype=np.int64) if rank == mpi_root else None + mpi_comm.Gather(local_len, recvcounts, root=mpi_root) + + if rank == mpi_root: + displs = np.empty(mpi_comm.size, dtype=np.int64) + displs[0] = 0 + displs[1:] = np.cumsum(recvcounts[:-1]) + + total_len = int(np.sum(recvcounts)) + global_array = np.empty(total_len, dtype=dtype) + + else: + displs = None + global_array = None + mpi_type = from_numpy_dtype(dtype) + + mpi_comm.Gatherv(arrays_flat, [global_array, recvcounts, displs, mpi_type], root=mpi_root) + + if rank != mpi_root: + return None + + num_arr = len(arrays) + gathered = global_array.reshape(-1, num_arr, order="C") + return tuple(gathered[:, j].copy() for j in range(num_arr)) + +#=============================================================================== +def gather_vlen_array(v, mpi_comm, mpi_root=0): + + """ Gather 1D arrays of possibly different lengths onto root process + Other processes return None + Local arrays must be of the same type + """ + + result = gather_vlen_arrays((v,), mpi_comm, mpi_root=mpi_root) + if mpi_comm.rank == mpi_root: + return result[0] + return None From 85bcec6d4b8f17fd3323b2d31216df75b00bbf75 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 16:45:04 +0200 Subject: [PATCH 083/133] clean up check_regular_ring_map --- psydac/feec/polar/examples/utils_congapol.py | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/psydac/feec/polar/examples/utils_congapol.py b/psydac/feec/polar/examples/utils_congapol.py index 22a681431..e633ee070 100644 --- a/psydac/feec/polar/examples/utils_congapol.py +++ b/psydac/feec/polar/examples/utils_congapol.py @@ -68,36 +68,46 @@ def print_map_polar_coeffs(map_discrete): def check_regular_ring_map(map_discrete, verbose=False): + """ + Check the first two radial rings of a 2D spline mapping + + Performs 3 checks: + 1. that the pole ring (i=0) collapses to a single point + 2. the 1st ring (i=1) has constant radius + 3. the 1st ring is uniformly spaced in angle + and prints diagnostic information + + """ n_s = map_discrete._fields[0]._space._spaces[0]._nbasis n_theta = map_discrete._fields[0]._space._spaces[1]._nbasis assert n_s == map_discrete._fields[1]._space._spaces[0]._nbasis assert n_theta == map_discrete._fields[1]._space._spaces[1]._nbasis - # deg_s = degree[0] - # print('ncells_s = ', n_s-deg_s, ' = ', ncells[0]) + if verbose: print('n_s = ', n_s) print('n_theta = ', n_theta) print() + # read mapping coefficients map_0_c = map_discrete._fields[0]._coeffs.toarray() map_1_c = map_discrete._fields[1]._coeffs.toarray() - # print('pole: ', map_0_c[0], map_1_c[0]) + # shift map_0_c -= map_0_c[0] map_1_c -= map_1_c[0] - # print(map_0_c**2 + map_1_c**2) + # distance of every coefficient in the pole ring from the first pole coefficient (should be 0) radius_pole = np.sqrt(map_0_c[:n_theta] ** 2 + map_1_c[:n_theta] ** 2) + # distance from the pole of the first radial ring radius_first_ring = np.sqrt( map_0_c[n_theta:2 * n_theta] ** 2 + map_1_c[n_theta:2 * n_theta] ** 2 ) rho_1 = radius_first_ring[0] + # compute angles of the 1st ring cs = map_0_c[n_theta:2 * n_theta] / rho_1 sn = map_1_c[n_theta:2 * n_theta] / rho_1 theta = np.arctan2(sn, cs) - # print('cos theta_j ?', map_0_c[4:8]/rho_1) - # print('sin theta_j ?', map_1_c[4:8]/rho_1) delta_theta = np.mod(theta[1:] - theta[:-1], 2 * np.pi) if verbose: @@ -107,11 +117,14 @@ def check_regular_ring_map(map_discrete, verbose=False): print() circle_error = np.linalg.norm(radius_pole[1:] - radius_pole[:-1]) + #check that 1st ring points are uniformly spaced in angle regularity_error = np.linalg.norm(delta_theta[1:] - delta_theta[:-1]) - regularity_check = abs(circle_error) + abs(regularity_error) < 1e-12 + first_ring_radius_error = np.linalg.norm(radius_first_ring[1:] - radius_first_ring[:-1]) + regularity_check = circle_error + regularity_error + first_ring_radius_error < 1e-12 print('CHECK: spline mapping is regular: ', regularity_check) if not regularity_check: - print(' - circle error on 1st ring:', circle_error) + print(' - circle error on pole ring:', circle_error) print(' - regularity error on 1st ring:', regularity_error) + print(' - first ring radius error:', first_ring_radius_error) print() From 4be687289c1138769849b53de5720e7127601935 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 17:13:50 +0200 Subject: [PATCH 084/133] remove unnecessary flag --- psydac/feec/polar/examples/poisson_2d.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 9f643ea47..f7abfbd12 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -322,7 +322,7 @@ def dtype(self): def run_poisson_2d(*, test_case, ncells, degree, shift_D, R, use_spline_mapping, smooth_method, - cgtol, cgiter, alphaCONGA, study='poisson', verbose=False): + cgtol, cgiter, alphaCONGA, verbose=False): timing = {} timing['assembly'] = 0.0 timing['projection'] = 0.0 @@ -330,8 +330,6 @@ def run_poisson_2d(*, test_case, ncells, degree, timing['diagnostics'] = 0.0 timing['export'] = 0.0 - assert study == 'poisson' # for now - # Method of manufactured solution if test_case == 'disk': model = Poisson2D.disk_domain(R=R, shift_D=shift_D) From 99d935b20c8216fb4969feb83e4b1a026d54042c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 17:31:54 +0200 Subject: [PATCH 085/133] remove unused variable --- psydac/feec/polar/examples/poisson_2d.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index f7abfbd12..8a316741b 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -402,12 +402,6 @@ def run_poisson_2d(*, test_case, ncells, degree, mapping = model.mapping domain = mapping(logical_domain) - rp_str = f'{ncells[0]}_{ncells[1]}_p={degree[0]}_t={test_case}_D{shift_D}_m={smooth_method}' - if use_spline_mapping: - rp_str += '_sm' - else: - rp_str += '_pm' # WARNING: check that polar_mapping == True ? - # ========================== SYMBOLIC DEFINITION ==============================# # Equations @@ -755,9 +749,7 @@ def add_colorbar(im, ax): ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') ax.set_aspect('equal') - # fig.savefig(f'plots/phi_{rp_str}.png') fig.show() - # plt.cla() # Plot numerical error fig, axes = plt.subplots(1, 1, figsize=(4.8, 4.8)) @@ -767,14 +759,9 @@ def add_colorbar(im, ax): ax.set_xlabel(r'$x$', rotation='horizontal') ax.set_ylabel(r'$y$', rotation='horizontal') ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') - # ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) - # ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) ax.set_aspect('equal') - # fig.savefig(f'plots/err_{rp_str}.png') fig.show() - # plt.clf() - # fig.clf() else: @@ -826,7 +813,6 @@ def add_colorbar(im, ax): ax.set_aspect('equal') # Show figure - #fig.savefig(f'plots/phi_and_err_{rp_str}.png') fig.suptitle(f'Rank {mpi_rank}') fig.tight_layout() fig.show() From 8a048a718c812624e3d5bbe68c92abcbab3e4475 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 17:38:48 +0200 Subject: [PATCH 086/133] change docstring --- psydac/feec/polar/examples/poisson_2d.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 8a316741b..96a6cc53e 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -108,7 +108,7 @@ def disk_domain(R, shift_D): - The angular coordinate theta belongs to the interval [0, 2 * pi). : code - $\phi(x,y) = sin(3.5 \pi (R^2 - x^2 - y^2)/R^2)$. + $\phi(x,y) = (1 - ((x^2 + y^2) / R^2) ** 4) * sin(kx * x) * cos(ky * y)$. """ domain = ((0, R), (0, 2 * np.pi)) mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) @@ -121,7 +121,6 @@ def disk_domain(R, shift_D): ky = 2 * pi / (R * (1 + k)) x, y = sympy.symbols('x, y') phi = (1 - ((x * x + y * y) / (R * R)) ** 4) * sin(kx * x) * cos(ky * y) - # phi = (1 - ((X * X + Y * Y) / (R * R)) ** 4) rho = - phi.diff(x, x) - phi.diff(y, y) obj = Poisson2D(domain, mapping, phi, rho) obj.coordinates = (x, y) From bf7dc690f12282210b1aff098be80dec806386b5 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Mon, 13 Apr 2026 17:42:11 +0200 Subject: [PATCH 087/133] remove some comments --- psydac/feec/polar/examples/poisson_2d.py | 48 ------------------------ 1 file changed, 48 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 96a6cc53e..42ad36092 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -169,16 +169,6 @@ def target_domain(): phi = (1 - s ** 8) * sin(kx * (x - 0.5)) * cos(ky * y) rho = - lapl(phi) - # c1 = params['c1'] - # c2 = params['c2'] - # y_tilde = (y - c2)/(1 + k) - # x_tilde = (x - c1)/(1 - k) - # D_tilde = (2 * D)/(1 - k) - # r = (x_tilde**2 + y_tilde**2) - # R = sqrt((2 * r)/(1 - D_tilde * x_tilde + sqrt((1 - D_tilde)**2 - D_tilde**2 * r))) - # phi = (1 - R**8) * sin(kx * (x - 0.5)) * cos(ky * y) - # rho = - laplace(phi) - return Poisson2D(domain, mapping, phi, rho) # ... @@ -412,13 +402,6 @@ def run_poisson_2d(*, test_case, ncells, degree, # but needs to comment "Check linearity" in LinearForm._init_ # rhs = LinearForm(v0, integral(domain, model.rho * v0)) - from sympy.abc import x, y # try - - # f = 2 * 7 * pi * cos(7 * pi / 2 * (1 - x ** 2 - y ** 2)) + (7 * pi) ** 2 * (x ** 2 + y ** 2) * sin( - # 7 * pi / 2 * (1 - x ** 2 - y ** 2)) - # f = x+y - - # f = model.rho X, Y = model.coordinates x, y = model.mapping.expressions f = model.rho.subs({X: x, Y: y}) @@ -434,10 +417,6 @@ def run_poisson_2d(*, test_case, ncells, degree, u0L2norm = Norm(u0, domain, kind='L2') u0H1norm = Norm(u0, domain, kind='H1') - # L2norm = Norm(err_diff*sqrt(mapping.jacobian.det()), logical_domain, kind = 'L2') - # H1norm = Norm(mapping.jacobian.T**(-1) * grad(err_diff) - # * sqrt(mapping.jacobian.det()), logical_domain, kind = 'L2') - # ============================= DISCRETIZATION ================================# if use_spline_mapping: domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) @@ -551,14 +530,6 @@ def run_poisson_2d(*, test_case, ncells, degree, xp = Sp_inv.dot(bp) xsol = proj.convert_to_tensor_basis(xp) info = Sp_inv.get_info() - # from psydac.linalg.utilities import array_to_psydac - # import scipy - # L = proj.L[:, :, p2: -p2].reshape(3, 2 * ne2) - # E = np.block([[L, np.zeros((3, (ne1 + p1 - 2) * ne2))], - # [np.zeros(((ne1 + p1 - 2) * ne2, 2 * ne2)), np.eye((ne1 + p1 - 2) * ne2)]]) - # xparray = scipy.linalg.solve(Sp.toarray(), bp.toarray()) - # xarray = E.T @ xparray - # xsol = array_to_psydac(xarray, V0_h.coeff_space) elif smooth_method == 'C1conga': Sc_inv = inverse(Sc, 'cg', tol=cgtol, maxiter=cgiter, verbose=verbose) xsol = Sc_inv.dot(bc) @@ -608,28 +579,9 @@ def run_poisson_2d(*, test_case, ncells, degree, rel_err_l2 = err_l2 / ref_u0L2 rel_err_h1 = err_h1 / ref_u0H1 - # previous option: ok but H1 norm has problems ? - # ref_u0L2 = u0L2norm_h.assemble(u0 = phi_ref) - # err2 = L2norm_h.assemble(u0 = phi_ref - phi) - # err2 = L2norm_h.assemble(u0 = phi) - - # assert np.allclose(err2, err2_matrix, rtol = 1e-7, atol = 1e-7) - - # and H1 error - # t0 = time() - # errh1_semi = H1norm_h.assemble(u0 = phi_ref - phi) - # errh1_semi = errH1_h.assemble(u0 = phi) # BUG ?? - # u0H1_semi = u0H1norm_h.assemble(u0 = phi) - # errh1 = np.sqrt(errh1_semi**2 + err2**2) - # u0H1 = np.sqrt(u0H1_semi**2 + u0L2**2) - # errh1 = np.sqrt(errh1_semi**2 + err2**2) - # errh1_semi_sq = phi_diff.dot(S_nobc.dot(phi_diff)) - # errh1_matrix = np.sqrt(err2_sq + errh1_semi_sq) - t1 = time() timing['diagnostics'] = t1 - t0 - # assert np.allclose(errh1, errh1_matrix, rtol = 1e-7, atol = 1e-7) # Write solution to HDF5 file t0 = time() V0_h.export_fields('fields.h5', phi=phi) From 487eda0114f6b30853caafd566d483fde758039f Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 15 Apr 2026 08:59:22 +0200 Subject: [PATCH 088/133] remove unused variables --- psydac/feec/polar/examples/poisson_2d.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 42ad36092..116e954de 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -410,13 +410,6 @@ def run_poisson_2d(*, test_case, ncells, degree, err_diff = model.phi - u0 - # NOTE (mcp, dec 2024): norms and errors now computed only for discrete solutions (using M and S matrices) - errL2 = Norm(err_diff, domain, kind='L2') - errH1 = Norm(err_diff, domain, kind='H1') - - u0L2norm = Norm(u0, domain, kind='L2') - u0H1norm = Norm(u0, domain, kind='H1') - # ============================= DISCRETIZATION ================================# if use_spline_mapping: domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) @@ -431,13 +424,6 @@ def run_poisson_2d(*, test_case, ncells, degree, aS_h = discretize(aS, domain_h, (V0_h, V0_h), backend=backend) rhs_h = discretize(rhs, domain_h, V0_h, backend=backend) - - errL2_h = discretize(errL2, domain_h, V0_h, backend=backend) - errH1_h = discretize(errH1, domain_h, V0_h, backend=backend) - - u0L2norm_h = discretize(u0L2norm, domain_h, V0_h, backend=backend) - u0H1norm_h = discretize(u0H1norm, domain_h, V0_h, backend=backend) - M = aM_h.assemble() S = aS_h.assemble() b = rhs_h.assemble() From a7bde8de7ce534ce0f914743a7f7507627784717 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 15 Apr 2026 09:49:17 +0200 Subject: [PATCH 089/133] clean up debugging functions --- psydac/feec/polar/examples/utils_congapol.py | 29 ++++---------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/psydac/feec/polar/examples/utils_congapol.py b/psydac/feec/polar/examples/utils_congapol.py index e633ee070..32ad2b1ff 100644 --- a/psydac/feec/polar/examples/utils_congapol.py +++ b/psydac/feec/polar/examples/utils_congapol.py @@ -2,39 +2,29 @@ def print_map_polar_coeffs(map_discrete): + """ + Used for debugging purposes. Prints information about discrete polar coefficients + """ # Print spline mapping print('Spline mapping:') - # print(map_discrete) + print(map_discrete) print('vars(map_discrete):') print(vars(map_discrete)) print() - # print(vars(map_discrete._control_points)) - # print(vars(map_discrete._control_points._mapping)) - # print(vars(map_discrete._control_points._mapping._control_points)) - # print(map_discrete._control_points._mapping._fields) print('vars(map_discrete._fields[0]) :') print(vars(map_discrete._fields[0])) print() print('vars(map_discrete._fields[0]._space._spaces[0]) :') - # print(vars(map_discrete._fields[0]._space)) print(vars(map_discrete._fields[0]._space._spaces[0])) print('vars(map_discrete._fields[0]._space._spaces[1]) :') print(vars(map_discrete._fields[0]._space._spaces[1])) n_s = map_discrete._fields[0]._space._spaces[0]._nbasis n_theta = map_discrete._fields[0]._space._spaces[1]._nbasis - assert n_s == map_discrete._fields[1]._space._spaces[0]._nbasis - assert n_theta == map_discrete._fields[1]._space._spaces[1]._nbasis - # deg_s = degree[0] - # print('ncells_s = ', n_s-deg_s, ' = ', ncells[0]) print('n_s = ', n_s) print('n_theta = ', n_theta) print() - # print('map_discrete._fields[0]._coeffs :') - # print(map_discrete._fields[0]._coeffs) - # print() - map_0_c = map_discrete._fields[0]._coeffs.toarray() map_1_c = map_discrete._fields[1]._coeffs.toarray() @@ -52,19 +42,10 @@ def print_map_polar_coeffs(map_discrete): cs = map_0_c[n_theta:2 * n_theta] / rho_1 sn = map_1_c[n_theta:2 * n_theta] / rho_1 theta = np.arctan2(sn, cs) - # print('cos theta_j ?', map_0_c[4:8]/rho_1) - # print('sin theta_j ?', map_1_c[4:8]/rho_1) - # print('theta_j:', theta) print('radius_pole:', radius_pole) print('radius first ring:', radius_first_ring) print('D theta_j:', np.mod(theta[1:] - theta[:-1], 2 * np.pi)) - # print('theta_j ?', np.arcsin(map_1_c[4:8]/rho_1)) - # exit() - - # angle2 = - # print(vars(map_discrete._control_points._mapping._fields[0]._coeffs)) - # exit() def check_regular_ring_map(map_discrete, verbose=False): @@ -75,7 +56,7 @@ def check_regular_ring_map(map_discrete, verbose=False): 1. that the pole ring (i=0) collapses to a single point 2. the 1st ring (i=1) has constant radius 3. the 1st ring is uniformly spaced in angle - and prints diagnostic information + and prints diagnostic information. Used for debugging purposes. """ n_s = map_discrete._fields[0]._space._spaces[0]._nbasis From ac3be5e0f6de433d3a9b10993b12a9ae00808873 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 15 Apr 2026 10:40:48 +0200 Subject: [PATCH 090/133] docstring for Laplacian class --- psydac/feec/polar/examples/poisson_2d.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 116e954de..6292656e1 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -41,6 +41,10 @@ # ============================================================================== class Laplacian: + """ + Symbolic Laplace operator associated with a mapping F from logical to physical coordinates. + Builds Laplacian in logical coordinates using the metric induced by F. + """ def __init__(self, mapping): assert isinstance(mapping, Mapping) From 514fb1142c5366ab9257585d24e60c5c3a48c16c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 16 Apr 2026 13:11:39 +0200 Subject: [PATCH 091/133] clean up Poisson example --- psydac/feec/polar/examples/poisson_2d.py | 32 +++++++----------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 6292656e1..02d84b483 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -92,15 +92,10 @@ def __init__(self, domain, mapping, phi, rho): self._phi = phi self._rho = rho - s, t = mapping.logical_coordinates self._phi_callable = sympy.lambdify([s, t], phi) self._rho_callable = sympy.lambdify([s, t], rho) - # x, y = mapping.coordinates - # self._phi_callable = sympy.lambdify([x, y], phi) - # # self._rho_callable = sympy.lambdify([x, y], rho) - # ... @staticmethod def disk_domain(R, shift_D): @@ -126,7 +121,12 @@ def disk_domain(R, shift_D): x, y = sympy.symbols('x, y') phi = (1 - ((x * x + y * y) / (R * R)) ** 4) * sin(kx * x) * cos(ky * y) rho = - phi.diff(x, x) - phi.diff(y, y) - obj = Poisson2D(domain, mapping, phi, rho) + + x_log, y_log = mapping.expressions + phi_log = phi.subs({x: x_log, y: y_log}) + rho_log = rho.subs({x: x_log, y: y_log}) + + obj = Poisson2D(domain, mapping, phi_log, rho_log) obj.coordinates = (x, y) return obj @@ -404,13 +404,8 @@ def run_poisson_2d(*, test_case, ncells, degree, aS = BilinearForm((u0, v0), integral(domain, dot(grad(u0), grad(v0)))) # model.rho is in logical coordinates instead of physical but it works anyways # but needs to comment "Check linearity" in LinearForm._init_ - # rhs = LinearForm(v0, integral(domain, model.rho * v0)) - - X, Y = model.coordinates - x, y = model.mapping.expressions - f = model.rho.subs({X: x, Y: y}) - rhs = LinearForm(v0, integral(domain, f * v0)) + rhs = LinearForm(v0, integral(domain, model.rho * v0), check_linearity=False) err_diff = model.phi - u0 @@ -438,15 +433,10 @@ def run_poisson_2d(*, test_case, ncells, degree, # =================== PROJECT THE EXACT SOLUTION =========================# - from psydac.feec.pull_push import pull_2d_h1 - from sympy import lambdify from psydac.feec.global_geometric_projectors import GlobalGeometricProjectorH1 Pi0 = GlobalGeometricProjectorH1(V0_h) - phi_symref = model.phi - phi_calref = lambdify(domain.coordinates, phi_symref) - phi_callog = pull_2d_h1(phi_calref, F) - phi_ref = Pi0(phi_callog) + phi_ref = Pi0(model.phi_callable) phi_ref.coeffs.update_ghost_regions() @@ -634,11 +624,7 @@ def run_poisson_2d(*, test_case, ncells, degree, phi, = Vnew.import_fields('fields.h5', 'phi') # Callable exact solution in logical coordinates - X, Y = model.coordinates - x, y = model.mapping.expressions - expr_phi_e = model.phi.subs({X: x, Y: y}) - x1, x2 = model.mapping.logical_coordinates - phi_e = sympy.lambdify([x1, x2], expr_phi_e) + phi_e = model.phi_callable # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = Vnew.local_domain From 501a18bb0c4c12d30ff4a6920349ebdc17a56125 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 16 Apr 2026 13:28:06 +0200 Subject: [PATCH 092/133] changed params dict in disk --- psydac/feec/polar/examples/poisson_2d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 02d84b483..e88529038 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -110,10 +110,10 @@ def disk_domain(R, shift_D): $\phi(x,y) = (1 - ((x^2 + y^2) / R^2) ** 4) * sin(kx * x) * cos(ky * y)$. """ domain = ((0, R), (0, 2 * np.pi)) - mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) + params = dict(c1=shift_D * R * R, c2=0, k=0, D=shift_D) + mapping = TargetMapping('TM', **params) # physical field (cf use of physical ref solution in Maxwell case) - params = dict(c1=0, c2=0, k=0, D=shift_D) k = params['k'] D = params['D'] kx = 2 * pi / (R * (1 - k + D)) From 36357d0ad590779a8c4d00492c1d7900243c42f1 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 16 Apr 2026 13:33:07 +0200 Subject: [PATCH 093/133] remove coordinates property --- psydac/feec/polar/examples/poisson_2d.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index e88529038..0051ee167 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -126,10 +126,7 @@ def disk_domain(R, shift_D): phi_log = phi.subs({x: x_log, y: y_log}) rho_log = rho.subs({x: x_log, y: y_log}) - obj = Poisson2D(domain, mapping, phi_log, rho_log) - obj.coordinates = (x, y) - - return obj + return Poisson2D(domain, mapping, phi_log, rho_log) # ... @staticmethod @@ -532,14 +529,6 @@ def run_poisson_2d(*, test_case, ncells, degree, phi = FemField(V0_h, coeffs=xsol) phi.coeffs.update_ghost_regions() - # ref solution: projected exact solution - - # phi_ref = Pi0(model.phi_callable) - - # def P0_phys(f_phys, P0, domain, mappings_list): - # phi = lambdify(domain.coordinates, f_phys) - # P0(f_log) - # L2 and H1 norms ref_u0L2_2 = phi_ref.coeffs.inner(M.dot(phi_ref.coeffs)) # l2 norm of ref solution ref_u0H1_semi2 = phi_ref.coeffs.inner(S.dot(phi_ref.coeffs)) From 71788a68eb00423336a29879eb940c720fd9007d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 10:33:22 +0200 Subject: [PATCH 094/133] testing my branch of sympde --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2a47c1260..1240e8807 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ 'termcolor', # Our packages from PyPi - 'sympde == 0.19.2', + 'sympde @ https://github.com/pyccel/sympde/archive/refs/heads/fix_check_linearity.zip', 'pyccel >= 2.2.3', 'gelato == 0.12', From be219a69d0730cacc41a6791e69f5e6cc5056541 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 10:39:18 +0200 Subject: [PATCH 095/133] change mapping params to Rational --- psydac/feec/polar/examples/poisson_2d.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 0051ee167..ff737bfe9 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -7,7 +7,7 @@ import numpy as np import matplotlib.pyplot as plt -from sympy import sqrt, sin, cos, pi +from sympy import sqrt, sin, cos, pi, Rational from sympde.topology.analytical_mapping import PolarMapping, TargetMapping, CzarnyMapping from sympde.topology.domain import Square, Domain @@ -151,7 +151,7 @@ def target_domain(): """ domain = ((0, 1), (0, 2 * np.pi)) - params = dict(c1=0, c2=0, k=0.3, D=0.2) + params = dict(c1=0, c2=0, k=Rational(3, 10), D=Rational(2, 10)) mapping = TargetMapping('F', **params) from sympy import sin, cos, pi, sqrt @@ -367,6 +367,8 @@ def run_poisson_2d(*, test_case, ncells, degree, e1, e2 = V.coeff_space.ends # ==================== MAPPING & PHYSICAL DOMAIN ==============================# + #TODO: use sympde domain in Poisson2D + # maybe define a parent class Model logical_domain = Square('Omega', bounds1=model.domain[0], bounds2=model.domain[1]) if use_spline_mapping: From 923fa53b01006b40a6de9e2ef5b7108a23c0a892 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 12:06:37 +0200 Subject: [PATCH 096/133] change czarny mapping params to Rational --- psydac/feec/polar/examples/poisson_2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index ff737bfe9..78f2dc127 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -187,7 +187,7 @@ def czarny_domain(): """ domain = ((0, 1), (0, 2 * np.pi)) - params = dict(c1=0, c2=0, eps=0.2, b=1.4) + params = dict(c1=0, c2=0, eps=Rational(1, 5), b=Rational(7, 5)) mapping = CzarnyMapping('F', **params) from sympy import sin, cos, pi From 79f70cf30443d168b48bd7d40e3d3b788f5bc7c2 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 13:09:58 +0200 Subject: [PATCH 097/133] changed domain in Poisson2D to sympde domain --- psydac/feec/polar/examples/poisson_2d.py | 27 ++++++++++-------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 78f2dc127..7807ec107 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -2,7 +2,6 @@ import sympy from mpi4py import MPI from time import time, sleep -# import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import make_axes_locatable import numpy as np @@ -109,7 +108,7 @@ def disk_domain(R, shift_D): : code $\phi(x,y) = (1 - ((x^2 + y^2) / R^2) ** 4) * sin(kx * x) * cos(ky * y)$. """ - domain = ((0, R), (0, 2 * np.pi)) + logical_domain = Square('Omega', bounds1=(0, R), bounds2=(0, 2 * np.pi)) params = dict(c1=shift_D * R * R, c2=0, k=0, D=shift_D) mapping = TargetMapping('TM', **params) @@ -126,7 +125,7 @@ def disk_domain(R, shift_D): phi_log = phi.subs({x: x_log, y: y_log}) rho_log = rho.subs({x: x_log, y: y_log}) - return Poisson2D(domain, mapping, phi_log, rho_log) + return Poisson2D(logical_domain, mapping, phi_log, rho_log) # ... @staticmethod @@ -150,12 +149,11 @@ def target_domain(): """ - domain = ((0, 1), (0, 2 * np.pi)) + logical_domain = Square('Omega', bounds1=(0, 1), bounds2=(0, 2 * np.pi)) params = dict(c1=0, c2=0, k=Rational(3, 10), D=Rational(2, 10)) mapping = TargetMapping('F', **params) - from sympy import sin, cos, pi, sqrt - # from sympy.abc import x, y + from sympy import sin, cos, pi lapl = Laplacian(mapping) s, t = mapping.logical_coordinates @@ -170,7 +168,7 @@ def target_domain(): phi = (1 - s ** 8) * sin(kx * (x - 0.5)) * cos(ky * y) rho = - lapl(phi) - return Poisson2D(domain, mapping, phi, rho) + return Poisson2D(logical_domain, mapping, phi, rho) # ... @staticmethod @@ -186,7 +184,7 @@ def czarny_domain(): """ - domain = ((0, 1), (0, 2 * np.pi)) + logical_domain = Square('Omega', bounds1=(0, 1), bounds2=(0, 2 * np.pi)) params = dict(c1=0, c2=0, eps=Rational(1, 5), b=Rational(7, 5)) mapping = CzarnyMapping('F', **params) @@ -200,7 +198,7 @@ def czarny_domain(): phi = (1 - s ** 8) * sin(pi * x) * cos(pi * y) rho = - lapl(phi) - return Poisson2D(domain, mapping, phi, rho) + return Poisson2D(logical_domain, mapping, phi, rho) # ... @property @@ -352,8 +350,8 @@ def run_poisson_2d(*, test_case, ncells, degree, # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# # Create uniform grid - grid_1 = np.linspace(*model.domain[0], num=ne1 + 1) - grid_2 = np.linspace(*model.domain[1], num=ne2 + 1) + grid_1 = np.linspace(*model.domain.bounds1, num=ne1 + 1) + grid_2 = np.linspace(*model.domain.bounds2, num=ne2 + 1) # Create 1D finite element spaces V1 = SplineSpace(p1, grid=grid_1, periodic=False) @@ -367,10 +365,7 @@ def run_poisson_2d(*, test_case, ncells, degree, e1, e2 = V.coeff_space.ends # ==================== MAPPING & PHYSICAL DOMAIN ==============================# - #TODO: use sympde domain in Poisson2D - # maybe define a parent class Model - logical_domain = Square('Omega', bounds1=model.domain[0], - bounds2=model.domain[1]) + #TODO: maybe define a parent class Model if use_spline_mapping: # Create spline mapping by interpolation of analytical mapping map_analytic = model.mapping.get_callable_mapping() @@ -392,7 +387,7 @@ def run_poisson_2d(*, test_case, ncells, degree, else: # Only symbolic mapping is necessary mapping = model.mapping - domain = mapping(logical_domain) + domain = mapping(model.domain) # ========================== SYMBOLIC DEFINITION ==============================# From 406e7082e001a3b67b0a658b1ce8639c6f89aedc Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 13:51:32 +0200 Subject: [PATCH 098/133] minor cleanup --- psydac/feec/polar/examples/poisson_2d.py | 35 +++++++++--------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 7807ec107..6317cce63 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -6,15 +6,14 @@ import numpy as np import matplotlib.pyplot as plt -from sympy import sqrt, sin, cos, pi, Rational +from sympy import sin, cos, pi, Rational -from sympde.topology.analytical_mapping import PolarMapping, TargetMapping, CzarnyMapping +from sympde.topology.analytical_mapping import TargetMapping, CzarnyMapping from sympde.topology.domain import Square, Domain from sympde.topology import ScalarFunctionSpace, elements_of from sympde.expr import BilinearForm, LinearForm, integral, Norm, Functional from sympde.calculus import dot, grad, laplace from sympde.topology.mapping import Mapping -from sympde.expr.evaluation import LogicalExpr from psydac.api.discretization import discretize from psydac.linalg.stencil import StencilVector, StencilMatrix @@ -153,8 +152,6 @@ def target_domain(): params = dict(c1=0, c2=0, k=Rational(3, 10), D=Rational(2, 10)) mapping = TargetMapping('F', **params) - from sympy import sin, cos, pi - lapl = Laplacian(mapping) s, t = mapping.logical_coordinates x, y = mapping.expressions @@ -188,8 +185,6 @@ def czarny_domain(): params = dict(c1=0, c2=0, eps=Rational(1, 5), b=Rational(7, 5)) mapping = CzarnyMapping('F', **params) - from sympy import sin, cos, pi - lapl = Laplacian(mapping) s, t = mapping.logical_coordinates x, y = mapping.expressions @@ -353,17 +348,16 @@ def run_poisson_2d(*, test_case, ncells, degree, grid_1 = np.linspace(*model.domain.bounds1, num=ne1 + 1) grid_2 = np.linspace(*model.domain.bounds2, num=ne2 + 1) + periodic = [False, True] + # Create 1D finite element spaces - V1 = SplineSpace(p1, grid=grid_1, periodic=False) - V2 = SplineSpace(p2, grid=grid_2, periodic=True) + V1 = SplineSpace(p1, grid=grid_1, periodic=periodic[0]) + V2 = SplineSpace(p2, grid=grid_2, periodic=periodic[1]) # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, [False, True] , comm = mpi_comm) + domain_decomposition = DomainDecomposition(ncells, periodic , comm=mpi_comm) V = TensorFemSpace(domain_decomposition, V1, V2) - s1, s2 = V.coeff_space.starts - e1, e2 = V.coeff_space.ends - # ==================== MAPPING & PHYSICAL DOMAIN ==============================# #TODO: maybe define a parent class Model if use_spline_mapping: @@ -397,19 +391,18 @@ def run_poisson_2d(*, test_case, ncells, degree, aM = BilinearForm((u0, v0), integral(domain, u0 * v0)) aS = BilinearForm((u0, v0), integral(domain, dot(grad(u0), grad(v0)))) # model.rho is in logical coordinates instead of physical but it works anyways - # but needs to comment "Check linearity" in LinearForm._init_ - rhs = LinearForm(v0, integral(domain, model.rho * v0), check_linearity=False) + rhs = LinearForm(v0, integral(domain, model.rho * v0)) err_diff = model.phi - u0 # ============================= DISCRETIZATION ================================# if use_spline_mapping: - domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) + domain_h = discretize(domain, filename='geo.h5', comm=mpi_comm) V0_h = discretize(V0, domain_h) F = list(domain_h.mappings.values()).pop() else: - domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) + domain_h = discretize(domain, ncells=ncells, periodic=periodic, comm=mpi_comm) V0_h = discretize(V0, domain_h, degree=degree) F = mapping.get_callable_mapping() @@ -445,7 +438,7 @@ def run_poisson_2d(*, test_case, ncells, degree, Sp = proj.change_matrix_basis(S) bp = proj.change_rhs_basis(b) alpha = 'None' - if smooth_method == 'polar-std': + elif smooth_method == 'polar-std': # Build standard polar map from control points of standard polar map n1, n2 = [W.nbasis for W in V0_h.spaces] rho = np.array([i1 / (n1 - 1) for i1 in range(n1)]) @@ -467,7 +460,6 @@ def run_poisson_2d(*, test_case, ncells, degree, alpha = alphaCONGA P0 = C1PolarProjection_V0(V0_h, gamma=gamma, hbc=True) # hbc imposes the boundary conditions Sc = CongaLaplacian(S, M, P0, alpha) - A = Sc.tosparse() bc = P0.T.dot(b) elif smooth_method == 'C0conga': alpha = alphaCONGA @@ -481,8 +473,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # Apply homogeneous Dirichlet boundary conditions for the conforming # smooth_method case 'polar' and non-conforming case 'None' - # NOTE: this does not effect ghost regions - S_nobc = S.copy() + # NOTE: this does not affect ghost regions e1 = V0_h.coeff_space.ends[0] if e1 == V0_h.coeff_space.npts[0] - 1: if smooth_method in ('polar-std', 'polar-spec'): @@ -598,7 +589,7 @@ def run_poisson_2d(*, test_case, ncells, degree, Vnew = map_discrete.space mapping = map_discrete else: - dd = DomainDecomposition(ncells, [False, True], comm=MPI.COMM_SELF) + dd = DomainDecomposition(ncells, periodic, comm=MPI.COMM_SELF) Vnew = TensorFemSpace(dd, V1, V2) # Import solution vector into new serial field From edad37cd679736553658f2508f38a50c644d5f2a Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 14:37:23 +0200 Subject: [PATCH 099/133] move plotting to a separate function --- psydac/feec/polar/examples/poisson_2d.py | 180 +++++++++-------------- 1 file changed, 66 insertions(+), 114 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 6317cce63..5ff1c3a75 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -11,8 +11,8 @@ from sympde.topology.analytical_mapping import TargetMapping, CzarnyMapping from sympde.topology.domain import Square, Domain from sympde.topology import ScalarFunctionSpace, elements_of -from sympde.expr import BilinearForm, LinearForm, integral, Norm, Functional -from sympde.calculus import dot, grad, laplace +from sympde.expr import BilinearForm, LinearForm, integral +from sympde.calculus import dot, grad from sympde.topology.mapping import Mapping from psydac.api.discretization import discretize @@ -577,25 +577,24 @@ def run_poisson_2d(*, test_case, ncells, degree, N = 10 V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) - # plot only with the root process - distribute_viz = False - if not distribute_viz: - # Non-master processes stop here - if mpi_rank != 0: - return - if use_spline_mapping: - geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) - map_discrete = [*geometry.mappings.values()].pop() - Vnew = map_discrete.space - mapping = map_discrete - else: - dd = DomainDecomposition(ncells, periodic, comm=MPI.COMM_SELF) - Vnew = TensorFemSpace(dd, V1, V2) + # Non-master processes stop here + if mpi_rank != 0: + return + + plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2) + + return locals() + +def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2): - # Import solution vector into new serial field + if use_spline_mapping: + geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) + map_discrete = [*geometry.mappings.values()].pop() + Vnew = map_discrete.space else: - Vnew = V + dd = DomainDecomposition(ncells, periodic, comm=MPI.COMM_SELF) + Vnew = TensorFemSpace(dd, V1, V2) # Import solution vector into new serial field phi, = Vnew.import_fields('fields.h5', 'phi') @@ -616,113 +615,66 @@ def run_poisson_2d(*, test_case, ncells, degree, print('ex[0,0] = ', ex[0, 0]) # Compute physical coordinates of logical grid - map_temp = map_discrete if use_spline_mapping else F + map_temp = model.mapping.get_callable_mapping() pcoords = np.array([[map_temp(e1, e2) for e2 in eta2] for e1 in eta1]) xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] - plot_only_sol = False - def add_colorbar(im, ax): divider = make_axes_locatable(ax) cax = divider.append_axes("right", size=0.2, pad=0.2) cbar = ax.get_figure().colorbar(im, cax=cax) return cbar + # Create figure with 3 subplots: + # 1. exact solution on exact domain + # 2. numerical solution on mapped domain (analytical or spline) + # 3. numerical error on mapped domain (analytical or spline) + fig, axes = plt.subplots(1, 3, figsize=(15, 4.8)) - if plot_only_sol: - - # plot only numerical solution on mapped domain (analytical or spline) - fig, axes = plt.subplots(1, 1, figsize=(4.8, 4.8)) - - - if use_spline_mapping: - # Recompute physical coordinates of logical grid using spline mapping - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) - xx = pcoords[:, :, 0] - yy = pcoords[:, :, 1] - - # Plot numerical solution - ax = axes # [0] - im = ax.contourf(xx, yy, num, 40, cmap='jet') - add_colorbar(im, ax) - ax.set_xlabel(r'$x$', rotation='horizontal') - ax.set_ylabel(r'$y$', rotation='horizontal') - ax.set_title(r'$\phi(x,y)$') - ax.plot(xx[:, ::N], yy[:, ::N], 'k') - ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') - ax.set_aspect('equal') - - fig.show() - - # Plot numerical error - fig, axes = plt.subplots(1, 1, figsize=(4.8, 4.8)) - ax = axes # [2] - im = ax.contourf(xx, yy, err, 40, cmap='jet') - add_colorbar(im, ax) - ax.set_xlabel(r'$x$', rotation='horizontal') - ax.set_ylabel(r'$y$', rotation='horizontal') - ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') - ax.set_aspect('equal') - - fig.show() - - - else: - - # Create figure with 3 subplots: - # 1. exact solution on exact domain - # 2. numerical solution on mapped domain (analytical or spline) - # 3. numerical error on mapped domain (analytical or spline) - fig, axes = plt.subplots(1, 3, figsize=(15, 4.8)) - - if use_spline_mapping: - # Recompute physical coordinates of logical grid using spline mapping - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) - xx = pcoords[:, :, 0] - yy = pcoords[:, :, 1] - - # Plot exact solution - ax = axes[0] - im = ax.contourf(xx, yy, ex, 40, cmap='jet') - add_colorbar(im, ax) - ax.set_xlabel(r'$x$', rotation='horizontal') - ax.set_ylabel(r'$y$', rotation='horizontal') - ax.set_title(r'$\phi_{ex}(x,y)$') - ax.plot(xx[:, ::N], yy[:, ::N], 'k') - ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') - ax.set_aspect('equal') - - - # Plot numerical solution - ax = axes[1] - im = ax.contourf(xx, yy, num, 40, cmap='jet') - add_colorbar(im, ax) - ax.set_xlabel(r'$x$', rotation='horizontal') - ax.set_ylabel(r'$y$', rotation='horizontal') - ax.set_title(r'$\phi(x,y)$') - ax.plot(xx[:, ::N], yy[:, ::N], 'k') - ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') - ax.set_aspect('equal') - - # Plot numerical error - ax = axes[2] - im = ax.contourf(xx, yy, err, 40, cmap='jet') - add_colorbar(im, ax) - ax.set_xlabel(r'$x$', rotation='horizontal') - ax.set_ylabel(r'$y$', rotation='horizontal') - ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') - ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) - ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) - ax.set_aspect('equal') - - # Show figure - fig.suptitle(f'Rank {mpi_rank}') - fig.tight_layout() - fig.show() - - return locals() - + if use_spline_mapping: + # Recompute physical coordinates of logical grid using spline mapping + pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) + xx = pcoords[:, :, 0] + yy = pcoords[:, :, 1] + + # Plot exact solution + ax = axes[0] + im = ax.contourf(xx, yy, ex, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi_{ex}(x,y)$') + ax.plot(xx[:, ::N], yy[:, ::N], 'k') + ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.set_aspect('equal') + + + # Plot numerical solution + ax = axes[1] + im = ax.contourf(xx, yy, num, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi(x,y)$') + ax.plot(xx[:, ::N], yy[:, ::N], 'k') + ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.set_aspect('equal') + + # Plot numerical error + ax = axes[2] + im = ax.contourf(xx, yy, err, 40, cmap='jet') + add_colorbar(im, ax) + ax.set_xlabel(r'$x$', rotation='horizontal') + ax.set_ylabel(r'$y$', rotation='horizontal') + ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') + ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) + ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) + ax.set_aspect('equal') + + # Show figure + fig.tight_layout() + fig.show() # ============================================================================== # Parser From 2a9b82a03e87e23754782b5a47823ef991385b44 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 14:51:49 +0200 Subject: [PATCH 100/133] add refine param to plotting --- psydac/feec/polar/examples/poisson_2d.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 5ff1c3a75..2db4c7be1 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -581,11 +581,11 @@ def run_poisson_2d(*, test_case, ncells, degree, if mpi_rank != 0: return - plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2) + plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=N) return locals() -def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2): +def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=10): if use_spline_mapping: geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) @@ -604,15 +604,12 @@ def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2): # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = Vnew.local_domain - print([sk1, sk2], [ek1, ek2]) - eta1 = refine_array_1d(V1.breaks[sk1:ek1 + 2], N) - eta2 = refine_array_1d(V2.breaks[sk2:ek2 + 2], N) + eta1 = refine_array_1d(V1.breaks[sk1:ek1 + 2], refine) + eta2 = refine_array_1d(V2.breaks[sk2:ek2 + 2], refine) num = np.array([[phi(e1, e2) for e2 in eta2] for e1 in eta1]) ex = np.array([[phi_e(e1, e2) for e2 in eta2] for e1 in eta1]) err = num - ex - print('num[0,0] = ', num[0, 0]) - print('ex[0,0] = ', ex[0, 0]) # Compute physical coordinates of logical grid map_temp = model.mapping.get_callable_mapping() @@ -645,8 +642,8 @@ def add_colorbar(im, ax): ax.set_xlabel(r'$x$', rotation='horizontal') ax.set_ylabel(r'$y$', rotation='horizontal') ax.set_title(r'$\phi_{ex}(x,y)$') - ax.plot(xx[:, ::N], yy[:, ::N], 'k') - ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.plot(xx[:, ::refine], yy[:, ::refine], 'k') + ax.plot(xx[::refine, :].T, yy[::refine, :].T, 'k') ax.set_aspect('equal') @@ -657,8 +654,8 @@ def add_colorbar(im, ax): ax.set_xlabel(r'$x$', rotation='horizontal') ax.set_ylabel(r'$y$', rotation='horizontal') ax.set_title(r'$\phi(x,y)$') - ax.plot(xx[:, ::N], yy[:, ::N], 'k') - ax.plot(xx[::N, :].T, yy[::N, :].T, 'k') + ax.plot(xx[:, ::refine], yy[:, ::refine], 'k') + ax.plot(xx[::refine, :].T, yy[::refine, :].T, 'k') ax.set_aspect('equal') # Plot numerical error @@ -668,8 +665,8 @@ def add_colorbar(im, ax): ax.set_xlabel(r'$x$', rotation='horizontal') ax.set_ylabel(r'$y$', rotation='horizontal') ax.set_title(r'$\phi(x,y) - \phi_{ex}(x,y)$') - ax.plot( xx[:,::N] , yy[:,::N] , 'k' ) - ax.plot( xx[::N,:].T, yy[::N,:].T, 'k' ) + ax.plot( xx[:,::refine] , yy[:,::refine] , 'k' ) + ax.plot( xx[::refine,:].T, yy[::refine,:].T, 'k' ) ax.set_aspect('equal') # Show figure From b7903ae0329836474374e1c231208718aa072eed Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 15:06:37 +0200 Subject: [PATCH 101/133] changes in mapping for plotting --- psydac/feec/polar/examples/poisson_2d.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 2db4c7be1..58d6e4ca9 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -585,6 +585,9 @@ def run_poisson_2d(*, test_case, ncells, degree, return locals() +# ============================================================================== +# Plotting +# ============================================================================== def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=10): if use_spline_mapping: @@ -612,8 +615,12 @@ def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=10 err = num - ex # Compute physical coordinates of logical grid - map_temp = model.mapping.get_callable_mapping() - pcoords = np.array([[map_temp(e1, e2) for e2 in eta2] for e1 in eta1]) + if use_spline_mapping: + map_plot = map_discrete + else: + map_plot = model.mapping.get_callable_mapping() + + pcoords = np.array([[map_plot(e1, e2) for e2 in eta2] for e1 in eta1]) xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] @@ -629,12 +636,6 @@ def add_colorbar(im, ax): # 3. numerical error on mapped domain (analytical or spline) fig, axes = plt.subplots(1, 3, figsize=(15, 4.8)) - if use_spline_mapping: - # Recompute physical coordinates of logical grid using spline mapping - pcoords = np.array([[map_discrete(e1, e2) for e2 in eta2] for e1 in eta1]) - xx = pcoords[:, :, 0] - yy = pcoords[:, :, 1] - # Plot exact solution ax = axes[0] im = ax.contourf(xx, yy, ex, 40, cmap='jet') @@ -646,7 +647,6 @@ def add_colorbar(im, ax): ax.plot(xx[::refine, :].T, yy[::refine, :].T, 'k') ax.set_aspect('equal') - # Plot numerical solution ax = axes[1] im = ax.contourf(xx, yy, num, 40, cmap='jet') From 956923978a71ae2be0daa5c9520469bfd77d963b Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 15:41:55 +0200 Subject: [PATCH 102/133] codacy is complaining --- psydac/feec/polar/examples/poisson_2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 58d6e4ca9..c9e4ddeb1 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -438,7 +438,7 @@ def run_poisson_2d(*, test_case, ncells, degree, Sp = proj.change_matrix_basis(S) bp = proj.change_rhs_basis(b) alpha = 'None' - elif smooth_method == 'polar-std': + if smooth_method == 'polar-std': # Build standard polar map from control points of standard polar map n1, n2 = [W.nbasis for W in V0_h.spaces] rho = np.array([i1 / (n1 - 1) for i1 in range(n1)]) From b6184cf37116c7e15fec9bee567e486aa3c41cf1 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 5 May 2026 15:55:54 +0200 Subject: [PATCH 103/133] codacy is complaining --- psydac/feec/polar/examples/poisson_2d.py | 1 + 1 file changed, 1 insertion(+) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index c9e4ddeb1..0cb91e99d 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -433,6 +433,7 @@ def run_poisson_2d(*, test_case, ncells, degree, # stiffness/mass matrices and right-hand-side vector to C1 space t0 = time() bc = None + Sc = None if smooth_method == 'polar-spec': proj = C1Projector(F) Sp = proj.change_matrix_basis(S) From ba6bca6d064b5b247b2c6472463915053868afb0 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 8 May 2026 11:41:14 +0200 Subject: [PATCH 104/133] define a function compute_errors --- psydac/feec/polar/examples/poisson_2d.py | 59 +++++++++++++++--------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 0cb91e99d..476d48bdd 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -1,4 +1,6 @@ # coding: utf-8 +from dataclasses import dataclass + import sympy from mpi4py import MPI from time import time, sleep @@ -300,6 +302,12 @@ def codomain(self): def dtype(self): return float +@dataclass +class ErrorDiagnostics: + ref_l2: float + ref_h1: float + rel_l2: float + rel_h1: float ############################################################################### @@ -518,25 +526,7 @@ def run_poisson_2d(*, test_case, ncells, degree, phi = FemField(V0_h, coeffs=xsol) phi.coeffs.update_ghost_regions() - # L2 and H1 norms - ref_u0L2_2 = phi_ref.coeffs.inner(M.dot(phi_ref.coeffs)) # l2 norm of ref solution - ref_u0H1_semi2 = phi_ref.coeffs.inner(S.dot(phi_ref.coeffs)) - ref_u0L2 = np.sqrt(ref_u0L2_2) - ref_u0H1 = np.sqrt(ref_u0H1_semi2 + ref_u0L2_2) # H1 norm of ref solution - - # L2 and H1 errors - t0 = time() - phi_diff = phi_ref.coeffs - phi.coeffs - - err_l2_2 = phi_diff.inner(M.dot(phi_diff)) - err_h1_semi2 = phi_diff.inner(S.dot(phi_diff)) - - err_l2 = np.sqrt(err_l2_2) - err_h1 = np.sqrt(err_h1_semi2 + err_l2_2) - - rel_err_l2 = err_l2 / ref_u0L2 - rel_err_h1 = err_h1 / ref_u0H1 - + errors = compute_errors(phi, phi_ref, M, S) t1 = time() timing['diagnostics'] = t1 - t0 @@ -558,10 +548,10 @@ def run_poisson_2d(*, test_case, ncells, degree, print('> Degree :: [{p1},{p2}]'.format(p1=p1, p2=p2)) print('> Penalization alpha :: {alpha} '.format(alpha=alpha)) print( '> CG info :: ',info ) - print('> L2 norm solution :: {:.2e}'.format(ref_u0L2)) - print('> H1 norm solution :: {:.2e}'.format(ref_u0H1)) - print('> L2 error (relative) :: {:.2e}'.format(rel_err_l2)) - print('> H1 error (relative) :: {:.2e}'.format(rel_err_h1)) + print('> L2 norm solution :: {:.2e}'.format(errors.ref_l2)) + print('> H1 norm solution :: {:.2e}'.format(errors.ref_h1)) + print('> L2 error (relative) :: {:.2e}'.format(errors.rel_l2)) + print('> H1 error (relative) :: {:.2e}'.format(errors.rel_h1)) print('') print('> Assembly time :: {:.2e}'.format(timing['assembly'])) if smooth_method: @@ -586,6 +576,29 @@ def run_poisson_2d(*, test_case, ncells, degree, return locals() +def compute_errors(phi, phi_ref, M, S): + + # L2 and H1 norms + ref_l2_2 = phi_ref.coeffs.inner(M.dot(phi_ref.coeffs)) + ref_h1_semi2 = phi_ref.coeffs.inner(S.dot(phi_ref.coeffs)) + ref_l2 = np.sqrt(ref_l2_2) # L2 norm of ref solution + ref_h1 = np.sqrt(ref_h1_semi2 + ref_l2_2) # H1 norm of ref solution + + # L2 and H1 errors + phi_diff = phi_ref.coeffs - phi.coeffs + + err_l2_2 = phi_diff.inner(M.dot(phi_diff)) + err_h1_semi2 = phi_diff.inner(S.dot(phi_diff)) + err_l2 = np.sqrt(err_l2_2) + err_h1 = np.sqrt(err_h1_semi2 + err_l2_2) + + return ErrorDiagnostics( + ref_l2=ref_l2, + ref_h1=ref_h1, + rel_l2=err_l2 / ref_l2, + rel_h1=err_h1 / ref_h1, + ) + # ============================================================================== # Plotting # ============================================================================== From 47e1bac588dcd0b2013fb97e43130081bba94d64 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 8 May 2026 15:04:57 +0200 Subject: [PATCH 105/133] moved add_colorbar to utils --- psydac/feec/polar/examples/maxwell_2d.py | 19 +------------------ psydac/feec/polar/examples/utils_congapol.py | 8 ++++++++ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 94552145e..011c890ea 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -12,16 +12,9 @@ import matplotlib.pyplot as plt from psydac.fem.basic import FemField -from utils_congapol import print_map_polar_coeffs, check_regular_ring_map +from utils_congapol import print_map_polar_coeffs, check_regular_ring_map, add_colorbar -# from scipy.sparse.linalg import spsolve - -# def visdir_name(study): -# vdn = 'plots_{study}/' -# os.makedirs(vdn, exist_ok=True) -# return vdn - # ====================== TIME DISCRETIZATION ==================================# def step_faraday_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, **kwargs): @@ -34,8 +27,6 @@ def step_faraday_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, **kwargs): b -= dt * D1.dot(P1.dot(e)) -# e += 0 - def step_ampere_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, *, pc=None, tol=1e-7, verbose=False): """ Exactly integrate the semi-discrete Amperè equation over one time-step: @@ -125,14 +116,6 @@ def vect_norm_2(vv): # =========================== VISUALIZATION ===================================# -def add_colorbar(im, ax, **kwargs): - from mpl_toolkits.axes_grid1 import make_axes_locatable - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size=0.2, pad=0.3) - cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) - return cbar - - def plot_field_and_error(name, t, x, y, field_h, field_ex, *gridlines, only_field=True): # import matplotlib.pyplot as plt if only_field: diff --git a/psydac/feec/polar/examples/utils_congapol.py b/psydac/feec/polar/examples/utils_congapol.py index 32ad2b1ff..977d78203 100644 --- a/psydac/feec/polar/examples/utils_congapol.py +++ b/psydac/feec/polar/examples/utils_congapol.py @@ -109,3 +109,11 @@ def check_regular_ring_map(map_discrete, verbose=False): print(' - regularity error on 1st ring:', regularity_error) print(' - first ring radius error:', first_ring_radius_error) print() + +def add_colorbar(im, ax, **kwargs): + from mpl_toolkits.axes_grid1 import make_axes_locatable + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size=0.2, pad=0.3) + cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) + return cbar + From 7ba6f43eea55d2b6c50d2fac6490aff2fc0dfed2 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 12 May 2026 13:56:36 +0200 Subject: [PATCH 106/133] separated plotting objects creation --- psydac/feec/polar/examples/maxwell_2d.py | 211 +++++++++++++---------- 1 file changed, 123 insertions(+), 88 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 011c890ea..f3f60e216 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -124,8 +124,6 @@ def plot_field_and_error(name, t, x, y, field_h, field_ex, *gridlines, only_fiel else: fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(15, 6)) axes = [ax0, ax1] - print(type(x)) - print(type(y)) im0 = ax0.contourf(x, y, field_h, 50) ax0.set_title(r'${0}_h$'.format(name)) if not only_field: @@ -374,7 +372,73 @@ def l2_norm_of(f_log): f2_with_det = lambda eta1, eta2: f_log(eta1, eta2) ** 2 * np.sqrt(F.metric_det(eta1, eta2)) return np.sqrt(derham_h.V0.integral(f2_with_det)) + def build_plot_context(): + """ + Build all serial objects needed for plotting fields. + + This is used by: + - study='L2_proj' + - initial Maxwell plots + - final Maxwell plots + """ + if mpi_rank != 0: + return None + + if use_spline_mapping: + domain_h_serial = discretize(domain, filename='geo.h5') + F_serial = [*domain_h_serial.mappings.values()].pop() + else: + domain_h_serial = discretize(domain, ncells=ncells, periodic=[False, True]) + F_serial = F + + derham_h_serial = discretize(derham, domain_h_serial, degree=degree) + V0_s, V1_s, V2_s = derham_h_serial.spaces + V1_sx, V1_sy = V1_s.spaces + + grid_x1 = V0_s.breaks[0].copy() + grid_x2 = V0_s.breaks[1].copy() + + # Fix division by zero without taking care of the limit as s --> 0 + grid_x1[0] = 1e-20 + + N = 5 + x1 = refine_array_1d(grid_x1, N) + x2 = refine_array_1d(grid_x2, N) + + x1, x2 = np.meshgrid(x1, x2, indexing='ij') + + x = np.empty_like(x1) + y = np.empty_like(x1) + + for i in range(x1.shape[0]): + for j in range(x1.shape[1]): + x[i, j], y[i, j] = F_serial(x1[i, j], x2[i, j]) + + gridlines_x1 = (x[:, ::N], y[:, ::N]) + gridlines_x2 = (x[::N, :].T, y[::N, :].T) + gridlines = (gridlines_x1, gridlines_x2) + + return { + 'domain_h_serial': domain_h_serial, + 'derham_h_serial': derham_h_serial, + 'V0_s': V0_s, + 'V1_s': V1_s, + 'V2_s': V2_s, + 'V1_sx': V1_sx, + 'V1_sy': V1_sy, + 'F_serial': F_serial, + 'x1': x1, + 'x2': x2, + 'x': x, + 'y': y, + 'gridlines': gridlines, + } + def run_study_L2_proj(): + """ + Only for serial runs + + """ omega = 4 print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') xs, ys = domain.coordinates @@ -396,7 +460,6 @@ def run_study_L2_proj(): l = LinearForm(v, integral(domain, dot(f_phys, v))) lh = discretize(l, domain_h, V1) tilde_f = lh.assemble() - # exit() # create fields and point to coeffs @@ -440,14 +503,6 @@ def run_study_L2_proj(): cst_wo_det = lambda x1, x2: 1 cst_wi_det = lambda x1, x2: 1 * np.sqrt(F.metric_det(x1,x2)) - mydet = lambda x1, x2: x1**2 - - # for point1 in [0.1, 0.01, 0.001]: - # for point2 in [0.1, 0.01, 0.001]: - # print(f'x1 = {x1}, x2 = {x2}, det_err = {F.metric_det(x1,x2)-mydet(x1,x2)}') - # print(f'a = {F.metric_det(.1,.1), cst_wo_det(.1,.1), cst_wi_det(.1,.1)}') - # print(f'b = {F.metric_det(.01,.01), cst_wo_det(.01,.01), cst_wi_det(.01,.01)}') - # print(f'c = {F.metric_det(.001,.001), cst_wo_det(.001,.001), cst_wi_det(.001,.001)}') int_wo_det = derham_h.V0.integral(cst_wo_det) int_wi_det = derham_h.V0.integral(cst_wi_det) print('V0 - integral of 1 (no det): {:.2e}'.format(int_wo_det)) @@ -464,47 +519,52 @@ def run_study_L2_proj(): if plot_time <= 0: return locals() + plot_data = build_plot_context() + if plot_data is None: + return locals() + + x1 = plot_data['x1'] + x2 = plot_data['x2'] + x = plot_data['x'] + y = plot_data['y'] + gridlines = plot_data['gridlines'] + # plot - # fx_values = np.empty_like(x1) - # fy_values = np.empty_like(x1) - # fx_filter_values = np.empty_like(x1) - # fy_filter_values = np.empty_like(x1) - # - # fx_ex_values = np.empty_like(x1) - # fy_ex_values = np.empty_like(x1) - # - # for i, x1i in enumerate(x1[:, 0]): - # for j, x2j in enumerate(x2[0, :]): - # - # - # xij, yij = F(x1i, x2j) - # fx_values[i, j], fy_values[i, j] = \ - # push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) - # fx_filter_values[i, j], fy_filter_values[i, j] = \ - # push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) - # fx_ex_values[i, j], fy_ex_values[i, j] = \ - # fx_call(xij, yij), fy_call(xij, yij) - - # fig2 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) - # fig2.savefig(f'{visdir}/fx_{rp_str}.png') - # - # fig3 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) - # fig3.savefig(f'{visdir}/fy_{rp_str}.png') - # - # print('done: showing fh') - # - # fig2.clf() - # fig2 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) - # fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') - # - # fig3.clf() - # fig3 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) - # fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') + fx_values = np.empty_like(x1) + fy_values = np.empty_like(x1) + fx_filter_values = np.empty_like(x1) + fy_filter_values = np.empty_like(x1) + fx_ex_values = np.empty_like(x1) + fy_ex_values = np.empty_like(x1) - print('done: showing fh_filter') + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + xij, yij = F(x1i, x2j) + fx_values[i, j], fy_values[i, j] = \ + push_2d_hcurl(fh.fields[0], fh.fields[1], x1i, x2j, F) + fx_filter_values[i, j], fy_filter_values[i, j] = \ + push_2d_hcurl(fh_filter.fields[0], fh_filter.fields[1], x1i, x2j, F) + fx_ex_values[i, j], fy_ex_values[i, j] = \ + fx_call(xij, yij), fy_call(xij, yij) + + fig1 = plot_field_and_error(r'f^x', 0, x, y, fx_values, fx_ex_values, *gridlines) + #fig2.savefig(f'{visdir}/fx_{rp_str}.png') + + fig2 = plot_field_and_error(r'f^y', 0, x, y, fy_values, fy_ex_values, *gridlines) + #fig3.savefig(f'{visdir}/fy_{rp_str}.png') + + print('done: showing fh') + fig3 = plot_field_and_error(r'f^x filter', 0, x, y, fx_filter_values, fx_ex_values, *gridlines) + #fig2.savefig(f'{visdir}/fx_filter_{rp_str}.png') + + fig4 = plot_field_and_error(r'f^y filter', 0, x, y, fy_filter_values, fy_ex_values, *gridlines) + #fig3.savefig(f'{visdir}/fy_filter_{rp_str}.png') + + print('done: showing fh_filter') return locals() # ============================================================================== @@ -571,6 +631,10 @@ def run_study_L2_proj(): Pi0, Pi1, Pi2 = derham_h.projectors(nquads=[degree[0] + 10, degree[1] + 10]) + if study_L2_proj: + run_study_L2_proj() + return locals() + # Geometric Projectors # Time integration setup @@ -658,11 +722,6 @@ def run_study_L2_proj(): # VISUALIZATION SETUP # ============================================================================== - - if study_L2_proj: - run_study_L2_proj() - return locals() - # print( x2) # def plot_fields_along_s(tstr): # , j0=0, j1=0): @@ -709,48 +768,24 @@ def run_study_L2_proj(): # fig1.tight_layout() # fig1.show() - # ... # Plot initial conditions - # TODO: improve - if mpi_rank == 0: - if use_spline_mapping: - geometry = Geometry(filename='geo.h5') - domain_h_serial = discretize(domain, filename='geo.h5') - F_serial = [*domain_h_serial.mappings.values()].pop() - else: - domain_h_serial = discretize(domain, ncells=ncells, periodic=[False, True]) - F_serial = F + plot_data = build_plot_context() + if plot_data is not None: + V1_sx = plot_data['V1_sx'] + V1_sy = plot_data['V1_sy'] + V2_s = plot_data['V2_s'] + F_serial = plot_data['F_serial'] + x1 = plot_data['x1'] + x2 = plot_data['x2'] + x = plot_data['x'] + y = plot_data['y'] + gridlines = plot_data['gridlines'] - derham_h_serial = discretize(derham, domain_h_serial, degree=degree) - V0_s, V1_s, V2_s = derham_h_serial.spaces - V1_sx, V1_sy = V1_s.spaces Ex_serial, = V1_sx.import_fields('Ex.h5', 'Ex_field') Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') B_serial, = V2_s.import_fields('B.h5', 'B_field') - grid_x1 = V0_s.breaks[0] - grid_x2 = V0_s.breaks[1] - # Fix division by zero without taking care of the limit as s --> 0 - grid_x1[0] = 1e-20 - - # Very fine grids for evaluation of solution - N = 5 - x1 = refine_array_1d(grid_x1, N) - x2 = refine_array_1d(grid_x2, N) - - x1, x2 = np.meshgrid(x1, x2, indexing='ij') - x = np.empty_like(x1) - y = np.empty_like(x1) - - for i in range(x1.shape[0]): - for j in range(x1.shape[1]): - x[i, j], y[i, j] = F_serial(x1[i, j], x2[i, j]) - - gridlines_x1 = (x[:, ::N], y[:, ::N]) - gridlines_x2 = (x[::N, :].T, y[::N, :].T) - gridlines = (gridlines_x1, gridlines_x2) - Ex_ex_values = np.empty_like(x1) Ey_ex_values = np.empty_like(x1) Bz_ex_values = np.empty_like(x1) @@ -955,7 +990,7 @@ def Strang_update(dtau): V1y.export_fields('Ey_final.h5', Ey_field=Ey_field) V2.export_fields('B_final.h5', B_field=B_field) - if mpi_rank == 0: + if plot_data is not None: Ex_serial, = V1_sx.import_fields('Ex_final.h5', 'Ex_field') Ey_serial, = V1_sy.import_fields('Ey_final.h5', 'Ey_field') B_serial, = V2_s.import_fields('B_final.h5', 'B_field') From cde57dba49e14ae9965657a384396f1655b768bf Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 12 May 2026 14:13:01 +0200 Subject: [PATCH 107/133] remove some old comments --- psydac/feec/polar/examples/maxwell_2d.py | 83 +++--------------------- 1 file changed, 10 insertions(+), 73 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index f3f60e216..7f40b49d0 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -77,18 +77,10 @@ def vect_norm_2(vv): spectral_rho = 1 conv = False CC_m = dC_m @ C_m - # print(f'type(CC_m) = {type(CC_m)}') - # print('going through CC_m.tmp_vectors...') - # for tv in CC_m.tmp_vectors: - # print(f'type(tv) = {type(tv)}') while not (conv or ncfl > max_ncfl): - # print(f'... ') - # print(f'#1 type(vv) = {type(vv)}') ncfl += 1 vv *= (1. / norm_vv) - # print(f'#2 type(vv) = {type(vv)}') CC_m.dot(vv.copy(), out=vv) - # print(f'#3 type(vv) = {type(vv)}') norm_vv = vect_norm_2(vv) old_spectral_rho = spectral_rho spectral_rho = norm_vv.copy() # copy ?? @@ -117,7 +109,6 @@ def vect_norm_2(vv): # =========================== VISUALIZATION ===================================# def plot_field_and_error(name, t, x, y, field_h, field_ex, *gridlines, only_field=True): - # import matplotlib.pyplot as plt if only_field: fig, ax0 = plt.subplots(1, 1, figsize=(7, 6)) axes = [ax0] @@ -130,7 +121,6 @@ def plot_field_and_error(name, t, x, y, field_h, field_ex, *gridlines, only_fiel im1 = ax1.contourf(x, y, field_ex - field_h, 50) ax1.set_title(r'${0} - {0}_h$'.format(name)) for ax in axes: - # if not only_field: ax.plot(*gridlines[0], color='k') ax.plot(*gridlines[1], color='k') ax.set_xlabel('x', fontsize=14) @@ -160,10 +150,6 @@ def update_plot(fig, t, x, y, field_h, field_ex): def plot_curve_along_s(name, s_str, time_str, theta0, s, curve_h, curve_ref=None, left_s=None, left_curve_h=None, left_curve_ref=None, ): - # import matplotlib.pyplot as plt - - # t = np.arange(0.0, 2.0, 0.01) - # s = 1 + np.sin(2 * np.pi * t) fig, ax = plt.subplots() ax.plot(s, curve_h, label=f'{name}_h') @@ -176,7 +162,6 @@ def plot_curve_along_s(name, s_str, time_str, theta0, ax.set(xlabel=s_str, title=f'field {name} along {s_str} for theta={theta0}, at {time_str}') ax.legend() - # ax.grid() return fig @@ -643,8 +628,8 @@ def run_study_L2_proj(): t = 0 if study_maxwell: - # Callable exact fields + # Callable exact fields Ex_ex = lambda t: (lambda x, y, t0=t: Ex_ex_t(t0, x, y)) Ey_ex = lambda t: (lambda x, y, t0=t: Ey_ex_t(t0, x, y)) Bz_ex = lambda t: (lambda x, y, t0=t: Bz_ex_t(t0, x, y)) @@ -659,7 +644,6 @@ def run_study_L2_proj(): e = E_log.coeffs b = B_log.coeffs - if study == 'maxwell_wave': D1.dot(e, out=b) @@ -667,41 +651,28 @@ def run_study_L2_proj(): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - V1x, V1y = V1.spaces #call them s, theta - Ex_field = FemField(V1x, coeffs=e[0]) - Ey_field = FemField(V1y, coeffs=e[1]) + V1_s, V1_theta = V1.spaces + Ex_field = FemField(V1_s, coeffs=e[0]) + Ey_field = FemField(V1_theta, coeffs=e[1]) B_field = FemField(V2, coeffs=b) - V1x.export_fields('Ex.h5', Ex_field=Ex_field) - V1y.export_fields('Ey.h5', Ey_field=Ey_field) + V1_s.export_fields('Ex.h5', Ex_field=Ex_field) + V1_theta.export_fields('Ey.h5', Ey_field=Ey_field) V2.export_fields('B.h5', B_field=B_field) if use_scipy: print(" -------------- SCIPY operators ------------ ") - - # M1_sp = M1.tosparse() - # M2_sp = M2.tosparse() conga_curl_sp = (D1 @ P1).tosparse() step_faraday_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, strong=True, store_M1inv=False) step_ampere_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, M1=M1, M2=M2, strong=False) - # M1_inv = spsolve(M1) else: M1_inv = inverse(M1, 'cg', verbose=verbose, tol=tol) step_ampere_2d = M1_inv @ P1_T @ D1_T @ M2 step_faraday_2d = D1 @ P1 - # Time step size - # dx_min_1 = np.sqrt(np.diff(grid_x, axis=0)**2 + np.diff(grid_y, axis=0)**2).min() - # dx_min_2 = np.sqrt(np.diff(grid_x, axis=1)**2 + np.diff(grid_y, axis=1)**2).min() - # dx_min = min(dx_min_1, dx_min_2) - # dt = Cp * dx_min / c - # only use radial grid-step because angular goes to zero near pole - # dt = Cp * dx_min_1 / c - # print(f'dt = {dt}') - Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2, tau=tend, light_c=1) if plot_time > 0: @@ -722,8 +693,6 @@ def run_study_L2_proj(): # VISUALIZATION SETUP # ============================================================================== - # print( x2) - # def plot_fields_along_s(tstr): # , j0=0, j1=0): # # # if j1 is None: @@ -755,19 +724,6 @@ def run_study_L2_proj(): # Prepare plots if plot_interval: - # Plot physical grid and mapping's metric determinant - # fig1, ax1 = plt.subplots(1, 1, figsize=(8, 6)) - # im = ax1.contourf(x, y, np.sqrt(F.metric_det(x1, x2))) - # add_colorbar(im, ax1, label=r'Metric determinant $\sqrt{g}$ of mapping $F$') - # ax1.plot(*gridlines_x1, color='k') - # ax1.plot(*gridlines_x2, color='k') - # ax1.set_title('Mapped grid of {} x {} cells'.format(ncells, ncells)) - # ax1.set_xlabel('x', fontsize=14) - # ax1.set_ylabel('y', fontsize=14) - # ax1.set_aspect('equal') - # fig1.tight_layout() - # fig1.show() - # Plot initial conditions plot_data = build_plot_context() @@ -829,8 +785,6 @@ def run_study_L2_proj(): plt.close(fig) # fig.clf() - # fig3.show() - # Magnetic field, z component fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) if show_figs: @@ -904,8 +858,6 @@ def run_study_L2_proj(): print('L2 norm of rel. error on Ey(t,x,y) at initial time: {:.2e}'.format(error_l2_Ey)) print('L2 norm of rel. error on Bz(t,x,y) at initial time: {:.2e}'.format(error_l2_Bz)) - # input('\nSimulation setup done... press any key to start') - # ============================================================================== # SOLUTION # ============================================================================== @@ -943,19 +895,6 @@ def Strang_update(dtau): Strang_update(dt) - # # Strang splitting, 2nd order - # # b := b - dt/2 * curl e - # step_faraday_2d.dot(e, out = db) - # b.mul_iadd(- dt/2, db) - - # # e := e + dt * curl b - # step_ampere_2d.dot(b, out = de) - # e.mul_iadd(dt, de) - - # # b := b - dt/2 * curl e - # step_faraday_2d.dot(e, out = db) - # b.mul_iadd(- dt/2, db) - elif splitting_order == 4: Strang_update(dt * gamma_1) @@ -983,11 +922,11 @@ def Strang_update(dtau): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - Ex_field = FemField(V1x, coeffs=e[0]) - Ey_field = FemField(V1y, coeffs=e[1]) + Ex_field = FemField(V1_s, coeffs=e[0]) + Ey_field = FemField(V1_theta, coeffs=e[1]) B_field = FemField(V2, coeffs=b) - V1x.export_fields('Ex_final.h5', Ex_field=Ex_field) - V1y.export_fields('Ey_final.h5', Ey_field=Ey_field) + V1_s.export_fields('Ex_final.h5', Ex_field=Ex_field) + V1_theta.export_fields('Ey_final.h5', Ey_field=Ey_field) V2.export_fields('B_final.h5', B_field=B_field) if plot_data is not None: @@ -1010,8 +949,6 @@ def Strang_update(dtau): Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - # ... - # Error at final time error_Ex = abs(Ex_ex_values - Ex_values).max() error_Ey = abs(Ey_ex_values - Ey_values).max() From d456677b08db90fa848aae36591a3527ce43a116 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 12 May 2026 14:25:39 +0200 Subject: [PATCH 108/133] uncomment plot_fields_along_s --- psydac/feec/polar/examples/maxwell_2d.py | 55 +++++++++++------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 7f40b49d0..b8705fc33 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -693,33 +693,30 @@ def run_study_L2_proj(): # VISUALIZATION SETUP # ============================================================================== - # def plot_fields_along_s(tstr): # , j0=0, j1=0): - # - # # if j1 is None: - # j0 = 0 - # j1 = x2.shape[1] // 2 - # theta0 = x2[0, j0] - # theta1 = x2[0, j1] - # name = 'Ex' - # fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', - # x1[:, j0], Ex_values[:, j0], Ex_ex_values[:, j0], - # -x1[:, j1], Ex_values[:, j1], Ex_ex_values[:, j1]) - # # fig_line = plot_curve_along_s(name, 's', tstr, -x1[:,j1], f'{theta1}', Ex_values[:,j1], Ex_ex_values[:,j1]) - # fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - # # plt.close(fig_line) - # fig_line.clf() - # name = 'Ey' - # fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', - # x1[:, j0], Ey_values[:, j0], Ey_ex_values[:, j0], - # -x1[:, j1], Ey_values[:, j1], Ey_ex_values[:, j1]) - # fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - # fig_line.clf() - # name = 'Bz' - # fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', - # x1[:, j0], Bz_values[:, j0], Bz_ex_values[:, j0], - # -x1[:, j1], Bz_values[:, j1], Bz_ex_values[:, j1]) - # fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - # plt.close(fig_line) + def plot_fields_along_s(tstr): + + j0 = 0 + j1 = x2.shape[1] // 2 + theta0 = x2[0, j0] + theta1 = x2[0, j1] + name = 'Ex' + fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + x1[:, j0], Ex_values[:, j0], Ex_ex_values[:, j0], + -x1[:, j1], Ex_values[:, j1], Ex_ex_values[:, j1]) + fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + fig_line.clf() + name = 'Ey' + fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + x1[:, j0], Ey_values[:, j0], Ey_ex_values[:, j0], + -x1[:, j1], Ey_values[:, j1], Ey_ex_values[:, j1]) + fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + fig_line.clf() + name = 'Bz' + fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', + x1[:, j0], Bz_values[:, j0], Bz_ex_values[:, j0], + -x1[:, j1], Bz_values[:, j1], Bz_ex_values[:, j1]) + fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') + plt.close(fig_line) # Prepare plots if plot_interval: @@ -765,7 +762,7 @@ def run_study_L2_proj(): Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) # fields along s for fixed theta - #plot_fields_along_s(tstr='t0') # , j0=0, j1=ncells[1]//2) + plot_fields_along_s(tstr='t0') # Electric field, x component fig = plot_field_and_error(r'E^x', 0, x, y, Ex_values, Ex_ex_values, *gridlines) @@ -1022,7 +1019,7 @@ def Strang_update(dtau): fig1.tight_layout() # fields along s, final time - # plot_fields_along_s(tstr='T') # , j0=0, j1=ncells[1]//2) + plot_fields_along_s(tstr='T') # Electric field, x component fig = plot_field_and_error(r'E^x', tend, x, y, Ex_values, Ex_ex_values, *gridlines) From 70ee0b3f05616d83addecefdeaa5e0e8d18a4d49 Mon Sep 17 00:00:00 2001 From: Martin Campos Pinto Date: Tue, 12 May 2026 17:40:42 +0200 Subject: [PATCH 109/133] remove some old todos --- psydac/feec/polar/examples/maxwell_2d.py | 10 ++-------- psydac/feec/polar/examples/waveTE.py | 2 -- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index f3f60e216..d538e8444 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -1,6 +1,3 @@ -# TODO: Test weak divergence of E = 0 and strong divergence of B = 0 -# NB: Use L2 projection for the initial condition of E to test the weak div of E - """ Solve the Transverse Electric Time dependent Maxwell Problem on an analytical disk domain. @@ -307,8 +304,6 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, check_regular_ring_map(map_discrete) - # STOP: CHECK that mapping is consistent with paper ? - # Create symbolic mapping with callable mapping as spline mapping = Mapping('M', dim=2) mapping.set_callable_mapping(map_discrete) @@ -625,7 +620,7 @@ def run_study_L2_proj(): M1_raw = a1_h.assemble() M2_raw = a2_h.assemble() - # regularization of M1 and M2 so that they are bounded and invertible: TODO try various factors (htheta*hs), this one probably too small but Maxwell simlulation was good already :) + # regularization of M1 and M2 so that they are bounded and invertible: M1 = (htheta * hs) * (I1 - P1.T) @ (I1 - P1) + P1.T @ M1_raw @ P1 M2 = (htheta * hs) * (I2 - P2.T) @ (I2 - P2) + P2.T @ M2_raw @ P2 @@ -937,8 +932,7 @@ def Strang_update(dtau): for ts in range(1, nsteps + 1): print(f'step = {ts}/{nsteps}') - # TODO: allow for high-order splitting - + if splitting_order == 2: Strang_update(dt) diff --git a/psydac/feec/polar/examples/waveTE.py b/psydac/feec/polar/examples/waveTE.py index 6a7b82495..ab379c2e6 100644 --- a/psydac/feec/polar/examples/waveTE.py +++ b/psydac/feec/polar/examples/waveTE.py @@ -61,8 +61,6 @@ def Bz_ex(self, t, x, y): def main(): - # TODO: update with D_shift - # Physical domain is rectangle [0, R] x [0, 2pi] R = 2.0 From f41754d26afd9dc774e56c6ef0d0f1655cdbc63a Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 10:41:52 +0200 Subject: [PATCH 110/133] remove add_colorbar from Poisson --- psydac/feec/polar/examples/poisson_2d.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 476d48bdd..8aab9d6bb 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -4,7 +4,6 @@ import sympy from mpi4py import MPI from time import time, sleep -from mpl_toolkits.axes_grid1 import make_axes_locatable import numpy as np import matplotlib.pyplot as plt @@ -18,6 +17,7 @@ from sympde.topology.mapping import Mapping from psydac.api.discretization import discretize +from psydac.feec.polar.examples.utils_congapol import add_colorbar from psydac.linalg.stencil import StencilVector, StencilMatrix from psydac.linalg.basic import LinearOperator from psydac.linalg.solvers import inverse @@ -638,12 +638,6 @@ def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=10 xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] - def add_colorbar(im, ax): - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size=0.2, pad=0.2) - cbar = ax.get_figure().colorbar(im, cax=cax) - return cbar - # Create figure with 3 subplots: # 1. exact solution on exact domain # 2. numerical solution on mapped domain (analytical or spline) From 0659eb7ec6cadcb2ee7f6022ad3327da0a375a31 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 11:16:01 +0200 Subject: [PATCH 111/133] create space V only when use_spline_mapping --- psydac/feec/polar/examples/poisson_2d.py | 46 ++++++++++++------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 8aab9d6bb..71074cc09 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -350,21 +350,22 @@ def run_poisson_2d(*, test_case, ncells, degree, ne1, ne2 = ncells p1, p2 = degree - # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# + periodic = [False, True] - # Create uniform grid - grid_1 = np.linspace(*model.domain.bounds1, num=ne1 + 1) - grid_2 = np.linspace(*model.domain.bounds2, num=ne2 + 1) + # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - periodic = [False, True] + if use_spline_mapping: + # Create uniform grid + grid_1 = np.linspace(*model.domain.bounds1, num=ne1 + 1) + grid_2 = np.linspace(*model.domain.bounds2, num=ne2 + 1) - # Create 1D finite element spaces - V1 = SplineSpace(p1, grid=grid_1, periodic=periodic[0]) - V2 = SplineSpace(p2, grid=grid_2, periodic=periodic[1]) + # Create 1D finite element spaces + V1 = SplineSpace(p1, grid=grid_1, periodic=periodic[0]) + V2 = SplineSpace(p2, grid=grid_2, periodic=periodic[1]) - # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, periodic , comm=mpi_comm) - V = TensorFemSpace(domain_decomposition, V1, V2) + # Create 2D tensor product finite element space + domain_decomposition = DomainDecomposition(ncells, periodic , comm=mpi_comm) + V = TensorFemSpace(domain_decomposition, V1, V2) # ==================== MAPPING & PHYSICAL DOMAIN ==============================# #TODO: maybe define a parent class Model @@ -566,13 +567,13 @@ def run_poisson_2d(*, test_case, ncells, degree, # =============================== VISUALIZATION ===============================# N = 10 - V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) + V0_h.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) # Non-master processes stop here if mpi_rank != 0: return - plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=N) + plot_solution(use_spline_mapping, model, ncells, periodic, V0_h, refine=N) return locals() @@ -602,16 +603,21 @@ def compute_errors(phi, phi_ref, M, S): # ============================================================================== # Plotting # ============================================================================== -def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=10): +def plot_solution(use_spline_mapping, model, ncells, periodic, V0_h, refine=10): + """ + Plot exact solution, numerical solution and error + """ if use_spline_mapping: geometry = Geometry(filename='geo.h5', comm=MPI.COMM_SELF) map_discrete = [*geometry.mappings.values()].pop() Vnew = map_discrete.space + map_plot = map_discrete else: dd = DomainDecomposition(ncells, periodic, comm=MPI.COMM_SELF) - Vnew = TensorFemSpace(dd, V1, V2) + Vnew = TensorFemSpace(dd, *V0_h.spaces) + map_plot = model.mapping.get_callable_mapping() # Import solution vector into new serial field phi, = Vnew.import_fields('fields.h5', 'phi') @@ -621,19 +627,15 @@ def plot_solution(use_spline_mapping, model, ncells, periodic, V1, V2, refine=10 # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = Vnew.local_domain + V1_plot, V2_plot = Vnew.spaces - eta1 = refine_array_1d(V1.breaks[sk1:ek1 + 2], refine) - eta2 = refine_array_1d(V2.breaks[sk2:ek2 + 2], refine) + eta1 = refine_array_1d(V1_plot.breaks[sk1:ek1 + 2], refine) + eta2 = refine_array_1d(V2_plot.breaks[sk2:ek2 + 2], refine) num = np.array([[phi(e1, e2) for e2 in eta2] for e1 in eta1]) ex = np.array([[phi_e(e1, e2) for e2 in eta2] for e1 in eta1]) err = num - ex # Compute physical coordinates of logical grid - if use_spline_mapping: - map_plot = map_discrete - else: - map_plot = model.mapping.get_callable_mapping() - pcoords = np.array([[map_plot(e1, e2) for e2 in eta2] for e1 in eta1]) xx = pcoords[:, :, 0] yy = pcoords[:, :, 1] From 76c0bab6e8eda3dbb58afaf5eeda5168ea84b710 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 12:12:37 +0200 Subject: [PATCH 112/133] added docstring to Poisson --- psydac/feec/polar/examples/poisson_2d.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 71074cc09..93d235e78 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -1,4 +1,15 @@ -# coding: utf-8 +""" +Solve manufactured 2D Poisson problems on polar mapped domains. + +The script builds analytical or spline-approximated polar domains (disk, target or Czarny), +assembles and solves the scalar Poisson system, applies optional treatments of the polar singularity +(C0/C1 CONGA or C1 polar projectors), solves the resulting linear system, +computes L2/H1 errors against the exact solution, and plots the result. + +Example of run: +mpirun -n 2 python poisson_2d.py -S -n 8 10 -d 2 2 -t disk -D 0.2 -m 'C0conga' +""" + from dataclasses import dataclass import sympy @@ -35,7 +46,6 @@ from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C1PolarProjection_V0 -# backend = PSYDAC_BACKENDS['numba'] backend = PSYDAC_BACKENDS['python'] @@ -795,6 +805,3 @@ def parse_input_arguments(): import matplotlib.pyplot as plt plt.show() - -## example of run: -# mpirun -n 2 python poisson_2d.py -S -n 8 10 -d 2 2 -t disk -D 0.2 -m 'C0conga' From 27dbf60a20f822ddbb38033cd69ef921be69dab1 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 12:37:15 +0200 Subject: [PATCH 113/133] remove some unused variables --- psydac/feec/polar/examples/analyticalTE.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/psydac/feec/polar/examples/analyticalTE.py b/psydac/feec/polar/examples/analyticalTE.py index 72ec3d816..d9ef46d0d 100644 --- a/psydac/feec/polar/examples/analyticalTE.py +++ b/psydac/feec/polar/examples/analyticalTE.py @@ -39,7 +39,7 @@ class CircularCavitySolution: """ - def __init__(self, R, c, m, n, D=0, scale=1, variables='log'): + def __init__(self, R, c, m, n, D=0, scale=1): from numpy import pi from scipy.special import jnp_zeros pnm = jnp_zeros(n, m)[-1] @@ -57,8 +57,6 @@ def __init__(self, R, c, m, n, D=0, scale=1, variables='log'): self._R = R assert 0 <= D < .5 self._D = D - # assert variables in ['log', 'phys'] - # self._logical = (variables == 'log') # Exact solutions for electric and magnetic field with polar parametrization of disk domain def Es_ex(self, t, s, theta): @@ -157,9 +155,11 @@ def Bz_ex(self, t, x, y): def main(): - # TODO: update with D_shift + """ + This function is not currently used. It is kept for possible future development. + """ - # Physical domain is rectangle [0, R] x [0, 2pi] + # Logical domain is rectangle [0, R] x [0, 2pi] R = 2.0 # Speed of light equal c and scaling of the fields by a scale factor @@ -209,7 +209,6 @@ def main(): Ey_values = np.empty_like(rho) B_values = np.empty_like(rho) - valerr = 0 for i, x1i in enumerate(rho[:, 0]): for j, x2j in enumerate(theta[0, :]): Ex_values[i, j], Ey_values[i, j] = \ From 81cb257218157ddf5999cf71adc2a8fc1f287825 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 13:08:26 +0200 Subject: [PATCH 114/133] remove some unused things --- psydac/feec/polar/examples/analyticalTE.py | 22 ++++++-------------- psydac/feec/polar/examples/waveTE.py | 24 +++------------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/psydac/feec/polar/examples/analyticalTE.py b/psydac/feec/polar/examples/analyticalTE.py index d9ef46d0d..03be4702f 100644 --- a/psydac/feec/polar/examples/analyticalTE.py +++ b/psydac/feec/polar/examples/analyticalTE.py @@ -1,19 +1,12 @@ from sympde.topology import Square from sympde.topology.analytical_mapping import PolarMapping + +from psydac.api.tests.test_api_feec_2d import add_colorbar from psydac.feec.pull_push import push_2d_hcurl, push_2d_l2 from numpy import pi import numpy as np import matplotlib.pyplot as plt - -def add_colorbar(im, ax, **kwargs): - from mpl_toolkits.axes_grid1 import make_axes_locatable - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size=0.2, pad=0.3) - cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) - return cbar - - class CircularCavitySolution: """ Time-harmonic solution of Maxwell's equations in a disk-like domain with @@ -110,10 +103,9 @@ def B_ex(self, t, s, theta, s_factor=True): return val # The magnitude of B is approximately equal to scale / 3 - def Bt_ex(self, t, s, theta): + def dB_dt_ex(self, t, s, theta): ''' = dB/dt - todo: change the name ''' from numpy import cos, sin from scipy.special import jv @@ -132,8 +124,6 @@ def get_radius_angle(self, x, y): from numpy import sqrt, arctan2 # , sin, cos r = sqrt(x * x + y * y) alpha = arctan2(y, x) - # print(f'r*np.cos(alpha) - x = {r*np.cos(alpha) - x}') - # print(f'r*np.sin(alpha) - y = {r*np.sin(alpha) - y}') return r, alpha def Ex_ex(self, t, x, y): @@ -178,7 +168,7 @@ def main(): Es_ex = exact_solution.Es_ex Et_ex = exact_solution.Et_ex B_ex = exact_solution.B_ex - Bt_ex = exact_solution.Bt_ex + dB_dt_ex = exact_solution.dB_dt_ex # Logical domain: [0, R] x [0, 2pi] logical_domain = Square('Omega', bounds1=[0, R], bounds2=[0, 2 * pi]) @@ -195,7 +185,7 @@ def main(): Es = lambda x, y: Es_ex(t, x, y) Et = lambda x, y: Et_ex(t, x, y) B = lambda x, y: B_ex(t, x, y) - Bt = lambda x, y: Bt_ex(t, x, y) + dB_dt = lambda x, y: dB_dt_ex(t, x, y) # Plot of fields N = 100 @@ -253,7 +243,7 @@ def main(): Ex_values[i, j], Ey_values[i, j] = \ push_2d_hcurl(Es, Et, x1_ij, x2_ij, F) - Bt_values[i, j] = push_2d_l2(Bt, x1_ij, x2_ij, F) + Bt_values[i, j] = push_2d_l2(dB_dt, x1_ij, x2_ij, F) fig, axs = plt.subplots(2, 3, figsize=(15, 15)) im1 = axs[0, 0].contourf(x, y, np.sqrt(Ex_values ** 2 + Ey_values ** 2)) diff --git a/psydac/feec/polar/examples/waveTE.py b/psydac/feec/polar/examples/waveTE.py index ab379c2e6..ccd17d2c2 100644 --- a/psydac/feec/polar/examples/waveTE.py +++ b/psydac/feec/polar/examples/waveTE.py @@ -1,41 +1,24 @@ from sympde.topology import Square from sympde.topology.analytical_mapping import PolarMapping + +from psydac.feec.polar.examples.utils_congapol import add_colorbar from psydac.feec.pull_push import push_2d_hcurl, push_2d_l2 from numpy import pi import numpy as np import matplotlib.pyplot as plt - -def add_colorbar(im, ax, **kwargs): - from mpl_toolkits.axes_grid1 import make_axes_locatable - divider = make_axes_locatable(ax) - cax = divider.append_axes("right", size=0.2, pad=0.3) - cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) - return cbar - - class GaussianSolution: """ just a Gaussian field with B = curl E """ def __init__(self, sigma, x0, y0, scale=1): - from numpy import pi - from scipy.special import jnp_zeros self.x0 = x0 self.y0 = y0 self.sigma = sigma self.scale = scale - # self._logical = (variables == 'log') - - # def get_radius_angle(self, x, y): - # from numpy import sqrt, arctan2 #, sin, cos - # r = sqrt(x*x + y*y) - # alpha = arctan2(y, x) - # return r, alpha - def Ex_ex(self, t, x, y): from numpy import exp, cos, sqrt sig2 = self.sigma ** 2 @@ -61,7 +44,7 @@ def Bz_ex(self, t, x, y): def main(): - # Physical domain is rectangle [0, R] x [0, 2pi] + # Logical domain is rectangle [0, R] x [0, 2pi] R = 2.0 # Speed of light equal c and scaling of the fields by a scale factor @@ -106,7 +89,6 @@ def main(): Ey_values = np.empty_like(rho) B_values = np.empty_like(rho) - valerr = 0 for i, x1i in enumerate(rho[:, 0]): for j, x2j in enumerate(theta[0, :]): Ex_values[i, j], Ey_values[i, j] = \ From ab097db79b28394d018bd3fcb9eb78c6d6fca44c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 15:17:03 +0200 Subject: [PATCH 115/133] rename phi, rho -> phi_log, rho_log in Poisson2D --- psydac/feec/polar/examples/poisson_2d.py | 62 +++++++++++------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 93d235e78..da6c32094 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -94,21 +94,20 @@ class Poisson2D: """ - def __init__(self, domain, mapping, phi, rho): + def __init__(self, domain, mapping, phi_log, rho_log): assert isinstance(mapping, Mapping) self._domain = domain self._mapping = mapping - self._phi = phi - self._rho = rho + self._phi_log = phi_log + self._rho_log = rho_log s, t = mapping.logical_coordinates - self._phi_callable = sympy.lambdify([s, t], phi) - self._rho_callable = sympy.lambdify([s, t], rho) + self._phi_log_callable = sympy.lambdify([s, t], phi_log) + self._rho_log_callable = sympy.lambdify([s, t], rho_log) - # ... @staticmethod - def disk_domain(R, shift_D): + def disk(R, shift_D): r""" Solve Poisson's equation on a disk of radius R centered at (x,y) = (0, 0), with logical coordinates (s, theta): @@ -138,9 +137,8 @@ def disk_domain(R, shift_D): return Poisson2D(logical_domain, mapping, phi_log, rho_log) - # ... @staticmethod - def target_domain(): + def target(): r""" Solve Poisson's equation on a polar domain, with logical coordinates (s, theta): @@ -174,14 +172,13 @@ def target_domain(): kx = 2 * pi / (1 - k + D) ky = 2 * pi / (1 + k) - phi = (1 - s ** 8) * sin(kx * (x - 0.5)) * cos(ky * y) - rho = - lapl(phi) + phi_log = (1 - s ** 8) * sin(kx * (x - 0.5)) * cos(ky * y) + rho_log = - lapl(phi_log) - return Poisson2D(logical_domain, mapping, phi, rho) + return Poisson2D(logical_domain, mapping, phi_log, rho_log) - # ... @staticmethod - def czarny_domain(): + def czarny(): r""" Solve Poisson's equation on a czarny domain, with logical coordinates (s, theta): @@ -202,12 +199,11 @@ def czarny_domain(): x, y = mapping.expressions # Manufactured solution in logical coordinates - phi = (1 - s ** 8) * sin(pi * x) * cos(pi * y) - rho = - lapl(phi) + phi_log = (1 - s ** 8) * sin(pi * x) * cos(pi * y) + rho_log = - lapl(phi_log) - return Poisson2D(logical_domain, mapping, phi, rho) + return Poisson2D(logical_domain, mapping, phi_log, rho_log) - # ... @property def domain(self): return self._domain @@ -217,20 +213,20 @@ def mapping(self): return self._mapping @property - def phi(self): - return self._phi + def phi_log(self): + return self._phi_log @property - def rho(self): - return self._rho + def rho_log(self): + return self._rho_log @property - def phi_callable(self): - return self._phi_callable + def phi_log_callable(self): + return self._phi_log_callable @property - def rho_callable(self): - return self._rho_callable + def rho_log_callable(self): + return self._rho_log_callable # ====================== CONGA (PENALIZED) POISSON ============================# @@ -333,11 +329,11 @@ def run_poisson_2d(*, test_case, ncells, degree, # Method of manufactured solution if test_case == 'disk': - model = Poisson2D.disk_domain(R=R, shift_D=shift_D) + model = Poisson2D.disk(R=R, shift_D=shift_D) elif test_case == 'target': - model = Poisson2D.target_domain() + model = Poisson2D.target() elif test_case == 'czarny': - model = Poisson2D.czarny_domain() + model = Poisson2D.czarny() else: raise ValueError("Only available test-cases are 'disk', 'target' and 'czarny'") @@ -411,9 +407,9 @@ def run_poisson_2d(*, test_case, ncells, degree, aS = BilinearForm((u0, v0), integral(domain, dot(grad(u0), grad(v0)))) # model.rho is in logical coordinates instead of physical but it works anyways - rhs = LinearForm(v0, integral(domain, model.rho * v0)) + rhs = LinearForm(v0, integral(domain, model.rho_log * v0)) - err_diff = model.phi - u0 + err_diff = model.phi_log - u0 # ============================= DISCRETIZATION ================================# if use_spline_mapping: @@ -442,7 +438,7 @@ def run_poisson_2d(*, test_case, ncells, degree, from psydac.feec.global_geometric_projectors import GlobalGeometricProjectorH1 Pi0 = GlobalGeometricProjectorH1(V0_h) - phi_ref = Pi0(model.phi_callable) + phi_ref = Pi0(model.phi_log_callable) phi_ref.coeffs.update_ghost_regions() @@ -633,7 +629,7 @@ def plot_solution(use_spline_mapping, model, ncells, periodic, V0_h, refine=10): phi, = Vnew.import_fields('fields.h5', 'phi') # Callable exact solution in logical coordinates - phi_e = model.phi_callable + phi_e = model.phi_log_callable # Compute numerical solution (and error) on refined logical grid [sk1, sk2], [ek1, ek2] = Vnew.local_domain From 2ad02db6b5d181287e5604f93cdb4897de3f5a1e Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Fri, 15 May 2026 15:42:09 +0200 Subject: [PATCH 116/133] rename domain -> domain_log --- psydac/feec/polar/examples/poisson_2d.py | 35 ++++++++++++------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index da6c32094..e69f6706d 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -94,10 +94,10 @@ class Poisson2D: """ - def __init__(self, domain, mapping, phi_log, rho_log): + def __init__(self, domain_log, mapping, phi_log, rho_log): assert isinstance(mapping, Mapping) - self._domain = domain + self._domain_log = domain_log self._mapping = mapping self._phi_log = phi_log self._rho_log = rho_log @@ -118,7 +118,7 @@ def disk(R, shift_D): : code $\phi(x,y) = (1 - ((x^2 + y^2) / R^2) ** 4) * sin(kx * x) * cos(ky * y)$. """ - logical_domain = Square('Omega', bounds1=(0, R), bounds2=(0, 2 * np.pi)) + domain_log = Square('Omega', bounds1=(0, R), bounds2=(0, 2 * np.pi)) params = dict(c1=shift_D * R * R, c2=0, k=0, D=shift_D) mapping = TargetMapping('TM', **params) @@ -128,14 +128,14 @@ def disk(R, shift_D): kx = 2 * pi / (R * (1 - k + D)) ky = 2 * pi / (R * (1 + k)) x, y = sympy.symbols('x, y') - phi = (1 - ((x * x + y * y) / (R * R)) ** 4) * sin(kx * x) * cos(ky * y) - rho = - phi.diff(x, x) - phi.diff(y, y) + phi_phys = (1 - ((x * x + y * y) / (R * R)) ** 4) * sin(kx * x) * cos(ky * y) + rho_phys = - phi_phys.diff(x, x) - phi_phys.diff(y, y) x_log, y_log = mapping.expressions - phi_log = phi.subs({x: x_log, y: y_log}) - rho_log = rho.subs({x: x_log, y: y_log}) + phi_log = phi_phys.subs({x: x_log, y: y_log}) + rho_log = rho_phys.subs({x: x_log, y: y_log}) - return Poisson2D(logical_domain, mapping, phi_log, rho_log) + return Poisson2D(domain_log, mapping, phi_log, rho_log) @staticmethod def target(): @@ -158,7 +158,7 @@ def target(): """ - logical_domain = Square('Omega', bounds1=(0, 1), bounds2=(0, 2 * np.pi)) + domain_log = Square('Omega', bounds1=(0, 1), bounds2=(0, 2 * np.pi)) params = dict(c1=0, c2=0, k=Rational(3, 10), D=Rational(2, 10)) mapping = TargetMapping('F', **params) @@ -175,7 +175,7 @@ def target(): phi_log = (1 - s ** 8) * sin(kx * (x - 0.5)) * cos(ky * y) rho_log = - lapl(phi_log) - return Poisson2D(logical_domain, mapping, phi_log, rho_log) + return Poisson2D(domain_log, mapping, phi_log, rho_log) @staticmethod def czarny(): @@ -190,7 +190,7 @@ def czarny(): """ - logical_domain = Square('Omega', bounds1=(0, 1), bounds2=(0, 2 * np.pi)) + domain_log = Square('Omega', bounds1=(0, 1), bounds2=(0, 2 * np.pi)) params = dict(c1=0, c2=0, eps=Rational(1, 5), b=Rational(7, 5)) mapping = CzarnyMapping('F', **params) @@ -202,11 +202,11 @@ def czarny(): phi_log = (1 - s ** 8) * sin(pi * x) * cos(pi * y) rho_log = - lapl(phi_log) - return Poisson2D(logical_domain, mapping, phi_log, rho_log) + return Poisson2D(domain_log, mapping, phi_log, rho_log) @property - def domain(self): - return self._domain + def domain_log(self): + return self._domain_log @property def mapping(self): @@ -362,8 +362,8 @@ def run_poisson_2d(*, test_case, ncells, degree, if use_spline_mapping: # Create uniform grid - grid_1 = np.linspace(*model.domain.bounds1, num=ne1 + 1) - grid_2 = np.linspace(*model.domain.bounds2, num=ne2 + 1) + grid_1 = np.linspace(*model.domain_log.bounds1, num=ne1 + 1) + grid_2 = np.linspace(*model.domain_log.bounds2, num=ne2 + 1) # Create 1D finite element spaces V1 = SplineSpace(p1, grid=grid_1, periodic=periodic[0]) @@ -396,7 +396,7 @@ def run_poisson_2d(*, test_case, ncells, degree, else: # Only symbolic mapping is necessary mapping = model.mapping - domain = mapping(model.domain) + domain = mapping(model.domain_log) # ========================== SYMBOLIC DEFINITION ==============================# @@ -405,7 +405,6 @@ def run_poisson_2d(*, test_case, ncells, degree, u0, v0 = elements_of(V0, names='u0, v0') aM = BilinearForm((u0, v0), integral(domain, u0 * v0)) aS = BilinearForm((u0, v0), integral(domain, dot(grad(u0), grad(v0)))) - # model.rho is in logical coordinates instead of physical but it works anyways rhs = LinearForm(v0, integral(domain, model.rho_log * v0)) From 68516f411f6a03e6c2b70f910d98dcc9ac96e74f Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 19 May 2026 10:14:03 +0200 Subject: [PATCH 117/133] some simplifications in Maxwell --- psydac/feec/polar/examples/maxwell_2d.py | 118 ++++++++++------------- 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 4351ed26a..92ff183bd 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -166,8 +166,7 @@ def plot_curve_along_s(name, s_str, time_str, theta0, def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, splitting_order, shift_D, use_spline_mapping, plot_time, tol, - cfl=0.9, show_figs=True, plot_final=True, - study='maxwell_bessel', use_scipy=True, verbose=False): + cfl=0.9, show_figs=True, study='maxwell_bessel', use_scipy=True, verbose=False): import numpy as np from numpy import pi # import matplotlib.pyplot as plt @@ -294,24 +293,16 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, mapping.set_callable_mapping(map_discrete) # In order to create a sympde.Domain object from this mapping we have # to create first a HDF5 file and then load as sympde.Domain.fromfile - # t0 = time() geometry = Geometry.from_discrete_mapping(map_discrete, comm=mpi_comm) geometry.export('geo.h5') - # t1 = time() - # timing['export'] += t1 - t0 domain = Domain.from_file('geo.h5') # TODO (MCP 07.2024): check that mapping = domain.mapping ?? else: # Only symbolic mapping is necessary - # mapping = model.mapping domain = mapping(logical_domain) - # F = mapping.get_callable_mapping() - - # domain = mapping(logical_domain) - # DeRham sequence derham = Derham(domain, sequence=['h1', 'hcurl', 'l2']) @@ -622,67 +613,65 @@ def run_study_L2_proj(): t = 0 - if study_maxwell: + # Callable exact fields + Ex_ex = lambda t: (lambda x, y, t0=t: Ex_ex_t(t0, x, y)) + Ey_ex = lambda t: (lambda x, y, t0=t: Ey_ex_t(t0, x, y)) + Bz_ex = lambda t: (lambda x, y, t0=t: Bz_ex_t(t0, x, y)) - # Callable exact fields - Ex_ex = lambda t: (lambda x, y, t0=t: Ex_ex_t(t0, x, y)) - Ey_ex = lambda t: (lambda x, y, t0=t: Ey_ex_t(t0, x, y)) - Bz_ex = lambda t: (lambda x, y, t0=t: Bz_ex_t(t0, x, y)) + # Initial conditions, discrete fields -- here with a pull-back in the projections + E_log = Pi1((Ex_ex(t), Ey_ex(t))) + E_log.coeffs.update_ghost_regions() + B_log = Pi2(Bz_ex(t)) + B_log.coeffs.update_ghost_regions() - # Initial conditions, discrete fields -- here with a pull-back in the projections - E_log = Pi1((Ex_ex(t), Ey_ex(t))) - E_log.coeffs.update_ghost_regions() - B_log = Pi2(Bz_ex(t)) - B_log.coeffs.update_ghost_regions() + # Initial conditions, spline coefficients + e = E_log.coeffs + b = B_log.coeffs - # Initial conditions, spline coefficients - e = E_log.coeffs - b = B_log.coeffs - - if study == 'maxwell_wave': - D1.dot(e, out=b) + if study == 'maxwell_wave': + D1.dot(e, out=b) - # Conga Projection - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + # Conga Projection + P1.dot(e.copy(), out=e) + P2.dot(b.copy(), out=b) - V1_s, V1_theta = V1.spaces - Ex_field = FemField(V1_s, coeffs=e[0]) - Ey_field = FemField(V1_theta, coeffs=e[1]) - B_field = FemField(V2, coeffs=b) - V1_s.export_fields('Ex.h5', Ex_field=Ex_field) - V1_theta.export_fields('Ey.h5', Ey_field=Ey_field) - V2.export_fields('B.h5', B_field=B_field) + V1_s, V1_theta = V1.spaces + Ex_field = FemField(V1_s, coeffs=e[0]) + Ey_field = FemField(V1_theta, coeffs=e[1]) + B_field = FemField(V2, coeffs=b) + V1_s.export_fields('Ex.h5', Ex_field=Ex_field) + V1_theta.export_fields('Ey.h5', Ey_field=Ey_field) + V2.export_fields('B.h5', B_field=B_field) - if use_scipy: + if use_scipy: - print(" -------------- SCIPY operators ------------ ") - conga_curl_sp = (D1 @ P1).tosparse() - step_faraday_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, strong=True, - store_M1inv=False) - step_ampere_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, M1=M1, M2=M2, - strong=False) + print(" -------------- SCIPY operators ------------ ") + conga_curl_sp = (D1 @ P1).tosparse() + step_faraday_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, strong=True, + store_M1inv=False) + step_ampere_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, M1=M1, M2=M2, + strong=False) - else: - M1_inv = inverse(M1, 'cg', verbose=verbose, tol=tol) - step_ampere_2d = M1_inv @ P1_T @ D1_T @ M2 - step_faraday_2d = D1 @ P1 + else: + M1_inv = inverse(M1, 'cg', verbose=verbose, tol=tol) + step_ampere_2d = M1_inv @ P1_T @ D1_T @ M2 + step_faraday_2d = D1 @ P1 - Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2, tau=tend, light_c=1) + Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2, tau=tend, light_c=1) - if plot_time > 0: - plot_interval = max(int(plot_time / dt), 1) - else: - plot_interval = 0 - print(f'plot_interval = {plot_interval}, corresponding to a time = {plot_interval * dt}') + if plot_time > 0: + plot_interval = max(int(plot_time / dt), 1) + else: + plot_interval = 0 + print(f'plot_interval = {plot_interval}, corresponding to a time = {plot_interval * dt}') - # If final time is given, recompute number of time steps - if tend is None: - tend = nsteps * dt - print(f'final time (re)computed: {tend}') - else: - nsteps = Nt - print(f'nsteps recomputed: {nsteps}') + # If final time is given, recompute number of time steps + if tend is None: + tend = nsteps * dt + print(f'final time (re)computed: {tend}') + else: + nsteps = Nt + print(f'nsteps recomputed: {nsteps}') # ============================================================================== # VISUALIZATION SETUP @@ -713,11 +702,9 @@ def plot_fields_along_s(tstr): fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') plt.close(fig_line) - # Prepare plots + # Plot initial conditions if plot_interval: - # Plot initial conditions - plot_data = build_plot_context() if plot_data is not None: V1_sx = plot_data['V1_sx'] @@ -766,7 +753,6 @@ def plot_fields_along_s(tstr): else: fig.savefig(f'{visdir}/Ex_t0_{rp_str}.png') plt.close(fig) - # fig.clf() # Electric field, y component fig = plot_field_and_error(r'E^y', 0, x, y, Ey_values, Ey_ex_values, *gridlines) @@ -775,7 +761,6 @@ def plot_fields_along_s(tstr): else: fig.savefig(f'{visdir}/Ey_t0_{rp_str}.png') plt.close(fig) - # fig.clf() # Magnetic field, z component fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) @@ -932,7 +917,6 @@ def Strang_update(dtau): push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) - # Bz_values[i, j] = B(x1i, x2j) xij, yij = F(x1i, x2j) Ex_ex_values[i, j], Ey_ex_values[i, j] = \ @@ -972,7 +956,7 @@ def Strang_update(dtau): print('L2 norm of rel. error on Ey(t,x,y) at final time: {:.2e}'.format(error_l2_Ey)) print('L2 norm of rel. error on Bz(t,x,y) at final time: {:.2e}'.format(error_l2_Bz)) - if plot_final and mpi_rank == 0: + if mpi_rank == 0: # Plot exact and approximate solution at final time fig1, axs = plt.subplots(3, 3, figsize=(12, 12)) im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) From 880ef9406245a25b6b511a4f206f1fcae2950931 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 19 May 2026 14:23:54 +0200 Subject: [PATCH 118/133] improvements in plotting logic --- psydac/feec/polar/examples/maxwell_2d.py | 321 ++++++++++------------- 1 file changed, 141 insertions(+), 180 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 92ff183bd..d55822dc8 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -1,6 +1,9 @@ """ Solve the Transverse Electric Time dependent Maxwell Problem on an analytical disk domain. + +example of run: +python maxwell_2d.py -S -n 16 32 -d 3 3 -T 1 -D 0.2 -s 1 """ import os import numpy as np @@ -8,6 +11,7 @@ import matplotlib.pyplot as plt +from psydac.feec.global_geometric_projectors import evaluate_dofs_1d_0form from psydac.fem.basic import FemField from utils_congapol import print_map_polar_coeffs, check_regular_ring_map, add_colorbar @@ -165,11 +169,10 @@ def plot_curve_along_s(name, s_str, time_str, theta0, # =============================================================================# def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, - splitting_order, shift_D, use_spline_mapping, plot_time, tol, - cfl=0.9, show_figs=True, study='maxwell_bessel', use_scipy=True, verbose=False): + splitting_order, shift_D, use_spline_mapping, tol, + cfl=0.9, show_figs=False, study='maxwell_bessel', use_scipy=True, verbose=False): import numpy as np from numpy import pi - # import matplotlib.pyplot as plt from sympy import cos, sin, Tuple, exp, sqrt, atan2 @@ -257,9 +260,6 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, mpi_comm = MPI.COMM_WORLD mpi_size = mpi_comm.Get_size() mpi_rank = mpi_comm.Get_rank() - if mpi_rank != 0: - show_figs = False - # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# @@ -343,7 +343,7 @@ def l2_norm_of(f_log): f2_with_det = lambda eta1, eta2: f_log(eta1, eta2) ** 2 * np.sqrt(F.metric_det(eta1, eta2)) return np.sqrt(derham_h.V0.integral(f2_with_det)) - def build_plot_context(): + def build_eval_context(): """ Build all serial objects needed for plotting fields. @@ -487,10 +487,10 @@ def run_study_L2_proj(): print('V1.x - integral of 1 (no det): {:.2e}'.format(int_wo_det)) print('V1.x - integral of 1 (with det): {:.2e}'.format(int_wi_det)) - if plot_time <= 0: + if not show_figs: return locals() - plot_data = build_plot_context() + plot_data = build_eval_context() if plot_data is None: return locals() @@ -659,12 +659,6 @@ def run_study_L2_proj(): Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2, tau=tend, light_c=1) - if plot_time > 0: - plot_interval = max(int(plot_time / dt), 1) - else: - plot_interval = 0 - print(f'plot_interval = {plot_interval}, corresponding to a time = {plot_interval * dt}') - # If final time is given, recompute number of time steps if tend is None: tend = nsteps * dt @@ -703,137 +697,126 @@ def plot_fields_along_s(tstr): plt.close(fig_line) # Plot initial conditions - if plot_interval: - - plot_data = build_plot_context() - if plot_data is not None: - V1_sx = plot_data['V1_sx'] - V1_sy = plot_data['V1_sy'] - V2_s = plot_data['V2_s'] - F_serial = plot_data['F_serial'] - x1 = plot_data['x1'] - x2 = plot_data['x2'] - x = plot_data['x'] - y = plot_data['y'] - gridlines = plot_data['gridlines'] - - Ex_serial, = V1_sx.import_fields('Ex.h5', 'Ex_field') - Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') - B_serial, = V2_s.import_fields('B.h5', 'B_field') - - Ex_ex_values = np.empty_like(x1) - Ey_ex_values = np.empty_like(x1) - Bz_ex_values = np.empty_like(x1) - - Ex_values = np.empty_like(x1) - Ey_values = np.empty_like(x1) - Bz_values = np.empty_like(x1) - - for i, x1i in enumerate(x1[:, 0]): - for j, x2j in enumerate(x2[0, :]): - - Ex_values[i, j], Ey_values[i, j] = \ - push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) - - Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) - - xij, yij = F(x1i, x2j) - Ex_ex_values[i, j], Ey_ex_values[i, j] = \ - Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) - - Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) - - # fields along s for fixed theta - plot_fields_along_s(tstr='t0') - - # Electric field, x component - fig = plot_field_and_error(r'E^x', 0, x, y, Ex_values, Ex_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ex_t0_{rp_str}.png') - plt.close(fig) - - # Electric field, y component - fig = plot_field_and_error(r'E^y', 0, x, y, Ey_values, Ey_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ey_t0_{rp_str}.png') - plt.close(fig) - - # Magnetic field, z component - fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Bz_t0_{rp_str}.png') - plt.close(fig) - - if show_figs: - # Plot exact and approximate solutions at t = 0 - fig, axs = plt.subplots(3, 3, figsize=(12, 12)) - im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) - im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) - im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) - im3 = axs[1, 0].contourf(x, y, Ex_values, 50) - im4 = axs[1, 1].contourf(x, y, Ey_values, 50) - im5 = axs[1, 2].contourf(x, y, Bz_values, 50) - im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) - im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) - im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) - axs[0, 0].set_title(r'$E^x$ at t = 0') - axs[0, 1].set_title(r'$E^y$ at t = 0') - axs[0, 2].set_title(r'$B^z$ at t = 0') - axs[1, 0].set_title(r'$E_h^x$ at t = 0') - axs[1, 1].set_title(r'$E_h^y$ at t = 0') - axs[1, 2].set_title(r'$B_h^z$ at t = 0') - axs[2, 0].set_title(r'$E^x - E_h^x$ at t = 0') - axs[2, 1].set_title(r'$E^y - E_h^y$ at t = 0') - axs[2, 2].set_title(r'$B^z - B_h^z$ at t = 0') - for i in range(3): - for j in range(3): - axs[i, j].plot(*gridlines[0], color='k') - axs[i, j].plot(*gridlines[1], color='k') - axs[i, j].set_xlabel('x', fontsize=14) - axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') - axs[i, j].set_aspect('equal') - add_colorbar(im0, axs[0, 0]) - add_colorbar(im1, axs[0, 1]) - add_colorbar(im2, axs[0, 2]) - add_colorbar(im3, axs[1, 0]) - add_colorbar(im4, axs[1, 1]) - add_colorbar(im5, axs[1, 2]) - add_colorbar(im6, axs[2, 0]) - add_colorbar(im7, axs[2, 1]) - add_colorbar(im8, axs[2, 2]) - fig.suptitle('Compare Exact Solution and Approximate solution at initial time') - fig.tight_layout() - - # Need a small pause to show the plot of the initial condition - plt.pause(.1) - - # L2 norms (of ref solution) - normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) - normy = lambda x1, x2: Ey_ex_t(t, *F(x1, x2)) - normz = lambda x1, x2: Bz_ex_t(t, *F(x1, x2)) - - norm_l2_Ex = l2_norm_of(normx) - norm_l2_Ey = l2_norm_of(normy) - norm_l2_Bz = l2_norm_of(normz) - - # L2 errors - errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) - erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) - errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) - - error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex - error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey - error_l2_Bz = l2_norm_of(errz) / norm_l2_Bz - - print('L2 norm of rel. error on Ex(t,x,y) at initial time: {:.2e}'.format(error_l2_Ex)) - print('L2 norm of rel. error on Ey(t,x,y) at initial time: {:.2e}'.format(error_l2_Ey)) - print('L2 norm of rel. error on Bz(t,x,y) at initial time: {:.2e}'.format(error_l2_Bz)) + eval_data = build_eval_context() + if eval_data is not None: # when mpi_rank is 0, since this needs to be done in serial + V1_sx = eval_data['V1_sx'] + V1_sy = eval_data['V1_sy'] + V2_s = eval_data['V2_s'] + F_serial = eval_data['F_serial'] + x1 = eval_data['x1'] + x2 = eval_data['x2'] + x = eval_data['x'] + y = eval_data['y'] + gridlines = eval_data['gridlines'] + + Ex_serial, = V1_sx.import_fields('Ex.h5', 'Ex_field') + Ey_serial, = V1_sy.import_fields('Ey.h5', 'Ey_field') + B_serial, = V2_s.import_fields('B.h5', 'B_field') + + Ex_ex_values = np.empty_like(x1) + Ey_ex_values = np.empty_like(x1) + Bz_ex_values = np.empty_like(x1) + + Ex_values = np.empty_like(x1) + Ey_values = np.empty_like(x1) + Bz_values = np.empty_like(x1) + + for i, x1i in enumerate(x1[:, 0]): + for j, x2j in enumerate(x2[0, :]): + + Ex_values[i, j], Ey_values[i, j] = \ + push_2d_hcurl(Ex_serial, Ey_serial, x1i, x2j, F_serial) + + Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) + + xij, yij = F_serial(x1i, x2j) + Ex_ex_values[i, j], Ey_ex_values[i, j] = \ + Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) + + Bz_ex_values[i, j] = Bz_ex_t(t, xij, yij) + + # fields along s for fixed theta + plot_fields_along_s(tstr='t0') + + # Electric field, x component + fig = plot_field_and_error(r'E^x', 0, x, y, Ex_values, Ex_ex_values, *gridlines) + fig.savefig(f'{visdir}/Ex_t0_{rp_str}.png') + plt.close(fig) + + # Electric field, y component + fig = plot_field_and_error(r'E^y', 0, x, y, Ey_values, Ey_ex_values, *gridlines) + fig.savefig(f'{visdir}/Ey_t0_{rp_str}.png') + plt.close(fig) + + # Magnetic field, z component + fig = plot_field_and_error(r'B^z', 0, x, y, Bz_values, Bz_ex_values, *gridlines) + fig.savefig(f'{visdir}/Bz_t0_{rp_str}.png') + plt.close(fig) + + if show_figs: + # Plot exact and approximate solutions at t = 0 + fig, axs = plt.subplots(3, 3, figsize=(12, 12)) + im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) + im1 = axs[0, 1].contourf(x, y, Ey_ex_values, 50) + im2 = axs[0, 2].contourf(x, y, Bz_ex_values, 50) + im3 = axs[1, 0].contourf(x, y, Ex_values, 50) + im4 = axs[1, 1].contourf(x, y, Ey_values, 50) + im5 = axs[1, 2].contourf(x, y, Bz_values, 50) + im6 = axs[2, 0].contourf(x, y, Ex_values - Ex_ex_values, 50) + im7 = axs[2, 1].contourf(x, y, Ey_values - Ey_ex_values, 50) + im8 = axs[2, 2].contourf(x, y, Bz_values - Bz_ex_values, 50) + axs[0, 0].set_title(r'$E^x$ at t = 0') + axs[0, 1].set_title(r'$E^y$ at t = 0') + axs[0, 2].set_title(r'$B^z$ at t = 0') + axs[1, 0].set_title(r'$E_h^x$ at t = 0') + axs[1, 1].set_title(r'$E_h^y$ at t = 0') + axs[1, 2].set_title(r'$B_h^z$ at t = 0') + axs[2, 0].set_title(r'$E^x - E_h^x$ at t = 0') + axs[2, 1].set_title(r'$E^y - E_h^y$ at t = 0') + axs[2, 2].set_title(r'$B^z - B_h^z$ at t = 0') + for i in range(3): + for j in range(3): + axs[i, j].plot(*gridlines[0], color='k') + axs[i, j].plot(*gridlines[1], color='k') + axs[i, j].set_xlabel('x', fontsize=14) + axs[i, j].set_ylabel('y', fontsize=14, rotation='horizontal') + axs[i, j].set_aspect('equal') + add_colorbar(im0, axs[0, 0]) + add_colorbar(im1, axs[0, 1]) + add_colorbar(im2, axs[0, 2]) + add_colorbar(im3, axs[1, 0]) + add_colorbar(im4, axs[1, 1]) + add_colorbar(im5, axs[1, 2]) + add_colorbar(im6, axs[2, 0]) + add_colorbar(im7, axs[2, 1]) + add_colorbar(im8, axs[2, 2]) + fig.suptitle('Compare Exact Solution and Approximate solution at initial time') + fig.tight_layout() + + # Need a small pause to show the plot of the initial condition + plt.pause(.1) + + # L2 norms (of ref solution) + normx = lambda x1, x2: Ex_ex_t(t, *F(x1, x2)) + normy = lambda x1, x2: Ey_ex_t(t, *F(x1, x2)) + normz = lambda x1, x2: Bz_ex_t(t, *F(x1, x2)) + + norm_l2_Ex = l2_norm_of(normx) + norm_l2_Ey = l2_norm_of(normy) + norm_l2_Bz = l2_norm_of(normz) + + # L2 errors + errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) + erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) + errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) + + error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex + error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey + error_l2_Bz = l2_norm_of(errz) / norm_l2_Bz + + print('L2 norm of rel. error on Ex(t,x,y) at initial time: {:.2e}'.format(error_l2_Ex)) + print('L2 norm of rel. error on Ey(t,x,y) at initial time: {:.2e}'.format(error_l2_Ey)) + print('L2 norm of rel. error on Bz(t,x,y) at initial time: {:.2e}'.format(error_l2_Bz)) # ============================================================================== # SOLUTION @@ -866,7 +849,7 @@ def Strang_update(dtau): for ts in range(1, nsteps + 1): print(f'step = {ts}/{nsteps}') - + if splitting_order == 2: Strang_update(dt) @@ -892,9 +875,9 @@ def Strang_update(dtau): print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) N = 10 - V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) + if show_figs: + V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) - # if not plot_interval: P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) @@ -905,7 +888,7 @@ def Strang_update(dtau): V1_theta.export_fields('Ey_final.h5', Ey_field=Ey_field) V2.export_fields('B_final.h5', B_field=B_field) - if plot_data is not None: + if eval_data is not None: Ex_serial, = V1_sx.import_fields('Ex_final.h5', 'Ex_field') Ey_serial, = V1_sy.import_fields('Ey_final.h5', 'Ey_field') B_serial, = V2_s.import_fields('B_final.h5', 'B_field') @@ -918,7 +901,7 @@ def Strang_update(dtau): Bz_values[i, j] = push_2d_l2(B_serial, x1i, x2j, F_serial) - xij, yij = F(x1i, x2j) + xij, yij = F_serial(x1i, x2j) Ex_ex_values[i, j], Ey_ex_values[i, j] = \ Ex_ex_t(t, xij, yij), Ey_ex_t(t, xij, yij) @@ -956,7 +939,7 @@ def Strang_update(dtau): print('L2 norm of rel. error on Ey(t,x,y) at final time: {:.2e}'.format(error_l2_Ey)) print('L2 norm of rel. error on Bz(t,x,y) at final time: {:.2e}'.format(error_l2_Bz)) - if mpi_rank == 0: + if eval_data is not None and show_figs: # Plot exact and approximate solution at final time fig1, axs = plt.subplots(3, 3, figsize=(12, 12)) im0 = axs[0, 0].contourf(x, y, Ex_ex_values, 50) @@ -1001,27 +984,18 @@ def Strang_update(dtau): # Electric field, x component fig = plot_field_and_error(r'E^x', tend, x, y, Ex_values, Ex_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ex_T_{rp_str}.png') - plt.close(fig) # fig.clf() + fig.savefig(f'{visdir}/Ex_T_{rp_str}.png') + plt.close(fig) # fig.clf() # Electric field, y component fig = plot_field_and_error(r'E^y', tend, x, y, Ey_values, Ey_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Ey_T_{rp_str}.png') - plt.close(fig) # fig.clf() + fig.savefig(f'{visdir}/Ey_T_{rp_str}.png') + plt.close(fig) # fig.clf() # Magnetic field, z component fig = plot_field_and_error(r'B^z', tend, x, y, Bz_values, Bz_ex_values, *gridlines) - if show_figs: - fig.show() - else: - fig.savefig(f'{visdir}/Bz_T_{rp_str}.png') - plt.close(fig) + fig.savefig(f'{visdir}/Bz_T_{rp_str}.png') + plt.close(fig) return locals() @@ -1102,15 +1076,6 @@ def Strang_update(dtau): metavar='END_TIME', help='Run simulation until given final time' ) - # ... - - parser.add_argument('-p', - type=float, - default=1., - metavar='PLOT_TIME', - dest='plot_time', - help='Approx time between successive plots of solution, if I=0 no plots are made' - ) parser.add_argument('--tol', type=float, @@ -1140,7 +1105,3 @@ def Strang_update(dtau): # Keep matplotlib windows open # import matplotlib.pyplot as plt plt.show() - -## example of run: - -## python maxwell_2d.py -S -n 16 32 -d 3 3 -T 1 -D 0.2 -s 1 -p 100 \ No newline at end of file From d53efb6e6755e62c341c0a14331c0822e21b23bf Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 19 May 2026 15:38:19 +0200 Subject: [PATCH 119/133] fix empty windows --- psydac/feec/polar/examples/maxwell_2d.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index d55822dc8..14a1572be 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -170,7 +170,7 @@ def plot_curve_along_s(name, s_str, time_str, theta0, def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, splitting_order, shift_D, use_spline_mapping, tol, - cfl=0.9, show_figs=False, study='maxwell_bessel', use_scipy=True, verbose=False): + cfl=0.9, show_figs=True, study='maxwell_bessel', use_scipy=True, verbose=False): import numpy as np from numpy import pi @@ -682,13 +682,13 @@ def plot_fields_along_s(tstr): x1[:, j0], Ex_values[:, j0], Ex_ex_values[:, j0], -x1[:, j1], Ex_values[:, j1], Ex_ex_values[:, j1]) fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - fig_line.clf() + plt.close(fig_line) name = 'Ey' fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', x1[:, j0], Ey_values[:, j0], Ey_ex_values[:, j0], -x1[:, j1], Ey_values[:, j1], Ey_ex_values[:, j1]) fig_line.savefig(f'{visdir}/{name}_line_{tstr}_{rp_str}.png') - fig_line.clf() + plt.close(fig_line) name = 'Bz' fig_line = plot_curve_along_s(name, 's', tstr, f'{theta0} and {theta1}', x1[:, j0], Bz_values[:, j0], Bz_ex_values[:, j0], From 26fc230dae2115aa75ce3da3278824d0e72ba17e Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 21 May 2026 14:27:36 +0200 Subject: [PATCH 120/133] Fixed initial solution waveTE --- psydac/feec/polar/examples/maxwell_2d.py | 14 ++---- psydac/feec/polar/examples/waveTE.py | 61 +++++++++++++++++------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 14a1572be..336ad6a94 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -1,17 +1,16 @@ """ -Solve the Transverse Electric Time dependent Maxwell Problem -on an analytical disk domain. +Solve the Transverse Electric Time dependent Maxwell Problem on an analytical disk domain. -example of run: +Example of run: python maxwell_2d.py -S -n 16 32 -d 3 3 -T 1 -D 0.2 -s 1 """ + import os import numpy as np from mpi4py import MPI import matplotlib.pyplot as plt -from psydac.feec.global_geometric_projectors import evaluate_dofs_1d_0form from psydac.fem.basic import FemField from utils_congapol import print_map_polar_coeffs, check_regular_ring_map, add_colorbar @@ -69,8 +68,6 @@ def vect_norm_2(vv): vv = V.coeff_space.zeros() print(f'type(V.coeff_space) = {type(V.coeff_space)}') print(f'V.coeff_space.shape = {V.coeff_space.shape}, V.coeff_space.dimension = {V.coeff_space.dimension}') - # print(type(vv.[])) - # vv[:] = np.random.random(V.coeff_space.dimension) vv[:] = np.random.random(size=V.coeff_space.shape) norm_vv = vect_norm_2(vv) max_ncfl = 500 @@ -137,9 +134,6 @@ def plot_field_and_error(name, t, x, y, field_h, field_ex, *gridlines, only_fiel def update_plot(fig, t, x, y, field_h, field_ex): ax0, ax1, cax0, cax1 = fig.axes - # fig.cla() - # ax0.collections.clear(); cax0.clear() - # ax1.collections.clear(); cax1.clear() im0 = ax0.contourf(x, y, field_h, 50) im1 = ax1.contourf(x, y, field_ex - field_h, 50) fig.colorbar(im0, cax=cax0) @@ -227,6 +221,7 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, os.makedirs(visdir, exist_ok=True) if study == 'maxwell_wave': + # This is just the initial solution exact_solution = GaussianSolution(sigma=1e-1, x0=0, y0=0, scale=scale) else: exact_solution = CircularCavitySolution(R=R, c=c, m=m, n=n, scale=scale) @@ -248,7 +243,6 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # domain = ((0, 1), (0, 2 * np.pi)) mapping = TargetMapping('TM', c1=shift_D * R * R, c2=0, k=0, D=shift_D) - # use_spline_mapping = False #True # run parameters string rp_str = f'{ncells[0]}_{ncells[1]}_p{degree[0]}_D{shift_D}_s{smooth}' if use_spline_mapping: diff --git a/psydac/feec/polar/examples/waveTE.py b/psydac/feec/polar/examples/waveTE.py index ccd17d2c2..9f151396a 100644 --- a/psydac/feec/polar/examples/waveTE.py +++ b/psydac/feec/polar/examples/waveTE.py @@ -9,7 +9,29 @@ class GaussianSolution: """ - just a Gaussian field with B = curl E + Initial Gaussian circular wave for the TE Maxwell test. + + This class defines the initial condition used for the Gaussian wave + propagation experiment. It is not an exact time-dependent Maxwell solution. + The electric field is initialized as a localized rotational Gaussian pulse, + + E0(x, y) = scale * (y - y0, -(x - x0)) + * exp(-((x - x0)^2 + (y - y0)^2) / (2 sigma^2)), + + and the magnetic field is initialized as + + B0 = curl E0 = d_x Ey - d_y Ex. + + Parameters + ---------- + sigma : float + Width of the Gaussian pulse. + + x0, y0 : float + Center of the Gaussian pulse in physical coordinates. + + scale : float, optional + Amplitude scaling factor for the initial fields. """ def __init__(self, sigma, x0, y0, scale=1): @@ -19,31 +41,36 @@ def __init__(self, sigma, x0, y0, scale=1): self.sigma = sigma self.scale = scale - def Ex_ex(self, t, x, y): - from numpy import exp, cos, sqrt - sig2 = self.sigma ** 2 - r2 = (x - self.x0) ** 2 + (y - self.y0) ** 2 + def _gaussian(self, x, y): + from numpy import exp + X = x - self.x0 + Y = y - self.y0 + sig2 = self.sigma**2 + return self.scale * exp(-(X*X + Y*Y)/(2*sig2)) - return (y - self.y0) / sig2 * exp(-r2 / (2 * sig2)) + def Ex_ex(self, t, x, y): + Y = y - self.y0 + return Y * self._gaussian(x, y) def Ey_ex(self, t, x, y): - from numpy import exp, cos, sqrt - - sig2 = self.sigma ** 2 - r2 = (x - self.x0) ** 2 + (y - self.y0) ** 2 - - return -(x - self.x0) / sig2 * exp(-r2 / (2 * sig2)) + X = x - self.x0 + return -X * self._gaussian(x, y) def Bz_ex(self, t, x, y): """ - Bz = curl E (?) + Bz = curl E = d_x Ey - d_y Ex """ - from numpy import exp - - return -2*(y-self.y0)/self.sigma**2 * exp(-((x-self.x0)**2 + (y-self.y0)**2)/self.sigma**2) - + X = x - self.x0 + Y = y - self.y0 + sig2 = self.sigma**2 + r2 = X*X + Y*Y + return (r2/sig2 - 2.0) * self._gaussian(x, y) def main(): + """ + This function is not currently used. It is kept for possible future development. + """ + # Logical domain is rectangle [0, R] x [0, 2pi] R = 2.0 From 7eabc4bfff5c0d6806387d47de1d34568d90356c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 21 May 2026 17:15:22 +0200 Subject: [PATCH 121/133] added utility function create_tensor_spline_space --- psydac/feec/polar/examples/maxwell_2d.py | 38 +++++--------------- psydac/feec/polar/examples/poisson_2d.py | 31 +++++----------- psydac/feec/polar/examples/utils_congapol.py | 33 +++++++++++++++++ 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 336ad6a94..78f712b22 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -12,7 +12,7 @@ import matplotlib.pyplot as plt from psydac.fem.basic import FemField -from utils_congapol import print_map_polar_coeffs, check_regular_ring_map, add_colorbar +from utils_congapol import print_map_polar_coeffs, check_regular_ring_map, add_colorbar, create_tensor_spline_space # ====================== TIME DISCRETIZATION ==================================# @@ -187,10 +187,7 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, from psydac.linalg.basic import IdentityOperator from psydac.linalg.block import BlockLinearOperator from psydac.mapping.discrete import SplineMapping - from psydac.fem.splines import SplineSpace - from psydac.fem.tensor import TensorFemSpace from psydac.cad.geometry import Geometry - from psydac.ddm.cart import DomainDecomposition assert splitting_order in [2, 4] @@ -255,26 +252,14 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, mpi_size = mpi_comm.Get_size() mpi_rank = mpi_comm.Get_rank() - # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - - # Number of elements and spline degree - ne1, ne2 = ncells - p1, p2 = degree - - # Create uniform grid - grid_1 = np.linspace(*logical_bounds[0], num=ne1 + 1) - grid_2 = np.linspace(*logical_bounds[1], num=ne2 + 1) + if use_spline_mapping: - # Create 1D finite element spaces - V1 = SplineSpace(p1, grid=grid_1, periodic=False) - V2 = SplineSpace(p2, grid=grid_2, periodic=True) + # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, [False, True], comm = mpi_comm) - V = TensorFemSpace(domain_decomposition, V1, V2) + V = create_tensor_spline_space(ncells, degree, [False, True], + (logical_bounds[0], logical_bounds[1]), mpi_comm) # ==================== MAPPING & PHYSICAL DOMAIN ==============================# - if use_spline_mapping: # Create spline mapping by interpolation of analytical mapping map_analytic = mapping.get_callable_mapping() @@ -291,8 +276,6 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, geometry.export('geo.h5') domain = Domain.from_file('geo.h5') - # TODO (MCP 07.2024): check that mapping = domain.mapping ?? - else: # Only symbolic mapping is necessary domain = mapping(logical_domain) @@ -311,15 +294,12 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # Discrete physical domain and discrete DeRham sequence if use_spline_mapping: domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) - # V0_h = discretize(V0, domain_h) - derham_h = discretize(derham, domain_h) # , degree = degree) #, quad_order = [4, 4]) - F = map_analytic + derham_h = discretize(derham, domain_h) + F = list(domain_h.mappings.values()).pop() else: domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) - derham_h = discretize(derham, domain_h, degree=degree) # , quad_order = [4, 4]) - # V0_h = discretize(V0, domain_h, degree = degree) + derham_h = discretize(derham, domain_h, degree=degree) F = mapping.get_callable_mapping() - # F = mapping.get_callable_mapping() def phys_domain_integral(f_log): """ @@ -870,7 +850,7 @@ def Strang_update(dtau): N = 10 if show_figs: - V.plot_2d_decomposition(mapping.get_callable_mapping(), refine=N) + V0.plot_2d_decomposition(F, refine=N) P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index e69f6706d..76505621c 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -28,11 +28,10 @@ from sympde.topology.mapping import Mapping from psydac.api.discretization import discretize -from psydac.feec.polar.examples.utils_congapol import add_colorbar +from psydac.feec.polar.examples.utils_congapol import add_colorbar, create_tensor_spline_space from psydac.linalg.stencil import StencilVector, StencilMatrix from psydac.linalg.basic import LinearOperator from psydac.linalg.solvers import inverse -from psydac.fem.splines import SplineSpace from psydac.fem.tensor import TensorFemSpace from psydac.fem.basic import FemField from psydac.mapping.discrete import SplineMapping @@ -352,30 +351,18 @@ def run_poisson_2d(*, test_case, ncells, degree, mpi_size = mpi_comm.Get_size() mpi_rank = mpi_comm.Get_rank() - # Number of elements and spline degree - ne1, ne2 = ncells - p1, p2 = degree - periodic = [False, True] - # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - if use_spline_mapping: - # Create uniform grid - grid_1 = np.linspace(*model.domain_log.bounds1, num=ne1 + 1) - grid_2 = np.linspace(*model.domain_log.bounds2, num=ne2 + 1) - # Create 1D finite element spaces - V1 = SplineSpace(p1, grid=grid_1, periodic=periodic[0]) - V2 = SplineSpace(p2, grid=grid_2, periodic=periodic[1]) + # ==================== SPLINE SPACE FOR SPLINE MAPPINGS =======================# - # Create 2D tensor product finite element space - domain_decomposition = DomainDecomposition(ncells, periodic , comm=mpi_comm) - V = TensorFemSpace(domain_decomposition, V1, V2) + V = create_tensor_spline_space(ncells, degree, periodic, + (model.domain_log.bounds1, model.domain_log.bounds2), mpi_comm) - # ==================== MAPPING & PHYSICAL DOMAIN ==============================# - #TODO: maybe define a parent class Model - if use_spline_mapping: + #TODO: maybe define a parent class Model + + # ==================== MAPPING & PHYSICAL DOMAIN ==============================# # Create spline mapping by interpolation of analytical mapping map_analytic = model.mapping.get_callable_mapping() map_discrete = SplineMapping.from_mapping(V, map_analytic) @@ -550,8 +537,8 @@ def run_poisson_2d(*, test_case, ncells, degree, print('--------------------------------------------------') print(' RANK = {}'.format(mpi_rank)) print('--------------------------------------------------') - print('> Grid :: [{ne1},{ne2}]'.format(ne1=ne1, ne2=ne2)) - print('> Degree :: [{p1},{p2}]'.format(p1=p1, p2=p2)) + print('> Grid :: [{ne1},{ne2}]'.format(ne1=ncells[0], ne2=ncells[1])) + print('> Degree :: [{p1},{p2}]'.format(p1=degree[0], p2=degree[1])) print('> Penalization alpha :: {alpha} '.format(alpha=alpha)) print( '> CG info :: ',info ) print('> L2 norm solution :: {:.2e}'.format(errors.ref_l2)) diff --git a/psydac/feec/polar/examples/utils_congapol.py b/psydac/feec/polar/examples/utils_congapol.py index 977d78203..8a01c1821 100644 --- a/psydac/feec/polar/examples/utils_congapol.py +++ b/psydac/feec/polar/examples/utils_congapol.py @@ -1,5 +1,9 @@ import numpy as np +from psydac.ddm.cart import DomainDecomposition +from psydac.fem.splines import SplineSpace +from psydac.fem.tensor import TensorFemSpace + def print_map_polar_coeffs(map_discrete): """ @@ -117,3 +121,32 @@ def add_colorbar(im, ax, **kwargs): cbar = ax.get_figure().colorbar(im, cax=cax, **kwargs) return cbar +def create_tensor_spline_space(ncells, spline_degrees, periodic, bounds, mpi_comm=None): + """ + Create a 2D tensor-product spline finite element space on a rectangular + logical domain (e.g. with bounds ``[[0, R], [0, 2*pi]]``). + + Returns + ------- + TensorFemSpace + The 2D tensor-product spline finite element space. + """ + + # Number of elements and spline degree + ne1, ne2 = ncells + p1, p2 = spline_degrees + + # Create uniform grid + grid_1 = np.linspace(*bounds[0], num=ne1 + 1) + grid_2 = np.linspace(*bounds[1], num=ne2 + 1) + + # Create 1D finite element spaces + S1 = SplineSpace(p1, grid=grid_1, periodic=periodic[0]) + S2 = SplineSpace(p2, grid=grid_2, periodic=periodic[1]) + + # Create 2D tensor product finite element space + domain_decomposition = DomainDecomposition(ncells, periodic, comm=mpi_comm) + V = TensorFemSpace(domain_decomposition, S1, S2) + return V + + From 3d9e4c735a1b048971e8c07f73d15ec0c93030e3 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 21 May 2026 17:41:06 +0200 Subject: [PATCH 122/133] removed basic_fix variable --- psydac/feec/polar/examples/maxwell_2d.py | 33 ++++++------------------ 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 78f712b22..046f9b8c8 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -547,32 +547,15 @@ def run_study_L2_proj(): htheta = 2 * pi / ncells[1] # Mass matrices (StencilMatrix objects) - basic_fix = False - if basic_fix: - M1 = a1_h.assemble() - M2 = a2_h.assemble() - - M1[1, 1][0, :, :, :] = 0 - M_temp = M1[1, 1].transpose() - M_temp[0, :, :, :] = 0 - M1[1, 1] = M_temp.transpose() - M1[1, 1][0, :, 0, :] = 1e20 - - M2[0, :, :, :] = 0 - M_temp = M2.transpose() - M_temp[0, :, :, :] = 0 - M2 = M_temp.transpose() - M2[0, :, 0, :] = 1e20 - else: - # raw Mass matrices in W1 and W2 have unbounded values, this seems to be fine for the assembly but inverting these matrices leads to virtual nonsense... - # so they need to be regularized below - M1_raw = a1_h.assemble() - M2_raw = a2_h.assemble() - - # regularization of M1 and M2 so that they are bounded and invertible: - M1 = (htheta * hs) * (I1 - P1.T) @ (I1 - P1) + P1.T @ M1_raw @ P1 - M2 = (htheta * hs) * (I2 - P2.T) @ (I2 - P2) + P2.T @ M2_raw @ P2 + # raw Mass matrices in W1 and W2 have unbounded values, this seems to be fine for the assembly but inverting these matrices leads to virtual nonsense... + # so they need to be regularized below + M1_raw = a1_h.assemble() + M2_raw = a2_h.assemble() + + # regularization of M1 and M2 so that they are bounded and invertible: + M1 = (htheta * hs) * (I1 - P1.T) @ (I1 - P1) + P1.T @ M1_raw @ P1 + M2 = (htheta * hs) * (I2 - P2.T) @ (I2 - P2) + P2.T @ M2_raw @ P2 Pi0, Pi1, Pi2 = derham_h.projectors(nquads=[degree[0] + 10, degree[1] + 10]) From f6687dc9b81ca47d41ca470751e96cea9de07566 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Thu, 21 May 2026 18:08:38 +0200 Subject: [PATCH 123/133] fix error in L2 errors --- psydac/feec/polar/examples/maxwell_2d.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 046f9b8c8..7644474b2 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -882,10 +882,14 @@ def Strang_update(dtau): norm_l2_Ey = l2_norm_of(normy) norm_l2_Bz = l2_norm_of(normz) + E1_final = FemField(V1_s, coeffs=e[0]) + E2_final = FemField(V1_theta, coeffs=e[1]) + B_final = FemField(V2, coeffs=b) + # L2 errors - errx = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) - erry = lambda x1, x2: push_2d_hcurl(E_log.fields[0], E_log.fields[1], x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) - errz = lambda x1, x2: push_2d_l2(B_log, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) + errx = lambda x1, x2: push_2d_hcurl(E1_final, E2_final, x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) + erry = lambda x1, x2: push_2d_hcurl(E1_final, E2_final, x1, x2, F)[1] - Ey_ex_t(t, *F(x1, x2)) + errz = lambda x1, x2: push_2d_l2(B_final, x1, x2, F) - Bz_ex_t(t, *F(x1, x2)) error_l2_Ex = l2_norm_of(errx) / norm_l2_Ex error_l2_Ey = l2_norm_of(erry) / norm_l2_Ey From f5df0fca5eca01298aa60186301d31c3698b5c3c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Wed, 3 Jun 2026 10:21:45 +0200 Subject: [PATCH 124/133] remove unused functions --- psydac/feec/polar/examples/maxwell_2d.py | 28 ------------------------ 1 file changed, 28 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 7644474b2..e2d0d09c4 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -17,34 +17,6 @@ # ====================== TIME DISCRETIZATION ==================================# -def step_faraday_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, **kwargs): - """ - Exactly integrate the semi-discrete Faraday equation over one time-step: - - b_new = b - ∆t D1 P1 e - - """ - b -= dt * D1.dot(P1.dot(e)) - - -def step_ampere_2d(dt, e, b, M1, M2, D1, D1_T, P1, P1_T, P2, *, pc=None, tol=1e-7, verbose=False): - """ - Exactly integrate the semi-discrete Amperè equation over one time-step: - - e_new = e + ∆t (M1^{-1} P1^T D1^T M2) b - - """ - options = dict(tol=tol, verbose=verbose) - if pc: - from psydac.linalg.iterative_solvers import pcg as isolve - options['pc'] = pc - else: - from psydac.linalg.iterative_solvers import cg as isolve - - # b += 0 - e += dt * isolve(M1, P1_T.dot(D1_T.dot(M2.dot(b))), **options)[0] - - def compute_stable_dt(cfl, C_m, dC_m, V, tau=None, light_c=1): """ compute stable time step for a leap-frog Maxwell solver, From 5820abe471ddb7652fef91b82fbbfad5c1bf0d69 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 9 Jun 2026 14:38:48 +0200 Subject: [PATCH 125/133] a few small changes in Poisson --- psydac/feec/polar/examples/poisson_2d.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index 76505621c..d00774cb1 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -395,8 +395,6 @@ def run_poisson_2d(*, test_case, ncells, degree, rhs = LinearForm(v0, integral(domain, model.rho_log * v0)) - err_diff = model.phi_log - u0 - # ============================= DISCRETIZATION ================================# if use_spline_mapping: domain_h = discretize(domain, filename='geo.h5', comm=mpi_comm) @@ -507,7 +505,7 @@ def run_poisson_2d(*, test_case, ncells, degree, info = Sc_inv.get_info() elif smooth_method == 'None': pc = S.diagonal(inverse=True) - S_inv = inverse(S, 'pcg', pc=pc, tol=cgtol, maxiter=cgiter, verbose=verbose) + S_inv = inverse(S, 'cg', pc=pc, tol=cgtol, maxiter=cgiter, verbose=verbose) xsol = S_inv.dot(b) info = S_inv.get_info() t1 = time() @@ -519,6 +517,7 @@ def run_poisson_2d(*, test_case, ncells, degree, phi = FemField(V0_h, coeffs=xsol) phi.coeffs.update_ghost_regions() + t0 = time() errors = compute_errors(phi, phi_ref, M, S) t1 = time() timing['diagnostics'] = t1 - t0 @@ -740,8 +739,7 @@ def parse_input_arguments(): ) parser.add_argument('-v', - type=bool, - default=False, + action='store_true', dest='verbose', help='See CG iterations and L2 norm of the residuals' ) From 21be4da55d54ac6855250f7313b2339a145aa58c Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 9 Jun 2026 15:24:54 +0200 Subject: [PATCH 126/133] remove unused things from Maxwell --- psydac/feec/polar/examples/maxwell_2d.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index e2d0d09c4..1dc91aefa 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -12,7 +12,7 @@ import matplotlib.pyplot as plt from psydac.fem.basic import FemField -from utils_congapol import print_map_polar_coeffs, check_regular_ring_map, add_colorbar, create_tensor_spline_space +from utils_congapol import check_regular_ring_map, add_colorbar, create_tensor_spline_space # ====================== TIME DISCRETIZATION ==================================# @@ -137,18 +137,16 @@ def plot_curve_along_s(name, s_str, time_str, theta0, def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, splitting_order, shift_D, use_spline_mapping, tol, cfl=0.9, show_figs=True, study='maxwell_bessel', use_scipy=True, verbose=False): - import numpy as np from numpy import pi - from sympy import cos, sin, Tuple, exp, sqrt, atan2 + from sympy import cos, sin, Tuple, sqrt from sympde.topology import Square, Domain from sympde.topology.analytical_mapping import PolarMapping, TargetMapping from sympde.topology import Derham from sympde.topology import elements_of, element_of - from sympde.topology import NormalVector from sympde.topology.mapping import Mapping - from sympde.calculus import dot, cross + from sympde.calculus import dot from sympde.expr import integral from sympde.expr import BilinearForm, LinearForm @@ -178,13 +176,9 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # Mode number (m, n) = (2, 3) - # Courant parameter on uniform grid - Cp = 0.125 - # Exact/initial solution assert study in ['L2_proj', 'maxwell_bessel', 'maxwell_wave'] study_L2_proj = (study == 'L2_proj') - study_maxwell = not study_L2_proj visdir = f'plots_{study}' os.makedirs(visdir, exist_ok=True) @@ -273,14 +267,6 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, derham_h = discretize(derham, domain_h, degree=degree) F = mapping.get_callable_mapping() - def phys_domain_integral(f_log): - """ - Compute the integral of f over the physical domain. - Interface needed since FEM space `integral` function is over the symbolic domain... - """ - f_with_det = lambda eta1, eta2: f_log(eta1, eta2) * np.sqrt(F.metric_det(eta1, eta2)) - return derham_h.V0.integral(f_with_det) - def l2_norm_of(f_log): """ Compute the l2 norm of f over the physical domain. @@ -990,7 +976,7 @@ def Strang_update(dtau): parser.add_argument('-o', '--splitting_order', type=int, default=2, - choices=[2, 4, 6], + choices=[2, 4], help='Order of accuracy of operator splitting' ) From 75e0474dbdacd396c3cd179c828b70bd52163965 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 9 Jun 2026 15:40:30 +0200 Subject: [PATCH 127/133] docstring for run_study_L2_proj --- psydac/feec/polar/examples/maxwell_2d.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 1dc91aefa..d9fc5ba72 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -339,8 +339,11 @@ def build_eval_context(): def run_study_L2_proj(): """ + Study the H(curl) L2 projection on the mapped disk. Compares the standard and + filtered projected fields against a manufactured physical vector field, + prints L2 projection errors and plots the projected components and their errors. + Executed when --study is 'L2_proj'. Only for serial runs - """ omega = 4 print(f'studying L2 proj of f in H_0(curl) .. with omega = {omega}') @@ -356,8 +359,6 @@ def run_study_L2_proj(): fx_call = lambdify([xs, ys], f_x) fy_call = lambdify([xs, ys], f_y) - print('# compute tilde_f') - # tilde_f = derham_h.get_dual_dofs(space='V1', f=f_ex) v = element_of(derham.V1, name='u') l = LinearForm(v, integral(domain, dot(f_phys, v))) From ba35ee7929e5bf599bdf725607f7bf2a47de5ce2 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 9 Jun 2026 15:47:21 +0200 Subject: [PATCH 128/133] changed names of discrete derham spaces --- psydac/feec/polar/examples/maxwell_2d.py | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index d9fc5ba72..416b6beb9 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -362,7 +362,7 @@ def run_study_L2_proj(): v = element_of(derham.V1, name='u') l = LinearForm(v, integral(domain, dot(f_phys, v))) - lh = discretize(l, domain_h, V1) + lh = discretize(l, domain_h, V1_h) tilde_f = lh.assemble() # create fields and point to coeffs @@ -480,21 +480,21 @@ def run_study_L2_proj(): D1_T = D1.T # Extract spaces - V0, V1, V2 = derham_h.spaces + V0_h, V1_h, V2_h = derham_h.spaces - I1 = BlockLinearOperator(V1.coeff_space, V1.coeff_space) - I1[0, 0] = IdentityOperator(V1.coeff_space[0]) - I1[1, 1] = IdentityOperator(V1.coeff_space[1]) + I1 = BlockLinearOperator(V1_h.coeff_space, V1_h.coeff_space) + I1[0, 0] = IdentityOperator(V1_h.coeff_space[0]) + I1[1, 1] = IdentityOperator(V1_h.coeff_space[1]) - I2 = IdentityOperator(V2.coeff_space) + I2 = IdentityOperator(V2_h.coeff_space) # Conga projectors if smooth == 0: - P1 = C0PolarProjection_V1(V1, hbc=True) - P2 = C0PolarProjection_V2(V2) + P1 = C0PolarProjection_V1(V1_h, hbc=True) + P2 = C0PolarProjection_V2(V2_h) else: - P1 = C1PolarProjection_V1(V1, hbc=True) - P2 = C0PolarProjection_V2(V2) + P1 = C1PolarProjection_V1(V1_h, hbc=True) + P2 = C0PolarProjection_V2(V2_h) P1_T = P1.T P2_T = P2.T @@ -551,21 +551,21 @@ def run_study_L2_proj(): P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) - V1_s, V1_theta = V1.spaces + V1_s, V1_theta = V1_h.spaces Ex_field = FemField(V1_s, coeffs=e[0]) Ey_field = FemField(V1_theta, coeffs=e[1]) - B_field = FemField(V2, coeffs=b) + B_field = FemField(V2_h, coeffs=b) V1_s.export_fields('Ex.h5', Ex_field=Ex_field) V1_theta.export_fields('Ey.h5', Ey_field=Ey_field) - V2.export_fields('B.h5', B_field=B_field) + V2_h.export_fields('B.h5', B_field=B_field) if use_scipy: print(" -------------- SCIPY operators ------------ ") conga_curl_sp = (D1 @ P1).tosparse() - step_faraday_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, strong=True, + step_faraday_2d = SparseCurlAsOperator(W1=V1_h, W2=V2_h, strong_curl_sp=conga_curl_sp, strong=True, store_M1inv=False) - step_ampere_2d = SparseCurlAsOperator(W1=V1, W2=V2, strong_curl_sp=conga_curl_sp, M1=M1, M2=M2, + step_ampere_2d = SparseCurlAsOperator(W1=V1_h, W2=V2_h, strong_curl_sp=conga_curl_sp, M1=M1, M2=M2, strong=False) else: @@ -573,7 +573,7 @@ def run_study_L2_proj(): step_ampere_2d = M1_inv @ P1_T @ D1_T @ M2 step_faraday_2d = D1 @ P1 - Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2, tau=tend, light_c=1) + Nt, dt, norm_curlh = compute_stable_dt(cfl, C_m=step_ampere_2d, dC_m=step_faraday_2d, V=V2_h, tau=tend, light_c=1) # If final time is given, recompute number of time steps if tend is None: @@ -792,17 +792,17 @@ def Strang_update(dtau): N = 10 if show_figs: - V0.plot_2d_decomposition(F, refine=N) + V0_h.plot_2d_decomposition(F, refine=N) P1.dot(e.copy(), out=e) P2.dot(b.copy(), out=b) Ex_field = FemField(V1_s, coeffs=e[0]) Ey_field = FemField(V1_theta, coeffs=e[1]) - B_field = FemField(V2, coeffs=b) + B_field = FemField(V2_h, coeffs=b) V1_s.export_fields('Ex_final.h5', Ex_field=Ex_field) V1_theta.export_fields('Ey_final.h5', Ey_field=Ey_field) - V2.export_fields('B_final.h5', B_field=B_field) + V2_h.export_fields('B_final.h5', B_field=B_field) if eval_data is not None: Ex_serial, = V1_sx.import_fields('Ex_final.h5', 'Ex_field') @@ -843,7 +843,7 @@ def Strang_update(dtau): E1_final = FemField(V1_s, coeffs=e[0]) E2_final = FemField(V1_theta, coeffs=e[1]) - B_final = FemField(V2, coeffs=b) + B_final = FemField(V2_h, coeffs=b) # L2 errors errx = lambda x1, x2: push_2d_hcurl(E1_final, E2_final, x1, x2, F)[0] - Ex_ex_t(t, *F(x1, x2)) From b28334d568a4b6ffeaa5c1688e5d6a482e607a8d Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 9 Jun 2026 15:52:42 +0200 Subject: [PATCH 129/133] IdentityOperator --- psydac/feec/polar/examples/maxwell_2d.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 416b6beb9..6b3324f5c 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -482,10 +482,7 @@ def run_study_L2_proj(): # Extract spaces V0_h, V1_h, V2_h = derham_h.spaces - I1 = BlockLinearOperator(V1_h.coeff_space, V1_h.coeff_space) - I1[0, 0] = IdentityOperator(V1_h.coeff_space[0]) - I1[1, 1] = IdentityOperator(V1_h.coeff_space[1]) - + I1 = IdentityOperator(V1_h.coeff_space) I2 = IdentityOperator(V2_h.coeff_space) # Conga projectors @@ -1023,5 +1020,4 @@ def Strang_update(dtau): namespace = run_maxwell_2d_TE(**vars(args)) # Keep matplotlib windows open - # import matplotlib.pyplot as plt plt.show() From 234203bc363ed48710c672f17067d2526b4a55b8 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Jun 2026 08:15:51 +0200 Subject: [PATCH 130/133] add backend --- psydac/feec/polar/examples/maxwell_2d.py | 14 ++++++++------ psydac/feec/polar/examples/poisson_2d.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 6b3324f5c..8d6934282 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -151,11 +151,11 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, from sympde.expr import BilinearForm, LinearForm from psydac.api.discretization import discretize + from psydac.api.settings import PSYDAC_BACKENDS from psydac.feec.pull_push import push_2d_hcurl, push_2d_l2 from psydac.utilities.utils import refine_array_1d from psydac.linalg.solvers import inverse from psydac.linalg.basic import IdentityOperator - from psydac.linalg.block import BlockLinearOperator from psydac.mapping.discrete import SplineMapping from psydac.cad.geometry import Geometry @@ -166,6 +166,8 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, from analyticalTE import CircularCavitySolution # , constant_field from waveTE import GaussianSolution + backend = PSYDAC_BACKENDS['pyccel-gcc'] + # Radius physical domain R = 1. # 2.0 @@ -259,11 +261,11 @@ def run_maxwell_2d_TE(*, ncells, smooth, degree, nsteps, tend, # Discrete physical domain and discrete DeRham sequence if use_spline_mapping: - domain_h = discretize(domain, filename='geo.h5', comm = mpi_comm) + domain_h = discretize(domain, filename='geo.h5', comm=mpi_comm) derham_h = discretize(derham, domain_h) F = list(domain_h.mappings.values()).pop() else: - domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm = mpi_comm) + domain_h = discretize(domain, ncells=ncells, periodic=[False, True], comm=mpi_comm) derham_h = discretize(derham, domain_h, degree=degree) F = mapping.get_callable_mapping() @@ -362,7 +364,7 @@ def run_study_L2_proj(): v = element_of(derham.V1, name='u') l = LinearForm(v, integral(domain, dot(f_phys, v))) - lh = discretize(l, domain_h, V1_h) + lh = discretize(l, domain_h, V1_h, backend=backend) tilde_f = lh.assemble() # create fields and point to coeffs @@ -496,8 +498,8 @@ def run_study_L2_proj(): P2_T = P2.T # Discrete bilinear forms - a1_h = discretize(a1, domain_h, (derham_h.V1, derham_h.V1)) - a2_h = discretize(a2, domain_h, (derham_h.V2, derham_h.V2)) + a1_h = discretize(a1, domain_h, (derham_h.V1, derham_h.V1), backend=backend) + a2_h = discretize(a2, domain_h, (derham_h.V2, derham_h.V2), backend=backend) hs = R / ncells[0] htheta = 2 * pi / ncells[1] diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index d00774cb1..c11f0d34a 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -45,7 +45,7 @@ from psydac.feec.polar.conga_projections import C0PolarProjection_V0, C1PolarProjection_V0 -backend = PSYDAC_BACKENDS['python'] +backend = PSYDAC_BACKENDS['pyccel-gcc'] # ============================================================================== From 2dffb867de94172c74a52d0bc6cd89c74813f106 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Jun 2026 08:22:29 +0200 Subject: [PATCH 131/133] correct comments --- psydac/feec/polar/conga_projections.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/feec/polar/conga_projections.py b/psydac/feec/polar/conga_projections.py index 88885b428..715ea5e6c 100644 --- a/psydac/feec/polar/conga_projections.py +++ b/psydac/feec/polar/conga_projections.py @@ -144,11 +144,11 @@ def toarray(self): # --------- 1-FORMS CONGA PROJECTOR P1 ----------# # It is a BlockLinearOperator with 4 blocks Upper-Left (0, 0), Upper-Right (0, 1) -# Lower-Left (1, 0) and Lower-Right (1, 1). (0, 1) and (1, 0) are identical. +# Lower-Left (1, 0) and Lower-Right (1, 1). # # ______________________ # | | | -# | (0,0) | (1,0) | +# | (0,0) | (0,1) | # |___________|__________| # | | | # | (1,0) | (1,1) | @@ -823,7 +823,7 @@ def toarray(self): # # ______________________ # | | | -# | (0,0) | (1,0) | +# | (0,0) | (0,1) | # |___________|__________| # | | | # | (1,0) | (1,1) | From 801025fd0ef41bf23ebda536ea60f00d5554f7ac Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Jun 2026 10:10:42 +0200 Subject: [PATCH 132/133] write matrix vector mul in a better way --- psydac/feec/polar/examples/maxwell_2d.py | 36 ++++++++++-------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/psydac/feec/polar/examples/maxwell_2d.py b/psydac/feec/polar/examples/maxwell_2d.py index 8d6934282..ef252fa57 100644 --- a/psydac/feec/polar/examples/maxwell_2d.py +++ b/psydac/feec/polar/examples/maxwell_2d.py @@ -50,7 +50,7 @@ def vect_norm_2(vv): while not (conv or ncfl > max_ncfl): ncfl += 1 vv *= (1. / norm_vv) - CC_m.dot(vv.copy(), out=vv) + vv = CC_m @ vv norm_vv = vect_norm_2(vv) old_spectral_rho = spectral_rho spectral_rho = norm_vv.copy() # copy ?? @@ -372,20 +372,17 @@ def run_study_L2_proj(): fh = Pi1((fx_call, fy_call)) fh_filter = Pi1((fx_call, fy_call)) - fh_c = fh.coeffs - fh_filter_c = fh_filter.coeffs - M1_inv = inverse(M1, 'cg', verbose=verbose, tol=tol) print("using standard L2 projection") - M1_inv.dot(tilde_f, out=fh_c) - P1.dot(fh_c.copy(), out=fh_c) + fh_c = M1_inv @ tilde_f + fh_c = P1 @ fh_c print('fh_c:') print(fh_c.toarray()[:]) print("using filtered L2 projection") PTtilde_f = P1.T @ tilde_f - M1_inv.dot(PTtilde_f, out=fh_filter_c) - P1.dot(fh_filter_c.copy(), out=fh_filter_c) + fh_filter_c = M1_inv @ PTtilde_f + fh_filter_c = P1 @ fh_filter_c # compute error and exit @@ -544,11 +541,11 @@ def run_study_L2_proj(): b = B_log.coeffs if study == 'maxwell_wave': - D1.dot(e, out=b) + b = D1 @ e # Conga Projection - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + e = P1 @ e + b = P2 @ b V1_s, V1_theta = V1_h.spaces Ex_field = FemField(V1_s, coeffs=e[0]) @@ -737,22 +734,19 @@ def plot_fields_along_s(tstr): # SOLUTION # ============================================================================== - de = derham_h.V1.coeff_space.zeros() - db = derham_h.V2.coeff_space.zeros() - def Strang_update(dtau): # Strang splitting, 2nd order # b := b - dt/2 * curl e - step_faraday_2d.dot(e, out=db) + db = step_faraday_2d @ e b.mul_iadd(- dtau / 2, db) # e := e + dt * curl b - step_ampere_2d.dot(b, out=de) + de = step_ampere_2d @ b e.mul_iadd(dtau, de) # b := b - dt/2 * curl e - step_faraday_2d.dot(e, out=db) + db = step_faraday_2d @ e b.mul_iadd(- dtau / 2, db) # weights for Suzuki-Yoshida composition (4th-order splitting) @@ -784,8 +778,8 @@ def Strang_update(dtau): e.update_ghost_regions() b.update_ghost_regions() - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + e = P1 @ e + b = P2 @ b print('ts = {:4d}, t = {:8.4f}'.format(ts, t)) @@ -793,8 +787,8 @@ def Strang_update(dtau): if show_figs: V0_h.plot_2d_decomposition(F, refine=N) - P1.dot(e.copy(), out=e) - P2.dot(b.copy(), out=b) + e = P1 @ e + b = P2 @ b Ex_field = FemField(V1_s, coeffs=e[0]) Ey_field = FemField(V1_theta, coeffs=e[1]) From fb37885b467404e935a4f877c4d13d7adf4eebd3 Mon Sep 17 00:00:00 2001 From: Alisa Kirkinskaia Date: Tue, 16 Jun 2026 12:13:28 +0200 Subject: [PATCH 133/133] add comment for BC --- psydac/feec/polar/examples/poisson_2d.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/psydac/feec/polar/examples/poisson_2d.py b/psydac/feec/polar/examples/poisson_2d.py index c11f0d34a..29aba8734 100644 --- a/psydac/feec/polar/examples/poisson_2d.py +++ b/psydac/feec/polar/examples/poisson_2d.py @@ -474,6 +474,11 @@ def run_poisson_2d(*, test_case, ncells, degree, # Apply homogeneous Dirichlet boundary conditions for the conforming # smooth_method case 'polar' and non-conforming case 'None' # NOTE: this does not affect ghost regions + # For each angular index j on the last owned radial line, replace the assembled equation + # (S u)[e1, j] = b[e1, j] + # by the equation + # u[e1, j] = 0. + e1 = V0_h.coeff_space.ends[0] if e1 == V0_h.coeff_space.npts[0] - 1: if smooth_method in ('polar-std', 'polar-spec'): @@ -483,8 +488,8 @@ def run_poisson_2d(*, test_case, ncells, degree, bp[1][last, :] = 0. elif smooth_method == 'None': S[e1, :, :, :] = 0. - S[e1, :, 0, 0] = 1. - b[e1, :] = 0. + S[e1, :, 0, 0] = 1. #set diagonal entries to 1 + b[e1, :] = 0. #RHS is 0 at the outer radial boundary # ====================== SOLVE GALERKIN SYSTEM WITH CG ========================#