From 6e1000ffd8803d91833142241abd4cbaf69ea416 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 10 Mar 2023 15:37:03 +0100 Subject: [PATCH 01/77] added uptating ghost regions before assembling a form and a test showing where the error could show up --- psydac/api/fem.py | 4 ++ psydac/api/tests/test_assembly.py | 101 +++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/psydac/api/fem.py b/psydac/api/fem.py index 6cdbb9c19..4efaa9e3f 100644 --- a/psydac/api/fem.py +++ b/psydac/api/fem.py @@ -436,6 +436,8 @@ def assemble(self, *, reset=True, **kwargs): v = v[i] if isinstance(v, FemField): assert len(self.grid) == 1 + if not v.coeffs.ghost_regions_in_sync: + v.coeffs.update_ghost_regions() basis_v = BasisValues(v.space, nderiv = self.max_nderiv, trial=True, grid=self.grid[0]) bs, d, s, p = construct_test_space_arguments(basis_v) basis += bs @@ -968,6 +970,8 @@ def assemble(self, *, reset=True, **kwargs): i = self.get_space_indices_from_target(self.domain, self.target) v = v[i] if isinstance(v, FemField): + if not v.coeffs.ghost_regions_in_sync: + v.coeffs.update_ghost_regions() basis_v = BasisValues(v.space, nderiv = self.max_nderiv, trial=True, grid=self.grid) bs, d, s, p = construct_test_space_arguments(basis_v) basis += bs diff --git a/psydac/api/tests/test_assembly.py b/psydac/api/tests/test_assembly.py index f158f8f46..526db48c3 100644 --- a/psydac/api/tests/test_assembly.py +++ b/psydac/api/tests/test_assembly.py @@ -1,15 +1,17 @@ import pytest from sympy import pi, sin, cos, tan, atan, atan2 -from sympy import exp, sinh, cosh, tanh, atanh +from sympy import exp, sinh, cosh, tanh, atanh, Tuple from sympde.topology import Line, Square from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of +from sympde.topology import element_of, Derham from sympde.core import Constant from sympde.expr import BilinearForm from sympde.expr import LinearForm from sympde.expr import integral +from sympde.calculus import Dot +from psydac.linalg.solvers import inverse from psydac.api.discretization import discretize from psydac.fem.basic import FemField from psydac.api.settings import PSYDAC_BACKENDS @@ -163,6 +165,101 @@ def test_non_symmetric_BilinearForm(backend): print("PASSED") +def test_assembly_no_synchr_args(nc=4, deg=4, backend_language=None): + + ncells = [nc, nc] + degree = [deg, deg] + + domain = Square('OmegaLog_', bounds1 = (0.,1.), bounds2 = (0.,1.)) + domain_h = discretize(domain, ncells=ncells, periodic=[True,True]) + + derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree) + + # multi-patch (broken) spaces + V1h = derham_h.V1 + V2h = derham_h.V2 + + # broken (patch-wise) differential operators + bD0_b, bD1_b = derham_h.derivatives_as_matrices + + a = element_of(V1h.symbolic_space, name='a') + b = element_of(V1h.symbolic_space, name='b') + + expr = Dot(a,b) + + A = BilinearForm((a,b), integral(domain, expr)) + Ah = discretize(A, domain_h, (V1h,V1h), backend=PSYDAC_BACKENDS[backend_language]) + + dH1_b = Ah.assemble() + H1_b = inverse(dH1_b, 'cg', tol=1e-10) + + a = element_of(V2h.symbolic_space, name='a') + b = element_of(V2h.symbolic_space, name='b') + + expr = a*b + + A = BilinearForm((a,b), integral(domain, expr)) + Ah = discretize(A, domain_h, (V2h,V2h), backend=PSYDAC_BACKENDS[backend_language]) + + dH2_b = Ah.assemble() + H2_b = inverse(dH2_b, 'cg', tol=1e-10) + + u = element_of(V1h.symbolic_space, name='u') + rho = element_of(V2h.symbolic_space, name='rho') + f = element_of(V2h.symbolic_space, name='f') + g = element_of(V2h.symbolic_space, name='g') + h = element_of(V2h.symbolic_space, name='h') + + + #L2 proj rho u -> V1 + expr = g*h*rho + weight_int_prod = BilinearForm((g,h), integral(domain, expr)) + weight_int_prod_h = discretize(weight_int_prod, domain_h, (V2h,V2h), backend=PSYDAC_BACKENDS[backend_language]) + + expr = g*rho + int_prod = LinearForm(g, integral(domain, expr)) + int_prod_h = discretize(int_prod, domain_h, V2h, backend=PSYDAC_BACKENDS[backend_language]) + + + #initial solution + x,y = domain.coordinates + rho_init = 1 + u_init = Tuple(cos(2*pi*x) ,sin(2*pi*y)) + + expr = Dot(u_init, u) + l = LinearForm(u, integral(domain, expr)) + lh = discretize(l, domain_h, V1h, backend=PSYDAC_BACKENDS[backend_language]) + b = lh.assemble() + uh = H1_b.dot(b) + + f = element_of(V2h.symbolic_space, name='f') + expr = rho_init*f + lp = LinearForm(f, integral(domain, expr)) + lph = discretize(lp, domain_h, V2h, backend=PSYDAC_BACKENDS[backend_language]) + b = lph.assemble() + rhoh = H2_b.dot(b) + + expr = f + lp = LinearForm(f, integral(domain, expr)) + lph = discretize(lp, domain_h, V2h, backend=PSYDAC_BACKENDS[backend_language]) + b = lph.assemble() + const_1 = H2_b.dot(b) + + div = bD1_b + + rhoh1 = rhoh-div.dot(uh) + rhof1 = FemField(V2h, rhoh1) + rhoh2 = rhoh-div.dot(uh) + rhof2 = FemField(V2h, rhoh2) + weight_mass_matrix = weight_int_prod_h.assemble(rho=rhof1) + inte_bilin = const_1.dot(weight_mass_matrix.dot(const_1)) + + int_prod_rho = int_prod_h.assemble(rho=rhof2) + inte_lin = int_prod_rho.dot(const_1) + assert( abs(inte_bilin - 1.) < 1.e-9) + assert( abs(inte_lin - 1.) < 1.e-9) + #============================================================================== if __name__ == '__main__': test_field_and_constant(None) From 99364d7ef962d3786fc746b2398c3ba3a277fce4 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 10 Mar 2023 16:44:07 +0100 Subject: [PATCH 02/77] correction for the call of backups in the test --- psydac/api/tests/test_assembly.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/psydac/api/tests/test_assembly.py b/psydac/api/tests/test_assembly.py index 526db48c3..f4eaa696e 100644 --- a/psydac/api/tests/test_assembly.py +++ b/psydac/api/tests/test_assembly.py @@ -165,10 +165,12 @@ def test_non_symmetric_BilinearForm(backend): print("PASSED") -def test_assembly_no_synchr_args(nc=4, deg=4, backend_language=None): +def test_assembly_no_synchr_args(backend): - ncells = [nc, nc] - degree = [deg, deg] + kwargs = {'backend': PSYDAC_BACKENDS[backend]} if backend else {} + + ncells = [4, 4] + degree = [2, 2] domain = Square('OmegaLog_', bounds1 = (0.,1.), bounds2 = (0.,1.)) domain_h = discretize(domain, ncells=ncells, periodic=[True,True]) @@ -189,7 +191,7 @@ def test_assembly_no_synchr_args(nc=4, deg=4, backend_language=None): expr = Dot(a,b) A = BilinearForm((a,b), integral(domain, expr)) - Ah = discretize(A, domain_h, (V1h,V1h), backend=PSYDAC_BACKENDS[backend_language]) + Ah = discretize(A, domain_h, (V1h,V1h), **kwargs) dH1_b = Ah.assemble() H1_b = inverse(dH1_b, 'cg', tol=1e-10) @@ -200,7 +202,7 @@ def test_assembly_no_synchr_args(nc=4, deg=4, backend_language=None): expr = a*b A = BilinearForm((a,b), integral(domain, expr)) - Ah = discretize(A, domain_h, (V2h,V2h), backend=PSYDAC_BACKENDS[backend_language]) + Ah = discretize(A, domain_h, (V2h,V2h), **kwargs) dH2_b = Ah.assemble() H2_b = inverse(dH2_b, 'cg', tol=1e-10) @@ -215,11 +217,11 @@ def test_assembly_no_synchr_args(nc=4, deg=4, backend_language=None): #L2 proj rho u -> V1 expr = g*h*rho weight_int_prod = BilinearForm((g,h), integral(domain, expr)) - weight_int_prod_h = discretize(weight_int_prod, domain_h, (V2h,V2h), backend=PSYDAC_BACKENDS[backend_language]) + weight_int_prod_h = discretize(weight_int_prod, domain_h, (V2h,V2h), **kwargs) expr = g*rho int_prod = LinearForm(g, integral(domain, expr)) - int_prod_h = discretize(int_prod, domain_h, V2h, backend=PSYDAC_BACKENDS[backend_language]) + int_prod_h = discretize(int_prod, domain_h, V2h, **kwargs) #initial solution @@ -229,20 +231,20 @@ def test_assembly_no_synchr_args(nc=4, deg=4, backend_language=None): expr = Dot(u_init, u) l = LinearForm(u, integral(domain, expr)) - lh = discretize(l, domain_h, V1h, backend=PSYDAC_BACKENDS[backend_language]) + lh = discretize(l, domain_h, V1h, **kwargs) b = lh.assemble() uh = H1_b.dot(b) f = element_of(V2h.symbolic_space, name='f') expr = rho_init*f lp = LinearForm(f, integral(domain, expr)) - lph = discretize(lp, domain_h, V2h, backend=PSYDAC_BACKENDS[backend_language]) + lph = discretize(lp, domain_h, V2h, **kwargs) b = lph.assemble() rhoh = H2_b.dot(b) expr = f lp = LinearForm(f, integral(domain, expr)) - lph = discretize(lp, domain_h, V2h, backend=PSYDAC_BACKENDS[backend_language]) + lph = discretize(lp, domain_h, V2h, **kwargs) b = lph.assemble() const_1 = H2_b.dot(b) From 9d9e3d7c876ce3fb7882e5d59b9001935336f707 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 13 Mar 2023 15:55:26 +0100 Subject: [PATCH 03/77] added the Hvec projection, and the space on the derham sequence with the keyword get_vec --- psydac/api/discretization.py | 11 +++- psydac/api/feec.py | 54 +++++++++++++--- psydac/feec/global_projectors.py | 104 +++++++++++++++++++++++++++++++ psydac/feec/pull_push.py | 63 ++++++++++++++++++- 4 files changed, 217 insertions(+), 15 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 2c8cfa041..268cd2364 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -43,7 +43,7 @@ __all__ = ('discretize',) #============================================================================== -def discretize_derham(derham, domain_h, *args, **kwargs): +def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): ldim = derham.shape mapping = derham.spaces[0].domain.mapping @@ -52,7 +52,14 @@ def discretize_derham(derham, domain_h, *args, **kwargs): spaces = [discretize_space(V, domain_h, basis=basis, **kwargs) \ for V, basis in zip(derham.spaces, bases)] - return DiscreteDerham(mapping, *spaces) + if get_vec: + V0h = spaces[0] + X = VectorFunctionSpace('X', domain_h.domain, kind='h1') + Xh = ProductFemSpace(V0h, V0h) + Xh.symbolic_space = X + spaces.append(Xh) + + return DiscreteDerham(mapping, get_vec, *spaces) #============================================================================== def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): diff --git a/psydac/api/feec.py b/psydac/api/feec.py index daa0f1485..fcfd24b94 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -2,11 +2,11 @@ from psydac.feec.derivatives import Derivative_1D, Gradient_2D, Gradient_3D from psydac.feec.derivatives import ScalarCurl_2D, VectorCurl_2D, Curl_3D from psydac.feec.derivatives import Divergence_2D, Divergence_3D -from psydac.feec.global_projectors import Projector_H1, Projector_Hcurl +from psydac.feec.global_projectors import Projector_H1, Projector_Hcurl, Projector_H1vec from psydac.feec.global_projectors import Projector_Hdiv, Projector_L2 from psydac.feec.pull_push import pull_1d_h1, pull_1d_l2 -from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2 -from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2 +from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_v +from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_v __all__ = ('DiscreteDerham',) @@ -14,11 +14,19 @@ class DiscreteDerham(BasicDiscrete): """ Represent the discrete De Rham sequence. """ - def __init__(self, mapping, *spaces): + def __init__(self, mapping, get_vec=False, *spaces): - dim = len(spaces) - 1 + self.has_vec = get_vec + + if self.has_vec : + dim = len(spaces) - 2 + self._spaces = spaces[:-1] + self._Vvec = spaces[-1] + + else : + dim = len(spaces) - 1 + self._spaces = spaces self._dim = dim - self._spaces = spaces self._mapping = mapping if dim == 1: @@ -78,6 +86,11 @@ def V2(self): def V3(self): return self._spaces[3] + @property + def Vvec(self): + assert self.has_vec + return self._Vvec + @property def spaces(self): return self._spaces @@ -121,6 +134,9 @@ def projectors(self, *, kind='global', nquads=None): else: raise TypeError('projector of space type {} is not available'.format(kind)) + if self.has_vec : + Pvec = Projector_H1vec(self.Vvec) + if self.mapping: P0_m = lambda f: P0(pull_2d_h1(f, self.mapping)) P2_m = lambda f: P2(pull_2d_l2(f, self.mapping)) @@ -128,18 +144,36 @@ def projectors(self, *, kind='global', nquads=None): P1_m = lambda f: P1(pull_2d_hcurl(f, self.mapping)) elif kind == 'hdiv': P1_m = lambda f: P1(pull_2d_hdiv(f, self.mapping)) - return P0_m, P1_m, P2_m - return P0, P1, P2 + if self.has_vec : + Pvec_m = lambda f: Pvec(pull_2d_v(f, self.mapping)) + return P0_m, P1_m, P2_m, Pvec_m + else : + return P0_m, P1_m, P2_m + + if self.has_vec : + return P0, P1, P2, Pvec + else : + return P0, P1, P2 elif self.dim == 3: P0 = Projector_H1 (self.V0) P1 = Projector_Hcurl(self.V1, nquads) P2 = Projector_Hdiv (self.V2, nquads) P3 = Projector_L2 (self.V3, nquads) + if self.has_vec : + Pvec = Projector_H1vec(self.Vvec) if self.mapping: P0_m = lambda f: P0(pull_3d_h1 (f, self.mapping)) P1_m = lambda f: P1(pull_3d_hcurl(f, self.mapping)) P2_m = lambda f: P2(pull_3d_hdiv (f, self.mapping)) P3_m = lambda f: P3(pull_3d_l2 (f, self.mapping)) - return P0_m, P1_m, P2_m, P3_m - return P0, P1, P2, P3 + if self.has_vec : + Pvec_m = lambda f: Pvec(pull_3d_v(f, self.mapping)) + return P0_m, P1_m, P2_m, P3_m, Pvec_m + else : + return P0_m, P1_m, P2_m, P3_m + + if self.has_vec : + return P0, P1, P2, P3, Pvec + else : + return P0, P1, P2, P3 diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index 27c5499af..45738eec4 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -559,6 +559,63 @@ def __call__(self, fun): """ return super().__call__(fun) +class Projector_H1vec(GlobalProjector): + """ + Projector from H1^3 = H1 x H1 x H1 to a conforming finite element space, i.e. + a finite dimensional subspace of H1^3, constructed with tensor-product + B-splines in 2 or 3 dimensions. + This is a global projector constructed over a tensor-product grid in the + logical domain. The vertices of this grid are obtained as the tensor + product of the 1D splines' Greville points along each direction. + Parameters + ---------- + H1vec : ProductFemSpace + H1 x H1 x H1-conforming finite element space, codomain of the projection + operator. + """ + def _structure(self, dim): + if dim == 3: + return [ + ['I', 'I', 'I'], + ['I', 'I', 'I'], + ['I', 'I', 'I'] + ] + elif dim == 2: + return [ + ['I', 'I'], + ['I', 'I'] + ] + else: + raise NotImplementedError('The H1vec projector is only available in 2D or 3D.') + + def _function(self, dim): + if dim == 3: return evaluate_dofs_3d_vec + elif dim == 2: return evaluate_dofs_2d_vec + else: + raise NotImplementedError('The H1vec projector is only available in 2/3D.') + + #-------------------------------------------------------------------------- + def __call__(self, fun): + r""" + Project vector function onto the H1 x H1 x H1-conforming finite element + space. This happens in the logical domain $\hat{\Omega}$. + Parameters + ---------- + fun : list/tuple of callables + Scalar components of the real-valued vector function to be + projected, with arguments the coordinates (x_1, ..., x_N) of a + point in the logical domain. These correspond to the coefficients + of a vector-field. + $fun_i : \hat{\Omega} \mapsto \mathbb{R}$ with i = 1, ..., N. + Returns + ------- + field : FemField + Field obtained by projection (element of the H1^3-conforming + finite element space). This is also a real-valued vector function + in the logical domain. + """ + return super().__call__(fun) + #============================================================================== # 1D DEGREES OF FREEDOM #============================================================================== @@ -668,6 +725,24 @@ def evaluate_dofs_2d_2form( F[i1, i2] += quad_w1[i1, g1] * quad_w2[i2, g2] * \ f(quad_x1[i1, g1], quad_x2[i2, g2]) +#------------------------------------------------------------------------------ +def evaluate_dofs_2d_vec( + intp_x1, intp_x2, # interpolation points + F1, F2, # array of degrees of freedom (intent out) + f1, f2, # input scalar function (callable) + ): + + n1, n2 = F1.shape + for i1 in range(n1): + for i2 in range(n2): + F1[i1, i2] = f1(intp_x1[i1], intp_x2[i2]) + + n1, n2 = F2.shape + for i1 in range(n1): + for i2 in range(n2): + F2[i1, i2] = f2(intp_x1[i1], intp_x2[i2]) + + #============================================================================== # 3D DEGREES OF FREEDOM #============================================================================== @@ -786,3 +861,32 @@ def evaluate_dofs_3d_3form( F[i1, i2, i3] += \ quad_w1[i1, g1] * quad_w2[i2, g2] * quad_w3[i3, g3] * \ f(quad_x1[i1, g1], quad_x2[i2, g2], quad_x3[i3, g3]) + +#------------------------------------------------------------------------------ +def evaluate_dofs_3d_vec( + intp_x1, intp_x2, intp_x3, # interpolation points + F1, F2, F3, # array of degrees of freedom (intent out) + f1, f2, f3, # input scalar function (callable) + ): + + # evaluate input functions at interpolation points (make sure that points are in [0, 1]) + + + n1, n2, n3 = F1.shape + for i1 in range(n1): + for i2 in range(n2): + for i3 in range(n3): + F1[i1, i2, i3] = f1(intp_x1[i1], intp_x2[i2], intp_x3[i3]) + + n1, n2, n3 = F2.shape + for i1 in range(n1): + for i2 in range(n2): + for i3 in range(n3): + F2[i1, i2, i3] = f2(intp_x1[i1], intp_x2[i2], intp_x3[i3]) + + n1, n2, n3 = F3.shape + for i1 in range(n1): + for i2 in range(n2): + for i3 in range(n3): + F3[i1, i2, i3] = f3(intp_x1[i1], intp_x2[i2], intp_x3[i3]) + diff --git a/psydac/feec/pull_push.py b/psydac/feec/pull_push.py index 04e108d90..fc50e9091 100644 --- a/psydac/feec/pull_push.py +++ b/psydac/feec/pull_push.py @@ -65,6 +65,38 @@ def fun(xi1): #============================================================================== # 2D PULL-BACKS #============================================================================== +def pull_2d_v(funcs_ini, mapping): + #We should check if the metric terms are really the good ones! + + mapping = mapping.get_callable_mapping() + f1,f2 = mapping._func_eval + J_inv = mapping._jacobian_inv + + def fun1(xi1, xi2): + x = f1(xi1, xi2) + y = f2(xi1, xi2) + + a1_phys = funcs_ini[0](x, y) + a2_phys = funcs_ini[1](x, y) + + J_inv_value = J_inv(xi1, xi2) + value_1 = J_inv_value[0,0]*a1_phys + J_inv_value[0,1]*a2_phys + return value_1 + + def fun2(xi1, xi2): + x = f1(xi1, xi2) + y = f2(xi1, xi2) + + a1_phys = funcs_ini[0](x, y) + a2_phys = funcs_ini[1](x, y) + + J_inv_value = J_inv(xi1, xi2) + value_2 = J_inv_value[1,0]*a1_phys + J_inv_value[1,1]*a2_phys + return value_2 + + return fun1, fun2 + + def pull_2d_h1(func_ini, mapping): mapping = mapping.get_callable_mapping() @@ -171,12 +203,13 @@ def fun(xi1, xi2): # 3D PULL-BACKS #============================================================================== def pull_3d_v(funcs_ini, mapping): + #We should check if the metric terms are really the good ones! mapping = mapping.get_callable_mapping() f1,f2,f3 = mapping._func_eval J_inv = mapping._jacobian_inv - def fun(xi1, xi2, xi3): + def fun1(xi1, xi2, xi3): x = f1(xi1, xi2, xi3) y = f2(xi1, xi2, xi3) z = f3(xi1, xi2, xi3) @@ -187,11 +220,35 @@ def fun(xi1, xi2, xi3): J_inv_value = J_inv(xi1, xi2, xi3) value_1 = J_inv_value[0,0]*a1_phys + J_inv_value[0,1]*a2_phys + J_inv_value[0,2]*a3_phys + return value_1 + + def fun2(xi1, xi2, xi3): + x = f1(xi1, xi2, xi3) + y = f2(xi1, xi2, xi3) + z = f3(xi1, xi2, xi3) + + a1_phys = funcs_ini[0](x, y, z) + a2_phys = funcs_ini[1](x, y, z) + a3_phys = funcs_ini[2](x, y, z) + + J_inv_value = J_inv(xi1, xi2, xi3) value_2 = J_inv_value[1,0]*a1_phys + J_inv_value[1,1]*a2_phys + J_inv_value[1,2]*a3_phys + return value_2 + + def fun3(xi1, xi2, xi3): + x = f1(xi1, xi2, xi3) + y = f2(xi1, xi2, xi3) + z = f3(xi1, xi2, xi3) + + a1_phys = funcs_ini[0](x, y, z) + a2_phys = funcs_ini[1](x, y, z) + a3_phys = funcs_ini[2](x, y, z) + + J_inv_value = J_inv(xi1, xi2, xi3) value_3 = J_inv_value[2,0]*a1_phys + J_inv_value[2,1]*a2_phys + J_inv_value[2,2]*a3_phys - return value_1, value_2, value_3 + return value_3 - return fun + return fun1, fun2, fun3 #============================================================================== def pull_3d_h1(func_ini, mapping): From bb783cbd051c84abab6ab60987043620962d21d2 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 14 Mar 2023 14:38:49 +0100 Subject: [PATCH 04/77] added the basis projector operators, started to implement some tests --- psydac/feec/basis_projection_kernels.py | 508 +++++++++++++++++++++ psydac/feec/basis_projectors.py | 450 ++++++++++++++++++ psydac/feec/tests/test_basis_projectors.py | 55 +++ 3 files changed, 1013 insertions(+) create mode 100644 psydac/feec/basis_projection_kernels.py create mode 100644 psydac/feec/basis_projectors.py create mode 100644 psydac/feec/tests/test_basis_projectors.py diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py new file mode 100644 index 000000000..909768153 --- /dev/null +++ b/psydac/feec/basis_projection_kernels.py @@ -0,0 +1,508 @@ +def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', sub1 : 'int[:]', dim1_in : int, p1_out : int): + '''Kernel for assembling the matrix + + A_(i,j) = DOFS_i(fun*Lambda^in_j) , + + into the _data attribute of a StencilMatrix. + Here, DOFS_i are the degrees-of-freedom of the output space (codomain, must not be a product space), + Lambda^in_j are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + + Parameters + ---------- + mat : 2d float array + _data attribute of StencilMatrix. + + starts_in : int + Starting index of the input space (domain) of a distributed StencilMatrix. + + ends_in : int + Ending index of the input space (domain) of a distributed StencilMatrix. + + pads_in : int + Paddings of the input space (domain) of a distributed StencilMatrix. + + starts_out : int + Starting indices of the output space (codomain) of a distributed StencilMatrix. + + ends_out : int + Ending indices of the output space (codomain) of a distributed StencilMatrix. + + pads_out : int + Paddings of the output space (codomain) of a distributed StencilMatrix. + + fun_q : 1d float array + The function evaluated at the points (nq*ii + iq), where iq a local quadrature point of interval ii. + + wts1 : 2d float array + Quadrature weights in format (ii, iq). + + span1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq). + + basis1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). + + sub1 : 1d int array + Sub-interval indices in direction 1. + + dim1_in : int + Dimension of the first direction of the input space + + p1_out : int + Spline degree of the first direction of the output space + ''' + + from numpy import sum + + # Start/end indices and paddings for distributed stencil matrix of input space + # si1 = starts_in[0} + # ei1 = ends_in[0] + pi1 = pads_in[0] + + # Start/end indices for distributed stencil matrix of output space + so1 = starts_out[0] + # eo1 = ends_out[0] + po1 = pads_out[0] + + # Spline degrees of input space + p1 = basis1.shape[2] - 1 + + # number of quadrature points + nq1 = span1.shape[1] + + # Set output to zero + mat[:] = 0. + + # Dimensions of output space + dim1_out = span1.shape[0] - sum(sub1) + # Interval (either element or sub-interval thereof) + # ------------------------------------------------- + cumsub_i = 0 # Cumulative sub-interval index + for ii in range(span1.shape[0]): + cumsub_i += sub1[ii] + i = ii - cumsub_i # local DOF index + + # Quadrature point index in interval + # ---------------------------------- + for iq in range(nq1): + + funval = fun_q[nq1*ii + iq] * wts1[ii, iq] + + # Basis function of input space: + # ------------------------------ + for b1 in range(p1 + 1): + m = (span1[ii, iq] - p1 + b1) # global index + # basis value + value = funval * basis1[ii, iq, b1] + + # Find column index for _data: + if dim1_out <= dim1_in: + cut1 = p1 + else: + cut1 = p1_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col1 + col1_tmp = m - (i + so1) + if col1_tmp > cut1: + m = m - dim1_in + elif col1_tmp < -cut1: + m = m + dim1_in + # add padding + col1 = pi1 + m - (i + so1) + + # Row index: padding + local index. + mat[po1 + i, col1] += value + + +def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', sub1 : 'int[:]', sub2 : 'int[:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): + '''Kernel for assembling the matrix + + A_(ij,kl) = DOFS_ij(fun*Lambda^in_kl) , + + into the _data attribute of a StencilMatrix. + Here, DOFS_ij are the degrees-of-freedom of the output space (codomain, must not be a product space), + Lambda^in_kl are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + + Parameters + ---------- + mat : 4d float array + _data attribute of StencilMatrix. + + starts_in : 1d int array + Starting indices of the input space (domain) of a distributed StencilMatrix. + + ends_in : 1d int array + Ending indices of the input space (domain) of a distributed StencilMatrix. + + pads_in : 1d int array + Paddings of the input space (domain) of a distributed StencilMatrix. + + starts_out : 1d int array + Starting indices of the output space (codomain) of a distributed StencilMatrix. + + ends_out : 1d int array + Ending indices of the output space (codomain) of a distributed StencilMatrix. + + pads_out : 1d int array + Paddings of the output space (codomain) of a distributed StencilMatrix. + + fun_q : 2d float array + The function evaluated at the points (nq_i*ii + iq, nq_j*jj + jq), where iq a local quadrature point of interval ii. + + wts1 : 2d float array + Quadrature weights in direction eta1 in format (ii, iq). + + wts2 : 2d float array + Quadrature weights in direction eta2 in format (jj, jq). + + span1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq). + + span2 : 2d int array + Knot span indices in direction eta2 in format (jj, jq). + + basis1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). + + basis2 : 3d float array + Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). + + sub1 : 1d int array + Sub-interval indices in direction 1. + + sub2 : 1d int array + Sub-interval indices in direction 2. + + dim1_in : int + Dimension of the first direction of the input space + + dim2_in : int + Dimension of the second direction of the input space + + p1_out : int + Spline degree of the first direction of the output space + + p2_out : int + Spline degree of the second direction of the output space + ''' + + from numpy import sum + + # Start/end indices and paddings for distributed stencil matrix of input space + # si1 = starts_in[0] + # si2 = starts_in[1] + # ei1 = ends_in[0] + # ei2 = ends_in[1] + pi1 = pads_in[0] + pi2 = pads_in[1] + + # Start/end indices for distributed stencil matrix of output space + so1 = starts_out[0] + so2 = starts_out[1] + # eo1 = ends_out[0] + # eo2 = ends_out[1] + po1 = pads_out[0] + po2 = pads_out[1] + + # Spline degrees of input space + p1 = basis1.shape[2] - 1 + p2 = basis2.shape[2] - 1 + + # number of quadrature points + nq1 = span1.shape[1] + nq2 = span2.shape[1] + + # Set output to zero + mat[:] = 0. + + # Dimensions of output space + dim1_out = span1.shape[0] - sum(sub1) + dim2_out = span2.shape[0] - sum(sub2) + + # Interval (either element or sub-interval thereof) + # ------------------------------------------------- + cumsub_i = 0 # Cumulative sub-interval index + for ii in range(span1.shape[0]): + cumsub_i += sub1[ii] + i = ii - cumsub_i # local DOF index + + cumsub_j = 0 # Cumulative sub-interval index + for jj in range(span2.shape[0]): + cumsub_j += sub2[jj] + j = jj - cumsub_j # local DOF index + + # Quadrature point index in interval + # ---------------------------------- + for iq in range(nq1): + for jq in range(nq2): + + funval = fun_q[nq1*ii + iq, nq2*jj + jq] * wts1[ii, iq] * wts2[jj, jq] + + # Basis function of input space: + # ------------------------------ + for b1 in range(p1 + 1): + m = (span1[ii, iq] - p1 + b1) # global index + # basis value + val1 = funval * basis1[ii, iq, b1] + + # Find column index for _data: + if dim1_out <= dim1_in: + cut1 = p1 + else: + cut1 = p1_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col1 + col1_tmp = m - (i + so1) + if col1_tmp > cut1: + m = m - dim1_in + elif col1_tmp < -cut1: + m = m + dim1_in + # add padding + col1 = pi1 + m - (i + so1) + + for b2 in range(p2 + 1): + # global index + n = (span2[jj, jq] - p2 + b2) + value = val1 * basis2[jj, jq, b2] + + # Find column index for _data: + if dim2_out <= dim2_in: + cut2 = p2 + else: + cut2 = p2_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col2 + col2_tmp = n - (j + so2) + if col2_tmp > cut2: + n = n - dim2_in + elif col2_tmp < -cut2: + n = n + dim2_in + # add padding + col2 = pi2 + n - (j + so2) + + # Row index: padding + local index. + mat[po1 + i, po2 + j, col1, col2] += value + + + +def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', wts3 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', span3 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', basis3 : 'float[:,:,:]', sub1 : 'int[:]', sub2 : 'int[:]', sub3 : 'int[:]', dim1_in : int, dim2_in : int, dim3_in : int, p1_out : int, p2_out : int, p3_out : int): + '''Kernel for assembling the matrix + + A_(ijk,mno) = DOFS_ijk(fun*Lambda^in_mno) , + + into the _data attribute of a StencilMatrix. + Here, DOFS_ijk are the degrees-of-freedom of the output space (codomain, must not be a product space), + Lambda^in_mno are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + + Parameters + ---------- + mat : 6d float array + _data attribute of StencilMatrix. + + starts_in : 1d int array + Starting indices of the input space (domain) of a distributed StencilMatrix. + + ends_in : 1d int array + Ending indices of the input space (domain) of a distributed StencilMatrix. + + pads_in : 1d int array + Paddings of the input space (domain) of a distributed StencilMatrix. + + starts_out : 1d int array + Starting indices of the output space (codomain) of a distributed StencilMatrix. + + ends_out : 1d int array + Ending indices of the output space (codomain) of a distributed StencilMatrix. + + pads_out : 1d int array + Paddings of the output space (codomain) of a distributed StencilMatrix. + + fun_q : 3d float array + The function evaluated at the points (nq_i*ii + iq, nq_j*jj + jq, nq_k*kk + kq), where iq a local quadrature point of interval ii. + + wts1 : 2d float array + Quadrature weights in direction eta1 in format (ii, iq). + + wts2 : 2d float array + Quadrature weights in direction eta2 in format (jj, jq). + + wts3 : 2d float array + Quadrature weights in direction eta3 in format (kk, kq). + + span1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq). + + span2 : 2d int array + Knot span indices in direction eta2 in format (jj, jq). + + span3 : 2d int array + Knot span indices in direction eta3 in format (kk, kq). + + basis1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). + + basis2 : 3d float array + Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). + + basis3 : 3d float array + Values of p3 + 1 non-zero eta-3 basis functions at quadrature points in format (kk, kq, basis function). + + sub1 : 1d int array + Sub-interval indices in direction 1. + + sub2 : 1d int array + Sub-interval indices in direction 2. + + sub3 : 1d int array + Sub-interval indices in direction 3. + + dim1_in : int + Dimension of the first direction of the input space + + dim2_in : int + Dimension of the second direction of the input space + + dim3_in : int + Dimension of the third direction of the input space + + p1_out : int + Spline degree of the first direction of the output space + + p2_out : int + Spline degree of the second direction of the output space + + p3_out : int + Spline degree of the third direction of the output space + ''' + + from numpy import sum + + # Start/end indices and paddings for distributed stencil matrix of input space + # si1 = starts_in[0] + # si2 = starts_in[1] + # si3 = starts_in[2] + # ei1 = ends_in[0] + # ei2 = ends_in[1] + # ei3 = ends_in[2] + pi1 = pads_in[0] + pi2 = pads_in[1] + pi3 = pads_in[2] + + # Start/end indices for distributed stencil matrix of output space + so1 = starts_out[0] + so2 = starts_out[1] + so3 = starts_out[2] + # eo1 = ends_out[0] + # eo2 = ends_out[1] + # eo3 = ends_out[2] + po1 = pads_out[0] + po2 = pads_out[1] + po3 = pads_out[2] + + # Spline degrees of input space + p1 = basis1.shape[2] - 1 + p2 = basis2.shape[2] - 1 + p3 = basis3.shape[2] - 1 + + # number of quadrature points + nq1 = span1.shape[1] + nq2 = span2.shape[1] + nq3 = span3.shape[1] + + # Set output to zero + mat[:] = 0. + + # Dimensions of output space + dim1_out = span1.shape[0] - sum(sub1) + dim2_out = span2.shape[0] - sum(sub2) + dim3_out = span3.shape[0] - sum(sub3) + + # Interval (either element or sub-interval thereof) + # ------------------------------------------------- + cumsub_i = 0 # Cumulative sub-interval index + for ii in range(span1.shape[0]): + cumsub_i += sub1[ii] + i = ii - cumsub_i # local DOF index + + cumsub_j = 0 # Cumulative sub-interval index + for jj in range(span2.shape[0]): + cumsub_j += sub2[jj] + j = jj - cumsub_j # local DOF index + + cumsub_k = 0 # Cumulative sub-interval index + for kk in range(span3.shape[0]): + cumsub_k += sub3[kk] + k = kk - cumsub_k # local DOF index + + # Quadrature point index in interval + # ---------------------------------- + for iq in range(nq1): + for jq in range(nq2): + for kq in range(nq3): + + funval = fun_q[nq1*ii + iq, nq2*jj + jq, nq3*kk + kq] * wts1[ii, iq] * wts2[jj, jq] * wts3[kk, kq] + + # Basis function of input space: + # ------------------------------ + for b1 in range(p1 + 1): + m = (span1[ii, iq] - p1 + b1) # global index + # basis value + val1 = funval * basis1[ii, iq, b1] + + # Find column index for _data: + if dim1_out <= dim1_in: + cut1 = p1 + else: + cut1 = p1_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col1 + col1_tmp = m - (i + so1) + if col1_tmp > cut1: + m = m - dim1_in + elif col1_tmp < -cut1: + m = m + dim1_in + # add padding + col1 = pi1 + m - (i + so1) + + for b2 in range(p2 + 1): + # global index + n = (span2[jj, jq] - p2 + b2) + val2 = val1 * basis2[jj, jq, b2] + + # Find column index for _data: + if dim2_out <= dim2_in: + cut2 = p2 + else: + cut2 = p2_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col2 + col2_tmp = n - (j + so2) + if col2_tmp > cut2: + n = n - dim2_in + elif col2_tmp < -cut2: + n = n + dim2_in + # add padding + col2 = pi2 + n - (j + so2) + + for b3 in range(p3 + 1): + # global index + o = (span3[kk, kq] - p3 + b3) + value = val2 * basis3[kk, kq, b3] + + # Find column index for _data: + if dim3_out <= dim3_in: + cut3 = p3 + else: + cut3 = p3_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col3 + col3_tmp = o - (k + so3) + if col3_tmp > cut3: + o = o - dim3_in + elif col3_tmp < -cut3: + o = o + dim3_in + # add padding + col3 = pi3 + o - (k + so3) + + # Row index: padding + local index. + mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py new file mode 100644 index 000000000..c4105eac3 --- /dev/null +++ b/psydac/feec/basis_projectors.py @@ -0,0 +1,450 @@ +import numpy as np + +from psydac.linalg.stencil import StencilMatrix +from psydac.linalg.block import BlockLinearOperator +from psydac.linalg.basic import Vector +from psydac.fem.basic import FemSpace +from psydac.fem.tensor import TensorFemSpace +from psydac.feec.global_projectors import GlobalProjector +from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL +from psydac.linalg.basic import LinearOperator +from psydac.feec import basis_projection_kernels + + +class BasisProjectionOperator(LinearOperator): + """ + Class for "basis projection operators" PI_ijk(fun Lambda_mno) in the general form BP * P * DOF * EV^T * BV^T. + + Parameters + ---------- + P : struphy.psydac_api.projectors.Projector + Global commuting projector mapping into TensorFemSpace/ProductFemSpace W = P.space (codomain of operator). + + V : psydac.fem.basic.FemSpace + Finite element spline space (domain, input space). + + fun : list + Weight function(s) (callables) in a 2d list of shape corresponding to number of components of domain/codomain. + + transposed : bool + Whether to assemble the transposed operator. + """ + + def __init__(self, P, V, fun, transposed=False): + + # only for M1 Mac users + PSYDAC_BACKEND_GPYCCEL['flags'] = '-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none' + + assert isinstance(P, GlobalProjector) + assert isinstance(V, FemSpace) + + self._P = P + self._V = V + + + self._fun = fun + self._transposed = transposed + self._dtype = V.vector_space.dtype + + # set domain and codomain symbolic names + if hasattr(P.space.symbolic_space, 'name'): + P_name = P.space.symbolic_space.name + else: + P_name = 'H1vec' + + if hasattr(V.symbolic_space, 'name'): + V_name = V.symbolic_space.name + else: + V_name = 'H1vec' + + if transposed: + self._domain_symbolic_name = P_name + self._codomain_symbolic_name = V_name + else: + self._domain_symbolic_name = V_name + self._codomain_symbolic_name = P_name + + # ============= assemble tensor-product dof matrix ======= + dof_mat = BasisProjectionOperator.assemble_mat( + P, V, fun) + # ======================================================== + + self._dof_operator = dof_mat + + if transposed: + self._dof_operator = self._dof_operator.transpose() + + # set domain and codomain + self._domain = self.dof_operator.domain + self._codomain = self.dof_operator.codomain + + # temporary vectors for dot product + self._tmp_dom = self._dof_operator.domain.zeros() + self._tmp_codom = self._dof_operator.codomain.zeros() + + @property + def domain(self): + """ Domain vector space (input) of the operator. + """ + return self._domain + + @property + def codomain(self): + """ Codomain vector space (input) of the operator. + """ + return self._codomain + + @property + def dtype(self): + """ Datatype of the operator. + """ + return self._dtype + + @property + def tosparse(self): + raise NotImplementedError() + + @property + def toarray(self): + raise NotImplementedError() + + @property + def transposed(self): + """ If the transposed operator is in play. + """ + return self._transposed + + @property + def dof_operator(self): + """ The degrees of freedom operator as composite linear operator containing polar extraction and boundary operators. + """ + return self._dof_operator + + def dot(self, v, out=None, tol=1e-14, maxiter=1000, verbose=False): + """ + Applies the basis projection operator to the FE coefficients v. + + Parameters + ---------- + v : psydac.linalg.basic.Vector + Vector the operator shall be applied to. + + out : psydac.linalg.basic.Vector, optional + If given, the output will be written in-place into this vector. + + tol : float, optional + Stop tolerance in iterative solve (only used in polar case). + + maxiter : int, optional + Maximum number of iterations in iterative solve (only used in polar case). + + verbose : bool, optional + Whether to print some information in each iteration in iterative solve (only used in polar case). + + Returns + ------- + out : psydac.linalg.basic.Vector + The output (codomain) vector. + """ + + assert isinstance(v, Vector) + assert v.space == self.domain + + if out is None: + + if self.transposed: + # 1. apply inverse transposed inter-/histopolation matrix, 2. apply transposed dof operator + out = self.dof_operator.dot(self._P.solver.solve(v, transposed=True)) + else: + # 1. apply dof operator, 2. apply inverse inter-/histopolation matrix + out = self._P.solver.solve(self.dof_operator.dot(v)) + + else: + + assert isinstance(out, Vector) + assert out.space == self.codomain + + if self.transposed: + # 1. apply inverse transposed inter-/histopolation matrix, 2. apply transposed dof operator + self._P.solver.solve(v, out=self._tmp_dom, transposed=True) + self.dof_operator.dot(self._tmp_dom, out=out) + else: + # 1. apply dof operator, 2. apply inverse inter-/histopolation matrix + self.dof_operator.dot(v, out=self._tmp_codom) + self._P.solver.solve(self._tmp_codom, out=out) + + return out + + def transpose(self): + """ + Returns the transposed operator. + """ + return BasisProjectionOperator(self._P, self._V, self._fun, + self._V_extraction_op, self._V_boundary_op, + not self.transposed, self._polar_shift) + + @staticmethod + def assemble_mat(P, V, fun): + """ + Assembles the tensor-product DOF matrix sigma_i(fun*Lambda_j), where i=(i1, i2, ...) and j=(j1, j2, ...) depending on the number of spatial dimensions (1d, 2d or 3d). + + Parameters + ---------- + P : GlobalProjector + The psydac global tensor product projector defining the space onto which the input shall be projected. + + V : TensorFemSpace | ProductFemSpace + The spline space which shall be projected. + + fun : list + Weight function(s) (callables) in a 2d list of shape corresponding to number of components of domain/codomain. + + Returns + ------- + dof_mat : StencilMatrix | BlockLinearOperator + Degrees of freedom matrix in the full tensor product setting. + """ + + # input space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(V, TensorFemSpace): + _Vspaces = [V.vector_space] + _V1ds = [V.spaces] + else: + _Vspaces = V.vector_space + _V1ds = [comp.spaces for comp in V.spaces] + + # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(P.space, TensorFemSpace): + _Wspaces = [P.space.vector_space] + _W1ds = [P.space.spaces] + else: + _Wspaces = P.space.vector_space + _W1ds = [comp.spaces for comp in P.space.spaces] + + # retrieve number of quadrature points of each component (=1 for interpolation) + _nqs = [[P.grid_x[comp][direction].shape[1] + for direction in range(V.ldim)] for comp in range(len(_W1ds))] + + # blocks of dof matrix + blocks = [] + + # ouptut vector space (codomain), row of block + for Wspace, W1d, nq, fun_line in zip(_Wspaces, _W1ds, _nqs, fun): + blocks += [[]] + _Wdegrees = [space.degree for space in W1d] + + # input vector space (domain), column of block + for Vspace, V1d, f in zip(_Vspaces, _V1ds, fun_line): + + # instantiate cell of block matrix + dofs_mat = StencilMatrix( + Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) + + _starts_in = np.array(dofs_mat.domain.starts) + _ends_in = np.array(dofs_mat.domain.ends) + _pads_in = np.array(dofs_mat.domain.pads) + + _starts_out = np.array(dofs_mat.codomain.starts) + _ends_out = np.array(dofs_mat.codomain.ends) + _pads_out = np.array(dofs_mat.codomain.pads) + + _ptsG, _wtsG, _spans, _bases, _subs = prepare_projection_of_basis( + V1d, W1d, _starts_out, _ends_out, nq) + + _ptsG = [pts.flatten() for pts in _ptsG] + + _Vnbases = [space.nbasis for space in V1d] + + # Evaluate weight function at quadrature points + pts = np.meshgrid(*_ptsG, indexing='ij') + _fun_q = f(*pts).copy() + + # Call the kernel if weight function is not zero + if np.any(np.abs(_fun_q) > 1e-14): + + kernel = getattr( + basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd') + + kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, + _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_subs, *_Vnbases, *_Wdegrees) + + blocks[-1] += [dofs_mat] + + else: + blocks[-1] += [None] + + # build BlockLinearOperator (if necessary) and return + if len(blocks) == len(blocks[0]) == 1: + if blocks[0][0] is not None: + return blocks[0][0] + else: + return dofs_mat + else: + return BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) + + +def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): + '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. + + Parameters + ---------- + V1d : 3-list + Three SplineSpace objects from Psydac from the input space (to be projected). + + W1d : 3-list + Three SplineSpace objects from Psydac from the output space (projected onto). + + starts_out : 3-list + Global starting indices of process. + + ends_out : 3-list + Global ending indices of process. + + n_quad : 3_list + Number of quadrature points per histpolation interval. If not given, is set to V1d.degree + 1. + + Returns + ------- + ptsG : 3-tuple of 2d float arrays + Quadrature points (or Greville points for interpolation) in each dimension in format (interval, quadrature point). + + wtsG : 3-tuple of 2d float arrays + Quadrature weights (or ones for interpolation) in each dimension in format (interval, quadrature point). + + spans : 3-tuple of 2d int arrays + Knot span indices in each direction in format (n, nq). + + bases : 3-tuple of 3d float arrays + Values of p + 1 non-zero eta basis functions at quadrature points in format (n, nq, basis).''' + + import psydac.core.bsplines as bsp + + x_grid, subs, pts, wts, spans, bases = [], [], [], [], [], [] + + # Loop over direction, prepare point sets and evaluate basis functions + direction = 0 + for space_in, space_out, s, e in zip(V1d, W1d, starts_out, ends_out): + + greville_loc = space_out.greville[s: e + 1].copy() + histopol_loc = space_out.histopolation_grid[s: e + 2].copy() + + # make sure that greville points used for interpolation are in [0, 1] + assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) + + # k += 1 + # print(f'\nrank: {self._mpi_comm.Get_rank()} | Direction {k}, space_out attributes:') + # # # print('--------------------------------') + # print(f'rank: {self._mpi_comm.Get_rank()} | breaks : {space_out.breaks}') + # # # print(f'rank: {self._mpi_comm.Get_rank()} | degree : {space_out.degree}') + # # # print(f'rank: {self._mpi_comm.Get_rank()} | kind : {space_out.basis}') + # # print(f'rank: {self._mpi_comm.Get_rank()} | greville : {space_out.greville}') + # # print(f'rank: {self._mpi_comm.Get_rank()} | greville[s:e+1] : {greville_loc}') + # # # print(f'rank: {self._mpi_comm.Get_rank()} | ext_greville : {space_out.ext_greville}') + # print(f'rank: {self._mpi_comm.Get_rank()} | histopol_grid : {space_out.histopolation_grid}') + # print(f'rank: {self._mpi_comm.Get_rank()} | histopol_loc : {histopol_loc}') + # print(f'rank: {self._mpi_comm.Get_rank()} | dim W: {space_out.nbasis}') + # # # print(f'rank: {self._mpi_comm.Get_rank()} | project' + V1d[0].basis + V1d[1].basis + V1d[2].basis + ' to ' + W1d[0].basis + W1d[1].basis + W1d[2].basis) + + # interpolation + if space_out.basis == 'B': + x_grid += [greville_loc] + pts += [greville_loc[:, None]] + wts += [np.ones(pts[-1].shape, dtype=float)] + + # sub-interval index is always 0 for interpolation. + subs += [np.zeros(pts[-1].shape[0], dtype=int)] + + # histopolation + elif space_out.basis == 'M': + + if space_out.degree % 2 == 0: + union_breaks = space_out.breaks + else: + union_breaks = space_out.breaks[:-1] + + # Make union of Greville and break points + tmp = set(np.round_(space_out.histopolation_grid, decimals=14)).union( + np.round_(union_breaks, decimals=14)) + + tmp = list(tmp) + tmp.sort() + tmp_a = np.array(tmp) + + x_grid += [tmp_a[np.logical_and(tmp_a >= np.min( + histopol_loc) - 1e-14, tmp_a <= np.max(histopol_loc) + 1e-14)]] + + # determine subinterval index (= 0 or 1): + subs += [np.zeros(x_grid[-1][:-1].size, dtype=int)] + for n, x_h in enumerate(x_grid[-1][:-1]): + add = 1 + for x_g in histopol_loc: + if abs(x_h - x_g) < 1e-14: + add = 0 + subs[-1][n] += add + + # Gauss - Legendre quadrature points and weights + if n_quad is None: + # products of basis functions are integrated exactly + nq = space_in.degree + 1 + else: + nq = n_quad[direction] + + pts_loc, wts_loc = np.polynomial.legendre.leggauss(nq) + + x, w = bsp.quadrature_grid(x_grid[-1], pts_loc, wts_loc) + + pts += [x % 1.] + wts += [w] + + #print(f'rank: {self._mpi_comm.Get_rank()} | Direction {k}, x_grid : {x_grid[-1]}') + + # Knot span indices and V-basis functions evaluated at W-point sets + s, b = get_span_and_basis(pts[-1], space_in) + + spans += [s] + bases += [b] + + direction += 1 + + return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(subs) + + +def get_span_and_basis(pts, space): + '''Compute the knot span index and the values of p + 1 basis function at each point in pts. + + Parameters + ---------- + pts : np.array + 2d array of points (interval, quadrature point). + + space : SplineSpace + Psydac object, the 1d spline space to be projected. + + Returns + ------- + span : np.array + 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. + + basis : np.array + 3d array of values of basis functions indexed by (n, nq, basis function). + ''' + + import psydac.core.bsplines as bsp + + # Extract knot vectors, degree and kind of basis + T = space.knots + p = space.degree + + span = np.zeros(pts.shape, dtype=int) + basis = np.zeros((*pts.shape, p + 1), dtype=float) + + for n in range(pts.shape[0]): + for nq in range(pts.shape[1]): + # avoid 1. --> 0. for clamped interpolation + x = pts[n, nq] % (1. + 1e-14) + span_tmp = bsp.find_span(T, p, x) + basis[n, nq, :] = bsp.basis_funs_all_ders( + T, p, x, span_tmp, 0, normalization=space.basis) + span[n, nq] = span_tmp # % space.nbasis + + return span, basis diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py new file mode 100644 index 000000000..26f1759d6 --- /dev/null +++ b/psydac/feec/tests/test_basis_projectors.py @@ -0,0 +1,55 @@ +from sympde.topology import Square +from psydac.feec.multipatch.api import discretize +from sympde.topology import Derham +from psydac.feec.basis_projectors import BasisProjectionOperator +import numpy as np + +def test_basis_projector(): + ### INITIALISATION ### + domain = Square() + ncells = (4,4) + degree = (2,2) + nquads = [4*(d + 1) for d in degree] + perio = [True,True] + domain_h = discretize(domain, ncells=ncells, periodic=perio) + + derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + Xh = derham_h.Vvec + + P0, P1, P2, PX = derham_h.projectors(nquads=nquads) + f_x_plus_y = lambda x, y : x+y + f_cos_x_sin_y = lambda x,y : np.cos(x)*np.sin(y) + f_exp_x_exp_2y = lambda x,y : np.exp(x)*np.exp(2*y) + f_x2_plus_y = lambda x,y : x**2+y + + + ### TEST V0->V0 ### + fun = [[f_x_plus_y]] + P0_0fv = BasisProjectionOperator(P0, V0h, fun) + + const_1 = P0(lambda x,y :1) + + sol_with_op = P0_0fv.dot(const_1.coeffs) + sol_no_op = P0(f_x_plus_y) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + f_test = P0(f_cos_x_sin_y) + sol_with_op = P0_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_x_plus_y(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + + ### TEST V1 -> V0 ### + fun = [[f_x_plus_y,f_x2_plus_y]] + f_test = P1([f_cos_x_sin_y, f_exp_x_exp_2y]) + P1_0fv = BasisProjectionOperator(P0, V1h, fun) + sol_with_op = P1_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_x_plus_y(x,y)*f_test[0](x,y)+f_x2_plus_y(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + +if __name__ == '__main__': + test_basis_projector() \ No newline at end of file From 379c5b70b00867a0e2e1f8c558332860af2d7d3c Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 14 Mar 2023 18:11:14 +0100 Subject: [PATCH 05/77] added tests for the basis projectors, there seems to be a problem in periodic case --- psydac/feec/tests/test_basis_projectors.py | 187 ++++++++++++++++++--- 1 file changed, 161 insertions(+), 26 deletions(-) diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index 26f1759d6..10a2b13df 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -2,15 +2,23 @@ from psydac.feec.multipatch.api import discretize from sympde.topology import Derham from psydac.feec.basis_projectors import BasisProjectionOperator +from psydac.fem.basic import FemField + import numpy as np -def test_basis_projector(): +import pytest + +@pytest.mark.parametrize('nc', [4, 8, 15]) +@pytest.mark.parametrize('deg', [2,3]) +@pytest.mark.parametrize('perio', [[True, True], [True, False], [False, False]]) + +def test_basis_projector_2d(nc, deg, perio): ### INITIALISATION ### domain = Square() - ncells = (4,4) - degree = (2,2) + ncells = (nc,nc) + degree = (deg,deg) nquads = [4*(d + 1) for d in degree] - perio = [True,True] + perio = perio domain_h = discretize(domain, ncells=ncells, periodic=perio) derham = Derham(domain, ["H1", "Hdiv", "L2"]) @@ -21,35 +29,162 @@ def test_basis_projector(): Xh = derham_h.Vvec P0, P1, P2, PX = derham_h.projectors(nquads=nquads) - f_x_plus_y = lambda x, y : x+y - f_cos_x_sin_y = lambda x,y : np.cos(x)*np.sin(y) - f_exp_x_exp_2y = lambda x,y : np.exp(x)*np.exp(2*y) - f_x2_plus_y = lambda x,y : x**2+y - + #Bunch of (1,1)-periodic function for tests + f_1 = lambda x, y : x*(x-1)+3 + f_2 = lambda x, y : np.cos(2*np.pi*x) + f_3 = lambda x, y : np.sin(2*np.pi*x)*y*(y-1) + f_4 = lambda x, y : x*(x-1)*y*(y-1) + f_5 = lambda x, y : np.cos(2*np.pi*x)*np.sin(2*np.pi*y) + f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) + f_7 = lambda x, y : np.exp(y)+np.exp(1-y) + ### TEST V0 -> V1 ### + fun = [[f_6],[f_5]] + f_test = P0(f_3) + P0_1fv = BasisProjectionOperator(P1, V0h, fun) + sol_with_op = P0_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_6(x,y)*f_test(x,y),lambda x, y : f_5(x,y)*f_test(x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + return ### TEST V0->V0 ### - fun = [[f_x_plus_y]] + fun = [[f_1]] + f_test = P0(f_2) P0_0fv = BasisProjectionOperator(P0, V0h, fun) - - const_1 = P0(lambda x,y :1) - - sol_with_op = P0_0fv.dot(const_1.coeffs) - sol_no_op = P0(f_x_plus_y) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - f_test = P0(f_cos_x_sin_y) sol_with_op = P0_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_x_plus_y(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - + sol_no_op = P0(lambda x, y : f_1(x,y)*f_test(x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V1 -> V0 ### - fun = [[f_x_plus_y,f_x2_plus_y]] - f_test = P1([f_cos_x_sin_y, f_exp_x_exp_2y]) + fun = [[f_3,f_4]] + f_test = P1([f_1, f_5]) P1_0fv = BasisProjectionOperator(P0, V1h, fun) sol_with_op = P1_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_x_plus_y(x,y)*f_test[0](x,y)+f_x2_plus_y(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + sol_no_op = P0(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V0 ### + fun = [[f_6]] + f_test = P2(f_3) + P2_0fv = BasisProjectionOperator(P0, V2h, fun) + sol_with_op = P2_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_6(x,y)*f_test(x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V0 ### + fun = [[f_4,f_1]] + f_test = PX([f_4,f_5]) + PX_0fv = BasisProjectionOperator(P0, Xh, fun) + sol_with_op = PX_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V1 ### + fun = [[f_2,f_4],[f_5,f_1]] + f_test = P1([f_3,f_7]) + P1_1fv = BasisProjectionOperator(P1, V1h, fun) + sol_with_op = P1_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V1 ### + fun = [[f_4],[f_7]] + f_test = P2(f_1) + P2_1fv = BasisProjectionOperator(P1, V2h, fun) + sol_with_op = P2_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V1 ### + fun = [[f_3,f_6],[f_5,f_2]] + f_test = PX([f_3,f_1]) + PX_1fv = BasisProjectionOperator(P1, Xh, fun) + sol_with_op = PX_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0->V2 ### + fun = [[f_4]] + P0_2fv = BasisProjectionOperator(P2, V0h, fun) + f_test = P0(f_2) + sol_with_op = P0_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_4(x,y)*f_test(x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V2 ### + fun = [[f_5,f_2]] + f_test = P1([f_1, f_5]) + P1_2fv = BasisProjectionOperator(P2, V1h, fun) + sol_with_op = P1_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V2 ### + fun = [[f_1]] + f_test = P2(f_3) + P2_2fv = BasisProjectionOperator(P2, V2h, fun) + sol_with_op = P2_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_1(x,y)*f_test(x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V2 ### + fun = [[f_3,f_7]] + f_test = PX([f_4,f_5]) + PX_2fv = BasisProjectionOperator(P2, Xh, fun) + sol_with_op = PX_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0 -> X ### + fun = [[f_4],[f_1]] + f_test = P0(f_2) + P0_Xfv = BasisProjectionOperator(PX, V0h, fun) + sol_with_op = P0_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> X ### + fun = [[f_1,f_3],[f_5,f_7]] + f_test = P1([f_4,f_5]) + P1_Xfv = BasisProjectionOperator(PX, V1h, fun) + sol_with_op = P1_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> X ### + fun = [[f_2],[f_6]] + f_test = P2(f_1) + P2_Xfv = BasisProjectionOperator(PX, V2h, fun) + sol_with_op = P2_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> X ### + fun = [[f_1,f_2],[f_3,f_4]] + f_test = PX([f_5,f_6]) + PX_Xfv = BasisProjectionOperator(PX, Xh, fun) + sol_with_op = PX_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), + lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) + print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) + #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) if __name__ == '__main__': - test_basis_projector() \ No newline at end of file + test_basis_projector_2d(4, 2, [True,True]) \ No newline at end of file From 296ac74490fda6cf1747fbc486ac2b6943a8e5bb Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 15 Mar 2023 14:50:32 +0100 Subject: [PATCH 06/77] basis operators now available, all test passing --- psydac/feec/basis_projectors.py | 43 +++----------- psydac/feec/tests/test_basis_projectors.py | 67 +++++++++------------- psydac/utilities/utils.py | 10 ++++ 3 files changed, 44 insertions(+), 76 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index c4105eac3..a387fad6d 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -9,6 +9,8 @@ from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL from psydac.linalg.basic import LinearOperator from psydac.feec import basis_projection_kernels +from psydac.utilities.quadratures import gauss_legendre + class BasisProjectionOperator(LinearOperator): @@ -331,20 +333,6 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): # make sure that greville points used for interpolation are in [0, 1] assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) - # k += 1 - # print(f'\nrank: {self._mpi_comm.Get_rank()} | Direction {k}, space_out attributes:') - # # # print('--------------------------------') - # print(f'rank: {self._mpi_comm.Get_rank()} | breaks : {space_out.breaks}') - # # # print(f'rank: {self._mpi_comm.Get_rank()} | degree : {space_out.degree}') - # # # print(f'rank: {self._mpi_comm.Get_rank()} | kind : {space_out.basis}') - # # print(f'rank: {self._mpi_comm.Get_rank()} | greville : {space_out.greville}') - # # print(f'rank: {self._mpi_comm.Get_rank()} | greville[s:e+1] : {greville_loc}') - # # # print(f'rank: {self._mpi_comm.Get_rank()} | ext_greville : {space_out.ext_greville}') - # print(f'rank: {self._mpi_comm.Get_rank()} | histopol_grid : {space_out.histopolation_grid}') - # print(f'rank: {self._mpi_comm.Get_rank()} | histopol_loc : {histopol_loc}') - # print(f'rank: {self._mpi_comm.Get_rank()} | dim W: {space_out.nbasis}') - # # # print(f'rank: {self._mpi_comm.Get_rank()} | project' + V1d[0].basis + V1d[1].basis + V1d[2].basis + ' to ' + W1d[0].basis + W1d[1].basis + W1d[2].basis) - # interpolation if space_out.basis == 'B': x_grid += [greville_loc] @@ -357,21 +345,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): # histopolation elif space_out.basis == 'M': - if space_out.degree % 2 == 0: - union_breaks = space_out.breaks - else: - union_breaks = space_out.breaks[:-1] - - # Make union of Greville and break points - tmp = set(np.round_(space_out.histopolation_grid, decimals=14)).union( - np.round_(union_breaks, decimals=14)) - - tmp = list(tmp) - tmp.sort() - tmp_a = np.array(tmp) - - x_grid += [tmp_a[np.logical_and(tmp_a >= np.min( - histopol_loc) - 1e-14, tmp_a <= np.max(histopol_loc) + 1e-14)]] + x_grid += [space_out.histopolation_grid] # determine subinterval index (= 0 or 1): subs += [np.zeros(x_grid[-1][:-1].size, dtype=int)] @@ -388,16 +362,15 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): nq = space_in.degree + 1 else: nq = n_quad[direction] - - pts_loc, wts_loc = np.polynomial.legendre.leggauss(nq) - - x, w = bsp.quadrature_grid(x_grid[-1], pts_loc, wts_loc) + + pts_loc, wts_loc = gauss_legendre(nq-1) + global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid[-1], pts_loc, wts_loc) + x = global_quad_x[s:e+1] + w = global_quad_w[s:e+1] pts += [x % 1.] wts += [w] - #print(f'rank: {self._mpi_comm.Get_rank()} | Direction {k}, x_grid : {x_grid[-1]}') - # Knot span indices and V-basis functions evaluated at W-point sets s, b = get_span_and_basis(pts[-1], space_in) diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index 10a2b13df..11be7f55f 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -3,7 +3,7 @@ from sympde.topology import Derham from psydac.feec.basis_projectors import BasisProjectionOperator from psydac.fem.basic import FemField - +import matplotlib.pyplot as plt import numpy as np import pytest @@ -17,7 +17,7 @@ def test_basis_projector_2d(nc, deg, perio): domain = Square() ncells = (nc,nc) degree = (deg,deg) - nquads = [4*(d + 1) for d in degree] + nquads = [2*(d + 1) for d in degree] perio = perio domain_h = discretize(domain, ncells=ncells, periodic=perio) @@ -29,6 +29,7 @@ def test_basis_projector_2d(nc, deg, perio): Xh = derham_h.Vvec P0, P1, P2, PX = derham_h.projectors(nquads=nquads) + #Bunch of (1,1)-periodic function for tests f_1 = lambda x, y : x*(x-1)+3 f_2 = lambda x, y : np.cos(2*np.pi*x) @@ -38,23 +39,13 @@ def test_basis_projector_2d(nc, deg, perio): f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) f_7 = lambda x, y : np.exp(y)+np.exp(1-y) - ### TEST V0 -> V1 ### - fun = [[f_6],[f_5]] - f_test = P0(f_3) - P0_1fv = BasisProjectionOperator(P1, V0h, fun) - sol_with_op = P0_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_6(x,y)*f_test(x,y),lambda x, y : f_5(x,y)*f_test(x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - return ### TEST V0->V0 ### fun = [[f_1]] f_test = P0(f_2) P0_0fv = BasisProjectionOperator(P0, V0h, fun) sol_with_op = P0_0fv.dot(f_test.coeffs) sol_no_op = P0(lambda x, y : f_1(x,y)*f_test(x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V1 -> V0 ### fun = [[f_3,f_4]] @@ -62,8 +53,7 @@ def test_basis_projector_2d(nc, deg, perio): P1_0fv = BasisProjectionOperator(P0, V1h, fun) sol_with_op = P1_0fv.dot(f_test.coeffs) sol_no_op = P0(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V2 -> V0 ### fun = [[f_6]] @@ -71,8 +61,7 @@ def test_basis_projector_2d(nc, deg, perio): P2_0fv = BasisProjectionOperator(P0, V2h, fun) sol_with_op = P2_0fv.dot(f_test.coeffs) sol_no_op = P0(lambda x, y : f_6(x,y)*f_test(x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST X -> V0 ### fun = [[f_4,f_1]] @@ -80,8 +69,15 @@ def test_basis_projector_2d(nc, deg, perio): PX_0fv = BasisProjectionOperator(P0, Xh, fun) sol_with_op = PX_0fv.dot(f_test.coeffs) sol_no_op = P0(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0 -> V1 ### + fun = [[f_2],[f_6]] + f_test = P0(f_4) + P0_1fv = BasisProjectionOperator(P1, V0h, fun) + sol_with_op = P0_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V1 -> V1 ### fun = [[f_2,f_4],[f_5,f_1]] @@ -90,8 +86,7 @@ def test_basis_projector_2d(nc, deg, perio): sol_with_op = P1_1fv.dot(f_test.coeffs) sol_no_op = P1([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V2 -> V1 ### fun = [[f_4],[f_7]] @@ -99,8 +94,7 @@ def test_basis_projector_2d(nc, deg, perio): P2_1fv = BasisProjectionOperator(P1, V2h, fun) sol_with_op = P2_1fv.dot(f_test.coeffs) sol_no_op = P1([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST X -> V1 ### fun = [[f_3,f_6],[f_5,f_2]] @@ -109,8 +103,7 @@ def test_basis_projector_2d(nc, deg, perio): sol_with_op = PX_1fv.dot(f_test.coeffs) sol_no_op = P1([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V0->V2 ### fun = [[f_4]] @@ -118,8 +111,7 @@ def test_basis_projector_2d(nc, deg, perio): f_test = P0(f_2) sol_with_op = P0_2fv.dot(f_test.coeffs) sol_no_op = P2(lambda x, y : f_4(x,y)*f_test(x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V1 -> V2 ### fun = [[f_5,f_2]] @@ -127,8 +119,7 @@ def test_basis_projector_2d(nc, deg, perio): P1_2fv = BasisProjectionOperator(P2, V1h, fun) sol_with_op = P1_2fv.dot(f_test.coeffs) sol_no_op = P2(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V2 -> V2 ### fun = [[f_1]] @@ -136,8 +127,7 @@ def test_basis_projector_2d(nc, deg, perio): P2_2fv = BasisProjectionOperator(P2, V2h, fun) sol_with_op = P2_2fv.dot(f_test.coeffs) sol_no_op = P2(lambda x, y : f_1(x,y)*f_test(x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST X -> V2 ### fun = [[f_3,f_7]] @@ -145,8 +135,7 @@ def test_basis_projector_2d(nc, deg, perio): PX_2fv = BasisProjectionOperator(P2, Xh, fun) sol_with_op = PX_2fv.dot(f_test.coeffs) sol_no_op = P2(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V0 -> X ### fun = [[f_4],[f_1]] @@ -154,8 +143,7 @@ def test_basis_projector_2d(nc, deg, perio): P0_Xfv = BasisProjectionOperator(PX, V0h, fun) sol_with_op = P0_Xfv.dot(f_test.coeffs) sol_no_op = PX([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V1 -> X ### fun = [[f_1,f_3],[f_5,f_7]] @@ -164,8 +152,7 @@ def test_basis_projector_2d(nc, deg, perio): sol_with_op = P1_Xfv.dot(f_test.coeffs) sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST V2 -> X ### fun = [[f_2],[f_6]] @@ -173,8 +160,7 @@ def test_basis_projector_2d(nc, deg, perio): P2_Xfv = BasisProjectionOperator(PX, V2h, fun) sol_with_op = P2_Xfv.dot(f_test.coeffs) sol_no_op = PX([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ### TEST X -> X ### fun = [[f_1,f_2],[f_3,f_4]] @@ -183,8 +169,7 @@ def test_basis_projector_2d(nc, deg, perio): sol_with_op = PX_Xfv.dot(f_test.coeffs) sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) - print(max(np.abs(sol_with_op.toarray()-sol_no_op.coeffs.toarray()))) - #assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) if __name__ == '__main__': test_basis_projector_2d(4, 2, [True,True]) \ No newline at end of file diff --git a/psydac/utilities/utils.py b/psydac/utilities/utils.py index 86833bc1d..c42f99417 100644 --- a/psydac/utilities/utils.py +++ b/psydac/utilities/utils.py @@ -55,6 +55,16 @@ def unroll_edges(domain, xgrid): elif xgrid[-1] != xB: return np.array([*xgrid, xgrid[0] + (xB-xA)]) + +#=============================================================================== +def roll_edges(domain, points): + """If necessary, "roll" back intervals that cross boundary of periodic domain. + Changes are made in place to avoid duplicating the array + """ + xA, xB = domain + assert xA < xB + points %=(xB-xA) + points +=xA #=============================================================================== def split_space(Xh): """Split the flattened fem spaces into From ac91c73b8468017a0ce2bb40945a78111f235d01 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 15 Mar 2023 15:08:49 +0100 Subject: [PATCH 07/77] resolving the conflicts --- psydac/api/fem.py | 2 + psydac/api/tests/test_assembly.py | 117 +++++++++++------------------- 2 files changed, 43 insertions(+), 76 deletions(-) diff --git a/psydac/api/fem.py b/psydac/api/fem.py index 4efaa9e3f..a04bd556a 100644 --- a/psydac/api/fem.py +++ b/psydac/api/fem.py @@ -1360,6 +1360,8 @@ def assemble(self, **kwargs): for key in self._free_args: v = kwargs[key] if isinstance(v, FemField): + if not v.coeffs.ghost_regions_in_sync: + v.coeffs.update_ghost_regions() if v.space.is_product: coeffs = v.coeffs if self._symbolic_space.is_broken: diff --git a/psydac/api/tests/test_assembly.py b/psydac/api/tests/test_assembly.py index f4eaa696e..66326b8f4 100644 --- a/psydac/api/tests/test_assembly.py +++ b/psydac/api/tests/test_assembly.py @@ -1,20 +1,21 @@ import pytest +import numpy as np from sympy import pi, sin, cos, tan, atan, atan2 from sympy import exp, sinh, cosh, tanh, atanh, Tuple + from sympde.topology import Line, Square from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace from sympde.topology import element_of, Derham from sympde.core import Constant -from sympde.expr import BilinearForm -from sympde.expr import LinearForm +from sympde.expr import LinearForm, BilinearForm, Functional from sympde.expr import integral -from sympde.calculus import Dot from psydac.linalg.solvers import inverse from psydac.api.discretization import discretize from psydac.fem.basic import FemField from psydac.api.settings import PSYDAC_BACKENDS +from psydac.linalg.utilities import array_to_psydac #============================================================================== @pytest.fixture(params=[None, 'numba', 'pyccel-gcc']) @@ -165,102 +166,65 @@ def test_non_symmetric_BilinearForm(backend): print("PASSED") +#============================================================================== def test_assembly_no_synchr_args(backend): kwargs = {'backend': PSYDAC_BACKENDS[backend]} if backend else {} - ncells = [4, 4] - degree = [2, 2] + nc = 5 + ncells = (nc,) + degree = (2,) + periodic = (True,) - domain = Square('OmegaLog_', bounds1 = (0.,1.), bounds2 = (0.,1.)) - domain_h = discretize(domain, ncells=ncells, periodic=[True,True]) + domain = Line() + domain_h = discretize(domain, ncells=ncells, periodic=periodic) - derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham = Derham(domain) derham_h = discretize(derham, domain_h, degree=degree) - # multi-patch (broken) spaces + #spaces + V0h = derham_h.V0 V1h = derham_h.V1 - V2h = derham_h.V2 - - # broken (patch-wise) differential operators - bD0_b, bD1_b = derham_h.derivatives_as_matrices - - a = element_of(V1h.symbolic_space, name='a') - b = element_of(V1h.symbolic_space, name='b') - - expr = Dot(a,b) - - A = BilinearForm((a,b), integral(domain, expr)) - Ah = discretize(A, domain_h, (V1h,V1h), **kwargs) - - dH1_b = Ah.assemble() - H1_b = inverse(dH1_b, 'cg', tol=1e-10) - - a = element_of(V2h.symbolic_space, name='a') - b = element_of(V2h.symbolic_space, name='b') - - expr = a*b - A = BilinearForm((a,b), integral(domain, expr)) - Ah = discretize(A, domain_h, (V2h,V2h), **kwargs) - - dH2_b = Ah.assemble() - H2_b = inverse(dH2_b, 'cg', tol=1e-10) - - u = element_of(V1h.symbolic_space, name='u') - rho = element_of(V2h.symbolic_space, name='rho') - f = element_of(V2h.symbolic_space, name='f') - g = element_of(V2h.symbolic_space, name='g') - h = element_of(V2h.symbolic_space, name='h') + #differential operator + div, = derham_h.derivatives_as_matrices + rho = element_of(V1h.symbolic_space, name='rho') + g = element_of(V1h.symbolic_space, name='g') + h = element_of(V1h.symbolic_space, name='h') #L2 proj rho u -> V1 expr = g*h*rho weight_int_prod = BilinearForm((g,h), integral(domain, expr)) - weight_int_prod_h = discretize(weight_int_prod, domain_h, (V2h,V2h), **kwargs) + weight_int_prod_h = discretize(weight_int_prod, domain_h, (V1h,V1h), **kwargs) expr = g*rho int_prod = LinearForm(g, integral(domain, expr)) - int_prod_h = discretize(int_prod, domain_h, V2h, **kwargs) - - - #initial solution - x,y = domain.coordinates - rho_init = 1 - u_init = Tuple(cos(2*pi*x) ,sin(2*pi*y)) - - expr = Dot(u_init, u) - l = LinearForm(u, integral(domain, expr)) - lh = discretize(l, domain_h, V1h, **kwargs) - b = lh.assemble() - uh = H1_b.dot(b) - - f = element_of(V2h.symbolic_space, name='f') - expr = rho_init*f - lp = LinearForm(f, integral(domain, expr)) - lph = discretize(lp, domain_h, V2h, **kwargs) - b = lph.assemble() - rhoh = H2_b.dot(b) - - expr = f - lp = LinearForm(f, integral(domain, expr)) - lph = discretize(lp, domain_h, V2h, **kwargs) - b = lph.assemble() - const_1 = H2_b.dot(b) - - div = bD1_b - - rhoh1 = rhoh-div.dot(uh) - rhof1 = FemField(V2h, rhoh1) - rhoh2 = rhoh-div.dot(uh) - rhof2 = FemField(V2h, rhoh2) + int_prod_h = discretize(int_prod, domain_h, V1h, **kwargs) + + func = Functional(rho, domain) + func_h = discretize(func, domain_h, V1h, **kwargs) + + uh = array_to_psydac(np.array([i for i in range(nc)]), V0h.vector_space) + const_1 = array_to_psydac(np.array([1/nc]*nc), V1h.vector_space) + + rhoh1 = div.dot(uh) + rhof1 = FemField(V1h, rhoh1) + rhoh2 = div.dot(uh) + rhof2 = FemField(V1h, rhoh2) + rhoh3 = div.dot(uh) + rhof3 = FemField(V1h, rhoh3) weight_mass_matrix = weight_int_prod_h.assemble(rho=rhof1) inte_bilin = const_1.dot(weight_mass_matrix.dot(const_1)) int_prod_rho = int_prod_h.assemble(rho=rhof2) inte_lin = int_prod_rho.dot(const_1) - assert( abs(inte_bilin - 1.) < 1.e-9) - assert( abs(inte_lin - 1.) < 1.e-9) + + inte_norm = func_h.assemble(rho=rhof3) + + assert( abs(inte_bilin) < 1.e-12) + assert( abs(inte_lin) < 1.e-12) + assert( abs(inte_norm) < 1.e-12) #============================================================================== if __name__ == '__main__': @@ -268,3 +232,4 @@ def test_assembly_no_synchr_args(backend): test_multiple_fields(None) test_math_imports(None) test_non_symmetric_BilinearForm(None) + test_assembly_no_synchr_args(None) \ No newline at end of file From ec67c87731537754c65345a28af999fba4073b56 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 15 Mar 2023 16:05:09 +0100 Subject: [PATCH 08/77] forgotten add file --- psydac/feec/global_projectors.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index 45738eec4..8969d8a15 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -12,6 +12,8 @@ from psydac.fem.tensor import TensorFemSpace from psydac.fem.vector import ProductFemSpace +from psydac.utilities.utils import roll_edges + from abc import ABCMeta, abstractmethod #============================================================================== @@ -142,6 +144,9 @@ def __init__(self, space, nquads = None): if quad_x[j] is None: u, w = uw[j] global_quad_x, global_quad_w = quadrature_grid(V.histopolation_grid, u, w) + #"roll" back points to the interval to ensure that the quadrature points are + #in the domain. Probably only usefull on periodic cases + roll_edges(V.domain, global_quad_x) quad_x[j] = global_quad_x[s:e+1] quad_w[j] = global_quad_w[s:e+1] local_x, local_w = quad_x[j], quad_w[j] From 064a5784d641ac1f5d55492f569791fcf903c24a Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 15 Mar 2023 17:39:15 +0100 Subject: [PATCH 09/77] fix for the transpose, added the fix in block linop --- psydac/api/feec.py | 8 ++++---- psydac/feec/basis_projectors.py | 11 +++++++---- psydac/linalg/basic.py | 1 - psydac/linalg/block.py | 8 ++++---- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index fcfd24b94..47d31c0ea 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -108,7 +108,7 @@ def derivatives_as_operators(self): return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- - def projectors(self, *, kind='global', nquads=None): + def projectors(self, *, kind='global', nquads=None, use_map = True): if not (kind == 'global'): raise NotImplementedError('only global projectors are available') @@ -116,7 +116,7 @@ def projectors(self, *, kind='global', nquads=None): if self.dim == 1: P0 = Projector_H1(self.V0) P1 = Projector_L2(self.V1, nquads) - if self.mapping: + if self.mapping and use_map: P0_m = lambda f: P0(pull_1d_h1(f, self.mapping)) P1_m = lambda f: P1(pull_1d_l2(f, self.mapping)) return P0_m, P1_m @@ -137,7 +137,7 @@ def projectors(self, *, kind='global', nquads=None): if self.has_vec : Pvec = Projector_H1vec(self.Vvec) - if self.mapping: + if self.mapping and use_map: P0_m = lambda f: P0(pull_2d_h1(f, self.mapping)) P2_m = lambda f: P2(pull_2d_l2(f, self.mapping)) if kind == 'hcurl': @@ -162,7 +162,7 @@ def projectors(self, *, kind='global', nquads=None): P3 = Projector_L2 (self.V3, nquads) if self.has_vec : Pvec = Projector_H1vec(self.Vvec) - if self.mapping: + if self.mapping and use_map: P0_m = lambda f: P0(pull_3d_h1 (f, self.mapping)) P1_m = lambda f: P1(pull_3d_hcurl(f, self.mapping)) P2_m = lambda f: P2(pull_3d_hdiv (f, self.mapping)) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index a387fad6d..fd2cb69fc 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -10,6 +10,7 @@ from psydac.linalg.basic import LinearOperator from psydac.feec import basis_projection_kernels from psydac.utilities.quadratures import gauss_legendre +from psydac.fem.basic import FemField @@ -181,9 +182,7 @@ def transpose(self): """ Returns the transposed operator. """ - return BasisProjectionOperator(self._P, self._V, self._fun, - self._V_extraction_op, self._V_boundary_op, - not self.transposed, self._polar_shift) + return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed) @staticmethod def assemble_mat(P, V, fun): @@ -259,7 +258,11 @@ def assemble_mat(P, V, fun): # Evaluate weight function at quadrature points pts = np.meshgrid(*_ptsG, indexing='ij') - _fun_q = f(*pts).copy() + if isinstance(f, FemField): + assert(isinstance(f.space,TensorFemSpace)) + _fun_q = f.space.eval_fields_regular_tensor_grid(pts, f) + else : + _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields # Call the kernel if weight function is not zero if np.any(np.abs(_fun_q) > 1e-14): diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 2dc33c008..61cc3d076 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -688,7 +688,6 @@ def transpose(self): new_cod = self._domain assert isinstance(new_dom, VectorSpace) assert isinstance(new_cod, VectorSpace) - print(*t_multiplicants) return ComposedLinearOperator(self._codomain, self._domain, *t_multiplicants) def dot(self, v, out=None): diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index 200734f97..4564d0231 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -1255,13 +1255,13 @@ def set_backend(self, backend): if interface: def func(blocks, v, out, **args): - vs = [vi._interface_data[d_axis, d_ext] for vi in v.blocks] if isinstance(v, BlockVector) else v._data - outs = [outi._data for outi in out.blocks] if isinstance(out, BlockVector) else out._data + vs = [vi._interface_data[d_axis, d_ext] for vi in v.blocks] if isinstance(v, BlockVector) else [v._data] + outs = [outi._data for outi in out.blocks] if isinstance(out, BlockVector) else [out._data] dot(*blocks, *vs, *outs, **args) else: def func(blocks, v, out, **args): - vs = [vi._data for vi in v.blocks] if isinstance(v, BlockVector) else v._data - outs = [outi._data for outi in out.blocks] if isinstance(out, BlockVector) else out._data + vs = [vi._interface_data[d_axis, d_ext] for vi in v.blocks] if isinstance(v, BlockVector) else [v._data] + outs = [outi._data for outi in out.blocks] if isinstance(out, BlockVector) else [out._data] dot(*blocks, *vs, *outs, **args) self._func = func From 20a64d94f695e8d5e1c77dd21f73469a4becc0e8 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 16 Mar 2023 14:54:33 +0100 Subject: [PATCH 10/77] fix in linalg.block, add tests and a fix (probably temporary) when passing a FemField to basis projector --- psydac/feec/basis_projectors.py | 11 ++++++----- psydac/feec/tests/test_basis_projectors.py | 11 ++++++++++- psydac/linalg/block.py | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index fd2cb69fc..a894bc86e 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -258,11 +258,12 @@ def assemble_mat(P, V, fun): # Evaluate weight function at quadrature points pts = np.meshgrid(*_ptsG, indexing='ij') - if isinstance(f, FemField): - assert(isinstance(f.space,TensorFemSpace)) - _fun_q = f.space.eval_fields_regular_tensor_grid(pts, f) - else : - _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields + #needs to be improved when passing FemFields, + #a lot of evaluations that are probably not needed + #_fun_q = f.space.eval_fields_irregular_tensor_grid(_ptsG, f) could be a solution but failing + #if the grid is not order, for example on periodic domains... + f = np.vectorize(f) + _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields # Call the kernel if weight function is not zero if np.any(np.abs(_fun_q) > 1e-14): diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index 11be7f55f..bf1580336 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -62,7 +62,7 @@ def test_basis_projector_2d(nc, deg, perio): sol_with_op = P2_0fv.dot(f_test.coeffs) sol_no_op = P0(lambda x, y : f_6(x,y)*f_test(x,y)) assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - + ### TEST X -> V0 ### fun = [[f_4,f_1]] f_test = PX([f_4,f_5]) @@ -171,5 +171,14 @@ def test_basis_projector_2d(nc, deg, perio): lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + ###TEST WITH FemField as parameter### + pf = P1([f_4,f_1]) + fun = [[pf[0],pf[1]]] + f_test = PX([f_4,f_5]) + PX_0fv = BasisProjectionOperator(P0, Xh, fun) + sol_with_op = PX_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + if __name__ == '__main__': test_basis_projector_2d(4, 2, [True,True]) \ No newline at end of file diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index 4564d0231..2f1fe4a7c 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -1260,7 +1260,7 @@ def func(blocks, v, out, **args): dot(*blocks, *vs, *outs, **args) else: def func(blocks, v, out, **args): - vs = [vi._interface_data[d_axis, d_ext] for vi in v.blocks] if isinstance(v, BlockVector) else [v._data] + vs = [vi._data for vi in v.blocks] if isinstance(v, BlockVector) else [v._data] outs = [outi._data for outi in out.blocks] if isinstance(out, BlockVector) else [out._data] dot(*blocks, *vs, *outs, **args) From 867fc96c7c0b1535dea0ce95006e79c728a30598 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 22 Mar 2023 08:39:35 +0100 Subject: [PATCH 11/77] Fix to able the basis projection operators to be used with femfields as parameter --- psydac/api/feec.py | 8 ++++---- psydac/feec/.lock_acquisition.lock | 0 psydac/feec/basis_projectors.py | 18 ++++++++++++------ psydac/feec/tests/test_basis_projectors.py | 10 ++++++++++ psydac/fem/tensor.py | 2 +- 5 files changed, 27 insertions(+), 11 deletions(-) create mode 100755 psydac/feec/.lock_acquisition.lock diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 47d31c0ea..4d6aa45a4 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -108,7 +108,7 @@ def derivatives_as_operators(self): return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- - def projectors(self, *, kind='global', nquads=None, use_map = True): + def projectors(self, *, kind='global', nquads=None): if not (kind == 'global'): raise NotImplementedError('only global projectors are available') @@ -116,7 +116,7 @@ def projectors(self, *, kind='global', nquads=None, use_map = True): if self.dim == 1: P0 = Projector_H1(self.V0) P1 = Projector_L2(self.V1, nquads) - if self.mapping and use_map: + if self.mapping : P0_m = lambda f: P0(pull_1d_h1(f, self.mapping)) P1_m = lambda f: P1(pull_1d_l2(f, self.mapping)) return P0_m, P1_m @@ -137,7 +137,7 @@ def projectors(self, *, kind='global', nquads=None, use_map = True): if self.has_vec : Pvec = Projector_H1vec(self.Vvec) - if self.mapping and use_map: + if self.mapping : P0_m = lambda f: P0(pull_2d_h1(f, self.mapping)) P2_m = lambda f: P2(pull_2d_l2(f, self.mapping)) if kind == 'hcurl': @@ -162,7 +162,7 @@ def projectors(self, *, kind='global', nquads=None, use_map = True): P3 = Projector_L2 (self.V3, nquads) if self.has_vec : Pvec = Projector_H1vec(self.Vvec) - if self.mapping and use_map: + if self.mapping : P0_m = lambda f: P0(pull_3d_h1 (f, self.mapping)) P1_m = lambda f: P1(pull_3d_hcurl(f, self.mapping)) P2_m = lambda f: P2(pull_3d_hdiv (f, self.mapping)) diff --git a/psydac/feec/.lock_acquisition.lock b/psydac/feec/.lock_acquisition.lock new file mode 100755 index 000000000..e69de29bb diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index a894bc86e..c9a760166 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -253,18 +253,24 @@ def assemble_mat(P, V, fun): V1d, W1d, _starts_out, _ends_out, nq) _ptsG = [pts.flatten() for pts in _ptsG] - _Vnbases = [space.nbasis for space in V1d] # Evaluate weight function at quadrature points pts = np.meshgrid(*_ptsG, indexing='ij') #needs to be improved when passing FemFields, #a lot of evaluations that are probably not needed - #_fun_q = f.space.eval_fields_irregular_tensor_grid(_ptsG, f) could be a solution but failing - #if the grid is not order, for example on periodic domains... - f = np.vectorize(f) - _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields - + if isinstance(f, FemField): + assert(isinstance(f.space,TensorFemSpace)) + _fun_q = f.space.eval_fields_irregular_tensor_grid(_ptsG, f) + _fun_q = np.squeeze(_fun_q) #since we only evaluate one field the result is a 3D + #array with last dim 1, we need to squeeze it in order to use the pyccelized kernels + elif isinstance(f, float) or isinstance(f, int): + shape_grid = tuple([len(pts_i) for pts_i in _ptsG]) + _fun_q = np.full(shape_grid, f) + else : + f = np.vectorize(f) + _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields + # Call the kernel if weight function is not zero if np.any(np.abs(_fun_q) > 1e-14): diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index bf1580336..f93bcd4e5 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -172,6 +172,7 @@ def test_basis_projector_2d(nc, deg, perio): assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) ###TEST WITH FemField as parameter### + ### X->V0 with V1 field ### pf = P1([f_4,f_1]) fun = [[pf[0],pf[1]]] f_test = PX([f_4,f_5]) @@ -180,5 +181,14 @@ def test_basis_projector_2d(nc, deg, perio): sol_no_op = P0(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + ### X->V2 with V1 field ### + pf = P1([f_2,f_6]) + fun = [[pf[0],pf[1]]] + f_test = PX([f_3,f_1]) + P2_0fv = BasisProjectionOperator(P2, Xh, fun) + sol_with_op = P2_0fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + if __name__ == '__main__': test_basis_projector_2d(4, 2, [True,True]) \ No newline at end of file diff --git a/psydac/fem/tensor.py b/psydac/fem/tensor.py index 99098f98b..2bd284ceb 100644 --- a/psydac/fem/tensor.py +++ b/psydac/fem/tensor.py @@ -341,7 +341,7 @@ def preprocess_irregular_tensor_grid(self, grid, der=0, overlap=0): for i in range(self.ldim): # Check the that the grid is sorted. grid_i = grid[i] - assert all(grid_i[j] <= grid_i[j + 1] for j in range(len(grid_i) - 1)) + #assert all(grid_i[j] <= grid_i[j + 1] for j in range(len(grid_i) - 1)) # Get the cell indexes cell_index_i = cell_index(self.breaks[i], grid_i) From 31b9cb843b029a22b689096a3e2ded84422b856c Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 31 Mar 2023 09:56:35 +0200 Subject: [PATCH 12/77] remove ome useless args in dot function of basis projection operator --- psydac/feec/basis_projectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index c9a760166..9361f9723 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -123,7 +123,7 @@ def dof_operator(self): """ return self._dof_operator - def dot(self, v, out=None, tol=1e-14, maxiter=1000, verbose=False): + def dot(self, v, out=None): """ Applies the basis projection operator to the FE coefficients v. From c0d8b8d2229edda72a2eafc53eddb22da73665a0 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 20 Apr 2023 08:18:14 +0200 Subject: [PATCH 13/77] solving problem with points that could go outside of the domains in periodic cases, rectangular tests for the basis projection operators --- psydac/feec/basis_projectors.py | 37 ++-- psydac/feec/tests/test_basis_projectors.py | 190 ++++++++++++++++++++- psydac/utilities/utils.py | 1 + 3 files changed, 205 insertions(+), 23 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 9361f9723..ae36e6b56 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -11,6 +11,7 @@ from psydac.feec import basis_projection_kernels from psydac.utilities.quadratures import gauss_legendre from psydac.fem.basic import FemField +from psydac.utilities.utils import roll_edges @@ -135,15 +136,6 @@ def dot(self, v, out=None): out : psydac.linalg.basic.Vector, optional If given, the output will be written in-place into this vector. - tol : float, optional - Stop tolerance in iterative solve (only used in polar case). - - maxiter : int, optional - Maximum number of iterations in iterative solve (only used in polar case). - - verbose : bool, optional - Whether to print some information in each iteration in iterative solve (only used in polar case). - Returns ------- out : psydac.linalg.basic.Vector @@ -270,7 +262,7 @@ def assemble_mat(P, V, fun): else : f = np.vectorize(f) _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields - + # Call the kernel if weight function is not zero if np.any(np.abs(_fun_q) > 1e-14): @@ -331,6 +323,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): import psydac.core.bsplines as bsp + x_grid, subs, pts, wts, spans, bases = [], [], [], [], [], [] # Loop over direction, prepare point sets and evaluate basis functions @@ -341,25 +334,24 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): histopol_loc = space_out.histopolation_grid[s: e + 2].copy() # make sure that greville points used for interpolation are in [0, 1] - assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) + #assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) # interpolation if space_out.basis == 'B': - x_grid += [greville_loc] + x_grid = greville_loc pts += [greville_loc[:, None]] wts += [np.ones(pts[-1].shape, dtype=float)] - # sub-interval index is always 0 for interpolation. subs += [np.zeros(pts[-1].shape[0], dtype=int)] # histopolation elif space_out.basis == 'M': - x_grid += [space_out.histopolation_grid] + x_grid = space_out.histopolation_grid # determine subinterval index (= 0 or 1): - subs += [np.zeros(x_grid[-1][:-1].size, dtype=int)] - for n, x_h in enumerate(x_grid[-1][:-1]): + subs += [np.zeros(x_grid.size, dtype=int)] + for n, x_h in enumerate(x_grid): add = 1 for x_g in histopol_loc: if abs(x_h - x_g) < 1e-14: @@ -372,13 +364,16 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): nq = space_in.degree + 1 else: nq = n_quad[direction] - + pts_loc, wts_loc = gauss_legendre(nq-1) - global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid[-1], pts_loc, wts_loc) + pts_loc, wts_loc = pts_loc[::-1], wts_loc[::-1] + global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) + #"roll" back points to the interval to ensure that the quadrature points are + #in the domain. Probably only usefull on periodic cases + roll_edges(space_out.domain, global_quad_x) x = global_quad_x[s:e+1] w = global_quad_w[s:e+1] - - pts += [x % 1.] + pts += [x] wts += [w] # Knot span indices and V-basis functions evaluated at W-point sets @@ -424,7 +419,7 @@ def get_span_and_basis(pts, space): for n in range(pts.shape[0]): for nq in range(pts.shape[1]): # avoid 1. --> 0. for clamped interpolation - x = pts[n, nq] % (1. + 1e-14) + x = pts[n, nq] #% (1. + 1e-14) span_tmp = bsp.find_span(T, p, x) basis[n, nq, :] = bsp.basis_funs_all_ders( T, p, x, span_tmp, 0, normalization=space.basis) diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index f93bcd4e5..9e28ea612 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -18,7 +18,6 @@ def test_basis_projector_2d(nc, deg, perio): ncells = (nc,nc) degree = (deg,deg) nquads = [2*(d + 1) for d in degree] - perio = perio domain_h = discretize(domain, ncells=ncells, periodic=perio) derham = Derham(domain, ["H1", "Hdiv", "L2"]) @@ -190,5 +189,192 @@ def test_basis_projector_2d(nc, deg, perio): sol_no_op = P2(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) +@pytest.mark.parametrize('nc', [4, 8, 15]) +@pytest.mark.parametrize('deg', [2,3]) +@pytest.mark.parametrize('perio', [[True, True], [True, False], [False, False]]) + +def test_basis_projector_non_unit_square_2d(nc, deg, perio): + ### INITIALISATION ### + domain = Square('Omega', bounds1 = (-1,1), bounds2 = (-1,1)) + ncells = (nc,nc) + degree = (deg,deg) + nquads = [2*(d + 1) for d in degree] + domain_h = discretize(domain, ncells=ncells, periodic=perio) + + derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + Xh = derham_h.Vvec + + P0, P1, P2, PX = derham_h.projectors(nquads=nquads) + + #Bunch of (1,1)-periodic function for tests + f_1 = lambda x, y : x*(x-1)+3 + f_2 = lambda x, y : np.cos(2*np.pi*x) + f_3 = lambda x, y : np.sin(2*np.pi*x)*y*(y-1) + f_4 = lambda x, y : x*(x-1)*y*(y-1) + f_5 = lambda x, y : np.cos(2*np.pi*x)*np.sin(2*np.pi*y) + f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) + f_7 = lambda x, y : np.exp(y)+np.exp(1-y) + + ### TEST V0->V0 ### + fun = [[f_2]] + f_test = P0(f_1) + P0_0fv = BasisProjectionOperator(P0, V0h, fun) + sol_with_op = P0_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_2(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V0 ### + fun = [[f_3,f_4]] + f_test = P1([f_5, f_1]) + P1_0fv = BasisProjectionOperator(P0, V1h, fun) + sol_with_op = P1_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V0 ### + fun = [[f_6]] + f_test = P2(f_3) + P2_0fv = BasisProjectionOperator(P0, V2h, fun) + sol_with_op = P2_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_6(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V0 ### + fun = [[f_4,f_1]] + f_test = PX([f_4,f_5]) + PX_0fv = BasisProjectionOperator(P0, Xh, fun) + sol_with_op = PX_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0 -> V1 ### + fun = [[f_2],[f_6]] + f_test = P0(f_4) + #x_array = np.linspace(-1,1,50) + #ex_array = [f_4(x,0.7) for x in x_array] + #p_array = [f_test(x,0.7) for x in x_array] + #plt.plot(x_array, ex_array) + #plt.plot(x_array, p_array) + #plt.show() + P0_1fv = BasisProjectionOperator(P1, V0h, fun) + sol_with_op = P0_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V1 ### + fun = [[f_2,f_4],[f_5,f_1]] + f_test = P1([f_3,f_7]) + P1_1fv = BasisProjectionOperator(P1, V1h, fun) + sol_with_op = P1_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V1 ### + fun = [[f_4],[f_7]] + f_test = P2(f_1) + P2_1fv = BasisProjectionOperator(P1, V2h, fun) + sol_with_op = P2_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V1 ### + fun = [[f_3,f_6],[f_5,f_2]] + f_test = PX([f_3,f_1]) + PX_1fv = BasisProjectionOperator(P1, Xh, fun) + sol_with_op = PX_1fv.dot(f_test.coeffs) + sol_no_op = P1([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0->V2 ### + fun = [[f_4]] + P0_2fv = BasisProjectionOperator(P2, V0h, fun) + f_test = P0(f_2) + sol_with_op = P0_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_4(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V2 ### + fun = [[f_5,f_2]] + f_test = P1([f_1, f_5]) + P1_2fv = BasisProjectionOperator(P2, V1h, fun) + sol_with_op = P1_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V2 ### + fun = [[f_1]] + f_test = P2(f_3) + P2_2fv = BasisProjectionOperator(P2, V2h, fun) + sol_with_op = P2_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_1(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V2 ### + fun = [[f_3,f_7]] + f_test = PX([f_4,f_5]) + PX_2fv = BasisProjectionOperator(P2, Xh, fun) + sol_with_op = PX_2fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0 -> X ### + fun = [[f_4],[f_1]] + f_test = P0(f_2) + P0_Xfv = BasisProjectionOperator(PX, V0h, fun) + sol_with_op = P0_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> X ### + fun = [[f_1,f_3],[f_5,f_7]] + f_test = P1([f_4,f_5]) + P1_Xfv = BasisProjectionOperator(PX, V1h, fun) + sol_with_op = P1_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> X ### + fun = [[f_2],[f_6]] + f_test = P2(f_1) + P2_Xfv = BasisProjectionOperator(PX, V2h, fun) + sol_with_op = P2_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> X ### + fun = [[f_1,f_2],[f_3,f_4]] + f_test = PX([f_5,f_6]) + PX_Xfv = BasisProjectionOperator(PX, Xh, fun) + sol_with_op = PX_Xfv.dot(f_test.coeffs) + sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), + lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ###TEST WITH FemField as parameter### + ### X->V0 with V1 field ### + pf = P1([f_4,f_1]) + fun = [[pf[0],pf[1]]] + f_test = PX([f_4,f_5]) + PX_0fv = BasisProjectionOperator(P0, Xh, fun) + sol_with_op = PX_0fv.dot(f_test.coeffs) + sol_no_op = P0(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### X->V2 with V1 field ### + pf = P1([f_2,f_6]) + fun = [[pf[0],pf[1]]] + f_test = PX([f_3,f_1]) + P2_0fv = BasisProjectionOperator(P2, Xh, fun) + sol_with_op = P2_0fv.dot(f_test.coeffs) + sol_no_op = P2(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + if __name__ == '__main__': - test_basis_projector_2d(4, 2, [True,True]) \ No newline at end of file + test_basis_projector_non_unit_square_2d(4, 2, [False,False]) \ No newline at end of file diff --git a/psydac/utilities/utils.py b/psydac/utilities/utils.py index c42f99417..104bb90be 100644 --- a/psydac/utilities/utils.py +++ b/psydac/utilities/utils.py @@ -63,6 +63,7 @@ def roll_edges(domain, points): """ xA, xB = domain assert xA < xB + points -=xA points %=(xB-xA) points +=xA #=============================================================================== From 3d1478e7f7a3bdeeb8bb9da3660cfd10d0393fb5 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 3 May 2023 18:39:21 +0200 Subject: [PATCH 14/77] few changes in basis_projectors --- psydac/feec/basis_projectors.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index ae36e6b56..cda7a5e46 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -347,10 +347,26 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): # histopolation elif space_out.basis == 'M': + """if space_out.degree % 2 == 0: + union_breaks = space_out.breaks + else: + union_breaks = space_out.breaks[:-1] + + # Make union of Greville and break points + tmp = set(np.round_(space_out.histopolation_grid, decimals=14)).union( + np.round_(union_breaks, decimals=14)) + + tmp = list(tmp) + tmp.sort() + tmp_a = np.array(tmp) + + x_grid = tmp_a[np.logical_and(tmp_a >= np.min( + histopol_loc) - 1e-14, tmp_a <= np.max(histopol_loc) + 1e-14)]""" + x_grid = space_out.histopolation_grid # determine subinterval index (= 0 or 1): - subs += [np.zeros(x_grid.size, dtype=int)] + subs += [np.zeros(len(x_grid), dtype=int)] for n, x_h in enumerate(x_grid): add = 1 for x_g in histopol_loc: @@ -364,15 +380,14 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): nq = space_in.degree + 1 else: nq = n_quad[direction] - pts_loc, wts_loc = gauss_legendre(nq-1) pts_loc, wts_loc = pts_loc[::-1], wts_loc[::-1] global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) #"roll" back points to the interval to ensure that the quadrature points are #in the domain. Probably only usefull on periodic cases roll_edges(space_out.domain, global_quad_x) - x = global_quad_x[s:e+1] - w = global_quad_w[s:e+1] + x = global_quad_x#[s:e+1] + w = global_quad_w#[s:e+1] pts += [x] wts += [w] From 6db4a901a25d3e753fbad0c6bc06731399f60807 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 5 May 2023 08:22:52 +0200 Subject: [PATCH 15/77] few fixes after the merge of devel --- psydac/api/discretization.py | 2 +- psydac/api/feec.py | 1 - psydac/feec/basis_projectors.py | 8 +++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index e503c0cc2..a29e17383 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -55,7 +55,7 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): if get_vec: V0h = spaces[0] X = VectorFunctionSpace('X', domain_h.domain, kind='h1') - Xh = ProductFemSpace(V0h, V0h) + Xh = VectorFemSpace(V0h, V0h) Xh.symbolic_space = X spaces.append(Xh) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index a666c69bc..55d5c8137 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -31,7 +31,6 @@ def __init__(self, mapping, get_vec=False, *spaces): dim = len(spaces) - 1 self._spaces = spaces - dim = len(spaces) - 1 self._dim = dim self._mapping = mapping self._callable_mapping = mapping.get_callable_mapping() if mapping else None diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index cda7a5e46..6c19f5b78 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -170,10 +170,12 @@ def dot(self, v, out=None): return out - def transpose(self): + def transpose(self, conjugate=False): """ Returns the transposed operator. """ + if conjugate==True: + raise NotImplementedError("No complex here!") return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed) @staticmethod @@ -386,8 +388,8 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): #"roll" back points to the interval to ensure that the quadrature points are #in the domain. Probably only usefull on periodic cases roll_edges(space_out.domain, global_quad_x) - x = global_quad_x#[s:e+1] - w = global_quad_w#[s:e+1] + x = global_quad_x[s:e+1] + w = global_quad_w[s:e+1] pts += [x] wts += [w] From 37fd71b08eeb0edb6265062085c97f26334ce3e4 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 12 May 2023 13:35:43 +0200 Subject: [PATCH 16/77] changes on pull_back for the v_space to match other changes and added test for the basis projection operator on mapped domains --- psydac/api/feec.py | 10 +- psydac/feec/basis_projection_kernels.py | 24 +-- psydac/feec/basis_projectors.py | 50 ++---- psydac/feec/pull_push.py | 87 +++++---- psydac/feec/tests/test_basis_projectors.py | 198 ++++++++++++++++++++- 5 files changed, 273 insertions(+), 96 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 55d5c8137..cf62a6995 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -118,7 +118,7 @@ def derivatives_as_operators(self): return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- - def projectors(self, *, kind='global', nquads=None): + def projectors(self, *, kind='global', nquads=None, get_reference=False): if not (kind == 'global'): raise NotImplementedError('only global projectors are available') @@ -126,7 +126,7 @@ def projectors(self, *, kind='global', nquads=None): if self.dim == 1: P0 = Projector_H1(self.V0) P1 = Projector_L2(self.V1, nquads) - if self.mapping: + if self.mapping and not get_reference: P0_m = lambda f: P0(pull_1d_h1(f, self.callable_mapping)) P1_m = lambda f: P1(pull_1d_l2(f, self.callable_mapping)) return P0_m, P1_m @@ -145,9 +145,9 @@ def projectors(self, *, kind='global', nquads=None): raise TypeError('projector of space type {} is not available'.format(kind)) if self.has_vec : - Pvec = Projector_H1vec(self.Vvec) + Pvec = Projector_H1vec(self.Vvec, nquads) - if self.mapping: + if self.mapping and not get_reference: P0_m = lambda f: P0(pull_2d_h1(f, self.callable_mapping)) P2_m = lambda f: P2(pull_2d_l2(f, self.callable_mapping)) if kind == 'hcurl': @@ -172,7 +172,7 @@ def projectors(self, *, kind='global', nquads=None): P3 = Projector_L2 (self.V3, nquads) if self.has_vec : Pvec = Projector_H1vec(self.Vvec) - if self.mapping : + if self.mapping and not get_reference: P0_m = lambda f: P0(pull_3d_h1 (f, self.callable_mapping)) P1_m = lambda f: P1(pull_3d_hcurl(f, self.callable_mapping)) P2_m = lambda f: P2(pull_3d_hdiv (f, self.callable_mapping)) diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py index 909768153..1cedbf2cc 100644 --- a/psydac/feec/basis_projection_kernels.py +++ b/psydac/feec/basis_projection_kernels.py @@ -1,4 +1,4 @@ -def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', sub1 : 'int[:]', dim1_in : int, p1_out : int): +def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', dim1_in : int, p1_out : int): '''Kernel for assembling the matrix A_(i,j) = DOFS_i(fun*Lambda^in_j) , @@ -74,12 +74,11 @@ def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int mat[:] = 0. # Dimensions of output space - dim1_out = span1.shape[0] - sum(sub1) + dim1_out = span1.shape[0] # Interval (either element or sub-interval thereof) # ------------------------------------------------- cumsub_i = 0 # Cumulative sub-interval index for ii in range(span1.shape[0]): - cumsub_i += sub1[ii] i = ii - cumsub_i # local DOF index # Quadrature point index in interval @@ -114,7 +113,7 @@ def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int mat[po1 + i, col1] += value -def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', sub1 : 'int[:]', sub2 : 'int[:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): +def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): '''Kernel for assembling the matrix A_(ij,kl) = DOFS_ij(fun*Lambda^in_kl) , @@ -216,19 +215,17 @@ def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : mat[:] = 0. # Dimensions of output space - dim1_out = span1.shape[0] - sum(sub1) - dim2_out = span2.shape[0] - sum(sub2) + dim1_out = span1.shape[0] + dim2_out = span2.shape[0] # Interval (either element or sub-interval thereof) # ------------------------------------------------- cumsub_i = 0 # Cumulative sub-interval index for ii in range(span1.shape[0]): - cumsub_i += sub1[ii] i = ii - cumsub_i # local DOF index cumsub_j = 0 # Cumulative sub-interval index for jj in range(span2.shape[0]): - cumsub_j += sub2[jj] j = jj - cumsub_j # local DOF index # Quadrature point index in interval @@ -285,7 +282,7 @@ def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : -def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', wts3 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', span3 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', basis3 : 'float[:,:,:]', sub1 : 'int[:]', sub2 : 'int[:]', sub3 : 'int[:]', dim1_in : int, dim2_in : int, dim3_in : int, p1_out : int, p2_out : int, p3_out : int): +def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', wts3 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', span3 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', basis3 : 'float[:,:,:]', dim1_in : int, dim2_in : int, dim3_in : int, p1_out : int, p2_out : int, p3_out : int): '''Kernel for assembling the matrix A_(ijk,mno) = DOFS_ijk(fun*Lambda^in_mno) , @@ -413,25 +410,22 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i mat[:] = 0. # Dimensions of output space - dim1_out = span1.shape[0] - sum(sub1) - dim2_out = span2.shape[0] - sum(sub2) - dim3_out = span3.shape[0] - sum(sub3) + dim1_out = span1.shape[0] + dim2_out = span2.shape[0] + dim3_out = span3.shape[0] # Interval (either element or sub-interval thereof) # ------------------------------------------------- cumsub_i = 0 # Cumulative sub-interval index for ii in range(span1.shape[0]): - cumsub_i += sub1[ii] i = ii - cumsub_i # local DOF index cumsub_j = 0 # Cumulative sub-interval index for jj in range(span2.shape[0]): - cumsub_j += sub2[jj] j = jj - cumsub_j # local DOF index cumsub_k = 0 # Cumulative sub-interval index for kk in range(span3.shape[0]): - cumsub_k += sub3[kk] k = kk - cumsub_k # local DOF index # Quadrature point index in interval diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 6c19f5b78..885b104a8 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -18,12 +18,15 @@ class BasisProjectionOperator(LinearOperator): """ Class for "basis projection operators" PI_ijk(fun Lambda_mno) in the general form BP * P * DOF * EV^T * BV^T. + Be carefull that PI is the Projector on the reference domain and fun has to be define on the reference domain, + in other terms, this class does dot handle mappings Parameters ---------- - P : struphy.psydac_api.projectors.Projector + P : psydac.feec.global_projection.GlobalProjector Global commuting projector mapping into TensorFemSpace/ProductFemSpace W = P.space (codomain of operator). - + Has to be the projection on the reference domain + V : psydac.fem.basic.FemSpace Finite element spline space (domain, input space). @@ -49,6 +52,7 @@ def __init__(self, P, V, fun, transposed=False): self._fun = fun self._transposed = transposed self._dtype = V.vector_space.dtype + assert(self._dtype == float) # set domain and codomain symbolic names if hasattr(P.space.symbolic_space, 'name'): @@ -174,8 +178,7 @@ def transpose(self, conjugate=False): """ Returns the transposed operator. """ - if conjugate==True: - raise NotImplementedError("No complex here!") + #conjugate not implemented return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed) @staticmethod @@ -243,7 +246,7 @@ def assemble_mat(P, V, fun): _ends_out = np.array(dofs_mat.codomain.ends) _pads_out = np.array(dofs_mat.codomain.pads) - _ptsG, _wtsG, _spans, _bases, _subs = prepare_projection_of_basis( + _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( V1d, W1d, _starts_out, _ends_out, nq) _ptsG = [pts.flatten() for pts in _ptsG] @@ -251,8 +254,7 @@ def assemble_mat(P, V, fun): # Evaluate weight function at quadrature points pts = np.meshgrid(*_ptsG, indexing='ij') - #needs to be improved when passing FemFields, - #a lot of evaluations that are probably not needed + if isinstance(f, FemField): assert(isinstance(f.space,TensorFemSpace)) _fun_q = f.space.eval_fields_irregular_tensor_grid(_ptsG, f) @@ -272,7 +274,7 @@ def assemble_mat(P, V, fun): basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd') kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, - _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_subs, *_Vnbases, *_Wdegrees) + _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_Vnbases, *_Wdegrees) blocks[-1] += [dofs_mat] @@ -343,39 +345,12 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): x_grid = greville_loc pts += [greville_loc[:, None]] wts += [np.ones(pts[-1].shape, dtype=float)] - # sub-interval index is always 0 for interpolation. - subs += [np.zeros(pts[-1].shape[0], dtype=int)] # histopolation elif space_out.basis == 'M': - """if space_out.degree % 2 == 0: - union_breaks = space_out.breaks - else: - union_breaks = space_out.breaks[:-1] - - # Make union of Greville and break points - tmp = set(np.round_(space_out.histopolation_grid, decimals=14)).union( - np.round_(union_breaks, decimals=14)) - - tmp = list(tmp) - tmp.sort() - tmp_a = np.array(tmp) - - x_grid = tmp_a[np.logical_and(tmp_a >= np.min( - histopol_loc) - 1e-14, tmp_a <= np.max(histopol_loc) + 1e-14)]""" - x_grid = space_out.histopolation_grid - - # determine subinterval index (= 0 or 1): - subs += [np.zeros(len(x_grid), dtype=int)] - for n, x_h in enumerate(x_grid): - add = 1 - for x_g in histopol_loc: - if abs(x_h - x_g) < 1e-14: - add = 0 - subs[-1][n] += add - + # Gauss - Legendre quadrature points and weights if n_quad is None: # products of basis functions are integrated exactly @@ -400,8 +375,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): bases += [b] direction += 1 - - return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(subs) + return tuple(pts), tuple(wts), tuple(spans), tuple(bases) def get_span_and_basis(pts, space): diff --git a/psydac/feec/pull_push.py b/psydac/feec/pull_push.py index 89ada4c9c..2cd08fcd7 100644 --- a/psydac/feec/pull_push.py +++ b/psydac/feec/pull_push.py @@ -8,6 +8,7 @@ # ------------------- 'pull_1d_h1', 'pull_1d_l2', + 'pull_2d_v', 'pull_2d_h1', 'pull_2d_hcurl', 'pull_2d_hdiv', @@ -64,36 +65,35 @@ def f_logical(eta1): #============================================================================== # 2D PULL-BACKS #============================================================================== -def pull_2d_v(funcs_ini, mapping): +def pull_2d_v(f, F): #We should check if the metric terms are really the good ones! - mapping = mapping.get_callable_mapping() - f1,f2 = mapping._func_eval - J_inv = mapping._jacobian_inv + assert isinstance(F, BasicCallableMapping) + assert F.ldim == 2 + + f1, f2 = f - def fun1(xi1, xi2): - x = f1(xi1, xi2) - y = f2(xi1, xi2) + def f1_logical(eta1, eta2): + x, y = F(eta1, eta2) - a1_phys = funcs_ini[0](x, y) - a2_phys = funcs_ini[1](x, y) + a1_phys = f1(x, y) + a2_phys = f2(x, y) - J_inv_value = J_inv(xi1, xi2) + J_inv_value = F.jacobian_inv(eta1, eta2) value_1 = J_inv_value[0,0]*a1_phys + J_inv_value[0,1]*a2_phys return value_1 - def fun2(xi1, xi2): - x = f1(xi1, xi2) - y = f2(xi1, xi2) + def f2_logical(eta1, eta2): + x, y = F(eta1, eta2) - a1_phys = funcs_ini[0](x, y) - a2_phys = funcs_ini[1](x, y) + a1_phys = f1(x, y) + a2_phys = f2(x, y) - J_inv_value = J_inv(xi1, xi2) + J_inv_value = F.jacobian_inv(eta1, eta2) value_2 = J_inv_value[1,0]*a1_phys + J_inv_value[1,1]*a2_phys return value_2 - return fun1, fun2 + return f1_logical, f2_logical def pull_2d_h1(f, F): @@ -192,30 +192,47 @@ def f_logical(eta1, eta2): # TODO [YG 05.10.2022]: # Remove? But it makes sense to return a vector-valued function... -def pull_3d_v(funcs_ini, mapping): - #We should check if the metric terms are really the good ones! +def pull_3d_v(f, F): + + assert isinstance(F, BasicCallableMapping) + assert F.ldim == 3 + + f1, f2, f3 = f + + def f1_logical(eta1, eta2, eta3): + x, y, z = F(eta1, eta2, eta3) - mapping = mapping.get_callable_mapping() - f1,f2,f3 = mapping._func_eval - J_inv = mapping._jacobian_inv + a1_phys = f1(x, y, z) + a2_phys = f2(x, y, z) + a3_phys = f3(x, y, z) - def fun(xi1, xi2, xi3): - x = f1(xi1, xi2, xi3) - y = f2(xi1, xi2, xi3) - z = f3(xi1, xi2, xi3) + J_inv_value = F.jacobian_inv(eta1, eta2, eta3) + value_1 = J_inv_value[0,0]*a1_phys + J_inv_value[0,1]*a2_phys + J_inv_value[0,2]*a3_phys + return value_1 - a1_phys = funcs_ini[0](x, y, z) - a2_phys = funcs_ini[1](x, y, z) - a3_phys = funcs_ini[2](x, y, z) + def f2_logical(eta1, eta2, eta3): + x, y, z = F(eta1, eta2, eta3) - J_inv_value = J_inv(xi1, xi2, xi3) - value_1 = J_inv_value[0, 0] * a1_phys + J_inv_value[0, 1] * a2_phys + J_inv_value[0, 2] * a3_phys - value_2 = J_inv_value[1, 0] * a1_phys + J_inv_value[1, 1] * a2_phys + J_inv_value[1, 2] * a3_phys - value_3 = J_inv_value[2, 0] * a1_phys + J_inv_value[2, 1] * a2_phys + J_inv_value[2, 2] * a3_phys + a1_phys = f1(x, y, z) + a2_phys = f2(x, y, z) + a3_phys = f3(x, y, z) - return value_1, value_2, value_3 + J_inv_value = F.jacobian_inv(eta1, eta2, eta3) + value_2 = J_inv_value[1,0]*a1_phys + J_inv_value[1,1]*a2_phys + J_inv_value[1,2]*a3_phys + return value_2 - return fun + def f3_logical(eta1, eta2, eta3): + x, y, z = F(eta1, eta2, eta3) + + a1_phys = f1(x, y, z) + a2_phys = f2(x, y, z) + a3_phys = f3(x, y, z) + + J_inv_value = F.jacobian_inv(eta1, eta2, eta3) + value_2 = J_inv_value[2,0]*a1_phys + J_inv_value[2,1]*a2_phys + J_inv_value[2,2]*a3_phys + return value_2 + + return f1_logical, f2_logical, f3_logical #============================================================================== def pull_3d_h1(f, F): diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index 9e28ea612..20987e738 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -1,13 +1,22 @@ -from sympde.topology import Square +from sympde.topology import Square, Domain from psydac.feec.multipatch.api import discretize from sympde.topology import Derham from psydac.feec.basis_projectors import BasisProjectionOperator from psydac.fem.basic import FemField import matplotlib.pyplot as plt import numpy as np +import os import pytest +try: + mesh_dir = os.environ['PSYDAC_MESH_DIR'] + +except: + base_dir = os.path.dirname(os.path.realpath(__file__)) + base_dir = os.path.join(base_dir, '..', '..', '..') + mesh_dir = os.path.join(base_dir, 'mesh') + @pytest.mark.parametrize('nc', [4, 8, 15]) @pytest.mark.parametrize('deg', [2,3]) @pytest.mark.parametrize('perio', [[True, True], [True, False], [False, False]]) @@ -195,7 +204,7 @@ def test_basis_projector_2d(nc, deg, perio): def test_basis_projector_non_unit_square_2d(nc, deg, perio): ### INITIALISATION ### - domain = Square('Omega', bounds1 = (-1,1), bounds2 = (-1,1)) + domain = Square('Omega', bounds1 = (0,1), bounds2 = (-1,1)) ncells = (nc,nc) degree = (deg,deg) nquads = [2*(d + 1) for d in degree] @@ -376,5 +385,188 @@ def test_basis_projector_non_unit_square_2d(nc, deg, perio): sol_no_op = P2(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) +@pytest.mark.parametrize('deg', [2,3]) +@pytest.mark.parametrize('filename', ['identity_2d.h5', 'collela_2d.h5']) + +def test_basis_projector_2d_mapping(deg, filename): + #We cannot really test on physical domains since the FemFields can only be evaluated on the ref domain + ### INITIALISATION ### + meshname = os.path.join(mesh_dir, filename) + domain = Domain.from_file(meshname) + degree = (deg,deg) + nquads = [2*(d + 1) for d in degree] + domain_h = discretize(domain, filename=meshname) + mapping = domain.mapping + F = mapping.get_callable_mapping() + derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham_h = discretize(derham, domain_h, get_vec = True) + V0h = derham_h.V0 + V1h = derham_h.V1 + V2h = derham_h.V2 + Xh = derham_h.Vvec + + P0, P1, P2, PX = derham_h.projectors(nquads=nquads) + P0_ref, P1_ref, P2_ref, PX_ref = derham_h.projectors(nquads=nquads, get_reference=True) + + #Bunch of function for tests + f_1 = lambda x, y : x*(x-1)+3 + f_2 = lambda x, y : np.cos(2*np.pi*x) + f_3 = lambda x, y : np.sin(2*np.pi*x)*y*(y-1) + f_4 = lambda x, y : x*(x-1)*y*(y-1) + f_5 = lambda x, y : np.cos(2*np.pi*x)*np.sin(2*np.pi*y) + f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) + f_7 = lambda x, y : np.exp(y)+np.exp(1-y) + + ### TEST V0->V0 ### + fun = [[f_1]] + f_test = P0(f_2) + P0_0fv = BasisProjectionOperator(P0_ref, V0h, fun) + sol_with_op = P0_0fv.dot(f_test.coeffs) + sol_no_op = P0_ref(lambda x, y : f_1(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V0 ### + fun = [[f_3,f_4]] + f_test = P1([f_1, f_5]) + P1_0fv = BasisProjectionOperator(P0_ref, V1h, fun) + sol_with_op = P1_0fv.dot(f_test.coeffs) + sol_no_op = P0_ref(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V0 ### + fun = [[f_6]] + f_test = P2(f_3) + P2_0fv = BasisProjectionOperator(P0_ref, V2h, fun) + sol_with_op = P2_0fv.dot(f_test.coeffs) + sol_no_op = P0_ref(lambda x, y : f_6(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V0 ### + fun = [[f_4,f_1]] + f_test = PX([f_4,f_5]) + PX_0fv = BasisProjectionOperator(P0_ref, Xh, fun) + sol_with_op = PX_0fv.dot(f_test.coeffs) + sol_no_op = P0_ref(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0 -> V1 ### + fun = [[f_2],[f_6]] + f_test = P0(f_4) + P0_1fv = BasisProjectionOperator(P1_ref, V0h, fun) + sol_with_op = P0_1fv.dot(f_test.coeffs) + sol_no_op = P1_ref([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V1 ### + fun = [[f_2,f_4],[f_5,f_1]] + f_test = P1([f_3,f_7]) + P1_1fv = BasisProjectionOperator(P1_ref, V1h, fun) + sol_with_op = P1_1fv.dot(f_test.coeffs) + sol_no_op = P1_ref([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V1 ### + fun = [[f_4],[f_7]] + f_test = P2(f_1) + P2_1fv = BasisProjectionOperator(P1_ref, V2h, fun) + sol_with_op = P2_1fv.dot(f_test.coeffs) + sol_no_op = P1_ref([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V1 ### + fun = [[f_3,f_6],[f_5,f_2]] + f_test = PX([f_3,f_1]) + PX_1fv = BasisProjectionOperator(P1_ref, Xh, fun) + sol_with_op = PX_1fv.dot(f_test.coeffs) + sol_no_op = P1_ref([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0->V2 ### + fun = [[f_4]] + P0_2fv = BasisProjectionOperator(P2_ref, V0h, fun) + f_test = P0(f_2) + sol_with_op = P0_2fv.dot(f_test.coeffs) + sol_no_op = P2_ref(lambda x, y : f_4(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> V2 ### + fun = [[f_5,f_2]] + f_test = P1([f_1, f_5]) + P1_2fv = BasisProjectionOperator(P2_ref, V1h, fun) + sol_with_op = P1_2fv.dot(f_test.coeffs) + sol_no_op = P2_ref(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> V2 ### + fun = [[f_1]] + f_test = P2(f_3) + P2_2fv = BasisProjectionOperator(P2_ref, V2h, fun) + sol_with_op = P2_2fv.dot(f_test.coeffs) + sol_no_op = P2_ref(lambda x, y : f_1(x,y)*f_test(x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> V2 ### + fun = [[f_3,f_7]] + f_test = PX([f_4,f_5]) + PX_2fv = BasisProjectionOperator(P2_ref, Xh, fun) + sol_with_op = PX_2fv.dot(f_test.coeffs) + sol_no_op = P2_ref(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V0 -> X ### + fun = [[f_4],[f_1]] + f_test = P0(f_2) + P0_Xfv = BasisProjectionOperator(PX_ref, V0h, fun) + sol_with_op = P0_Xfv.dot(f_test.coeffs) + sol_no_op = PX_ref([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V1 -> X ### + fun = [[f_1,f_3],[f_5,f_7]] + f_test = P1([f_4,f_5]) + P1_Xfv = BasisProjectionOperator(PX_ref, V1h, fun) + sol_with_op = P1_Xfv.dot(f_test.coeffs) + sol_no_op = PX_ref([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), + lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST V2 -> X ### + fun = [[f_2],[f_6]] + f_test = P2(f_1) + P2_Xfv = BasisProjectionOperator(PX_ref, V2h, fun) + sol_with_op = P2_Xfv.dot(f_test.coeffs) + sol_no_op = PX_ref([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### TEST X -> X ### + fun = [[f_1,f_2],[f_3,f_4]] + f_test = PX([f_5,f_6]) + PX_Xfv = BasisProjectionOperator(PX_ref, Xh, fun) + sol_with_op = PX_Xfv.dot(f_test.coeffs) + sol_no_op = PX_ref([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), + lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ###TEST WITH FemField as parameter### + ### X->V0 with V1 field ### + pf = P1([f_4,f_1]) + fun = [[pf[0],pf[1]]] + f_test = PX([f_4,f_5]) + PX_0fv = BasisProjectionOperator(P0_ref, Xh, fun) + sol_with_op = PX_0fv.dot(f_test.coeffs) + sol_no_op = P0_ref(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + + ### X->V2 with V1 field ### + pf = P1([f_2,f_6]) + fun = [[pf[0],pf[1]]] + f_test = PX([f_3,f_1]) + P2_0fv = BasisProjectionOperator(P2_ref, Xh, fun) + sol_with_op = P2_0fv.dot(f_test.coeffs) + sol_no_op = P2_ref(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) + assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) + if __name__ == '__main__': - test_basis_projector_non_unit_square_2d(4, 2, [False,False]) \ No newline at end of file + test_basis_projector_2d_mapping(2, 'collela_2d.h5') \ No newline at end of file From 04c5b82ffb1df90d6a467ca51d6bb9c54e0e40e1 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 12 May 2023 13:50:41 +0200 Subject: [PATCH 17/77] fix useless stuff for codacity --- psydac/feec/basis_projection_kernels.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py index 1cedbf2cc..81982870c 100644 --- a/psydac/feec/basis_projection_kernels.py +++ b/psydac/feec/basis_projection_kernels.py @@ -42,9 +42,6 @@ def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int basis1 : 3d float array Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - sub1 : 1d int array - Sub-interval indices in direction 1. - dim1_in : int Dimension of the first direction of the input space @@ -52,8 +49,6 @@ def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int Spline degree of the first direction of the output space ''' - from numpy import sum - # Start/end indices and paddings for distributed stencil matrix of input space # si1 = starts_in[0} # ei1 = ends_in[0] @@ -166,12 +161,6 @@ def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : basis2 : 3d float array Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). - sub1 : 1d int array - Sub-interval indices in direction 1. - - sub2 : 1d int array - Sub-interval indices in direction 2. - dim1_in : int Dimension of the first direction of the input space @@ -185,8 +174,6 @@ def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : Spline degree of the second direction of the output space ''' - from numpy import sum - # Start/end indices and paddings for distributed stencil matrix of input space # si1 = starts_in[0] # si2 = starts_in[1] @@ -344,15 +331,6 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i basis3 : 3d float array Values of p3 + 1 non-zero eta-3 basis functions at quadrature points in format (kk, kq, basis function). - sub1 : 1d int array - Sub-interval indices in direction 1. - - sub2 : 1d int array - Sub-interval indices in direction 2. - - sub3 : 1d int array - Sub-interval indices in direction 3. - dim1_in : int Dimension of the first direction of the input space @@ -372,8 +350,6 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i Spline degree of the third direction of the output space ''' - from numpy import sum - # Start/end indices and paddings for distributed stencil matrix of input space # si1 = starts_in[0] # si2 = starts_in[1] From 4e45d5b6313e1f6b49f5d2297f77547af4fb3276 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 12 May 2023 17:16:31 +0200 Subject: [PATCH 18/77] add the possibility to have a metric to compute the error on the Biconjugate gradient (stabilized) solvers --- psydac/linalg/solvers.py | 44 +++++++++++++++++++++-------- psydac/linalg/tests/test_solvers.py | 10 +++++-- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index d9cbb1bee..bf02f17fd 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -509,12 +509,15 @@ class BiConjugateGradient(InverseLinearOperator): verbose : bool If True, 2-norm of residual r is printed at each iteration. + metric : LinearOperator + The matrix giving the metric to compute the errors + References ---------- [1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015. """ - def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False): + def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, metric=None): assert isinstance(A, LinearOperator) assert A.domain.dimension == A.codomain.dimension @@ -532,7 +535,7 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False): self._domain = domain self._codomain = codomain self._solver = 'bicg' - self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose} + self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose, "metric":metric} self._check_options(**self._options) self._tmps = {key: domain.zeros() for key in ("v", "r", "p", "vs", "rs", "ps")} self._info = None @@ -555,6 +558,12 @@ def _check_options(self, **kwargs): elif key == 'verbose': assert value is not None, "verbose may not be None" assert isinstance(value, bool), "verbose must be a bool" + elif key == 'metric': + if value is not None: + assert isinstance(value, LinearOperator), "metric must be a LinearOperator or None" + assert value.domain == value.codomain, "metric must be square with same domain and codomain " + assert value.domain == self._codomain, "metric must be defined on the codomain of the operator to solve " + else: raise ValueError(f"Key '{key}' not understood. See self._options for allowed keys.") @@ -610,7 +619,9 @@ def solve(self, b, out=None): tol = options["tol"] maxiter = options["maxiter"] verbose = options["verbose"] - + M = options["metric"] + if M == None: + M=IdentityOperator(domain,codomain) assert isinstance(b, Vector) assert b.space is domain @@ -640,7 +651,7 @@ def solve(self, b, out=None): p.copy(out=ps) v.copy(out=vs) - res_sqr = r.dot(r).real + res_sqr = r.dot(M.dot(r)).real tol_sqr = tol**2 if verbose: @@ -700,7 +711,7 @@ def solve(self, b, out=None): ps += rs # ||r||_2 := (r, r) - res_sqr = r.dot(r).real + res_sqr = r.dot(M.dot(r)).real if verbose: print( template.format(m, sqrt(res_sqr)) ) @@ -744,12 +755,15 @@ class BiConjugateGradientStabilized(InverseLinearOperator): verbose : bool If True, 2-norm of residual r is printed at each iteration. + metric : LinearOperator + The matrix giving the metric to compute the errors + References ---------- [1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015. """ - def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False): + def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, metric=None): assert isinstance(A, LinearOperator) assert A.domain.dimension == A.codomain.dimension @@ -766,13 +780,13 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False): self._domain = domain self._codomain = codomain self._solver = 'bicgstab' - self._options = {"x0": x0, "tol": tol, "maxiter": maxiter, "verbose": verbose} + self._options = {"x0": x0, "tol": tol, "maxiter": maxiter, "verbose": verbose, "metric":metric} self._check_options(**self._options) self._tmps = {key: domain.zeros() for key in ("v", "r", "p", "vs", "r0", "s")} self._info = None def _check_options(self, **kwargs): - keys = ('x0', 'tol', 'maxiter', 'verbose') + keys = ('x0', 'tol', 'maxiter', 'verbose', 'metric') for key, value in kwargs.items(): idx = [key == keys[i] for i in range(len(keys))] assert any(idx), "key not supported, check options" @@ -793,9 +807,14 @@ def _check_options(self, **kwargs): elif true_idx == 3: assert value is not None, "verbose may not be None" assert isinstance(value, bool), "verbose must be a bool" + elif true_idx == 4: + if value is not None: + assert isinstance(value, LinearOperator), "metric must be a LinearOperator or None" + assert value.domain == value.codomain, "metric must be square with same domain and codomain " + assert value.domain == self._codomain, "metric must be defined on the codomain of the operator to solve " def _update_options( self ): - self._options = {"x0":self._x0, "tol":self._tol, "maxiter": self._maxiter, "verbose": self._verbose} + self._options = {"x0":self._x0, "tol":self._tol, "maxiter": self._maxiter, "verbose": self._verbose, "metric":self._metric} def transpose(self, conjugate=False): At = self._A.transpose(conjugate=conjugate) @@ -846,6 +865,9 @@ def solve(self, b, out=None): tol = options["tol"] maxiter = options["maxiter"] verbose = options["verbose"] + M = options["metric"] + if M == None: + M=IdentityOperator(domain,codomain) assert isinstance(b, Vector) assert b.space is domain @@ -878,7 +900,7 @@ def solve(self, b, out=None): r.copy(out=s) s *= 0.0 - res_sqr = r.dot(r).real + res_sqr = r.dot(M.dot(r)).real tol_sqr = tol ** 2 if verbose: @@ -937,7 +959,7 @@ def solve(self, b, out=None): r -= vs # ||r||_2 := (r, r) - res_sqr = r.dot(r).real + res_sqr = r.dot(M.dot(r)).real if res_sqr < tol_sqr: break diff --git a/psydac/linalg/tests/test_solvers.py b/psydac/linalg/tests/test_solvers.py index 09229b611..b6cf6977d 100644 --- a/psydac/linalg/tests/test_solvers.py +++ b/psydac/linalg/tests/test_solvers.py @@ -3,7 +3,7 @@ import pytest from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilVectorSpace, StencilMatrix, StencilVector -from psydac.linalg.basic import LinearSolver +from psydac.linalg.basic import LinearSolver, IdentityOperator from psydac.ddm.cart import DomainDecomposition, CartDecomposition @@ -86,8 +86,10 @@ def test_bicgstab_tridiagonal(n, p, dtype, verbose=False): bt = (A.T).dot( xe ) bh = (A.H).dot( xe ) + metric = 2*IdentityOperator(V,V) + #Create the solvers - solv = inverse(A, 'bicgstab', tol=1e-13, verbose=True) + solv = inverse(A, 'bicgstab', tol=1e-13, verbose=True, metric=metric) solvt = solv.transpose() solvh = solv.H @@ -160,13 +162,15 @@ def test_bicg_tridiagonal(n, p, dtype, verbose=False): print( "="*80 ) print() + metric = 2*IdentityOperator(V,V) + # Manufacture right-hand-side vector from exact solution b = A.dot( xe ) bt = A.T.dot( xe ) bh = A.H.dot( xe ) #Create the solvers - solv = inverse(A, 'bicg', tol=1e-13, verbose=True) + solv = inverse(A, 'bicg', tol=1e-13, verbose=True, metric=metric) solvt = solv.transpose() solvh = solv.H From 6cf88554e75016fbe7119fe0f8dcf5fc0ca4e4a2 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 22 May 2023 09:14:19 +0200 Subject: [PATCH 19/77] add nb cells in basis projectors to use eval field regular tensor grid --- psydac/api/settings.py | 8 ++++++++ psydac/feec/basis_projectors.py | 17 +++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/psydac/api/settings.py b/psydac/api/settings.py index 81dcbfb0b..75d93fcb6 100644 --- a/psydac/api/settings.py +++ b/psydac/api/settings.py @@ -19,6 +19,13 @@ 'tag':'gpyccel', 'openmp':False} +PSYDAC_BACKEND_GPYCCEL_MPI = {'name': 'pyccel', + 'compiler': 'gfortran' if pyccel_legacy else 'GNU', + 'flags': '-O3 -march=native -mtune=native -mavx -ffast-math -ffree-line-length-none', + 'folder': '__gpyccel__', + 'tag':'gpyccel', + 'openmp':True} + PSYDAC_BACKEND_IPYCCEL = {'name': 'pyccel', 'compiler': 'ifort' if pyccel_legacy else 'intel', 'flags': '-O3', @@ -42,6 +49,7 @@ PSYDAC_BACKENDS = { 'python' : PSYDAC_BACKEND_PYTHON, 'pyccel-gcc' : PSYDAC_BACKEND_GPYCCEL, + 'pyccel-gcc-mpi' : PSYDAC_BACKEND_GPYCCEL_MPI, 'pyccel-intel': PSYDAC_BACKEND_IPYCCEL, 'pyccel-pgi' : PSYDAC_BACKEND_PGPYCCEL, 'numba' : PSYDAC_BACKEND_NUMBA, diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 885b104a8..782450414 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -246,7 +246,7 @@ def assemble_mat(P, V, fun): _ends_out = np.array(dofs_mat.codomain.ends) _pads_out = np.array(dofs_mat.codomain.pads) - _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( + _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( V1d, W1d, _starts_out, _ends_out, nq) _ptsG = [pts.flatten() for pts in _ptsG] @@ -257,7 +257,7 @@ def assemble_mat(P, V, fun): if isinstance(f, FemField): assert(isinstance(f.space,TensorFemSpace)) - _fun_q = f.space.eval_fields_irregular_tensor_grid(_ptsG, f) + _fun_q = f.space.eval_fields(_ptsG, f, npts_per_cell=_npt_pts)#_irregular_tensor_grid(_ptsG, f) _fun_q = np.squeeze(_fun_q) #since we only evaluate one field the result is a 3D #array with last dim 1, we need to squeeze it in order to use the pyccelized kernels elif isinstance(f, float) or isinstance(f, int): @@ -328,7 +328,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): import psydac.core.bsplines as bsp - x_grid, subs, pts, wts, spans, bases = [], [], [], [], [], [] + x_grid, pts, wts, spans, bases, np_pts_cell = [], [], [], [], [], [] # Loop over direction, prepare point sets and evaluate basis functions direction = 0 @@ -345,11 +345,12 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): x_grid = greville_loc pts += [greville_loc[:, None]] wts += [np.ones(pts[-1].shape, dtype=float)] + np_pts_cell += [1] # histopolation elif space_out.basis == 'M': - x_grid = space_out.histopolation_grid + x_grid = histopol_loc #space_out.histopolation_grid # Gauss - Legendre quadrature points and weights if n_quad is None: @@ -363,11 +364,11 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): #"roll" back points to the interval to ensure that the quadrature points are #in the domain. Probably only usefull on periodic cases roll_edges(space_out.domain, global_quad_x) - x = global_quad_x[s:e+1] - w = global_quad_w[s:e+1] + x = global_quad_x#[s:e+1] + w = global_quad_w#[s:e+1] pts += [x] wts += [w] - + np_pts_cell += [nq] # Knot span indices and V-basis functions evaluated at W-point sets s, b = get_span_and_basis(pts[-1], space_in) @@ -375,7 +376,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): bases += [b] direction += 1 - return tuple(pts), tuple(wts), tuple(spans), tuple(bases) + return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(np_pts_cell) def get_span_and_basis(pts, space): From a59ac87b5f4f1928014e4dbaeb92e13214dfabd4 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 26 May 2023 08:16:21 +0200 Subject: [PATCH 20/77] little rectification in basis proj operator --- psydac/feec/basis_projectors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 782450414..cd3eae789 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -257,7 +257,7 @@ def assemble_mat(P, V, fun): if isinstance(f, FemField): assert(isinstance(f.space,TensorFemSpace)) - _fun_q = f.space.eval_fields(_ptsG, f, npts_per_cell=_npt_pts)#_irregular_tensor_grid(_ptsG, f) + _fun_q = f.space.eval_fields(_ptsG, f)#, npts_per_cell=_npt_pts)#_irregular_tensor_grid(_ptsG, f) _fun_q = np.squeeze(_fun_q) #since we only evaluate one field the result is a 3D #array with last dim 1, we need to squeeze it in order to use the pyccelized kernels elif isinstance(f, float) or isinstance(f, int): @@ -364,8 +364,8 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): #"roll" back points to the interval to ensure that the quadrature points are #in the domain. Probably only usefull on periodic cases roll_edges(space_out.domain, global_quad_x) - x = global_quad_x#[s:e+1] - w = global_quad_w#[s:e+1] + x = global_quad_x + w = global_quad_w pts += [x] wts += [w] np_pts_cell += [nq] From ecdaab5f4b88687aee634036dc01a327698af1b1 Mon Sep 17 00:00:00 2001 From: vcarlier <105044741+vcarlier@users.noreply.github.com> Date: Wed, 31 May 2023 13:47:52 +0200 Subject: [PATCH 21/77] Delete .lock_acquisition.lock --- psydac/feec/.lock_acquisition.lock | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 psydac/feec/.lock_acquisition.lock diff --git a/psydac/feec/.lock_acquisition.lock b/psydac/feec/.lock_acquisition.lock deleted file mode 100755 index e69de29bb..000000000 From 43c0033c6946e2d242976c5420eb1575f6eab0d6 Mon Sep 17 00:00:00 2001 From: vcarlier <105044741+vcarlier@users.noreply.github.com> Date: Wed, 31 May 2023 13:48:35 +0200 Subject: [PATCH 22/77] Update test_assembly.py delete useless space --- psydac/api/tests/test_assembly.py | 1 - 1 file changed, 1 deletion(-) diff --git a/psydac/api/tests/test_assembly.py b/psydac/api/tests/test_assembly.py index 714112a6a..bacb8812d 100644 --- a/psydac/api/tests/test_assembly.py +++ b/psydac/api/tests/test_assembly.py @@ -233,4 +233,3 @@ def test_assembly_no_synchr_args(backend): test_math_imports(None) test_non_symmetric_BilinearForm(None) test_assembly_no_synchr_args(None) - From d099ad11f9e3afaa2978658dc8d61db76b540ab5 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 1 Jun 2023 13:42:34 +0200 Subject: [PATCH 23/77] optimization : can now set a Basis projection operator with given matrix so that you don't have to compute it, particularly usefull when transposing and one can now pass the space data in order to avoid multiple computation of the same data --- psydac/feec/basis_projectors.py | 92 ++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index cd3eae789..6928fed56 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -37,10 +37,10 @@ class BasisProjectionOperator(LinearOperator): Whether to assemble the transposed operator. """ - def __init__(self, P, V, fun, transposed=False): + def __init__(self, P, V, fun, transposed=False, preproc_grid=None, dof_mat=None): # only for M1 Mac users - PSYDAC_BACKEND_GPYCCEL['flags'] = '-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none' + #PSYDAC_BACKEND_GPYCCEL['flags'] = '-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none' assert isinstance(P, GlobalProjector) assert isinstance(V, FemSpace) @@ -72,9 +72,12 @@ def __init__(self, P, V, fun, transposed=False): self._domain_symbolic_name = V_name self._codomain_symbolic_name = P_name + self._preproc_grid = preproc_grid + # ============= assemble tensor-product dof matrix ======= - dof_mat = BasisProjectionOperator.assemble_mat( - P, V, fun) + if dof_mat == None: + dof_mat = BasisProjectionOperator.assemble_mat( + P, V, fun, self._preproc_grid) # ======================================================== self._dof_operator = dof_mat @@ -179,10 +182,13 @@ def transpose(self, conjugate=False): Returns the transposed operator. """ #conjugate not implemented - return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed) + if self.transposed: + return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed, preproc_grid=self._preproc_grid, dof_mat=self._dof_operator.transpose()) + else : + return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed, preproc_grid=self._preproc_grid, dof_mat=self._dof_operator) @staticmethod - def assemble_mat(P, V, fun): + def assemble_mat(P, V, fun, preproc_grid=None): """ Assembles the tensor-product DOF matrix sigma_i(fun*Lambda_j), where i=(i1, i2, ...) and j=(j1, j2, ...) depending on the number of spatial dimensions (1d, 2d or 3d). @@ -225,11 +231,12 @@ def assemble_mat(P, V, fun): # blocks of dof matrix blocks = [] - + i=0 # ouptut vector space (codomain), row of block for Wspace, W1d, nq, fun_line in zip(_Wspaces, _W1ds, _nqs, fun): blocks += [[]] _Wdegrees = [space.degree for space in W1d] + j=0 # input vector space (domain), column of block for Vspace, V1d, f in zip(_Vspaces, _V1ds, fun_line): @@ -246,7 +253,11 @@ def assemble_mat(P, V, fun): _ends_out = np.array(dofs_mat.codomain.ends) _pads_out = np.array(dofs_mat.codomain.pads) - _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + if preproc_grid != None : + _ptsG, _wtsG, _spans, _bases, _npt_pts = preproc_grid[i][j] + + else: + _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( V1d, W1d, _starts_out, _ends_out, nq) _ptsG = [pts.flatten() for pts in _ptsG] @@ -280,6 +291,8 @@ def assemble_mat(P, V, fun): else: blocks[-1] += [None] + j+=1 + i+=1 # build BlockLinearOperator (if necessary) and return if len(blocks) == len(blocks[0]) == 1: @@ -418,3 +431,66 @@ def get_span_and_basis(pts, space): span[n, nq] = span_tmp # % space.nbasis return span, basis + + +def preprocess_grid(P, V): + """ + Gather the results of prepare_projection_of_basis for the different SplineSpaces composing a space, + the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid + computing several time the same quantities + + Parameters + ---------- + P : GlobalProjector + The psydac global tensor product projector defining the space onto which the input shall be projected. + + V : TensorFemSpace | ProductFemSpace + The spline space which shall be projected. + + Returns + ------- + preproc : List of List of Tuple + List of List containing the outputs of prepare_projection_of_basis applied to the differents spaces + """ + + # input space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(V, TensorFemSpace): + _Vspaces = [V.vector_space] + _V1ds = [V.spaces] + else: + _Vspaces = V.vector_space + _V1ds = [comp.spaces for comp in V.spaces] + + # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(P.space, TensorFemSpace): + _Wspaces = [P.space.vector_space] + _W1ds = [P.space.spaces] + else: + _Wspaces = P.space.vector_space + _W1ds = [comp.spaces for comp in P.space.spaces] + + # retrieve number of quadrature points of each component (=1 for interpolation) + _nqs = [[P.grid_x[comp][direction].shape[1] + for direction in range(V.ldim)] for comp in range(len(_W1ds))] + + # blocks of dof matrix + preproc = [] + # ouptut vector space (codomain), row of block + for Wspace, W1d, nq in zip(_Wspaces, _W1ds, _nqs): + + line_pre = [] + # input vector space (domain), column of block + for Vspace, V1d in zip(_Vspaces, _V1ds): + + # instantiate cell of block matrix + dofs_mat = StencilMatrix( + Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) + + _starts_out = np.array(dofs_mat.codomain.starts) + _ends_out = np.array(dofs_mat.codomain.ends) + + _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + V1d, W1d, _starts_out, _ends_out, nq) + line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) + preproc.append(line_pre.copy()) + return preproc \ No newline at end of file From 50d223ed11ea6e7f6d4caf09c0ed87af224a1606 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 12 Jun 2023 08:16:20 +0200 Subject: [PATCH 24/77] optimzed te creation of he basis projection operator in case the coefficient are Femfields --- psydac/feec/basis_projection_kernels.py | 188 ++++++++++++++++ psydac/feec/basis_projectors.py | 250 ++++++++++++++++++--- psydac/feec/tests/test_basis_projectors.py | 1 + 3 files changed, 410 insertions(+), 29 deletions(-) diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py index 81982870c..8bbfd60de 100644 --- a/psydac/feec/basis_projection_kernels.py +++ b/psydac/feec/basis_projection_kernels.py @@ -476,3 +476,191 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i # Row index: padding + local index. mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value + + + + + +def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', coeffs_f : 'float[:,:]', span_c1 : 'int[:,:]', span_c2 : 'int[:,:]', basis_c1 : 'float[:,:,:]', basis_c2 : 'float[:,:,:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): + '''Kernel for assembling the matrix + + A_(ij,kl) = DOFS_ij(fun*Lambda^in_kl) , + + into the _data attribute of a StencilMatrix. + Here, DOFS_ij are the degrees-of-freedom of the output space (codomain, must not be a product space), + Lambda^in_kl are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + + Parameters + ---------- + mat : 4d float array + _data attribute of StencilMatrix. + + starts_in : 1d int array + Starting indices of the input space (domain) of a distributed StencilMatrix. + + ends_in : 1d int array + Ending indices of the input space (domain) of a distributed StencilMatrix. + + pads_in : 1d int array + Paddings of the input space (domain) of a distributed StencilMatrix. + + starts_out : 1d int array + Starting indices of the output space (codomain) of a distributed StencilMatrix. + + ends_out : 1d int array + Ending indices of the output space (codomain) of a distributed StencilMatrix. + + pads_out : 1d int array + Paddings of the output space (codomain) of a distributed StencilMatrix. + + fun_q : 2d float array + The function evaluated at the points (nq_i*ii + iq, nq_j*jj + jq), where iq a local quadrature point of interval ii. + + wts1 : 2d float array + Quadrature weights in direction eta1 in format (ii, iq). + + wts2 : 2d float array + Quadrature weights in direction eta2 in format (jj, jq). + + span1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq). + + span2 : 2d int array + Knot span indices in direction eta2 in format (jj, jq). + + basis1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). + + basis2 : 3d float array + Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). + + dim1_in : int + Dimension of the first direction of the input space + + dim2_in : int + Dimension of the second direction of the input space + + p1_out : int + Spline degree of the first direction of the output space + + p2_out : int + Spline degree of the second direction of the output space + ''' + + # Start/end indices and paddings for distributed stencil matrix of input space + # si1 = starts_in[0] + # si2 = starts_in[1] + # ei1 = ends_in[0] + # ei2 = ends_in[1] + pi1 = pads_in[0] + pi2 = pads_in[1] + + # Start/end indices for distributed stencil matrix of output space + so1 = starts_out[0] + so2 = starts_out[1] + # eo1 = ends_out[0] + # eo2 = ends_out[1] + po1 = pads_out[0] + po2 = pads_out[1] + + sc1 = starts_c[0] + sc2 = starts_c[1] + # ec1 = ends_out[0] + # ec2 = ends_out[1] + pc1 = pads_c[0] + pc2 = pads_c[1] + + # Spline degrees of input space + p1 = basis1.shape[2] - 1 + p2 = basis2.shape[2] - 1 + + p1_c = basis_c1.shape[2] - 1 + p2_c = basis_c2.shape[2] - 1 + + # number of quadrature points + nq1 = span1.shape[1] + nq2 = span2.shape[1] + + # Set output to zero + mat[:] = 0. + + # Dimensions of output space + dim1_out = span1.shape[0] + dim2_out = span2.shape[0] + + # Interval (either element or sub-interval thereof) + # ------------------------------------------------- + cumsub_i = 0 # Cumulative sub-interval index + for ii in range(span1.shape[0]): + i = ii - cumsub_i # local DOF index + + cumsub_j = 0 # Cumulative sub-interval index + for jj in range(span2.shape[0]): + j = jj - cumsub_j # local DOF index + + # Quadrature point index in interval + # ---------------------------------- + for iq in range(nq1): + for jq in range(nq2): + + f_val = 0. + + for b1 in range(p1_c + 1): + # global index + m = (span_c1[ii, iq] - p1_c + b1) + #local index + m_loc = m-sc1+pc1 + for b2 in range(p2_c + 1): + # global index + n = (span_c2[jj, jq] - p2_c + b2) + #local index + n_loc = n-sc2+pc2 + f_val += basis_c1[ii, iq, b1] * basis_c2[jj, jq, b2] *coeffs_f[m_loc,n_loc] + + funval = wts1[ii, iq] * wts2[jj, jq] * f_val + + # Basis function of input space: + # ------------------------------ + for b1 in range(p1 + 1): + m = (span1[ii, iq] - p1 + b1) # global index + # basis value + val1 = funval * basis1[ii, iq, b1] + + # Find column index for _data: + if dim1_out <= dim1_in: + cut1 = p1 + else: + cut1 = p1_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col1 + col1_tmp = m - (i + so1) + if col1_tmp > cut1: + m = m - dim1_in + elif col1_tmp < -cut1: + m = m + dim1_in + # add padding + col1 = pi1 + m - (i + so1) + + for b2 in range(p2 + 1): + # global index + n = (span2[jj, jq] - p2 + b2) + value = val1 * basis2[jj, jq, b2] + + # Find column index for _data: + if dim2_out <= dim2_in: + cut2 = p2 + else: + cut2 = p2_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col2 + col2_tmp = n - (j + so2) + if col2_tmp > cut2: + n = n - dim2_in + elif col2_tmp < -cut2: + n = n + dim2_in + # add padding + col2 = pi2 + n - (j + so2) + + # Row index: padding + local index. + mat[po1 + i, po2 + j, col1, col2] += value + diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 6928fed56..6344e14b8 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -240,7 +240,6 @@ def assemble_mat(P, V, fun, preproc_grid=None): # input vector space (domain), column of block for Vspace, V1d, f in zip(_Vspaces, _V1ds, fun_line): - # instantiate cell of block matrix dofs_mat = StencilMatrix( Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) @@ -253,44 +252,68 @@ def assemble_mat(P, V, fun, preproc_grid=None): _ends_out = np.array(dofs_mat.codomain.ends) _pads_out = np.array(dofs_mat.codomain.pads) - if preproc_grid != None : - _ptsG, _wtsG, _spans, _bases, _npt_pts = preproc_grid[i][j] + if isinstance(f,FemField): - else: - _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( - V1d, W1d, _starts_out, _ends_out, nq) + space_ff = f.space.vector_space + Vfd = f.space.spaces + _starts_c = np.array(space_ff.starts) + _ends_c = np.array(space_ff.ends) + _pads_c = np.array(space_ff.pads) - _ptsG = [pts.flatten() for pts in _ptsG] - _Vnbases = [space.nbasis for space in V1d] - - # Evaluate weight function at quadrature points - pts = np.meshgrid(*_ptsG, indexing='ij') - - if isinstance(f, FemField): - assert(isinstance(f.space,TensorFemSpace)) - _fun_q = f.space.eval_fields(_ptsG, f)#, npts_per_cell=_npt_pts)#_irregular_tensor_grid(_ptsG, f) - _fun_q = np.squeeze(_fun_q) #since we only evaluate one field the result is a 3D - #array with last dim 1, we need to squeeze it in order to use the pyccelized kernels - elif isinstance(f, float) or isinstance(f, int): - shape_grid = tuple([len(pts_i) for pts_i in _ptsG]) - _fun_q = np.full(shape_grid, f) - else : - f = np.vectorize(f) - _fun_q = f(*pts).copy() #this formulation does not work atm for FemFields + if preproc_grid != None : + _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = preproc_grid[i][j] - # Call the kernel if weight function is not zero - if np.any(np.abs(_fun_q) > 1e-14): + else : + _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ + prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) + _ptsG = [pts.flatten() for pts in _ptsG] + _Vnbases = [space.nbasis for space in V1d] + f_coeffs = f.coeffs._data kernel = getattr( - basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd') + basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd_ff') kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, - _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_Vnbases, *_Wdegrees) + _pads_out, _starts_c, _ends_c, _pads_c, *_wtsG, *_spans, *_bases, f_coeffs, *_spans_ff, + *_bases_ff, *_Vnbases, *_Wdegrees) blocks[-1] += [dofs_mat] - else: - blocks[-1] += [None] + else : + + if preproc_grid != None : + _ptsG, _wtsG, _spans, _bases, _npt_pts = preproc_grid[i][j] + + else: + _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + V1d, W1d, _starts_out, _ends_out, nq) + + _ptsG = [pts.flatten() for pts in _ptsG] + _Vnbases = [space.nbasis for space in V1d] + + # Evaluate weight function at quadrature points + pts = np.meshgrid(*_ptsG, indexing='ij') + + if isinstance(f, float) or isinstance(f, int): + shape_grid = tuple([len(pts_i) for pts_i in _ptsG]) + _fun_q = np.full(shape_grid, f) + else : + f = np.vectorize(f) + _fun_q = f(*pts) + + # Call the kernel if weight function is not zero + if np.any(np.abs(_fun_q) > 1e-14): + + kernel = getattr( + basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd') + + kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, + _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_Vnbases, *_Wdegrees) + + blocks[-1] += [dofs_mat] + + else: + blocks[-1] += [None] j+=1 i+=1 @@ -391,6 +414,96 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): direction += 1 return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(np_pts_cell) +def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_quad=None): + '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. + + Parameters + ---------- + V1d : 3-list + Three SplineSpace objects from Psydac from the input space (to be projected). + + W1d : 3-list + Three SplineSpace objects from Psydac from the output space (projected onto). + + starts_out : 3-list + Global starting indices of process. + + ends_out : 3-list + Global ending indices of process. + + n_quad : 3_list + Number of quadrature points per histpolation interval. If not given, is set to V1d.degree + 1. + + Returns + ------- + ptsG : 3-tuple of 2d float arrays + Quadrature points (or Greville points for interpolation) in each dimension in format (interval, quadrature point). + + wtsG : 3-tuple of 2d float arrays + Quadrature weights (or ones for interpolation) in each dimension in format (interval, quadrature point). + + spans : 3-tuple of 2d int arrays + Knot span indices in each direction in format (n, nq). + + bases : 3-tuple of 3d float arrays + Values of p + 1 non-zero eta basis functions at quadrature points in format (n, nq, basis).''' + + import psydac.core.bsplines as bsp + + + x_grid, pts, wts, spans, bases, spans_c, bases_c, np_pts_cell = [], [], [], [], [], [], [], [] + + # Loop over direction, prepare point sets and evaluate basis functions + direction = 0 + for space_in, space_out, space_coeff, s, e in zip(V1d, W1d, space_ff, starts_out, ends_out): + + greville_loc = space_out.greville[s: e + 1].copy() + histopol_loc = space_out.histopolation_grid[s: e + 2].copy() + + # make sure that greville points used for interpolation are in [0, 1] + #assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) + + # interpolation + if space_out.basis == 'B': + x_grid = greville_loc + pts += [greville_loc[:, None]] + wts += [np.ones(pts[-1].shape, dtype=float)] + np_pts_cell += [1] + + # histopolation + elif space_out.basis == 'M': + + x_grid = histopol_loc #space_out.histopolation_grid + + # Gauss - Legendre quadrature points and weights + if n_quad is None: + # products of basis functions are integrated exactly + nq = space_in.degree + 1 + else: + nq = n_quad[direction] + pts_loc, wts_loc = gauss_legendre(nq-1) + pts_loc, wts_loc = pts_loc[::-1], wts_loc[::-1] + global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) + #"roll" back points to the interval to ensure that the quadrature points are + #in the domain. Probably only usefull on periodic cases + roll_edges(space_out.domain, global_quad_x) + x = global_quad_x + w = global_quad_w + pts += [x] + wts += [w] + np_pts_cell += [nq] + # Knot span indices and V-basis functions evaluated at W-point sets + s, b = get_span_and_basis(pts[-1], space_in) + s_c, b_c = get_span_and_basis(pts[-1], space_coeff) + + spans += [s] + bases += [b] + spans_c +=[s_c] + bases_c +=[b_c] + + direction += 1 + return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(spans_c), tuple(bases_c), tuple(np_pts_cell) + def get_span_and_basis(pts, space): '''Compute the knot span index and the values of p + 1 basis function at each point in pts. @@ -475,6 +588,7 @@ def preprocess_grid(P, V): # blocks of dof matrix preproc = [] + # ouptut vector space (codomain), row of block for Wspace, W1d, nq in zip(_Wspaces, _W1ds, _nqs): @@ -493,4 +607,82 @@ def preprocess_grid(P, V): V1d, W1d, _starts_out, _ends_out, nq) line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) preproc.append(line_pre.copy()) + return preproc + + +def preprocess_grid_with_ff(P, V, f_type): + """ + Gather the results of prepare_projection_of_basis for the different SplineSpaces composing a space, + the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid + computing several time the same quantities + + Parameters + ---------- + P : GlobalProjector + The psydac global tensor product projector defining the space onto which the input shall be projected. + + V : TensorFemSpace | ProductFemSpace + The spline space which shall be projected. + + f_type : None | list + Instance of the callable that will be used in the projection basis. Only used to compute the grids + if some of those callable are FemFields to preocompute the grids. + + Returns + ------- + preproc : List of List of Tuple + List of List containing the outputs of prepare_projection_of_basis applied to the differents spaces + """ + + # input space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(V, TensorFemSpace): + _Vspaces = [V.vector_space] + _V1ds = [V.spaces] + else: + _Vspaces = V.vector_space + _V1ds = [comp.spaces for comp in V.spaces] + + # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(P.space, TensorFemSpace): + _Wspaces = [P.space.vector_space] + _W1ds = [P.space.spaces] + else: + _Wspaces = P.space.vector_space + _W1ds = [comp.spaces for comp in P.space.spaces] + + # retrieve number of quadrature points of each component (=1 for interpolation) + _nqs = [[P.grid_x[comp][direction].shape[1] + for direction in range(V.ldim)] for comp in range(len(_W1ds))] + + # blocks of dof matrix + preproc = [] + + # ouptut vector space (codomain), row of block + for Wspace, W1d, nq, f_line in zip(_Wspaces, _W1ds, _nqs, f_type): + + line_pre = [] + # input vector space (domain), column of block + for Vspace, V1d, f in zip(_Vspaces, _V1ds, f_line): + + + # instantiate cell of block matrix + dofs_mat = StencilMatrix( + Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) + + _starts_out = np.array(dofs_mat.codomain.starts) + _ends_out = np.array(dofs_mat.codomain.ends) + + if isinstance(f,FemField): + + Vfd = f.space.spaces + _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ + prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) + + line_pre.append((_ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts)) + + else : + _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + V1d, W1d, _starts_out, _ends_out, nq) + line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) + preproc.append(line_pre.copy()) return preproc \ No newline at end of file diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py index 20987e738..f45eed4f5 100644 --- a/psydac/feec/tests/test_basis_projectors.py +++ b/psydac/feec/tests/test_basis_projectors.py @@ -569,4 +569,5 @@ def test_basis_projector_2d_mapping(deg, filename): assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) if __name__ == '__main__': + test_basis_projector_2d(4, 2, [False,False]) test_basis_projector_2d_mapping(2, 'collela_2d.h5') \ No newline at end of file From 8146777e37aed81542c3cfae10e39c6a79e261a0 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 14 Jun 2023 17:08:34 +0200 Subject: [PATCH 25/77] add kernels in 1d and 3d, updated kernel docstrings for the femfield case --- psydac/feec/basis_projection_kernels.py | 457 ++++++++++++++++++++++-- 1 file changed, 437 insertions(+), 20 deletions(-) diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py index 8bbfd60de..317271620 100644 --- a/psydac/feec/basis_projection_kernels.py +++ b/psydac/feec/basis_projection_kernels.py @@ -478,7 +478,141 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value +def assemble_dofs_for_weighted_basisfuns_1d_ff(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', coeffs_f : 'float[:,:]', span_c1 : 'int[:,:]', basis_c1 : 'float[:,:,:]', dim1_in : int, p1_out : int): + '''Kernel for assembling the matrix + + A_(i,j) = DOFS_i(fun*Lambda^in_j) , + + into the _data attribute of a StencilMatrix. + Here, DOFS_i are the degrees-of-freedom of the output space (codomain, must not be a product space), + Lambda^in_j are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + + Parameters + ---------- + mat : 2d float array + _data attribute of StencilMatrix. + + starts_in : int + Starting index of the input space (domain) of a distributed StencilMatrix. + + ends_in : int + Ending index of the input space (domain) of a distributed StencilMatrix. + + pads_in : int + Paddings of the input space (domain) of a distributed StencilMatrix. + + starts_out : int + Starting indices of the output space (codomain) of a distributed StencilMatrix. + + ends_out : int + Ending indices of the output space (codomain) of a distributed StencilMatrix. + + pads_out : int + Paddings of the output space (codomain) of a distributed StencilMatrix. + + starts_c : 1d int array + Starting indices of the coefficient (femfield f) space. + + ends_c : 1d int array + Ending indices of the coefficient (femfield f) space. + + pads_c : 1d int array + Paddings of the coefficient (femfield f) space. + + wts1 : 2d float array + Quadrature weights in format (ii, iq). + + span1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq). + + basis1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). + + coeffs_f : 3d float array + Coefficient of the femfield f + + span_c1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq) for the coefficient FemField f. + + basis_c1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions of the space of f at quadrature points in format (ii, iq, basis function). + + dim1_in : int + Dimension of the first direction of the input space + + p1_out : int + Spline degree of the first direction of the output space + ''' + + # Start/end indices and paddings for distributed stencil matrix of input space + # si1 = starts_in[0} + # ei1 = ends_in[0] + pi1 = pads_in[0] + + # Start/end indices for distributed stencil matrix of output space + so1 = starts_out[0] + # eo1 = ends_out[0] + po1 = pads_out[0] + + sc1 = starts_c[0] + # ec1 = ends_c[0] + pc1 = pads_c[0] + + # Spline degrees of input space + p1 = basis1.shape[2] - 1 + + p1_c = basis_c1.shape[2] - 1 + + # number of quadrature points + nq1 = span1.shape[1] + + # Set output to zero + mat[:] = 0. + + # Dimensions of output space + dim1_out = span1.shape[0] + + #local dof index + for i in range(span1.shape[0]): + # Quadrature point index in interval + # ---------------------------------- + for iq in range(nq1): + + f_val = 0. + + for b1 in range(p1_c + 1): + # global index + m = (span_c1[i, iq] - p1_c + b1) + #local index + m_loc = m-sc1+pc1 + f_val += basis_c1[i, iq, b1] * coeffs_f[m_loc] + + funval = wts1[i, iq] * f_val + # Basis function of input space: + # ------------------------------ + for b1 in range(p1 + 1): + m = (span1[i, iq] - p1 + b1) # global index + # basis value + value = funval * basis1[i, iq, b1] + + # Find column index for _data: + if dim1_out <= dim1_in: + cut1 = p1 + else: + cut1 = p1_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col1 + col1_tmp = m - (i + so1) + if col1_tmp > cut1: + m = m - dim1_in + elif col1_tmp < -cut1: + m = m + dim1_in + # add padding + col1 = pi1 + m - (i + so1) + + # Row index: padding + local index. + mat[po1 + i, col1] += value def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', coeffs_f : 'float[:,:]', span_c1 : 'int[:,:]', span_c2 : 'int[:,:]', basis_c1 : 'float[:,:,:]', basis_c2 : 'float[:,:,:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): @@ -488,7 +622,7 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in into the _data attribute of a StencilMatrix. Here, DOFS_ij are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_kl are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + Lambda^in_kl are the basis functions of the input space (domain, must not be a product space), and is a FemField object. Parameters ---------- @@ -513,9 +647,15 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in pads_out : 1d int array Paddings of the output space (codomain) of a distributed StencilMatrix. - fun_q : 2d float array - The function evaluated at the points (nq_i*ii + iq, nq_j*jj + jq), where iq a local quadrature point of interval ii. - + starts_c : 1d int array + Starting indices of the coefficient (femfield f) space. + + ends_c : 1d int array + Ending indices of the coefficient (femfield f) space. + + pads_c : 1d int array + Paddings of the coefficient (femfield f) space. + wts1 : 2d float array Quadrature weights in direction eta1 in format (ii, iq). @@ -534,6 +674,21 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in basis2 : 3d float array Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). + coeffs_f : 2d float array + Coefficient of the femfield f + + span_c1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq) for the coefficient FemField f. + + span_c2 : 2d int array + Knot span indices in direction eta2 in format (jj, jq) for the coefficient FemField f. + + basis_c1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions of the space of f at quadrature points in format (ii, iq, basis function). + + basis_c2 : 3d float array + Values of p2 + 1 non-zero eta-2 basis functions of the space of f at quadrature points in format (jj, jq, basis function). + dim1_in : int Dimension of the first direction of the input space @@ -565,8 +720,8 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in sc1 = starts_c[0] sc2 = starts_c[1] - # ec1 = ends_out[0] - # ec2 = ends_out[1] + # ec1 = ends_c[0] + # ec2 = ends_c[1] pc1 = pads_c[0] pc2 = pads_c[1] @@ -590,13 +745,10 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in # Interval (either element or sub-interval thereof) # ------------------------------------------------- - cumsub_i = 0 # Cumulative sub-interval index - for ii in range(span1.shape[0]): - i = ii - cumsub_i # local DOF index + # local DOF index + for i in range(span1.shape[0]): - cumsub_j = 0 # Cumulative sub-interval index - for jj in range(span2.shape[0]): - j = jj - cumsub_j # local DOF index + for j in range(span2.shape[0]): # Quadrature point index in interval # ---------------------------------- @@ -607,24 +759,24 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in for b1 in range(p1_c + 1): # global index - m = (span_c1[ii, iq] - p1_c + b1) + m = (span_c1[i, iq] - p1_c + b1) #local index m_loc = m-sc1+pc1 for b2 in range(p2_c + 1): # global index - n = (span_c2[jj, jq] - p2_c + b2) + n = (span_c2[j, jq] - p2_c + b2) #local index n_loc = n-sc2+pc2 - f_val += basis_c1[ii, iq, b1] * basis_c2[jj, jq, b2] *coeffs_f[m_loc,n_loc] + f_val += basis_c1[i, iq, b1] * basis_c2[j, jq, b2] *coeffs_f[m_loc,n_loc] - funval = wts1[ii, iq] * wts2[jj, jq] * f_val + funval = wts1[i, iq] * wts2[j, jq] * f_val # Basis function of input space: # ------------------------------ for b1 in range(p1 + 1): - m = (span1[ii, iq] - p1 + b1) # global index + m = (span1[i, iq] - p1 + b1) # global index # basis value - val1 = funval * basis1[ii, iq, b1] + val1 = funval * basis1[i, iq, b1] # Find column index for _data: if dim1_out <= dim1_in: @@ -643,8 +795,8 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in for b2 in range(p2 + 1): # global index - n = (span2[jj, jq] - p2 + b2) - value = val1 * basis2[jj, jq, b2] + n = (span2[j, jq] - p2 + b2) + value = val1 * basis2[j, jq, b2] # Find column index for _data: if dim2_out <= dim2_in: @@ -664,3 +816,268 @@ def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in # Row index: padding + local index. mat[po1 + i, po2 + j, col1, col2] += value + + +def assemble_dofs_for_weighted_basisfuns_3d_ff(mat : 'float[:,:,:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', wts3 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', span3 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', basis3 : 'float[:,:,:]', coeffs_f : 'float[:,:,:]', span_c1 : 'int[:,:]', span_c2 : 'int[:,:]', span_c3 : 'int[:,:]', basis_c1 : 'float[:,:,:]', basis_c2 : 'float[:,:,:]', basis_c3 : 'float[:,:,:]', dim1_in : int, dim2_in : int, dim3_in : int, p1_out : int, p2_out : int, p3_out : int): + '''Kernel for assembling the matrix + + A_(ijk,mno) = DOFS_ijk(fun*Lambda^in_mno) , + + into the _data attribute of a StencilMatrix. + Here, DOFS_ijk are the degrees-of-freedom of the output space (codomain, must not be a product space), + Lambda^in_mno are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. + + Parameters + ---------- + mat : 6d float array + _data attribute of StencilMatrix. + + starts_in : 1d int array + Starting indices of the input space (domain) of a distributed StencilMatrix. + + ends_in : 1d int array + Ending indices of the input space (domain) of a distributed StencilMatrix. + + pads_in : 1d int array + Paddings of the input space (domain) of a distributed StencilMatrix. + + starts_out : 1d int array + Starting indices of the output space (codomain) of a distributed StencilMatrix. + + ends_out : 1d int array + Ending indices of the output space (codomain) of a distributed StencilMatrix. + + pads_out : 1d int array + Paddings of the output space (codomain) of a distributed StencilMatrix. + + starts_c : 1d int array + Starting indices of the coefficient (femfield f) space. + + ends_c : 1d int array + Ending indices of the coefficient (femfield f) space. + + pads_c : 1d int array + Paddings of the coefficient (femfield f) space. + + wts1 : 2d float array + Quadrature weights in direction eta1 in format (ii, iq). + + wts2 : 2d float array + Quadrature weights in direction eta2 in format (jj, jq). + + wts3 : 2d float array + Quadrature weights in direction eta3 in format (kk, kq). + + span1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq). + + span2 : 2d int array + Knot span indices in direction eta2 in format (jj, jq). + + span3 : 2d int array + Knot span indices in direction eta3 in format (kk, kq). + + basis1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). + + basis2 : 3d float array + Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). + + basis3 : 3d float array + Values of p3 + 1 non-zero eta-3 basis functions at quadrature points in format (kk, kq, basis function). + + coeffs_f : 3d float array + Coefficient of the femfield f + + span_c1 : 2d int array + Knot span indices in direction eta1 in format (ii, iq) for the coefficient FemField f. + + span_c2 : 2d int array + Knot span indices in direction eta2 in format (jj, jq) for the coefficient FemField f. + + span_c3 : 2d int array + Knot span indices in direction eta3 in format (jj, jq) for the coefficient FemField f. + + basis_c1 : 3d float array + Values of p1 + 1 non-zero eta-1 basis functions of the space of f at quadrature points in format (ii, iq, basis function). + + basis_c2 : 3d float array + Values of p2 + 1 non-zero eta-2 basis functions of the space of f at quadrature points in format (jj, jq, basis function). + + basis_c3 : 3d float array + Values of p3 + 1 non-zero eta-3 basis functions of the space of f at quadrature points in format (kk, kq, basis function). + + dim1_in : int + Dimension of the first direction of the input space + + dim2_in : int + Dimension of the second direction of the input space + + dim3_in : int + Dimension of the third direction of the input space + + p1_out : int + Spline degree of the first direction of the output space + + p2_out : int + Spline degree of the second direction of the output space + + p3_out : int + Spline degree of the third direction of the output space + ''' + + # Start/end indices and paddings for distributed stencil matrix of input space + # si1 = starts_in[0] + # si2 = starts_in[1] + # si3 = starts_in[2] + # ei1 = ends_in[0] + # ei2 = ends_in[1] + # ei3 = ends_in[2] + pi1 = pads_in[0] + pi2 = pads_in[1] + pi3 = pads_in[2] + + # Start/end indices for distributed stencil matrix of output space + so1 = starts_out[0] + so2 = starts_out[1] + so3 = starts_out[2] + # eo1 = ends_out[0] + # eo2 = ends_out[1] + # eo3 = ends_out[2] + po1 = pads_out[0] + po2 = pads_out[1] + po3 = pads_out[2] + + sc1 = starts_c[0] + sc2 = starts_c[1] + sc3 = starts_c[2] + # ec1 = ends_c[0] + # ec2 = ends_c[1] + # ec3 = ends_c[2] + pc1 = pads_c[0] + pc2 = pads_c[1] + pc3 = pads_c[2] + + # Spline degrees of input space + p1 = basis1.shape[2] - 1 + p2 = basis2.shape[2] - 1 + p3 = basis3.shape[2] - 1 + + p1_c = basis_c1.shape[2] - 1 + p2_c = basis_c2.shape[2] - 1 + p3_c = basis_c3.shape[2] - 1 + + # number of quadrature points + nq1 = span1.shape[1] + nq2 = span2.shape[1] + nq3 = span3.shape[1] + + # Set output to zero + mat[:] = 0. + + # Dimensions of output space + dim1_out = span1.shape[0] + dim2_out = span2.shape[0] + dim3_out = span3.shape[0] + + # Interval (either element or sub-interval thereof) + # ------------------------------------------------- + # local DOF index + for i in range(span1.shape[0]): + + for j in range(span2.shape[0]): + + for k in range(span3.shape[0]): + + # Quadrature point index in interval + # ---------------------------------- + for iq in range(nq1): + for jq in range(nq2): + for kq in range(nq3): + + f_val = 0. + + for b1 in range(p1_c + 1): + # global index + m = (span_c1[i, iq] - p1_c + b1) + #local index + m_loc = m-sc1+pc1 + for b2 in range(p2_c + 1): + # global index + n = (span_c2[j, jq] - p2_c + b2) + #local index + n_loc = n-sc2+pc2 + for b3 in range(p3_c + 1): + # global index + o = (span_c3[k, kq] - p3_c + b3) + #local index + o_loc = o-sc3+pc3 + f_val += basis_c1[i, iq, b1] * basis_c2[j, jq, b2] * basis_c3[k, kq, b3] * coeffs_f[m_loc,n_loc,o_loc] + + funval = wts1[i, iq] * wts2[j, jq] * wts3[k, kq] * f_val + + # Basis function of input space: + # ------------------------------ + for b1 in range(p1 + 1): + m = (span1[i, iq] - p1 + b1) # global index + # basis value + val1 = funval * basis1[i, iq, b1] + + # Find column index for _data: + if dim1_out <= dim1_in: + cut1 = p1 + else: + cut1 = p1_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col1 + col1_tmp = m - (i + so1) + if col1_tmp > cut1: + m = m - dim1_in + elif col1_tmp < -cut1: + m = m + dim1_in + # add padding + col1 = pi1 + m - (i + so1) + + for b2 in range(p2 + 1): + # global index + n = (span2[j, jq] - p2 + b2) + val2 = val1 * basis2[j, jq, b2] + + # Find column index for _data: + if dim2_out <= dim2_in: + cut2 = p2 + else: + cut2 = p2_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col2 + col2_tmp = n - (j + so2) + if col2_tmp > cut2: + n = n - dim2_in + elif col2_tmp < -cut2: + n = n + dim2_in + # add padding + col2 = pi2 + n - (j + so2) + + for b3 in range(p3 + 1): + # global index + o = (span3[k, kq] - p3 + b3) + value = val2 * basis3[k, kq, b3] + + # Find column index for _data: + if dim3_out <= dim3_in: + cut3 = p3 + else: + cut3 = p3_out + + # Diff of global indices, needs to be adjusted for boundary conditions --> col3 + col3_tmp = o - (k + so3) + if col3_tmp > cut3: + o = o - dim3_in + elif col3_tmp < -cut3: + o = o + dim3_in + # add padding + col3 = pi3 + o - (k + so3) + + # Row index: padding + local index. + mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value + From c6691c1aa61afdcffa68790be8adaecf0a665dcd Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 19 Jun 2023 10:47:50 +0200 Subject: [PATCH 26/77] add bc to basis projection, changed the dot product (not really more efficient in the end test in histopolation in splines --- psydac/feec/basis_projectors.py | 13 ++++++++----- psydac/fem/splines.py | 4 ++-- psydac/linalg/stencil.py | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 6344e14b8..63df99b39 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -414,7 +414,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): direction += 1 return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(np_pts_cell) -def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_quad=None): +def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, bc, n_quad=None): '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. Parameters @@ -496,6 +496,10 @@ def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_q s, b = get_span_and_basis(pts[-1], space_in) s_c, b_c = get_span_and_basis(pts[-1], space_coeff) + #set boundary weights to zero in prescribed direction + if direction in bc : + wts[-1][0]=0. + wts[-1][-1]=0. spans += [s] bases += [b] spans_c +=[s_c] @@ -610,7 +614,7 @@ def preprocess_grid(P, V): return preproc -def preprocess_grid_with_ff(P, V, f_type): +def preprocess_grid_with_ff(P, V, f_type, bc): """ Gather the results of prepare_projection_of_basis for the different SplineSpaces composing a space, the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid @@ -658,8 +662,7 @@ def preprocess_grid_with_ff(P, V, f_type): preproc = [] # ouptut vector space (codomain), row of block - for Wspace, W1d, nq, f_line in zip(_Wspaces, _W1ds, _nqs, f_type): - + for Wspace, W1d, nq, f_line, loc_b in zip(_Wspaces, _W1ds, _nqs, f_type, bc): line_pre = [] # input vector space (domain), column of block for Vspace, V1d, f in zip(_Vspaces, _V1ds, f_line): @@ -676,7 +679,7 @@ def preprocess_grid_with_ff(P, V, f_type): Vfd = f.space.spaces _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ - prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) + prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, loc_b, nq) line_pre.append((_ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts)) diff --git a/psydac/fem/splines.py b/psydac/fem/splines.py index 5001d43f7..bbcf31aac 100644 --- a/psydac/fem/splines.py +++ b/psydac/fem/splines.py @@ -181,7 +181,7 @@ def init_interpolation( self ): xgrid = self.greville ) - if self.periodic: + if True : # self.periodic: # Convert to CSC format and compute sparse LU decomposition self._interpolator = SparseSolver( csc_matrix( imat ) ) else: @@ -215,7 +215,7 @@ def init_histopolation( self ): xgrid = self.ext_greville ) self.hmat= imat - if self.periodic: + if True :# self.periodic: # Convert to CSC format and compute sparse LU decomposition self._histopolator = SparseSolver( csc_matrix( imat ) ) else: diff --git a/psydac/linalg/stencil.py b/psydac/linalg/stencil.py index 390003d9a..57ff02b4a 100644 --- a/psydac/linalg/stencil.py +++ b/psydac/linalg/stencil.py @@ -364,7 +364,7 @@ def dot(self, v): @staticmethod def _dot(v1, v2, pads, shifts): index = tuple( slice(m*p,-m*p) for p,m in zip(pads, shifts)) - return np.vdot(v1[index].flat, v2[index].flat) + return np.vdot(v1[index], v2[index]) def conjugate(self, out=None): if out is not None: From fb5cc8e30a094a68875498189ed733f2c737406c Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 22 Jun 2023 07:58:06 +0200 Subject: [PATCH 27/77] add boundary parameter to prepare the grid in basisProjector --- psydac/feec/basis_projectors.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 63df99b39..a99b1e936 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -13,6 +13,10 @@ from psydac.fem.basic import FemField from psydac.utilities.utils import roll_edges +from sympy.core.numbers import Zero + +from copy import deepcopy + class BasisProjectionOperator(LinearOperator): @@ -327,7 +331,7 @@ def assemble_mat(P, V, fun, preproc_grid=None): return BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) -def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): +def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, bc, n_quad=None): '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. Parameters @@ -405,7 +409,11 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): pts += [x] wts += [w] np_pts_cell += [nq] + # Knot span indices and V-basis functions evaluated at W-point sets + if direction in bc : + wts[-1][0]=0. + wts[-1][-1]=0. s, b = get_span_and_basis(pts[-1], space_in) spans += [s] @@ -685,7 +693,7 @@ def preprocess_grid_with_ff(P, V, f_type, bc): else : _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( - V1d, W1d, _starts_out, _ends_out, nq) + V1d, W1d, _starts_out, _ends_out, loc_b,nq) line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) - preproc.append(line_pre.copy()) + preproc.append(deepcopy(line_pre)) return preproc \ No newline at end of file From f8e42c4b8ddd8e5e85c2951cf4b87df671b3a3be Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 8 Aug 2023 16:27:04 +0200 Subject: [PATCH 28/77] add the possibility to customize the grid (cf. Maxwell ssc) --- psydac/api/discretization.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index a29e17383..fb3cc53cf 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -197,7 +197,7 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): #============================================================================== # TODO knots -def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, quad_order=None, basis='B', sequence='DR'): +def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type=None, nquads=None, basis='B', sequence='DR'): """ This function creates the discretized space starting from the symbolic space. @@ -219,11 +219,11 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, knots: list | dict The knots sequence of the h1 space in each direction. - quad_order: list + nquads: list The number of quadrature points in each direction. basis: str - The type of basis function can be 'b' for b-splines or 'M' for M-splines. + The type of basis function can be 'B' for B-splines or 'M' for M-splines. sequence: str The sequence used to reduce the space. The available choices are: @@ -323,20 +323,22 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, max_coords = interior.max_coords assert len(ncells) == len(periodic) == len(degree_i) == len(multiplicity_i) == len(min_coords) == len(max_coords) - - if knots is None: - # Create uniform grid - grids = [np.linspace(xmin, xmax, num=ne + 1) - for xmin, xmax, ne in zip(min_coords, max_coords, ncells)] - - # Create 1D finite element spaces and precompute quadrature data - spaces[i] = [SplineSpace( p, multiplicity=m, grid=grid , periodic=P) for p,m,grid,P in zip(degree_i, multiplicity_i,grids, periodic)] + if knots is not None and grid_type is not None : + raise(ValueError("grids and knots cannot be both provided")) + elif knots is None: + if grid_type is None : + grid_type = [np.linspace(-1,1,ne+1) for ne in ncells] + grids = [xmin*(1-grid)/2+xmax*(1+grid)/2 + for xmin, xmax, grid in zip(min_coords, max_coords, grid_type)] + + spaces[i] = [SplineSpace( p, multiplicity=m, grid=grid , periodic=P) + for p,m,grid,P in zip(degree_i, multiplicity_i,grids, periodic)] else: # Create 1D finite element spaces and precompute quadrature data - spaces[i] = [SplineSpace( p, knots=T , periodic=P) for p,T, P in zip(degree_i, knots[interior.name], periodic)] + spaces[i] = [SplineSpace( p, knots=T , periodic=P) for p,T, P in zip(degree_i, knots[interior.name], periodic)] carts = create_cart(ddms, spaces) - g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], quad_order=quad_order) for i,inter in enumerate(interiors)} + g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], nquads=nquads) for i,inter in enumerate(interiors)} for i,j in connectivity: ((axis_i, ext_i), (axis_j , ext_j)) = connectivity[i, j] From 170c944eb30eadaf285a62c3056b41d1e87b91a9 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 4 Sep 2023 13:57:48 +0200 Subject: [PATCH 29/77] few unsaved changes --- psydac/api/discretization.py | 10 +++++----- psydac/feec/basis_projection_kernels.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index fb3cc53cf..f0dd32026 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -53,9 +53,9 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): for V, basis in zip(derham.spaces, bases)] if get_vec: - V0h = spaces[0] + Vnh = spaces[0] X = VectorFunctionSpace('X', domain_h.domain, kind='h1') - Xh = VectorFemSpace(V0h, V0h) + Xh = VectorFemSpace(Vnh, Vnh) Xh.symbolic_space = X spaces.append(Xh) @@ -197,7 +197,7 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): #============================================================================== # TODO knots -def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type=None, nquads=None, basis='B', sequence='DR'): +def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type=None, quad_order=None, basis='B', sequence='DR'): """ This function creates the discretized space starting from the symbolic space. @@ -259,6 +259,7 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, # We build the dictionary g_spaces for each interior domain, where it conatians the interiors as keys and the spaces as values, # we then create the compatible spaces if needed with the suitable basis functions. + comm = domain_h.comm ldim = V.ldim is_rational_mapping = False @@ -330,7 +331,6 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type = [np.linspace(-1,1,ne+1) for ne in ncells] grids = [xmin*(1-grid)/2+xmax*(1+grid)/2 for xmin, xmax, grid in zip(min_coords, max_coords, grid_type)] - spaces[i] = [SplineSpace( p, multiplicity=m, grid=grid , periodic=P) for p,m,grid,P in zip(degree_i, multiplicity_i,grids, periodic)] else: @@ -338,7 +338,7 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, spaces[i] = [SplineSpace( p, knots=T , periodic=P) for p,T, P in zip(degree_i, knots[interior.name], periodic)] carts = create_cart(ddms, spaces) - g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], nquads=nquads) for i,inter in enumerate(interiors)} + g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], quad_order=quad_order) for i,inter in enumerate(interiors)} for i,j in connectivity: ((axis_i, ext_i), (axis_j , ext_j)) = connectivity[i, j] diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py index 317271620..a018cb06c 100644 --- a/psydac/feec/basis_projection_kernels.py +++ b/psydac/feec/basis_projection_kernels.py @@ -478,7 +478,7 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value -def assemble_dofs_for_weighted_basisfuns_1d_ff(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', coeffs_f : 'float[:,:]', span_c1 : 'int[:,:]', basis_c1 : 'float[:,:,:]', dim1_in : int, p1_out : int): +def assemble_dofs_for_weighted_basisfuns_1d_ff(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', coeffs_f : 'float[:sssssse44]', span_c1 : 'int[:,:]', basis_c1 : 'float[:,:,:]', dim1_in : int, p1_out : int): '''Kernel for assembling the matrix A_(i,j) = DOFS_i(fun*Lambda^in_j) , From d9e3ad9aaca2dc0cff5a7039be835911f18a7650 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 7 Sep 2023 16:36:10 +0200 Subject: [PATCH 30/77] change typo --- psydac/feec/basis_projection_kernels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py index a018cb06c..2df48ef40 100644 --- a/psydac/feec/basis_projection_kernels.py +++ b/psydac/feec/basis_projection_kernels.py @@ -478,7 +478,7 @@ def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_i mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value -def assemble_dofs_for_weighted_basisfuns_1d_ff(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', coeffs_f : 'float[:sssssse44]', span_c1 : 'int[:,:]', basis_c1 : 'float[:,:,:]', dim1_in : int, p1_out : int): +def assemble_dofs_for_weighted_basisfuns_1d_ff(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', coeffs_f : 'float[:]', span_c1 : 'int[:,:]', basis_c1 : 'float[:,:,:]', dim1_in : int, p1_out : int): '''Kernel for assembling the matrix A_(i,j) = DOFS_i(fun*Lambda^in_j) , From 0ac209997d78484a9039d2b24de9bc556b3b9ccc Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 8 Sep 2023 11:17:12 +0200 Subject: [PATCH 31/77] little fix after merge --- psydac/api/discretization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 4a306157f..559c07b62 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -386,7 +386,7 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, carts = create_cart(ddms, spaces) - g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], nquads=nquads, dtype=dtype) for i,inter in enumerate(interiors)} + g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], nquads=quad_order, dtype=dtype) for i,inter in enumerate(interiors)} for i,j in connectivity: From d3bb10e6a107626322e87119fbe556251d12fc49 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 8 Sep 2023 14:37:06 +0200 Subject: [PATCH 32/77] removed the posibility to impose BC on projectors --- psydac/feec/basis_projectors.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index a99b1e936..e752d95fd 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -268,9 +268,10 @@ def assemble_mat(P, V, fun, preproc_grid=None): _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = preproc_grid[i][j] else : + print("yes") _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) - + print(_ptsG) _ptsG = [pts.flatten() for pts in _ptsG] _Vnbases = [space.nbasis for space in V1d] f_coeffs = f.coeffs._data @@ -331,7 +332,7 @@ def assemble_mat(P, V, fun, preproc_grid=None): return BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) -def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, bc, n_quad=None): +def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. Parameters @@ -411,9 +412,6 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, bc, n_quad=None) np_pts_cell += [nq] # Knot span indices and V-basis functions evaluated at W-point sets - if direction in bc : - wts[-1][0]=0. - wts[-1][-1]=0. s, b = get_span_and_basis(pts[-1], space_in) spans += [s] @@ -422,7 +420,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, bc, n_quad=None) direction += 1 return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(np_pts_cell) -def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, bc, n_quad=None): +def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_quad=None): '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. Parameters @@ -505,9 +503,6 @@ def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, bc, s_c, b_c = get_span_and_basis(pts[-1], space_coeff) #set boundary weights to zero in prescribed direction - if direction in bc : - wts[-1][0]=0. - wts[-1][-1]=0. spans += [s] bases += [b] spans_c +=[s_c] @@ -622,7 +617,7 @@ def preprocess_grid(P, V): return preproc -def preprocess_grid_with_ff(P, V, f_type, bc): +def preprocess_grid_with_ff(P, V, f_type): """ Gather the results of prepare_projection_of_basis for the different SplineSpaces composing a space, the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid @@ -670,7 +665,7 @@ def preprocess_grid_with_ff(P, V, f_type, bc): preproc = [] # ouptut vector space (codomain), row of block - for Wspace, W1d, nq, f_line, loc_b in zip(_Wspaces, _W1ds, _nqs, f_type, bc): + for Wspace, W1d, nq, f_line in zip(_Wspaces, _W1ds, _nqs, f_type): line_pre = [] # input vector space (domain), column of block for Vspace, V1d, f in zip(_Vspaces, _V1ds, f_line): @@ -687,13 +682,13 @@ def preprocess_grid_with_ff(P, V, f_type, bc): Vfd = f.space.spaces _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ - prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, loc_b, nq) + prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) line_pre.append((_ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts)) else : _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( - V1d, W1d, _starts_out, _ends_out, loc_b,nq) + V1d, W1d, _starts_out, _ends_out,nq) line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) preproc.append(deepcopy(line_pre)) return preproc \ No newline at end of file From 4c60e4c761bc420e9c2fa2fc0967af0092e9e858 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 8 Sep 2023 14:41:24 +0200 Subject: [PATCH 33/77] confusion between nquads and quad_order --- psydac/api/discretization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 559c07b62..62856c10b 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -236,7 +236,7 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): #============================================================================== # TODO knots -def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type=None, quad_order=None, basis='B', sequence='DR'): +def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type=None, nquads=None, basis='B', sequence='DR'): """ This function creates the discretized space starting from the symbolic space. @@ -386,7 +386,7 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, carts = create_cart(ddms, spaces) - g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], nquads=quad_order, dtype=dtype) for i,inter in enumerate(interiors)} + g_spaces = {inter:TensorFemSpace( ddms[i], *spaces[i], cart=carts[i], nquads=nquads, dtype=dtype) for i,inter in enumerate(interiors)} for i,j in connectivity: From 0a3f9f2edd18fc6b94c29f3351c649eed39a1a2a Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 8 Sep 2023 14:55:31 +0200 Subject: [PATCH 34/77] cleaning the PR --- psydac/fem/splines.py | 4 +- psydac/fem/tensor.py | 2 +- psydac/linalg/solvers.py | 36 ++----- psydac/linalg/tests/test_solvers.py | 162 +--------------------------- 4 files changed, 13 insertions(+), 191 deletions(-) diff --git a/psydac/fem/splines.py b/psydac/fem/splines.py index d17d20486..e18d09c94 100644 --- a/psydac/fem/splines.py +++ b/psydac/fem/splines.py @@ -181,7 +181,7 @@ def init_interpolation( self, dtype=float ): xgrid = self.greville ) - if True : # self.periodic: + if self.periodic: # Convert to CSC format and compute sparse LU decomposition self._interpolator = SparseSolver( csc_matrix( imat ) ) else: @@ -215,7 +215,7 @@ def init_histopolation( self, dtype=float): xgrid = self.ext_greville ) self.hmat= imat - if True :# self.periodic: + if self.periodic: # Convert to CSC format and compute sparse LU decomposition self._histopolator = SparseSolver( csc_matrix( imat ) ) else: diff --git a/psydac/fem/tensor.py b/psydac/fem/tensor.py index 2e91e3b83..e2e363e2d 100644 --- a/psydac/fem/tensor.py +++ b/psydac/fem/tensor.py @@ -365,7 +365,7 @@ def preprocess_irregular_tensor_grid(self, grid, der=0, overlap=0): for i in range(self.ldim): # Check the that the grid is sorted. grid_i = grid[i] - #assert all(grid_i[j] <= grid_i[j + 1] for j in range(len(grid_i) - 1)) + assert all(grid_i[j] <= grid_i[j + 1] for j in range(len(grid_i) - 1)) # Get the cell indexes cell_index_i = cell_index(self.breaks[i], grid_i) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index 64c01d9f3..f1f0b40a8 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -490,15 +490,12 @@ class BiConjugateGradient(InverseLinearOperator): verbose : bool If True, 2-norm of residual r is printed at each iteration. - metric : LinearOperator - The matrix giving the metric to compute the errors - References ---------- [1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015. """ - def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, metric=None): + def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False): assert isinstance(A, LinearOperator) assert A.domain.dimension == A.codomain.dimension @@ -516,7 +513,7 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, metric= self._domain = domain self._codomain = codomain self._solver = 'bicg' - self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose, "metric":metric} + self._options = {"x0":x0, "tol":tol, "maxiter":maxiter, "verbose":verbose} self._check_options(**self._options) self._tmps = {key: domain.zeros() for key in ("v", "r", "p", "vs", "rs", "ps")} self._info = None @@ -536,12 +533,6 @@ def _check_options(self, **kwargs): assert value > 0, "maxiter must be positive" elif key == 'verbose': assert isinstance(value, bool), "verbose must be a bool" - elif key == 'metric': - if value is not None: - assert isinstance(value, LinearOperator), "metric must be a LinearOperator or None" - assert value.domain == value.codomain, "metric must be square with same domain and codomain " - assert value.domain == self._codomain, "metric must be defined on the codomain of the operator to solve " - else: raise ValueError(f"Key '{key}' not understood. See self._options for allowed keys.") @@ -589,9 +580,6 @@ def solve(self, b, out=None): tol = options["tol"] maxiter = options["maxiter"] verbose = options["verbose"] - M = options["metric"] - if M == None: - M=IdentityOperator(domain,codomain) assert isinstance(b, Vector) assert b.space is domain @@ -621,7 +609,7 @@ def solve(self, b, out=None): p.copy(out=ps) v.copy(out=vs) - res_sqr = r.dot(M.dot(r)).real + res_sqr = r.dot(r).real tol_sqr = tol**2 if verbose: @@ -720,15 +708,12 @@ class BiConjugateGradientStabilized(InverseLinearOperator): verbose : bool If True, 2-norm of residual r is printed at each iteration. - metric : LinearOperator - The matrix giving the metric to compute the errors - References ---------- [1] A. Maister, Numerik linearer Gleichungssysteme, Springer ed. 2015. """ - def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, metric=None): + def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False): assert isinstance(A, LinearOperator) assert A.domain.dimension == A.codomain.dimension @@ -745,13 +730,13 @@ def __init__(self, A, *, x0=None, tol=1e-6, maxiter=1000, verbose=False, metric= self._domain = domain self._codomain = codomain self._solver = 'bicgstab' - self._options = {"x0": x0, "tol": tol, "maxiter": maxiter, "verbose": verbose, "metric":metric} + self._options = {"x0": x0, "tol": tol, "maxiter": maxiter, "verbose": verbose} self._check_options(**self._options) self._tmps = {key: domain.zeros() for key in ("v", "r", "p", "vr", "r0")} self._info = None def _check_options(self, **kwargs): - keys = ('x0', 'tol', 'maxiter', 'verbose', 'metric') + keys = ('x0', 'tol', 'maxiter', 'verbose') for key, value in kwargs.items(): idx = [key == keys[i] for i in range(len(keys))] assert any(idx), "key not supported, check options" @@ -775,7 +760,7 @@ def _check_options(self, **kwargs): assert value.domain == self._codomain, "metric must be defined on the codomain of the operator to solve " def _update_options( self ): - self._options = {"x0":self._x0, "tol":self._tol, "maxiter": self._maxiter, "verbose": self._verbose, "metric":self._metric} + self._options = {"x0":self._x0, "tol":self._tol, "maxiter": self._maxiter, "verbose": self._verbose} def transpose(self, conjugate=False): At = self._A.transpose(conjugate=conjugate) @@ -825,9 +810,6 @@ def solve(self, b, out=None): tol = options["tol"] maxiter = options["maxiter"] verbose = options["verbose"] - M = options["metric"] - if M == None: - M=IdentityOperator(domain,codomain) assert isinstance(b, Vector) assert b.space is domain @@ -857,7 +839,7 @@ def solve(self, b, out=None): r.copy(out=r0) - res_sqr = r.dot(M.dot(r)).real + res_sqr = r.dot(r).real tol_sqr = tol ** 2 if verbose: @@ -907,7 +889,7 @@ def solve(self, b, out=None): r.mul_iadd(-w, vr) # ||r||_2 := (r, r) - res_sqr = r.dot(M.dot(r)).real + res_sqr = r.dot(r).real if res_sqr < tol_sqr: break diff --git a/psydac/linalg/tests/test_solvers.py b/psydac/linalg/tests/test_solvers.py index 8317b9e92..5f34fa33a 100644 --- a/psydac/linalg/tests/test_solvers.py +++ b/psydac/linalg/tests/test_solvers.py @@ -3,7 +3,7 @@ import pytest from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilVectorSpace, StencilMatrix, StencilVector -from psydac.linalg.basic import LinearSolver, IdentityOperator +from psydac.linalg.basic import LinearSolver from psydac.ddm.cart import DomainDecomposition, CartDecomposition @@ -54,166 +54,6 @@ def define_data(n, p, matrix_data, dtype=float): xe[s:e + 1] = np.random.random(e + 1 - s) return(V, A, xe) -#=============================================================================== -@pytest.mark.parametrize( 'n', [5, 10, 13] ) -@pytest.mark.parametrize('p', [2, 3]) -@pytest.mark.parametrize('dtype', [float,complex]) -def test_bicgstab_tridiagonal(n, p, dtype, verbose=False): - - #--------------------------------------------------------------------------- - # PARAMETERS - #--------------------------------------------------------------------------- - if dtype==complex: - V, A, xe = define_data(n, p, [1-10j,6+9j,3+5j], dtype=dtype) - else: - V, A, xe = define_data(n, p, [1,6,3], dtype=dtype) - # Tolerance for success: L2-norm of error in solution - tol = 1e-10 - - #--------------------------------------------------------------------------- - # TEST - #--------------------------------------------------------------------------- - if verbose: - # Title - print() - print( "="*80 ) - print( "SERIAL TEST: solve linear system A*x = b using biconjugate gradient" ) - print( "="*80 ) - print() - - # Manufacture right-hand-side vector from exact solution - b = A.dot( xe ) - bt = (A.T).dot( xe ) - bh = (A.H).dot( xe ) - - metric = 2*IdentityOperator(V,V) - - #Create the solvers - solv = inverse(A, 'bicgstab', tol=1e-13, verbose=True, metric=metric) - solvt = solv.transpose() - solvh = solv.H - - # Solve linear system using BiCGSTAB - x = solv @ b - xt = solvt.solve(bt) - xh = solvh.dot(bh) - info = solv.get_info() - - # Verify correctness of calculation: L2-norm of error - err = x-xe - err_norm = np.linalg.norm( err.toarray() ) - - errt = xt-xe - errt_norm = np.linalg.norm( errt.toarray() ) - - errh = xh-xe - errh_norm = np.linalg.norm( errh.toarray() ) - - #--------------------------------------------------------------------------- - # TERMINAL OUTPUT - #--------------------------------------------------------------------------- - if verbose: - print() - print( 'A =', A, sep='\n' ) - print( 'b =', b ) - print( 'x =', x ) - print( 'xe =', xe ) - print( 'info =', info ) - print() - print( "-"*40 ) - print( "L2-norm of error in solution = {:.2e}".format( err_norm ) ) - if err_norm < tol: - print( "PASSED" ) - else: - print( "FAIL" ) - print( "-"*40 ) - - #--------------------------------------------------------------------------- - # PYTEST - #--------------------------------------------------------------------------- - assert err_norm < tol - assert errt_norm < tol - assert errh_norm < tol - -#=============================================================================== -@pytest.mark.parametrize( 'n', [5, 10, 13] ) -@pytest.mark.parametrize('p', [2, 3]) -@pytest.mark.parametrize('dtype', [float,complex]) -def test_bicg_tridiagonal(n, p, dtype, verbose=False): - - #--------------------------------------------------------------------------- - # PARAMETERS - #--------------------------------------------------------------------------- - if dtype==complex: - V, A, xe = define_data(n, p, [1-10j,6+9j,3+5j], dtype=dtype) - else: - V, A, xe = define_data(n, p, [1,6,3], dtype=dtype) - # Tolerance for success: L2-norm of error in solution - tol = 1e-10 - - #--------------------------------------------------------------------------- - # TEST - #--------------------------------------------------------------------------- - if verbose: - # Title - print() - print( "="*80 ) - print( "SERIAL TEST: solve linear system A*x = b using biconjugate gradient" ) - print( "="*80 ) - print() - - metric = 2*IdentityOperator(V,V) - - # Manufacture right-hand-side vector from exact solution - b = A.dot( xe ) - bt = A.T.dot( xe ) - bh = A.H.dot( xe ) - - #Create the solvers - solv = inverse(A, 'bicg', tol=1e-13, verbose=True, metric=metric) - solvt = solv.transpose() - solvh = solv.H - - # Solve linear system using BiCG - x = solv @ b - xt = solvt.solve(bt) - xh = solvh.dot(bh) - info = solv.get_info() - - # Verify correctness of calculation: L2-norm of error - err = x-xe - err_norm = np.linalg.norm( err.toarray() ) - errt = xt-xe - errt_norm = np.linalg.norm( errt.toarray() ) - errh = xh-xe - errh_norm = np.linalg.norm( errh.toarray() ) - - #--------------------------------------------------------------------------- - # TERMINAL OUTPUT - #--------------------------------------------------------------------------- - if verbose: - print() - print( 'A =', A, sep='\n' ) - print( 'b =', b ) - print( 'x =', x ) - print( 'xe =', xe ) - print( 'info =', info ) - print() - print( "-"*40 ) - print( "L2-norm of error in solution = {:.2e}".format( err_norm ) ) - if err_norm < tol: - print( "PASSED" ) - else: - print( "FAIL" ) - print( "-"*40 ) - - #--------------------------------------------------------------------------- - # PYTEST - #--------------------------------------------------------------------------- - assert err_norm < tol - assert errt_norm < tol - assert errh_norm < tol - #=============================================================================== @pytest.mark.parametrize( 'n', [5, 10, 13] ) @pytest.mark.parametrize('p', [2, 3]) From a7c453ebafb48e5f0972d87122ab02bbba0d11a5 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 8 Sep 2023 14:57:37 +0200 Subject: [PATCH 35/77] keep cleaning --- psydac/linalg/solvers.py | 8 ++------ psydac/linalg/tests/test_solvers.py | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index f1f0b40a8..ec82edddb 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -580,6 +580,7 @@ def solve(self, b, out=None): tol = options["tol"] maxiter = options["maxiter"] verbose = options["verbose"] + assert isinstance(b, Vector) assert b.space is domain @@ -753,12 +754,7 @@ def _check_options(self, **kwargs): assert value > 0, "maxiter must be positive" elif true_idx == 3: assert isinstance(value, bool), "verbose must be a bool" - elif true_idx == 4: - if value is not None: - assert isinstance(value, LinearOperator), "metric must be a LinearOperator or None" - assert value.domain == value.codomain, "metric must be square with same domain and codomain " - assert value.domain == self._codomain, "metric must be defined on the codomain of the operator to solve " - + def _update_options( self ): self._options = {"x0":self._x0, "tol":self._tol, "maxiter": self._maxiter, "verbose": self._verbose} diff --git a/psydac/linalg/tests/test_solvers.py b/psydac/linalg/tests/test_solvers.py index 5f34fa33a..c3fe18221 100644 --- a/psydac/linalg/tests/test_solvers.py +++ b/psydac/linalg/tests/test_solvers.py @@ -54,6 +54,7 @@ def define_data(n, p, matrix_data, dtype=float): xe[s:e + 1] = np.random.random(e + 1 - s) return(V, A, xe) + #=============================================================================== @pytest.mark.parametrize( 'n', [5, 10, 13] ) @pytest.mark.parametrize('p', [2, 3]) From 17c69a0121dae7c46b1e5cac811058fdb6aba73e Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 8 Sep 2023 14:58:30 +0200 Subject: [PATCH 36/77] hope it's clean now --- psydac/linalg/solvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index ec82edddb..35ea01366 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -754,7 +754,7 @@ def _check_options(self, **kwargs): assert value > 0, "maxiter must be positive" elif true_idx == 3: assert isinstance(value, bool), "verbose must be a bool" - + def _update_options( self ): self._options = {"x0":self._x0, "tol":self._tol, "maxiter": self._maxiter, "verbose": self._verbose} From ca1eed91463082777b1becc542389c4f45a9a994 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 11 Sep 2023 11:16:00 +0200 Subject: [PATCH 37/77] add a comment on the symbolic space of Hvec --- psydac/api/discretization.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 62856c10b..6ef1cedd6 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -94,6 +94,8 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): if get_vec: Vnh = spaces[0] X = VectorFunctionSpace('X', domain_h.domain, kind='h1') + #Vn = Vnh.symbolic_space + #X = ProductSpace(Vn,Vn) #should fix sympde first Xh = VectorFemSpace(Vnh, Vnh) Xh.symbolic_space = X spaces.append(Xh) @@ -473,6 +475,7 @@ def discretize(a, *args, **kwargs): # return DiscreteSesquilinearForm(a, kernel_expr, *args, **kwargs) if isinstance(a, sym_BilinearForm): + print(kernel_expr) return DiscreteBilinearForm(a, kernel_expr, *args, **kwargs) elif isinstance(a, sym_LinearForm): From 16d7475ded3a57f524a14e9b1c0c28108444aa46 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 11 Sep 2023 11:57:51 +0200 Subject: [PATCH 38/77] solving a bug when different number of cells per patch were given. Adding an assert when using the grid_type feature --- psydac/api/discretization.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 6ef1cedd6..a0a31f502 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -300,7 +300,8 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, # We build the dictionary g_spaces for each interior domain, where it conatians the interiors as keys and the spaces as values, # we then create the compatible spaces if needed with the suitable basis functions. - + #store a boolean knowing if grid type was given or not for later use + is_grid_type = (grid_type is not None) comm = domain_h.comm ldim = V.ldim is_rational_mapping = False @@ -373,11 +374,13 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, max_coords = interior.max_coords assert len(ncells) == len(periodic) == len(degree_i) == len(multiplicity_i) == len(min_coords) == len(max_coords) - if knots is not None and grid_type is not None : + if knots is not None and is_grid_type : raise(ValueError("grids and knots cannot be both provided")) elif knots is None: - if grid_type is None : - grid_type = [np.linspace(-1,1,ne+1) for ne in ncells] + if not is_grid_type : + grid_type = [np.linspace(-1,1,num=ne+1) for ne in ncells] + else : + assert(len(grid_i)==ne+1 for grid_i, ne in zip(grid_type, ncells)) grids = [xmin*(1-grid)/2+xmax*(1+grid)/2 for xmin, xmax, grid in zip(min_coords, max_coords, grid_type)] spaces[i] = [SplineSpace( p, multiplicity=m, grid=grid , periodic=P) @@ -475,7 +478,6 @@ def discretize(a, *args, **kwargs): # return DiscreteSesquilinearForm(a, kernel_expr, *args, **kwargs) if isinstance(a, sym_BilinearForm): - print(kernel_expr) return DiscreteBilinearForm(a, kernel_expr, *args, **kwargs) elif isinstance(a, sym_LinearForm): From c1232d8955c83eaafd1cd74abeadc43c53d757e4 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 11 Sep 2023 14:59:54 +0200 Subject: [PATCH 39/77] removing the name variable causing the trouble --- psydac/api/ast/parser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/psydac/api/ast/parser.py b/psydac/api/ast/parser.py index 97587fced..2caede0e3 100644 --- a/psydac/api/ast/parser.py +++ b/psydac/api/ast/parser.py @@ -1233,8 +1233,9 @@ def _visit_ComputeKernelExpr(self, expr, op=None, lhs=None, **kwargs): lhs = lhs[:] # Create a new name for the temporaries used in each patch - name=lhs[0]._name[12:-8] - temps, rhs = cse_main.cse(rhs, symbols=cse_main.numbered_symbols(prefix=f'temp{name}')) + #name=lhs[0]._name[12:-8] + #temps, rhs = cse_main.cse(rhs, symbols=cse_main.numbered_symbols(prefix=f'temp{name}')) + temps, rhs = cse_main.cse(rhs, symbols=cse_main.numbered_symbols()) normal_vec_stmts = [] normal_vectors = expr.expr.atoms(NormalVector) From d755da55db6ce0bc30dc9844ae3fc6d4b151b21e Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 11 Sep 2023 16:34:13 +0200 Subject: [PATCH 40/77] added a feature to get a name, avoiding the problem with Zero objects --- psydac/api/ast/parser.py | 7 ++++--- psydac/api/ast/utilities.py | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/psydac/api/ast/parser.py b/psydac/api/ast/parser.py index 2caede0e3..695f4468c 100644 --- a/psydac/api/ast/parser.py +++ b/psydac/api/ast/parser.py @@ -30,6 +30,8 @@ from sympde.topology.derivatives import get_index_logical_derivatives +from psydac.api.ast.utilities import get_name + from .nodes import AtomicNode from .nodes import BasisAtom from .nodes import PhysicalBasisValue @@ -1233,9 +1235,8 @@ def _visit_ComputeKernelExpr(self, expr, op=None, lhs=None, **kwargs): lhs = lhs[:] # Create a new name for the temporaries used in each patch - #name=lhs[0]._name[12:-8] - #temps, rhs = cse_main.cse(rhs, symbols=cse_main.numbered_symbols(prefix=f'temp{name}')) - temps, rhs = cse_main.cse(rhs, symbols=cse_main.numbered_symbols()) + name = get_name(lhs) + temps, rhs = cse_main.cse(rhs, symbols=cse_main.numbered_symbols(prefix=f'temp{name}')) normal_vec_stmts = [] normal_vectors = expr.expr.atoms(NormalVector) diff --git a/psydac/api/ast/utilities.py b/psydac/api/ast/utilities.py index 05bdf5294..fd003aaff 100644 --- a/psydac/api/ast/utilities.py +++ b/psydac/api/ast/utilities.py @@ -1040,3 +1040,9 @@ def math_atoms_as_str(expr, lib='math'): sqrt = True return set.union(math_functions, math_constants) + +def get_name(lhs): + for term in lhs: + if term !=0: + return term._name + return "zero_term" From bbac085be24834cd5616fa6f0f5d4b70b379e629 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 11 Sep 2023 18:33:00 +0200 Subject: [PATCH 41/77] trying to fix --- psydac/api/ast/parser.py | 4 +--- psydac/api/ast/utilities.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/psydac/api/ast/parser.py b/psydac/api/ast/parser.py index 695f4468c..5602ded90 100644 --- a/psydac/api/ast/parser.py +++ b/psydac/api/ast/parser.py @@ -30,8 +30,6 @@ from sympde.topology.derivatives import get_index_logical_derivatives -from psydac.api.ast.utilities import get_name - from .nodes import AtomicNode from .nodes import BasisAtom from .nodes import PhysicalBasisValue @@ -75,7 +73,7 @@ from .nodes import Zeros, ZerosLike, Array from .fem import expand, expand_hdiv_hcurl -from psydac.api.ast.utilities import variables, math_atoms_as_str +from psydac.api.ast.utilities import variables, math_atoms_as_str, get_name from psydac.api.utilities import flatten from psydac.api.ast.utilities import build_pythran_types_header from psydac.api.ast.utilities import build_pyccel_types_decorator diff --git a/psydac/api/ast/utilities.py b/psydac/api/ast/utilities.py index fd003aaff..77145c76a 100644 --- a/psydac/api/ast/utilities.py +++ b/psydac/api/ast/utilities.py @@ -1044,5 +1044,5 @@ def math_atoms_as_str(expr, lib='math'): def get_name(lhs): for term in lhs: if term !=0: - return term._name + return term._name[12:-8] return "zero_term" From 766e12cb74828f942a03db1b53f09cb6f0169ddf Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 12 Sep 2023 09:45:54 +0200 Subject: [PATCH 42/77] fixing number of derivative computed by default to avoid segfault --- psydac/api/ast/fem.py | 2 +- psydac/fem/tensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/psydac/api/ast/fem.py b/psydac/api/ast/fem.py index e7c1f5a5f..0b1e47df5 100644 --- a/psydac/api/ast/fem.py +++ b/psydac/api/ast/fem.py @@ -347,7 +347,7 @@ def __init__(self, expr, terminal_expr, spaces, mapping_space=None, tag=None, ma fields = expand_hdiv_hcurl(fields) kwargs['nquads'] = nquads atoms_types = (ScalarFunction, VectorFunction, IndexedVectorFunction) - nderiv = 1 + nderiv = 0 terminal_expr = terminal_expr.expr if isinstance(terminal_expr, (ImmutableDenseMatrix, Matrix)): diff --git a/psydac/fem/tensor.py b/psydac/fem/tensor.py index e2e363e2d..12222b0b2 100644 --- a/psydac/fem/tensor.py +++ b/psydac/fem/tensor.py @@ -109,7 +109,7 @@ def __init__(self, domain_decomposition, *spaces, vector_space=None, cart=None, ends = self._vector_space.cart.domain_decomposition.ends # Compute extended 1D quadrature grids (local to process) along each direction - self._quad_grids = tuple({q: FemAssemblyGrid(V, s, e, nderiv=V.degree, nquads=q)} + self._quad_grids = tuple({q: FemAssemblyGrid(V, s, e, nderiv=max(V.degree,1), nquads=q)} for V, s, e, q in zip( self.spaces, starts, ends, self._nquads)) # Determine portion of logical domain local to process From 460cc9e1998329c9e82542b9d96e0e1efec07baf Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 12 Sep 2023 11:41:51 +0200 Subject: [PATCH 43/77] looks like this 1 is needed --- psydac/api/ast/fem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/api/ast/fem.py b/psydac/api/ast/fem.py index 0b1e47df5..e7c1f5a5f 100644 --- a/psydac/api/ast/fem.py +++ b/psydac/api/ast/fem.py @@ -347,7 +347,7 @@ def __init__(self, expr, terminal_expr, spaces, mapping_space=None, tag=None, ma fields = expand_hdiv_hcurl(fields) kwargs['nquads'] = nquads atoms_types = (ScalarFunction, VectorFunction, IndexedVectorFunction) - nderiv = 0 + nderiv = 1 terminal_expr = terminal_expr.expr if isinstance(terminal_expr, (ImmutableDenseMatrix, Matrix)): From ac921173725f1b0657aa284a99207a1bee1b7ecf Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 18 Sep 2023 09:51:05 +0200 Subject: [PATCH 44/77] add update ghost regions on composed linear op --- psydac/linalg/basic.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 6a545c601..398ec9af0 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -777,12 +777,14 @@ def dot(self, v, out=None): x = v for i in range(len(self._tmp_vectors)): + x.update_ghost_regions() y = self._tmp_vectors[-1-i] A = self._multiplicants[-1-i] A.dot(x, out=y) x = y A = self._multiplicants[0] + x.update_ghost_regions() if out is not None: A.dot(x, out=out) From e98d9ace780c7b8e837d7d30a7e1acd8d5a29189 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 18 Sep 2023 16:45:00 +0200 Subject: [PATCH 45/77] remove leftovers print in basis projectors --- psydac/feec/basis_projectors.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index e752d95fd..d751a23a0 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -268,10 +268,9 @@ def assemble_mat(P, V, fun, preproc_grid=None): _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = preproc_grid[i][j] else : - print("yes") _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) - print(_ptsG) + _ptsG = [pts.flatten() for pts in _ptsG] _Vnbases = [space.nbasis for space in V1d] f_coeffs = f.coeffs._data From 39938b03d306738a50e93796a1f58e4fc1944e50 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 19 Sep 2023 16:51:18 +0200 Subject: [PATCH 46/77] add option to update fun to basis projectors --- psydac/feec/basis_projectors.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index d751a23a0..083aee1e6 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -135,6 +135,13 @@ def dof_operator(self): """ return self._dof_operator + def update_fun(self, fun): + self._fun = fun + self._dof_operator = BasisProjectionOperator.assemble_mat( + self._P, self._V, fun, self._preproc_grid) + if self._transposed: + self._dof_operator = self._dof_operator.transpose() + def dot(self, v, out=None): """ Applies the basis projection operator to the FE coefficients v. From 1e6e336a2bb6621ec675f3e46b1c513c5b72899e Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 20 Sep 2023 13:25:10 +0200 Subject: [PATCH 47/77] reuse Blockmatrix in basis projector operator --- psydac/feec/basis_projectors.py | 71 ++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 083aee1e6..a69dbbb9e 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -78,16 +78,37 @@ def __init__(self, P, V, fun, transposed=False, preproc_grid=None, dof_mat=None) self._preproc_grid = preproc_grid + if isinstance(V, TensorFemSpace): + Vspaces = [V.vector_space] + else: + Vspaces = V.vector_space + + # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(P.space, TensorFemSpace): + Wspaces = [P.space.vector_space] + else: + Wspaces = P.space.vector_space + blocks = [] + for Wspace in Wspaces: + blocks += [[]] + # input vector space (domain), column of block + for Vspace in Vspaces: + dofs_mat = StencilMatrix( + Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) + blocks[-1] += [dofs_mat] + + self._dof_operator_pre = BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) + # ============= assemble tensor-product dof matrix ======= - if dof_mat == None: - dof_mat = BasisProjectionOperator.assemble_mat( - P, V, fun, self._preproc_grid) + + BasisProjectionOperator.assemble_mat( + P, V, fun, self._dof_operator_pre, self._preproc_grid) # ======================================================== - self._dof_operator = dof_mat - if transposed: - self._dof_operator = self._dof_operator.transpose() + self._dof_operator = self._dof_operator_pre.transpose() + else: + self._dof_operator = self._dof_operator_pre # set domain and codomain self._domain = self.dof_operator.domain @@ -137,10 +158,12 @@ def dof_operator(self): def update_fun(self, fun): self._fun = fun - self._dof_operator = BasisProjectionOperator.assemble_mat( - self._P, self._V, fun, self._preproc_grid) + BasisProjectionOperator.assemble_mat( + self._P, self._V, fun, self._dof_operator_pre, self._preproc_grid) if self._transposed: - self._dof_operator = self._dof_operator.transpose() + self._dof_operator = self._dof_operator_pre.transpose() + else: + self._dof_operator = self._dof_operator_pre def dot(self, v, out=None): """ @@ -199,7 +222,7 @@ def transpose(self, conjugate=False): return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed, preproc_grid=self._preproc_grid, dof_mat=self._dof_operator) @staticmethod - def assemble_mat(P, V, fun, preproc_grid=None): + def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): """ Assembles the tensor-product DOF matrix sigma_i(fun*Lambda_j), where i=(i1, i2, ...) and j=(j1, j2, ...) depending on the number of spatial dimensions (1d, 2d or 3d). @@ -241,19 +264,24 @@ def assemble_mat(P, V, fun, preproc_grid=None): for direction in range(V.ldim)] for comp in range(len(_W1ds))] # blocks of dof matrix - blocks = [] + i=0 # ouptut vector space (codomain), row of block for Wspace, W1d, nq, fun_line in zip(_Wspaces, _W1ds, _nqs, fun): - blocks += [[]] + _Wdegrees = [space.degree for space in W1d] j=0 # input vector space (domain), column of block for Vspace, V1d, f in zip(_Vspaces, _V1ds, fun_line): # instantiate cell of block matrix - dofs_mat = StencilMatrix( - Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) + """if isinstance(V, TensorFemSpace): + dofs_mat = dof_operator.blocks[i] + elif isinstance(P.space, TensorFemSpace): + dofs_mat = dof_operator.blocks[j] + else : + dofs_mat = dof_operator.blocks[i][j]""" + dofs_mat = dof_operator._blocks[i, j] _starts_in = np.array(dofs_mat.domain.starts) _ends_in = np.array(dofs_mat.domain.ends) @@ -288,7 +316,7 @@ def assemble_mat(P, V, fun, preproc_grid=None): _pads_out, _starts_c, _ends_c, _pads_c, *_wtsG, *_spans, *_bases, f_coeffs, *_spans_ff, *_bases_ff, *_Vnbases, *_Wdegrees) - blocks[-1] += [dofs_mat] + else : @@ -321,22 +349,9 @@ def assemble_mat(P, V, fun, preproc_grid=None): kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_Vnbases, *_Wdegrees) - blocks[-1] += [dofs_mat] - - else: - blocks[-1] += [None] j+=1 i+=1 - # build BlockLinearOperator (if necessary) and return - if len(blocks) == len(blocks[0]) == 1: - if blocks[0][0] is not None: - return blocks[0][0] - else: - return dofs_mat - else: - return BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) - def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. From 0bb7c7302473486fd84f6988bd206f41faf3f01d Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 21 Sep 2023 14:06:18 +0200 Subject: [PATCH 48/77] add idot to various subclasses --- psydac/linalg/basic.py | 40 ++++++++++++++++++++++++++++++++++++++++ psydac/linalg/block.py | 23 +++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 398ec9af0..8d4247dcd 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -492,6 +492,18 @@ def dot(self, v, out=None): return out else: return v.copy() + + def idot(self, v, out): + """ + Implements out += self @ v with a temporary. + Subclasses should provide an implementation without a temporary. + + """ + assert isinstance(v, Vector) + assert v.space == self.domain + assert isinstance(out, Vector) + assert out.space == self.codomain + out += v def __matmul__(self, B): assert isinstance(B, (LinearOperator, Vector)) @@ -524,6 +536,7 @@ def __init__(self, domain, codomain, c, A): self._scalar = scalar self._domain = domain self._codomain = codomain + self._tmp_idot = codomain.zeros() @property def domain(self): @@ -571,6 +584,17 @@ def dot(self, v, out=None): out = self._operator.dot(v) out *= self._scalar return out + + def idot(self, v, out): + assert isinstance(v, Vector) + assert v.space == self._domain + assert isinstance(out, Vector) + assert out.space == self._codomain + self._operator.dot(v, out = self._tmp_idot) + self._tmp_idot *=self._scalar + out += self._tmp_idot + return out + #=============================================================================== class SumLinearOperator(LinearOperator): @@ -728,6 +752,8 @@ def __init__(self, domain, codomain, *args): self._multiplicants = multiplicants self._tmp_vectors = tuple(tmp_vectors) + self._tmp_idot = codomain.zeros() + @property def tmp_vectors(self): return self._tmp_vectors @@ -792,6 +818,20 @@ def dot(self, v, out=None): out = A.dot(x) return out + def idot(self, v, out): + """ + Implements out += self @ v with a temporary. + Subclasses should provide an implementation without a temporary. + + """ + assert isinstance(v, Vector) + assert v.space == self.domain + assert isinstance(out, Vector) + assert out.space == self.codomain + self.dot(v, out=self._tmp_idot) + self._tmp_idot.update_ghost_regions() + out += self._tmp_idot + def exchange_assembly_data( self ): for op in self._multiplicants: op.exchange_assembly_data() diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index fb2737d56..33c17e1c7 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -656,6 +656,29 @@ def dot(self, v, out=None): out.ghost_regions_in_sync = False return out + + def idot(self, v, out): + + if self.n_block_cols == 1: + assert isinstance(v, Vector) + else: + assert isinstance(v, BlockVector) + + assert v.space is self.domain + + if self.n_block_rows == 1: + assert isinstance(out, Vector) + else: + assert isinstance(out, BlockVector) + assert out.space is self.codomain + + if not v.ghost_regions_in_sync: + v.update_ghost_regions() + + self._func(self._blocks_as_args, v, out, **self._args) + + out.ghost_regions_in_sync = False + return out #... @staticmethod From 9699be394843f93a820cfc148edfdafb5bb07c30 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 22 Sep 2023 11:51:47 +0200 Subject: [PATCH 49/77] order list in sumlinearoperator to avoid problems with multiple processors --- psydac/linalg/basic.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index 8d4247dcd..c3b517726 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -679,6 +679,7 @@ def transpose(self, conjugate=False): def simplifiy(addends): class_list = [addends[i].__class__.__name__ for i in range(len(addends))] unique_list = list(set(class_list)) + unique_list.sort() if len(unique_list) == 1: return addends out = () @@ -803,27 +804,23 @@ def dot(self, v, out=None): x = v for i in range(len(self._tmp_vectors)): - x.update_ghost_regions() y = self._tmp_vectors[-1-i] A = self._multiplicants[-1-i] A.dot(x, out=y) x = y + x.update_ghost_regions() A = self._multiplicants[0] - x.update_ghost_regions() if out is not None: A.dot(x, out=out) else: out = A.dot(x) + out.update_ghost_regions() return out def idot(self, v, out): - """ - Implements out += self @ v with a temporary. - Subclasses should provide an implementation without a temporary. - - """ + assert isinstance(v, Vector) assert v.space == self.domain assert isinstance(out, Vector) From 751c4978fffd52349d5c96d4d280acec69908cf8 Mon Sep 17 00:00:00 2001 From: Julian Owezarek Date: Wed, 27 Sep 2023 11:13:23 +0200 Subject: [PATCH 50/77] make KroneckerLinearSolver subclass LinearOperator --- psydac/linalg/block.py | 11 ++++++----- psydac/linalg/kron.py | 28 +++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index fb2737d56..997ccbead 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -7,10 +7,11 @@ from types import MappingProxyType from scipy.sparse import bmat, lil_matrix -from psydac.linalg.basic import VectorSpace, Vector, LinearOperator, LinearSolver, ZeroOperator -from psydac.ddm.cart import InterfaceCartDecomposition -from psydac.ddm.utilities import get_data_exchanger -from psydac.linalg.stencil import StencilVector, StencilMatrix +from psydac.linalg.basic import VectorSpace, Vector, LinearOperator, LinearSolver +from psydac.linalg.stencil import StencilMatrix +from psydac.linalg.kron import KroneckerLinearSolver +from psydac.ddm.cart import InterfaceCartDecomposition +from psydac.ddm.utilities import get_data_exchanger __all__ = ('BlockVectorSpace', 'BlockVector', 'BlockLinearOperator', 'BlockDiagonalSolver') @@ -1450,7 +1451,7 @@ def __getitem__( self, key ): def __setitem__( self, key, value ): assert 0 <= key < self._nblocks - assert isinstance( value, LinearSolver ) + assert isinstance( value, (LinearSolver, KroneckerLinearSolver) ) # Check domain of rhs assert value.space is self.space[key] diff --git a/psydac/linalg/kron.py b/psydac/linalg/kron.py index 0186f119f..1dbe729c9 100644 --- a/psydac/linalg/kron.py +++ b/psydac/linalg/kron.py @@ -371,7 +371,7 @@ def exchange_assembly_data( self ): def set_backend(self, backend): pass #============================================================================== -class KroneckerLinearSolver(LinearSolver): +class KroneckerLinearSolver(LinearOperator): """ A solver for Ax=b, where A is a Kronecker matrix from arbirary dimension d, defined by d solvers. We also need information about the space of b. @@ -400,6 +400,8 @@ def __init__(self, V, solvers): # general arguments self._space = V + self._domain = self._space + self._codomain = self._space self._solvers = solvers self._parallel = self._space.parallel self._dtype = self._space._dtype @@ -508,6 +510,30 @@ def space(self): """ return self._space + @property + def domain(self): + return self._space + + @property + def codomain(self): + return self._space + + @property + def dtype(self): + return None + + def toarray(self): + raise NotImplementedError('toarray() is not defined for KroneckerLinearSolvers.') + + def tosparse(self): + raise NotImplementedError('tosparse() is not defined for KroneckerLinearSolvers.') + + def transpose(self): + raise NotImplementedError('transpose() is not defined for KroneckerLinearSolvers.') + + def dot(self, v, out=None): + return self.solve(v, out=out) + @property def solvers(self): """ From 4f33eec7b48539b72cba45a0c105c4e85a22d7b6 Mon Sep 17 00:00:00 2001 From: Julian Owezarek Date: Thu, 28 Sep 2023 13:17:16 +0200 Subject: [PATCH 51/77] allow for different domain and codomain in KroneckerLinearSolver --- psydac/feec/global_projectors.py | 2 +- psydac/linalg/block.py | 17 ++++-- psydac/linalg/fft.py | 2 +- psydac/linalg/kron.py | 61 ++++++++++--------- psydac/linalg/tests/test_block.py | 12 ++-- .../linalg/tests/test_kron_direct_solver.py | 18 +++--- 6 files changed, 60 insertions(+), 52 deletions(-) diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index 773df5cf3..edf8a3ea2 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -161,7 +161,7 @@ def __init__(self, space, nquads = None): self._grid_x += [block_x] self._grid_w += [block_w] - solverblocks += [KroneckerLinearSolver(tensorspaces[i].vector_space, solvercells)] + solverblocks += [KroneckerLinearSolver(tensorspaces[i].vector_space, tensorspaces[i].vector_space, solvercells)] dataslice = tuple(slice(p, -p) for p in tensorspaces[i].vector_space.pads) dofs[i] = rhsblocks[i]._data[dataslice] diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index 997ccbead..4ffc14322 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -1326,7 +1326,7 @@ def func(blocks, v, out, **args): #=============================================================================== class BlockDiagonalSolver( LinearSolver ): """ - A LinearSolver that can be written as blocks of other LinearSolvers, + A LinearSolver that can be written as blocks of other (Kronecker-)LinearSolvers, i.e. it can be seen as a solver for linear equations with block-diagonal matrices. The space of this solver has to be of the type BlockVectorSpace. @@ -1337,14 +1337,14 @@ class BlockDiagonalSolver( LinearSolver ): Space of the new blocked linear solver. blocks : dict | list | tuple - LinearSolver objects (optional). + (Kronecker-)LinearSolver objects (optional). a) 'blocks' can be dictionary with . key = integer i >= 0 - . value = corresponding LinearSolver Lii + . value = corresponding (Kronecker-)LinearSolver Lii - b) 'blocks' can be list of LinearSolvers (or tuple of these) where blocks[i] - is the LinearSolver Lii (if None, we assume null operator) + b) 'blocks' can be list of (Kronecker-)LinearSolvers (or tuple of these) where blocks[i] + is the (Kronecker-)LinearSolver Lii (if None, we assume null operator) """ def __init__( self, V, blocks=None ): @@ -1454,6 +1454,11 @@ def __setitem__( self, key, value ): assert isinstance( value, (LinearSolver, KroneckerLinearSolver) ) # Check domain of rhs - assert value.space is self.space[key] + if isinstance(value, LinearSolver): + assert value.space is self.space[key] + else: + # restrictive, eventually to be removed assumption, that space = domain = codomain + assert value.domain is self.space[key] + assert value.codomain is self.space[key] self._blocks[key] = value diff --git a/psydac/linalg/fft.py b/psydac/linalg/fft.py index 6cd566353..323c29347 100644 --- a/psydac/linalg/fft.py +++ b/psydac/linalg/fft.py @@ -67,7 +67,7 @@ def __init__(self, space, functions): else: onedimsolver = DistributedFFTBase.OneDimSolver(functions) solvers = [onedimsolver] * space.ndim - self._isolver = KroneckerLinearSolver(space, solvers) + self._isolver = KroneckerLinearSolver(space, space, solvers) # ... @property diff --git a/psydac/linalg/kron.py b/psydac/linalg/kron.py index 1dbe729c9..04838d5d3 100644 --- a/psydac/linalg/kron.py +++ b/psydac/linalg/kron.py @@ -380,36 +380,45 @@ class KroneckerLinearSolver(LinearOperator): ---------- V : StencilVectorSpace The space b will live in; i.e. which gives us information about - the distribution of the right-hand sides. + the distribution of the right-hand side b. + + W : StencilVectorSpace + The space x will live in; i.e. which gives us information about + the distribution of the unknown vector x. solvers : list of LinearSolver The components of A in each dimension. Attributes ---------- - space : StencilVectorSpace - The space our vectors to solve live in. + domain : StencilVectorSpace + The space of the rhs vector b. + + codomain : StencilVectorSpace + The space of the unknown vector x. """ - def __init__(self, V, solvers): + def __init__(self, V, W, solvers): assert isinstance(V, StencilVectorSpace) + assert isinstance(W, StencilVectorSpace) assert hasattr( solvers, '__iter__' ) for solver in solvers: assert isinstance(solver, LinearSolver) assert V.ndim == len(solvers) + assert W.ndim == len(solvers) + assert V.npts == W.npts # general arguments - self._space = V - self._domain = self._space - self._codomain = self._space + self._domain = V + self._codomain = W self._solvers = solvers - self._parallel = self._space.parallel - self._dtype = self._space._dtype + self._parallel = self._domain.parallel + self._dtype = self._codomain._dtype if self._parallel: - self._mpi_type = V._mpi_type + self._mpi_type = self._domain._mpi_type else: self._mpi_type = None - self._ndim = self._space.ndim + self._ndim = self._codomain.ndim # compute and setup solver arguments self._setup_solvers() @@ -426,12 +435,12 @@ def _setup_solvers(self): (which potentially utilize MPI). """ # slice sizes - starts = np.array(self._space.starts) - ends = np.array(self._space.ends) + 1 + starts = np.array(self._domain.starts) + ends = np.array(self._domain.ends) + 1 self._slice = tuple([slice(s, e) for s,e in zip(starts, ends)]) # local and global sizes - nglobals = self._space.npts + nglobals = self._domain.npts nlocals = ends - starts self._localsize = np.product(nlocals) mglobals = self._localsize // nlocals @@ -448,15 +457,15 @@ def _setup_solvers(self): # useful e.g. if we have little data in some directions # (and thus no data distributed there) - if not self._parallel or self._space.cart.subcomm[i].size <= 1: + if not self._parallel or self._domain.cart.subcomm[i].size <= 1: # serial solve solver_passes[i] = KroneckerLinearSolver.KroneckerSolverSerialPass( self._solvers[i], nglobals[i], mglobals[i]) else: # for the parallel case, use Alltoallv solver_passes[i] = KroneckerLinearSolver.KroneckerSolverParallelPass( - self._solvers[i], self._space._mpi_type, i, - self._space.cart, mglobals[i], nglobals[i], nlocals[i], self._localsize) + self._solvers[i], self._domain._mpi_type, i, + self._domain.cart, mglobals[i], nglobals[i], nlocals[i], self._localsize) # we have a parallel solve pass now, so we are not completely local any more self._allserial = False @@ -501,22 +510,14 @@ def _allocate_temps(self): else: temp2 = np.empty((self._tempsize,), dtype=self._dtype) return temp1, temp2 - - @property - def space(self): - """ - Returns the space associated to this solver (i.e. where the information - about the cartesian distribution is taken from). - """ - return self._space @property def domain(self): - return self._space + return self._domain @property def codomain(self): - return self._space + return self._codomain @property def dtype(self): @@ -528,7 +529,7 @@ def toarray(self): def tosparse(self): raise NotImplementedError('tosparse() is not defined for KroneckerLinearSolvers.') - def transpose(self): + def transpose(self, conjugate=False): raise NotImplementedError('transpose() is not defined for KroneckerLinearSolvers.') def dot(self, v, out=None): @@ -548,11 +549,11 @@ def solve(self, rhs, out=None, transposed=False): """ # type checks - assert rhs.space is self._space + assert rhs.space is self._domain if out is not None: assert isinstance( out, StencilVector ) - assert out.space is self._space + assert out.space is self._codomain else: out = StencilVector( rhs.space ) diff --git a/psydac/linalg/tests/test_block.py b/psydac/linalg/tests/test_block.py index c67f49262..91e660d6d 100644 --- a/psydac/linalg/tests/test_block.py +++ b/psydac/linalg/tests/test_block.py @@ -248,8 +248,8 @@ def test_2D_block_diagonal_solver_serial_init( dtype, n1, n2, p1, p2, P1, P2 ): M12 = SparseSolver( spa.csc_matrix(m12) ) M21 = SparseSolver( spa.csc_matrix(m21) ) M22 = SparseSolver( spa.csc_matrix(m22) ) - M1 = KroneckerLinearSolver(V, [M11,M12]) - M2 = KroneckerLinearSolver(V, [M21,M22]) + M1 = KroneckerLinearSolver(V, V, [M11,M12]) + M2 = KroneckerLinearSolver(V, V, [M21,M22]) x1 = StencilVector( V ) x2 = StencilVector( V ) @@ -836,8 +836,8 @@ def test_block_diagonal_solver_serial_dot( dtype, n1, n2, p1, p2, P1, P2 ): M12 = SparseSolver( spa.csc_matrix(m12) ) M21 = SparseSolver( spa.csc_matrix(m21) ) M22 = SparseSolver( spa.csc_matrix(m22) ) - M1 = KroneckerLinearSolver(V, [M11,M12]) - M2 = KroneckerLinearSolver(V, [M21,M22]) + M1 = KroneckerLinearSolver(V, V, [M11,M12]) + M2 = KroneckerLinearSolver(V, V, [M21,M22]) x1 = StencilVector( V ) x2 = StencilVector( V ) @@ -1251,8 +1251,8 @@ def test_block_diagonal_solver_parallel_dot( dtype, n1, n2, p1, p2, P1, P2 ): M12 = SparseSolver( spa.csc_matrix(m12) ) M21 = SparseSolver( spa.csc_matrix(m21) ) M22 = SparseSolver( spa.csc_matrix(m22) ) - M1 = KroneckerLinearSolver(V, [M11,M12]) - M2 = KroneckerLinearSolver(V, [M21,M22]) + M1 = KroneckerLinearSolver(V, V, [M11,M12]) + M2 = KroneckerLinearSolver(V, V, [M21,M22]) x1 = StencilVector( V ) x2 = StencilVector( V ) diff --git a/psydac/linalg/tests/test_kron_direct_solver.py b/psydac/linalg/tests/test_kron_direct_solver.py index 836b0b1b3..a2fcc835b 100644 --- a/psydac/linalg/tests/test_kron_direct_solver.py +++ b/psydac/linalg/tests/test_kron_direct_solver.py @@ -70,10 +70,10 @@ def matrix_to_sparse(A): A.remove_spurious_entries() return SparseSolver(A.tosparse()) -def random_matrix(seed, space): - A = StencilMatrix(space, space) - p = space.pads[0] - dtype = space.dtype +def random_matrix(seed, domain, codomain): + A = StencilMatrix(domain, codomain) + p = domain.pads[0] # by definition of the StencilMatrix, domain.pads == codomain.pads + dtype = domain.dtype # by definition of the StencilMatrix, domain.dtype == codomain.dtype # for now, take matrices like this (as in the other tests) if dtype==complex: @@ -118,13 +118,14 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t Ds = [DomainDecomposition([n], periods=[P]) for n,P in zip(npts, periods)] carts = [CartDecomposition(Di, [n], *compute_global_starts_ends(Di, [n]), pads=[p], shifts=[1]) for Di,n,p in zip(Ds, npts, pads)] Vs = [StencilVectorSpace(carti, dtype=dtype) for carti in carts] + Ws = [StencilVectorSpace(carti, dtype=dtype) for carti in carts] localslice = tuple([slice(s, e+1) for s, e in zip(V.starts, V.ends)]) if verbose: print(f'[{rank}] Vector spaces built', flush=True) # bulid matrices (A) - A = [random_matrix(seed+i+1, Vi) for i,Vi in enumerate(Vs)] + A = [random_matrix(seed+i+1, Vi, Wi) for i,(Vi, Wi) in enumerate(zip(Vs, Ws))] solvers = [direct_solver(Ai) for Ai in A] if verbose: @@ -143,7 +144,7 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t X_glob = kron_solve_seq_ref(Y_glob, A, transposed) Xout = StencilVector(V) - X = KroneckerLinearSolver(V, solvers).solve(Y, out=Xout, transposed=transposed) + X = KroneckerLinearSolver(V, V, solvers).solve(Y, out=Xout, transposed=transposed) assert X is Xout if verbose: @@ -177,11 +178,12 @@ def test_direct_solvers(dtype, seed, n, p, P, nrhs, direct_solver, transposed): D = DomainDecomposition([n], periods=[P]) cart = CartDecomposition(D, [n], *compute_global_starts_ends(D, [n]), pads=[p], shifts=[1]) - # space (V) + # domain V and codomain W V = StencilVectorSpace( cart, dtype=dtype ) + W = StencilVectorSpace( cart, dtype=dtype ) # bulid matrices (A) - A = random_matrix(seed+1, V) + A = random_matrix(seed+1, V, W) solver = direct_solver(A) # vector to solve for (Y) From 792e019916dbcf4491b67b031f2bcf2fa5b448d5 Mon Sep 17 00:00:00 2001 From: Julian Owezarek Date: Thu, 28 Sep 2023 14:34:49 +0200 Subject: [PATCH 52/77] fix bug and bad tests --- psydac/linalg/kron.py | 2 +- psydac/linalg/tests/test_kron_direct_solver.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/psydac/linalg/kron.py b/psydac/linalg/kron.py index 04838d5d3..5832e41bc 100644 --- a/psydac/linalg/kron.py +++ b/psydac/linalg/kron.py @@ -910,5 +910,5 @@ def kronecker_solve(solvers, rhs, out=None, transposed=False): else: out = StencilVector(rhs.space) - kronsolver = KroneckerLinearSolver(rhs.space, solvers) + kronsolver = KroneckerLinearSolver(rhs.space, rhs.space, solvers) return kronsolver.solve(rhs, out=out, transposed=transposed) diff --git a/psydac/linalg/tests/test_kron_direct_solver.py b/psydac/linalg/tests/test_kron_direct_solver.py index a2fcc835b..c578d8c93 100644 --- a/psydac/linalg/tests/test_kron_direct_solver.py +++ b/psydac/linalg/tests/test_kron_direct_solver.py @@ -107,18 +107,24 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t # vector spaces comm = MPI.COMM_WORLD D = DomainDecomposition(npts, periods=periods, comm=comm) + D2 = DomainDecomposition(npts, periods=periods, comm=comm) # Partition the points global_starts, global_ends = compute_global_starts_ends(D, npts) + global_starts2, global_ends2 = compute_global_starts_ends(D2, npts) cart = CartDecomposition(D, npts, global_starts, global_ends, pads=pads, shifts=[1]*len(pads)) + cart2 = CartDecomposition(D2, npts, global_starts2, global_ends2, pads=pads, shifts=[1]*len(pads)) V = StencilVectorSpace(cart, dtype=dtype) + W = StencilVectorSpace(cart2, dtype=dtype) Ds = [DomainDecomposition([n], periods=[P]) for n,P in zip(npts, periods)] carts = [CartDecomposition(Di, [n], *compute_global_starts_ends(Di, [n]), pads=[p], shifts=[1]) for Di,n,p in zip(Ds, npts, pads)] Vs = [StencilVectorSpace(carti, dtype=dtype) for carti in carts] - Ws = [StencilVectorSpace(carti, dtype=dtype) for carti in carts] + Ds2 = [DomainDecomposition([n], periods=[P]) for n,P in zip(npts, periods)] + carts2 = [CartDecomposition(Di, [n], *compute_global_starts_ends(Di, [n]), pads=[p], shifts=[1]) for Di,n,p in zip(Ds2, npts, pads)] + Ws = [StencilVectorSpace(carti, dtype=dtype) for carti in carts2] localslice = tuple([slice(s, e+1) for s, e in zip(V.starts, V.ends)]) if verbose: @@ -142,9 +148,9 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t # solve in two different ways X_glob = kron_solve_seq_ref(Y_glob, A, transposed) - Xout = StencilVector(V) + Xout = StencilVector(W) - X = KroneckerLinearSolver(V, V, solvers).solve(Y, out=Xout, transposed=transposed) + X = KroneckerLinearSolver(V, W, solvers).solve(Y, out=Xout, transposed=transposed) assert X is Xout if verbose: @@ -180,10 +186,9 @@ def test_direct_solvers(dtype, seed, n, p, P, nrhs, direct_solver, transposed): # domain V and codomain W V = StencilVectorSpace( cart, dtype=dtype ) - W = StencilVectorSpace( cart, dtype=dtype ) # bulid matrices (A) - A = random_matrix(seed+1, V, W) + A = random_matrix(seed+1, V, V) solver = direct_solver(A) # vector to solve for (Y) From b8068f711c697d5dd5921eb8f400f2d129b8914f Mon Sep 17 00:00:00 2001 From: Julian Owezarek Date: Mon, 2 Oct 2023 12:33:46 +0200 Subject: [PATCH 53/77] add Kronecker M1 solver to tests --- .../linalg/tests/test_kron_direct_solver.py | 185 +++++++++++++++++- 1 file changed, 181 insertions(+), 4 deletions(-) diff --git a/psydac/linalg/tests/test_kron_direct_solver.py b/psydac/linalg/tests/test_kron_direct_solver.py index c578d8c93..6762ac76d 100644 --- a/psydac/linalg/tests/test_kron_direct_solver.py +++ b/psydac/linalg/tests/test_kron_direct_solver.py @@ -3,13 +3,26 @@ import pytest import time import numpy as np -from mpi4py import MPI -from psydac.ddm.cart import DomainDecomposition, CartDecomposition +from mpi4py import MPI + + from scipy.sparse import csc_matrix, dia_matrix, kron from scipy.sparse.linalg import splu -from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix -from psydac.linalg.kron import KroneckerLinearSolver + +from sympde.calculus import dot +from sympde.expr import BilinearForm, integral +from sympde.topology import Line +from sympde.topology import Cube +from sympde.topology import Derham +from sympde.topology import elements_of + +from psydac.api.discretization import discretize +from psydac.ddm.cart import DomainDecomposition, CartDecomposition +from psydac.linalg.block import BlockLinearOperator from psydac.linalg.direct_solvers import SparseSolver, BandedSolver +from psydac.linalg.kron import KroneckerLinearSolver +from psydac.linalg.solvers import inverse +from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix #=============================================================================== def compute_global_starts_ends(domain_decomposition, npts): @@ -169,6 +182,74 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t # compare for equality assert np.allclose( X[localslice], X_glob[localslice], rtol=1e-8, atol=1e-8 ) +def get_M1_block_kron_solver(V1, ncells, degree, periodic): + """ + Given a 3D DeRham sequenece (V0 = H(grad) --grad--> V1 = H(curl) --curl--> V2 = H(div) --div--> V3 = L2) + discreticed using ncells, degree and periodic, + + domain = Cube('C', bounds1=(0, 1), bounds2=(0, 1), bounds3=(0, 1)) + derham = Derham(domain) + domain_h = discretize(domain, ncells=ncells, periodic=periodic, comm=comm) + derham_h = discretize(derham, domain_h, degree=degree), + + returns the inverse of the mass matrix M1 as a BlockLinearOperator consisting of three KroneckerLinearSolvers on the diagonal. + """ + # assert 3D + assert len(ncells) == 3 + assert len(degree) == 3 + assert len(periodic) == 3 + + # 1D domain to be discreticed using the respective values of ncells, degree, periodic + domain_1d = Line('L', bounds=(0,1)) + derham_1d = Derham(domain_1d) + + # storage for the 1D mass matrices + M0_matrices = [] + M1_matrices = [] + + # assembly of the 1D mass matrices + for (n, p, P) in zip(ncells, degree, periodic): + + domain_1d_h = discretize(domain_1d, ncells=[n], periodic=[P]) + derham_1d_h = discretize(derham_1d, domain_1d_h, degree=[p]) + + u_1d_0, v_1d_0 = elements_of(derham_1d.V0, names='u_1d_0, v_1d_0') + u_1d_1, v_1d_1 = elements_of(derham_1d.V1, names='u_1d_1, v_1d_1') + + a_1d_0 = BilinearForm((u_1d_0, v_1d_0), integral(domain_1d, u_1d_0 * v_1d_0)) + a_1d_1 = BilinearForm((u_1d_1, v_1d_1), integral(domain_1d, u_1d_1 * v_1d_1)) + + a_1d_0_h = discretize(a_1d_0, domain_1d_h, (derham_1d_h.V0, derham_1d_h.V0)) + a_1d_1_h = discretize(a_1d_1, domain_1d_h, (derham_1d_h.V1, derham_1d_h.V1)) + + M_1d_0 = a_1d_0_h.assemble() + M_1d_1 = a_1d_1_h.assemble() + + M0_matrices.append(M_1d_0) + M1_matrices.append(M_1d_1) + + V1_1 = V1[0] + V1_2 = V1[1] + V1_3 = V1[2] + + B1_mat = [M1_matrices[0], M0_matrices[1], M0_matrices[2]] + B2_mat = [M0_matrices[0], M1_matrices[1], M0_matrices[2]] + B3_mat = [M0_matrices[0], M0_matrices[1], M1_matrices[2]] + + B1_solvers = [matrix_to_bandsolver(Ai) for Ai in B1_mat] + B2_solvers = [matrix_to_bandsolver(Ai) for Ai in B2_mat] + B3_solvers = [matrix_to_bandsolver(Ai) for Ai in B3_mat] + + B1_kron_inv = KroneckerLinearSolver(V1_1, V1_1, B1_solvers) + B2_kron_inv = KroneckerLinearSolver(V1_2, V1_2, B2_solvers) + B3_kron_inv = KroneckerLinearSolver(V1_3, V1_3, B3_solvers) + + M1_block_kron_solver = BlockLinearOperator(V1, V1, ((B1_kron_inv, None, None), + (None, B2_kron_inv, None), + (None, None, B3_kron_inv))) + + return M1_block_kron_solver + #=============================================================================== # tests of the direct solvers @pytest.mark.parametrize( 'dtype', [float, complex] ) @@ -370,6 +451,102 @@ def test_kron_solver_nd_par(seed, dim, dtype): npts_base = 4 compare_solve(seed, MPI.COMM_WORLD, [npts_base]*dim, [1]*dim, [False]*dim, matrix_to_sparse, dtype=dtype, transposed=False, verbose=False) + +#=============================================================================== + +# test Kronecker solver of the M1 mass matrix of our 3D DeRham sequence, as described in the get_M1_block_kron_solver method + +@pytest.mark.parametrize( 'ncells', [[8, 8, 8], [8, 16, 8]] ) +@pytest.mark.parametrize( 'degree', [[2, 2, 2]] ) +@pytest.mark.parametrize( 'periodic', [[True, True, True]] ) +@pytest.mark.parallel +def test_3d_m1_solver(ncells, degree, periodic): + + comm = MPI.COMM_WORLD + domain = Cube('C', bounds1=(0, 1), bounds2=(0, 1), bounds3=(0, 1)) + derham = Derham(domain) + domain_h = discretize(domain, ncells=ncells, periodic=periodic, comm=comm) + derham_h = discretize(derham, domain_h, degree=degree) + V1 = derham_h.V1.vector_space + P0, P1, P2, P3 = derham_h.projectors() + + # obtain an iterative M1 solver the usual way + u1, v1 = elements_of(derham.V1, names='u1, v1') + a1 = BilinearForm((u1, v1), integral(domain, dot(u1, v1))) + a1_h = discretize(a1, domain_h, (derham_h.V1, derham_h.V1)) + M1 = a1_h.assemble() + tol = 1e-12 + maxiter = 1000 + M1_iterative_solver = inverse(M1, 'cg', tol = tol, maxiter=maxiter) + + # obtain a direct M1 solver utilizing the Block-Kronecker structure of M1 + M1_direct_solver = get_M1_block_kron_solver(V1, ncells, degree, periodic) + + # obtain x and rhs = M1 @ x, both elements of derham_h.V1 + def get_A_fun(n=1, m=1, A0=1e04): + """Get the tuple A = (A1, A2, A3), where each entry is a function taking x,y,z as input.""" + + mu_tilde = np.sqrt(m**2 + n**2) + + eta = lambda x, y, z: x**2 * (1-x)**2 * y**2 * (1-y)**2 * z**2 * (1-z)**2 + + u1 = lambda x, y, z: A0 * (n/mu_tilde) * np.sin(np.pi * m * x) * np.cos(np.pi * n * y) + u2 = lambda x, y, z: -A0 * (m/mu_tilde) * np.cos(np.pi * m * x) * np.sin(np.pi * n * y) + u3 = lambda x, y, z: A0 * np.sin(np.pi * m * x) * np.sin(np.pi * n * y) + + A1 = lambda x, y, z: eta(x, y, z) * u1(x, y, z) + A2 = lambda x, y, z: eta(x, y, z) * u2(x, y, z) + A3 = lambda x, y, z: eta(x, y, z) * u3(x, y, z) + + A = (A1, A2, A3) + return A + x = P1(get_A_fun()).coeffs + rhs = M1 @ x + + # solve M1 @ x = rhs for x two ways + # pass -s to see timings + # on my local machine, executing + # mpirun -n 4 python -m pytest test_kron_direct_solver.py::test_3d_m1_solver -s + # I can report the following data: + + ### 4 processes, test case 1 (ncells=[8, 8, 8]): + + # Solving for x using the iterative solver: 23.73982548713684 seconds + # Solving for x using the iterative solver: 23.820897102355957 seconds + # Solving for x using the iterative solver: 23.783425092697144 seconds + # Solving for x using the iterative solver: 23.71373987197876 seconds + # Solving for x using the direct solver: 0.3333120346069336 seconds + # Solving for x using the direct solver: 0.3369138240814209 seconds + # Solving for x using the direct solver: 0.33652329444885254 seconds + # Solving for x using the direct solver: 0.34088802337646484 seconds + + ###4 processes, test case 2 (ncells=[8, 16, 8]): + # Solving for x using the iterative solver: 82.10541296005249 seconds + # Solving for x using the iterative solver: 81.88263297080994 seconds + # Solving for x using the iterative solver: 82.07102465629578 seconds + # Solving for x using the iterative solver: 82.00282955169678 seconds + # Solving for x using the direct solver: 0.1675126552581787 seconds + # Solving for x using the direct solver: 0.17473626136779785 seconds + # Solving for x using the direct solver: 0.15992450714111328 seconds + # Solving for x using the direct solver: 0.17931437492370605 seconds + + # Note that on consecutive solves, with only a slightly changing rhs and recycle=True, the iterative solver won't perform as bad anymore. + + start = time.time() + x_iterative = M1_iterative_solver @ rhs + stop = time.time() + print(f"Solving for x using the iterative solver: {stop-start} seconds") + + start = time.time() + x_direct = M1_direct_solver @ rhs + stop = time.time() + print(f"Solving for x using the direct solver: {stop-start} seconds") + + # assert rhs_iterative is within the tolerance close to rhs, and so is rhs_direct + rhs_iterative = M1 @ x_iterative + rhs_direct = M1 @ x_direct + assert np.linalg.norm((rhs-rhs_iterative).toarray()) < tol + assert np.linalg.norm((rhs-rhs_direct).toarray()) < tol #=============================================================================== if __name__ == '__main__': From 38dc6d75c502e46ec14456aff711a4ed1f78ef3c Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 10:24:04 +0200 Subject: [PATCH 54/77] enabling custom preconditioning --- psydac/linalg/solvers.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index d9e0d5b7e..f6a56f93f 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -301,7 +301,7 @@ class PConjugateGradient(InverseLinearOperator): Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems """ - def __init__(self, A, *, pc='jacobi', x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False): + def __init__(self, A, *, pc='jacobi', x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False, precond=None): assert isinstance(A, LinearOperator) assert A.domain.dimension == A.codomain.dimension @@ -318,7 +318,7 @@ def __init__(self, A, *, pc='jacobi', x0=None, tol=1e-6, maxiter=1000, verbose=F self._domain = domain self._codomain = codomain self._solver = 'pcg' - self._options = {"x0":x0, "pc":pc, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle} + self._options = {"x0":x0, "pc":pc, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle, "precond":precond} self._check_options(**self._options) tmps_codomain = {key: codomain.zeros() for key in ("p", "s")} tmps_domain = {key: domain.zeros() for key in ("v", "r")} @@ -330,7 +330,7 @@ def _check_options(self, **kwargs): if key == 'pc': assert value is not None, "pc may not be None" - assert value == 'jacobi', "unsupported preconditioner" + assert value == 'jacobi' or value == 'given_precond', "unsupported preconditioner" elif key == 'x0': if value is not None: assert isinstance(value, Vector), "x0 must be a Vector or None" @@ -345,6 +345,8 @@ def _check_options(self, **kwargs): assert isinstance(value, bool), "verbose must be a bool" elif key == 'recycle': assert isinstance(value, bool), "recycle must be a bool" + elif key == 'precond': + pass else: raise ValueError(f"Key '{key}' not understood. See self._options for allowed keys.") @@ -402,6 +404,8 @@ def solve(self, b, out=None): assert pc is not None if pc == 'jacobi': psolve = lambda r, out: InverseLinearOperator.jacobi(A, r, out) + elif pc == 'given_precond': + psolve = options["precond"] #elif pc == 'weighted_jacobi': # psolve = lambda r, out: InverseLinearOperator.weighted_jacobi(A, r, out) # allows for further specification not callable like this! #elif isinstance(pc, str): From 47fc88cf34d33a9a2544a06978c1f68887dab43d Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 15:26:23 +0200 Subject: [PATCH 55/77] clean PR : clean api --- psydac/api/ast/utilities.py | 1 - psydac/api/discretization.py | 24 +++++++++--------------- psydac/api/settings.py | 8 -------- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/psydac/api/ast/utilities.py b/psydac/api/ast/utilities.py index a90dcbc9b..a9746bc34 100644 --- a/psydac/api/ast/utilities.py +++ b/psydac/api/ast/utilities.py @@ -1042,7 +1042,6 @@ def math_atoms_as_str(expr, lib='math'): return set.union(math_functions, math_constants) def get_name(lhs): - """ Given a list of variable return the meaningful part of the name of the first variable that has a _name attribute. diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index a0a31f502..1524be7d6 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -238,7 +238,7 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): #============================================================================== # TODO knots -def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, grid_type=None, nquads=None, basis='B', sequence='DR'): +def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, nquads=None, basis='B', sequence='DR'): """ This function creates the discretized space starting from the symbolic space. @@ -300,8 +300,6 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, # We build the dictionary g_spaces for each interior domain, where it conatians the interiors as keys and the spaces as values, # we then create the compatible spaces if needed with the suitable basis functions. - #store a boolean knowing if grid type was given or not for later use - is_grid_type = (grid_type is not None) comm = domain_h.comm ldim = V.ldim is_rational_mapping = False @@ -374,20 +372,16 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, max_coords = interior.max_coords assert len(ncells) == len(periodic) == len(degree_i) == len(multiplicity_i) == len(min_coords) == len(max_coords) - if knots is not None and is_grid_type : - raise(ValueError("grids and knots cannot be both provided")) - elif knots is None: - if not is_grid_type : - grid_type = [np.linspace(-1,1,num=ne+1) for ne in ncells] - else : - assert(len(grid_i)==ne+1 for grid_i, ne in zip(grid_type, ncells)) - grids = [xmin*(1-grid)/2+xmax*(1+grid)/2 - for xmin, xmax, grid in zip(min_coords, max_coords, grid_type)] - spaces[i] = [SplineSpace( p, multiplicity=m, grid=grid , periodic=P) - for p,m,grid,P in zip(degree_i, multiplicity_i,grids, periodic)] + if knots is None: + # Create uniform grid + grids = [np.linspace(xmin, xmax, num=ne + 1) + for xmin, xmax, ne in zip(min_coords, max_coords, ncells)] + + # Create 1D finite element spaces and precompute quadrature data + spaces[i] = [SplineSpace( p, multiplicity=m, grid=grid , periodic=P) for p,m,grid,P in zip(degree_i, multiplicity_i,grids, periodic)] else: # Create 1D finite element spaces and precompute quadrature data - spaces[i] = [SplineSpace( p, knots=T , periodic=P) for p,T, P in zip(degree_i, knots[interior.name], periodic)] + spaces[i] = [SplineSpace( p, knots=T , periodic=P) for p,T, P in zip(degree_i, knots[interior.name], periodic)] carts = create_cart(ddms, spaces) diff --git a/psydac/api/settings.py b/psydac/api/settings.py index 75d93fcb6..81dcbfb0b 100644 --- a/psydac/api/settings.py +++ b/psydac/api/settings.py @@ -19,13 +19,6 @@ 'tag':'gpyccel', 'openmp':False} -PSYDAC_BACKEND_GPYCCEL_MPI = {'name': 'pyccel', - 'compiler': 'gfortran' if pyccel_legacy else 'GNU', - 'flags': '-O3 -march=native -mtune=native -mavx -ffast-math -ffree-line-length-none', - 'folder': '__gpyccel__', - 'tag':'gpyccel', - 'openmp':True} - PSYDAC_BACKEND_IPYCCEL = {'name': 'pyccel', 'compiler': 'ifort' if pyccel_legacy else 'intel', 'flags': '-O3', @@ -49,7 +42,6 @@ PSYDAC_BACKENDS = { 'python' : PSYDAC_BACKEND_PYTHON, 'pyccel-gcc' : PSYDAC_BACKEND_GPYCCEL, - 'pyccel-gcc-mpi' : PSYDAC_BACKEND_GPYCCEL_MPI, 'pyccel-intel': PSYDAC_BACKEND_IPYCCEL, 'pyccel-pgi' : PSYDAC_BACKEND_PGPYCCEL, 'numba' : PSYDAC_BACKEND_NUMBA, From 57734894a0b11c4266930594c4948fa9c9874e6f Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 15:29:40 +0200 Subject: [PATCH 56/77] clean PR : clean fem --- psydac/fem/tensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/fem/tensor.py b/psydac/fem/tensor.py index 12222b0b2..e2e363e2d 100644 --- a/psydac/fem/tensor.py +++ b/psydac/fem/tensor.py @@ -109,7 +109,7 @@ def __init__(self, domain_decomposition, *spaces, vector_space=None, cart=None, ends = self._vector_space.cart.domain_decomposition.ends # Compute extended 1D quadrature grids (local to process) along each direction - self._quad_grids = tuple({q: FemAssemblyGrid(V, s, e, nderiv=max(V.degree,1), nquads=q)} + self._quad_grids = tuple({q: FemAssemblyGrid(V, s, e, nderiv=V.degree, nquads=q)} for V, s, e, q in zip( self.spaces, starts, ends, self._nquads)) # Determine portion of logical domain local to process From 35c42a5079f1fe1a6629a4cdc54b3d2a347bdb61 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 15:53:16 +0200 Subject: [PATCH 57/77] clean PR : clean linalg --- psydac/linalg/basic.py | 39 ---- psydac/linalg/block.py | 49 +--- psydac/linalg/fft.py | 2 +- psydac/linalg/kron.py | 77 +++---- psydac/linalg/solvers.py | 10 +- psydac/linalg/tests/test_block.py | 12 +- .../linalg/tests/test_kron_direct_solver.py | 210 ++---------------- 7 files changed, 58 insertions(+), 341 deletions(-) diff --git a/psydac/linalg/basic.py b/psydac/linalg/basic.py index c3b517726..6a545c601 100644 --- a/psydac/linalg/basic.py +++ b/psydac/linalg/basic.py @@ -492,18 +492,6 @@ def dot(self, v, out=None): return out else: return v.copy() - - def idot(self, v, out): - """ - Implements out += self @ v with a temporary. - Subclasses should provide an implementation without a temporary. - - """ - assert isinstance(v, Vector) - assert v.space == self.domain - assert isinstance(out, Vector) - assert out.space == self.codomain - out += v def __matmul__(self, B): assert isinstance(B, (LinearOperator, Vector)) @@ -536,7 +524,6 @@ def __init__(self, domain, codomain, c, A): self._scalar = scalar self._domain = domain self._codomain = codomain - self._tmp_idot = codomain.zeros() @property def domain(self): @@ -584,17 +571,6 @@ def dot(self, v, out=None): out = self._operator.dot(v) out *= self._scalar return out - - def idot(self, v, out): - assert isinstance(v, Vector) - assert v.space == self._domain - assert isinstance(out, Vector) - assert out.space == self._codomain - self._operator.dot(v, out = self._tmp_idot) - self._tmp_idot *=self._scalar - out += self._tmp_idot - return out - #=============================================================================== class SumLinearOperator(LinearOperator): @@ -679,7 +655,6 @@ def transpose(self, conjugate=False): def simplifiy(addends): class_list = [addends[i].__class__.__name__ for i in range(len(addends))] unique_list = list(set(class_list)) - unique_list.sort() if len(unique_list) == 1: return addends out = () @@ -753,8 +728,6 @@ def __init__(self, domain, codomain, *args): self._multiplicants = multiplicants self._tmp_vectors = tuple(tmp_vectors) - self._tmp_idot = codomain.zeros() - @property def tmp_vectors(self): return self._tmp_vectors @@ -808,7 +781,6 @@ def dot(self, v, out=None): A = self._multiplicants[-1-i] A.dot(x, out=y) x = y - x.update_ghost_regions() A = self._multiplicants[0] if out is not None: @@ -816,19 +788,8 @@ def dot(self, v, out=None): A.dot(x, out=out) else: out = A.dot(x) - out.update_ghost_regions() return out - def idot(self, v, out): - - assert isinstance(v, Vector) - assert v.space == self.domain - assert isinstance(out, Vector) - assert out.space == self.codomain - self.dot(v, out=self._tmp_idot) - self._tmp_idot.update_ghost_regions() - out += self._tmp_idot - def exchange_assembly_data( self ): for op in self._multiplicants: op.exchange_assembly_data() diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index 159cd47fc..dd9855e4f 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -7,11 +7,10 @@ from types import MappingProxyType from scipy.sparse import bmat, lil_matrix -from psydac.linalg.basic import VectorSpace, Vector, LinearOperator, LinearSolver -from psydac.linalg.stencil import StencilMatrix -from psydac.linalg.kron import KroneckerLinearSolver -from psydac.ddm.cart import InterfaceCartDecomposition -from psydac.ddm.utilities import get_data_exchanger +from psydac.linalg.basic import VectorSpace, Vector, LinearOperator, LinearSolver, ZeroOperator +from psydac.ddm.cart import InterfaceCartDecomposition +from psydac.ddm.utilities import get_data_exchanger +from psydac.linalg.stencil import StencilVector, StencilMatrix __all__ = ('BlockVectorSpace', 'BlockVector', 'BlockLinearOperator', 'BlockDiagonalSolver') @@ -657,29 +656,6 @@ def dot(self, v, out=None): out.ghost_regions_in_sync = False return out - - def idot(self, v, out): - - if self.n_block_cols == 1: - assert isinstance(v, Vector) - else: - assert isinstance(v, BlockVector) - - assert v.space is self.domain - - if self.n_block_rows == 1: - assert isinstance(out, Vector) - else: - assert isinstance(out, BlockVector) - assert out.space is self.codomain - - if not v.ghost_regions_in_sync: - v.update_ghost_regions() - - self._func(self._blocks_as_args, v, out, **self._args) - - out.ghost_regions_in_sync = False - return out #... @staticmethod @@ -1349,7 +1325,7 @@ def func(blocks, v, out, **args): #=============================================================================== class BlockDiagonalSolver( LinearSolver ): """ - A LinearSolver that can be written as blocks of other (Kronecker-)LinearSolvers, + A LinearSolver that can be written as blocks of other LinearSolvers, i.e. it can be seen as a solver for linear equations with block-diagonal matrices. The space of this solver has to be of the type BlockVectorSpace. @@ -1364,10 +1340,10 @@ class BlockDiagonalSolver( LinearSolver ): a) 'blocks' can be dictionary with . key = integer i >= 0 - . value = corresponding (Kronecker-)LinearSolver Lii + . value = corresponding LinearSolver Lii - b) 'blocks' can be list of (Kronecker-)LinearSolvers (or tuple of these) where blocks[i] - is the (Kronecker-)LinearSolver Lii (if None, we assume null operator) + b) 'blocks' can be list of LinearSolvers (or tuple of these) where blocks[i] + is the LinearSolver Lii (if None, we assume null operator) """ def __init__( self, V, blocks=None ): @@ -1474,14 +1450,9 @@ def __getitem__( self, key ): def __setitem__( self, key, value ): assert 0 <= key < self._nblocks - assert isinstance( value, (LinearSolver, KroneckerLinearSolver) ) + assert isinstance( value, LinearSolver) # Check domain of rhs - if isinstance(value, LinearSolver): - assert value.space is self.space[key] - else: - # restrictive, eventually to be removed assumption, that space = domain = codomain - assert value.domain is self.space[key] - assert value.codomain is self.space[key] + assert value.space is self.space[key] self._blocks[key] = value diff --git a/psydac/linalg/fft.py b/psydac/linalg/fft.py index 323c29347..6cd566353 100644 --- a/psydac/linalg/fft.py +++ b/psydac/linalg/fft.py @@ -67,7 +67,7 @@ def __init__(self, space, functions): else: onedimsolver = DistributedFFTBase.OneDimSolver(functions) solvers = [onedimsolver] * space.ndim - self._isolver = KroneckerLinearSolver(space, space, solvers) + self._isolver = KroneckerLinearSolver(space, solvers) # ... @property diff --git a/psydac/linalg/kron.py b/psydac/linalg/kron.py index 5832e41bc..9ad814284 100644 --- a/psydac/linalg/kron.py +++ b/psydac/linalg/kron.py @@ -371,7 +371,7 @@ def exchange_assembly_data( self ): def set_backend(self, backend): pass #============================================================================== -class KroneckerLinearSolver(LinearOperator): +class KroneckerLinearSolver(LinearSolver): """ A solver for Ax=b, where A is a Kronecker matrix from arbirary dimension d, defined by d solvers. We also need information about the space of b. @@ -380,45 +380,34 @@ class KroneckerLinearSolver(LinearOperator): ---------- V : StencilVectorSpace The space b will live in; i.e. which gives us information about - the distribution of the right-hand side b. - - W : StencilVectorSpace - The space x will live in; i.e. which gives us information about - the distribution of the unknown vector x. + the distribution of the right-hand side. solvers : list of LinearSolver The components of A in each dimension. Attributes ---------- - domain : StencilVectorSpace - The space of the rhs vector b. - - codomain : StencilVectorSpace - The space of the unknown vector x. + space : StencilVectorSpace + The space our vectors to solve live in. """ - def __init__(self, V, W, solvers): + def __init__(self, V, solvers): assert isinstance(V, StencilVectorSpace) - assert isinstance(W, StencilVectorSpace) assert hasattr( solvers, '__iter__' ) for solver in solvers: assert isinstance(solver, LinearSolver) assert V.ndim == len(solvers) - assert W.ndim == len(solvers) - assert V.npts == W.npts # general arguments - self._domain = V - self._codomain = W + self._space = V self._solvers = solvers - self._parallel = self._domain.parallel - self._dtype = self._codomain._dtype + self._parallel = self._space.parallel + self._dtype = self._space._dtype if self._parallel: - self._mpi_type = self._domain._mpi_type + self._mpi_type = V._mpi_type else: self._mpi_type = None - self._ndim = self._codomain.ndim + self._ndim = self._space.ndim # compute and setup solver arguments self._setup_solvers() @@ -435,12 +424,12 @@ def _setup_solvers(self): (which potentially utilize MPI). """ # slice sizes - starts = np.array(self._domain.starts) - ends = np.array(self._domain.ends) + 1 + starts = np.array(self._space.starts) + ends = np.array(self._space.ends) + 1 self._slice = tuple([slice(s, e) for s,e in zip(starts, ends)]) # local and global sizes - nglobals = self._domain.npts + nglobals = self._space.npts nlocals = ends - starts self._localsize = np.product(nlocals) mglobals = self._localsize // nlocals @@ -457,15 +446,15 @@ def _setup_solvers(self): # useful e.g. if we have little data in some directions # (and thus no data distributed there) - if not self._parallel or self._domain.cart.subcomm[i].size <= 1: + if not self._parallel or self._space.cart.subcomm[i].size <= 1: # serial solve solver_passes[i] = KroneckerLinearSolver.KroneckerSolverSerialPass( self._solvers[i], nglobals[i], mglobals[i]) else: # for the parallel case, use Alltoallv solver_passes[i] = KroneckerLinearSolver.KroneckerSolverParallelPass( - self._solvers[i], self._domain._mpi_type, i, - self._domain.cart, mglobals[i], nglobals[i], nlocals[i], self._localsize) + self._solvers[i], self._space._mpi_type, i, + self._space.cart, mglobals[i], nglobals[i], nlocals[i], self._localsize) # we have a parallel solve pass now, so we are not completely local any more self._allserial = False @@ -512,28 +501,12 @@ def _allocate_temps(self): return temp1, temp2 @property - def domain(self): - return self._domain - - @property - def codomain(self): - return self._codomain - - @property - def dtype(self): - return None - - def toarray(self): - raise NotImplementedError('toarray() is not defined for KroneckerLinearSolvers.') - - def tosparse(self): - raise NotImplementedError('tosparse() is not defined for KroneckerLinearSolvers.') - - def transpose(self, conjugate=False): - raise NotImplementedError('transpose() is not defined for KroneckerLinearSolvers.') - - def dot(self, v, out=None): - return self.solve(v, out=out) + def space(self): + """ + Returns the space associated to this solver (i.e. where the information + about the cartesian distribution is taken from). + """ + return self._space @property def solvers(self): @@ -549,11 +522,11 @@ def solve(self, rhs, out=None, transposed=False): """ # type checks - assert rhs.space is self._domain + assert rhs.space is self._space if out is not None: assert isinstance( out, StencilVector ) - assert out.space is self._codomain + assert out.space is self._space else: out = StencilVector( rhs.space ) @@ -910,5 +883,5 @@ def kronecker_solve(solvers, rhs, out=None, transposed=False): else: out = StencilVector(rhs.space) - kronsolver = KroneckerLinearSolver(rhs.space, rhs.space, solvers) + kronsolver = KroneckerLinearSolver(rhs.space, solvers) return kronsolver.solve(rhs, out=out, transposed=transposed) diff --git a/psydac/linalg/solvers.py b/psydac/linalg/solvers.py index f6a56f93f..d9e0d5b7e 100644 --- a/psydac/linalg/solvers.py +++ b/psydac/linalg/solvers.py @@ -301,7 +301,7 @@ class PConjugateGradient(InverseLinearOperator): Stores a copy of the output in x0 to speed up consecutive calculations of slightly altered linear systems """ - def __init__(self, A, *, pc='jacobi', x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False, precond=None): + def __init__(self, A, *, pc='jacobi', x0=None, tol=1e-6, maxiter=1000, verbose=False, recycle=False): assert isinstance(A, LinearOperator) assert A.domain.dimension == A.codomain.dimension @@ -318,7 +318,7 @@ def __init__(self, A, *, pc='jacobi', x0=None, tol=1e-6, maxiter=1000, verbose=F self._domain = domain self._codomain = codomain self._solver = 'pcg' - self._options = {"x0":x0, "pc":pc, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle, "precond":precond} + self._options = {"x0":x0, "pc":pc, "tol":tol, "maxiter":maxiter, "verbose":verbose, "recycle":recycle} self._check_options(**self._options) tmps_codomain = {key: codomain.zeros() for key in ("p", "s")} tmps_domain = {key: domain.zeros() for key in ("v", "r")} @@ -330,7 +330,7 @@ def _check_options(self, **kwargs): if key == 'pc': assert value is not None, "pc may not be None" - assert value == 'jacobi' or value == 'given_precond', "unsupported preconditioner" + assert value == 'jacobi', "unsupported preconditioner" elif key == 'x0': if value is not None: assert isinstance(value, Vector), "x0 must be a Vector or None" @@ -345,8 +345,6 @@ def _check_options(self, **kwargs): assert isinstance(value, bool), "verbose must be a bool" elif key == 'recycle': assert isinstance(value, bool), "recycle must be a bool" - elif key == 'precond': - pass else: raise ValueError(f"Key '{key}' not understood. See self._options for allowed keys.") @@ -404,8 +402,6 @@ def solve(self, b, out=None): assert pc is not None if pc == 'jacobi': psolve = lambda r, out: InverseLinearOperator.jacobi(A, r, out) - elif pc == 'given_precond': - psolve = options["precond"] #elif pc == 'weighted_jacobi': # psolve = lambda r, out: InverseLinearOperator.weighted_jacobi(A, r, out) # allows for further specification not callable like this! #elif isinstance(pc, str): diff --git a/psydac/linalg/tests/test_block.py b/psydac/linalg/tests/test_block.py index 91e660d6d..c67f49262 100644 --- a/psydac/linalg/tests/test_block.py +++ b/psydac/linalg/tests/test_block.py @@ -248,8 +248,8 @@ def test_2D_block_diagonal_solver_serial_init( dtype, n1, n2, p1, p2, P1, P2 ): M12 = SparseSolver( spa.csc_matrix(m12) ) M21 = SparseSolver( spa.csc_matrix(m21) ) M22 = SparseSolver( spa.csc_matrix(m22) ) - M1 = KroneckerLinearSolver(V, V, [M11,M12]) - M2 = KroneckerLinearSolver(V, V, [M21,M22]) + M1 = KroneckerLinearSolver(V, [M11,M12]) + M2 = KroneckerLinearSolver(V, [M21,M22]) x1 = StencilVector( V ) x2 = StencilVector( V ) @@ -836,8 +836,8 @@ def test_block_diagonal_solver_serial_dot( dtype, n1, n2, p1, p2, P1, P2 ): M12 = SparseSolver( spa.csc_matrix(m12) ) M21 = SparseSolver( spa.csc_matrix(m21) ) M22 = SparseSolver( spa.csc_matrix(m22) ) - M1 = KroneckerLinearSolver(V, V, [M11,M12]) - M2 = KroneckerLinearSolver(V, V, [M21,M22]) + M1 = KroneckerLinearSolver(V, [M11,M12]) + M2 = KroneckerLinearSolver(V, [M21,M22]) x1 = StencilVector( V ) x2 = StencilVector( V ) @@ -1251,8 +1251,8 @@ def test_block_diagonal_solver_parallel_dot( dtype, n1, n2, p1, p2, P1, P2 ): M12 = SparseSolver( spa.csc_matrix(m12) ) M21 = SparseSolver( spa.csc_matrix(m21) ) M22 = SparseSolver( spa.csc_matrix(m22) ) - M1 = KroneckerLinearSolver(V, V, [M11,M12]) - M2 = KroneckerLinearSolver(V, V, [M21,M22]) + M1 = KroneckerLinearSolver(V, [M11,M12]) + M2 = KroneckerLinearSolver(V, [M21,M22]) x1 = StencilVector( V ) x2 = StencilVector( V ) diff --git a/psydac/linalg/tests/test_kron_direct_solver.py b/psydac/linalg/tests/test_kron_direct_solver.py index 6762ac76d..836b0b1b3 100644 --- a/psydac/linalg/tests/test_kron_direct_solver.py +++ b/psydac/linalg/tests/test_kron_direct_solver.py @@ -3,26 +3,13 @@ import pytest import time import numpy as np -from mpi4py import MPI - - +from mpi4py import MPI +from psydac.ddm.cart import DomainDecomposition, CartDecomposition from scipy.sparse import csc_matrix, dia_matrix, kron from scipy.sparse.linalg import splu - -from sympde.calculus import dot -from sympde.expr import BilinearForm, integral -from sympde.topology import Line -from sympde.topology import Cube -from sympde.topology import Derham -from sympde.topology import elements_of - -from psydac.api.discretization import discretize -from psydac.ddm.cart import DomainDecomposition, CartDecomposition -from psydac.linalg.block import BlockLinearOperator -from psydac.linalg.direct_solvers import SparseSolver, BandedSolver -from psydac.linalg.kron import KroneckerLinearSolver -from psydac.linalg.solvers import inverse from psydac.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix +from psydac.linalg.kron import KroneckerLinearSolver +from psydac.linalg.direct_solvers import SparseSolver, BandedSolver #=============================================================================== def compute_global_starts_ends(domain_decomposition, npts): @@ -83,10 +70,10 @@ def matrix_to_sparse(A): A.remove_spurious_entries() return SparseSolver(A.tosparse()) -def random_matrix(seed, domain, codomain): - A = StencilMatrix(domain, codomain) - p = domain.pads[0] # by definition of the StencilMatrix, domain.pads == codomain.pads - dtype = domain.dtype # by definition of the StencilMatrix, domain.dtype == codomain.dtype +def random_matrix(seed, space): + A = StencilMatrix(space, space) + p = space.pads[0] + dtype = space.dtype # for now, take matrices like this (as in the other tests) if dtype==complex: @@ -120,31 +107,24 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t # vector spaces comm = MPI.COMM_WORLD D = DomainDecomposition(npts, periods=periods, comm=comm) - D2 = DomainDecomposition(npts, periods=periods, comm=comm) # Partition the points global_starts, global_ends = compute_global_starts_ends(D, npts) - global_starts2, global_ends2 = compute_global_starts_ends(D2, npts) cart = CartDecomposition(D, npts, global_starts, global_ends, pads=pads, shifts=[1]*len(pads)) - cart2 = CartDecomposition(D2, npts, global_starts2, global_ends2, pads=pads, shifts=[1]*len(pads)) V = StencilVectorSpace(cart, dtype=dtype) - W = StencilVectorSpace(cart2, dtype=dtype) Ds = [DomainDecomposition([n], periods=[P]) for n,P in zip(npts, periods)] carts = [CartDecomposition(Di, [n], *compute_global_starts_ends(Di, [n]), pads=[p], shifts=[1]) for Di,n,p in zip(Ds, npts, pads)] Vs = [StencilVectorSpace(carti, dtype=dtype) for carti in carts] - Ds2 = [DomainDecomposition([n], periods=[P]) for n,P in zip(npts, periods)] - carts2 = [CartDecomposition(Di, [n], *compute_global_starts_ends(Di, [n]), pads=[p], shifts=[1]) for Di,n,p in zip(Ds2, npts, pads)] - Ws = [StencilVectorSpace(carti, dtype=dtype) for carti in carts2] localslice = tuple([slice(s, e+1) for s, e in zip(V.starts, V.ends)]) if verbose: print(f'[{rank}] Vector spaces built', flush=True) # bulid matrices (A) - A = [random_matrix(seed+i+1, Vi, Wi) for i,(Vi, Wi) in enumerate(zip(Vs, Ws))] + A = [random_matrix(seed+i+1, Vi) for i,Vi in enumerate(Vs)] solvers = [direct_solver(Ai) for Ai in A] if verbose: @@ -161,9 +141,9 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t # solve in two different ways X_glob = kron_solve_seq_ref(Y_glob, A, transposed) - Xout = StencilVector(W) + Xout = StencilVector(V) - X = KroneckerLinearSolver(V, W, solvers).solve(Y, out=Xout, transposed=transposed) + X = KroneckerLinearSolver(V, solvers).solve(Y, out=Xout, transposed=transposed) assert X is Xout if verbose: @@ -182,74 +162,6 @@ def compare_solve(seed, comm, npts, pads, periods, direct_solver, dtype=float, t # compare for equality assert np.allclose( X[localslice], X_glob[localslice], rtol=1e-8, atol=1e-8 ) -def get_M1_block_kron_solver(V1, ncells, degree, periodic): - """ - Given a 3D DeRham sequenece (V0 = H(grad) --grad--> V1 = H(curl) --curl--> V2 = H(div) --div--> V3 = L2) - discreticed using ncells, degree and periodic, - - domain = Cube('C', bounds1=(0, 1), bounds2=(0, 1), bounds3=(0, 1)) - derham = Derham(domain) - domain_h = discretize(domain, ncells=ncells, periodic=periodic, comm=comm) - derham_h = discretize(derham, domain_h, degree=degree), - - returns the inverse of the mass matrix M1 as a BlockLinearOperator consisting of three KroneckerLinearSolvers on the diagonal. - """ - # assert 3D - assert len(ncells) == 3 - assert len(degree) == 3 - assert len(periodic) == 3 - - # 1D domain to be discreticed using the respective values of ncells, degree, periodic - domain_1d = Line('L', bounds=(0,1)) - derham_1d = Derham(domain_1d) - - # storage for the 1D mass matrices - M0_matrices = [] - M1_matrices = [] - - # assembly of the 1D mass matrices - for (n, p, P) in zip(ncells, degree, periodic): - - domain_1d_h = discretize(domain_1d, ncells=[n], periodic=[P]) - derham_1d_h = discretize(derham_1d, domain_1d_h, degree=[p]) - - u_1d_0, v_1d_0 = elements_of(derham_1d.V0, names='u_1d_0, v_1d_0') - u_1d_1, v_1d_1 = elements_of(derham_1d.V1, names='u_1d_1, v_1d_1') - - a_1d_0 = BilinearForm((u_1d_0, v_1d_0), integral(domain_1d, u_1d_0 * v_1d_0)) - a_1d_1 = BilinearForm((u_1d_1, v_1d_1), integral(domain_1d, u_1d_1 * v_1d_1)) - - a_1d_0_h = discretize(a_1d_0, domain_1d_h, (derham_1d_h.V0, derham_1d_h.V0)) - a_1d_1_h = discretize(a_1d_1, domain_1d_h, (derham_1d_h.V1, derham_1d_h.V1)) - - M_1d_0 = a_1d_0_h.assemble() - M_1d_1 = a_1d_1_h.assemble() - - M0_matrices.append(M_1d_0) - M1_matrices.append(M_1d_1) - - V1_1 = V1[0] - V1_2 = V1[1] - V1_3 = V1[2] - - B1_mat = [M1_matrices[0], M0_matrices[1], M0_matrices[2]] - B2_mat = [M0_matrices[0], M1_matrices[1], M0_matrices[2]] - B3_mat = [M0_matrices[0], M0_matrices[1], M1_matrices[2]] - - B1_solvers = [matrix_to_bandsolver(Ai) for Ai in B1_mat] - B2_solvers = [matrix_to_bandsolver(Ai) for Ai in B2_mat] - B3_solvers = [matrix_to_bandsolver(Ai) for Ai in B3_mat] - - B1_kron_inv = KroneckerLinearSolver(V1_1, V1_1, B1_solvers) - B2_kron_inv = KroneckerLinearSolver(V1_2, V1_2, B2_solvers) - B3_kron_inv = KroneckerLinearSolver(V1_3, V1_3, B3_solvers) - - M1_block_kron_solver = BlockLinearOperator(V1, V1, ((B1_kron_inv, None, None), - (None, B2_kron_inv, None), - (None, None, B3_kron_inv))) - - return M1_block_kron_solver - #=============================================================================== # tests of the direct solvers @pytest.mark.parametrize( 'dtype', [float, complex] ) @@ -265,11 +177,11 @@ def test_direct_solvers(dtype, seed, n, p, P, nrhs, direct_solver, transposed): D = DomainDecomposition([n], periods=[P]) cart = CartDecomposition(D, [n], *compute_global_starts_ends(D, [n]), pads=[p], shifts=[1]) - # domain V and codomain W + # space (V) V = StencilVectorSpace( cart, dtype=dtype ) # bulid matrices (A) - A = random_matrix(seed+1, V, V) + A = random_matrix(seed+1, V) solver = direct_solver(A) # vector to solve for (Y) @@ -451,102 +363,6 @@ def test_kron_solver_nd_par(seed, dim, dtype): npts_base = 4 compare_solve(seed, MPI.COMM_WORLD, [npts_base]*dim, [1]*dim, [False]*dim, matrix_to_sparse, dtype=dtype, transposed=False, verbose=False) - -#=============================================================================== - -# test Kronecker solver of the M1 mass matrix of our 3D DeRham sequence, as described in the get_M1_block_kron_solver method - -@pytest.mark.parametrize( 'ncells', [[8, 8, 8], [8, 16, 8]] ) -@pytest.mark.parametrize( 'degree', [[2, 2, 2]] ) -@pytest.mark.parametrize( 'periodic', [[True, True, True]] ) -@pytest.mark.parallel -def test_3d_m1_solver(ncells, degree, periodic): - - comm = MPI.COMM_WORLD - domain = Cube('C', bounds1=(0, 1), bounds2=(0, 1), bounds3=(0, 1)) - derham = Derham(domain) - domain_h = discretize(domain, ncells=ncells, periodic=periodic, comm=comm) - derham_h = discretize(derham, domain_h, degree=degree) - V1 = derham_h.V1.vector_space - P0, P1, P2, P3 = derham_h.projectors() - - # obtain an iterative M1 solver the usual way - u1, v1 = elements_of(derham.V1, names='u1, v1') - a1 = BilinearForm((u1, v1), integral(domain, dot(u1, v1))) - a1_h = discretize(a1, domain_h, (derham_h.V1, derham_h.V1)) - M1 = a1_h.assemble() - tol = 1e-12 - maxiter = 1000 - M1_iterative_solver = inverse(M1, 'cg', tol = tol, maxiter=maxiter) - - # obtain a direct M1 solver utilizing the Block-Kronecker structure of M1 - M1_direct_solver = get_M1_block_kron_solver(V1, ncells, degree, periodic) - - # obtain x and rhs = M1 @ x, both elements of derham_h.V1 - def get_A_fun(n=1, m=1, A0=1e04): - """Get the tuple A = (A1, A2, A3), where each entry is a function taking x,y,z as input.""" - - mu_tilde = np.sqrt(m**2 + n**2) - - eta = lambda x, y, z: x**2 * (1-x)**2 * y**2 * (1-y)**2 * z**2 * (1-z)**2 - - u1 = lambda x, y, z: A0 * (n/mu_tilde) * np.sin(np.pi * m * x) * np.cos(np.pi * n * y) - u2 = lambda x, y, z: -A0 * (m/mu_tilde) * np.cos(np.pi * m * x) * np.sin(np.pi * n * y) - u3 = lambda x, y, z: A0 * np.sin(np.pi * m * x) * np.sin(np.pi * n * y) - - A1 = lambda x, y, z: eta(x, y, z) * u1(x, y, z) - A2 = lambda x, y, z: eta(x, y, z) * u2(x, y, z) - A3 = lambda x, y, z: eta(x, y, z) * u3(x, y, z) - - A = (A1, A2, A3) - return A - x = P1(get_A_fun()).coeffs - rhs = M1 @ x - - # solve M1 @ x = rhs for x two ways - # pass -s to see timings - # on my local machine, executing - # mpirun -n 4 python -m pytest test_kron_direct_solver.py::test_3d_m1_solver -s - # I can report the following data: - - ### 4 processes, test case 1 (ncells=[8, 8, 8]): - - # Solving for x using the iterative solver: 23.73982548713684 seconds - # Solving for x using the iterative solver: 23.820897102355957 seconds - # Solving for x using the iterative solver: 23.783425092697144 seconds - # Solving for x using the iterative solver: 23.71373987197876 seconds - # Solving for x using the direct solver: 0.3333120346069336 seconds - # Solving for x using the direct solver: 0.3369138240814209 seconds - # Solving for x using the direct solver: 0.33652329444885254 seconds - # Solving for x using the direct solver: 0.34088802337646484 seconds - - ###4 processes, test case 2 (ncells=[8, 16, 8]): - # Solving for x using the iterative solver: 82.10541296005249 seconds - # Solving for x using the iterative solver: 81.88263297080994 seconds - # Solving for x using the iterative solver: 82.07102465629578 seconds - # Solving for x using the iterative solver: 82.00282955169678 seconds - # Solving for x using the direct solver: 0.1675126552581787 seconds - # Solving for x using the direct solver: 0.17473626136779785 seconds - # Solving for x using the direct solver: 0.15992450714111328 seconds - # Solving for x using the direct solver: 0.17931437492370605 seconds - - # Note that on consecutive solves, with only a slightly changing rhs and recycle=True, the iterative solver won't perform as bad anymore. - - start = time.time() - x_iterative = M1_iterative_solver @ rhs - stop = time.time() - print(f"Solving for x using the iterative solver: {stop-start} seconds") - - start = time.time() - x_direct = M1_direct_solver @ rhs - stop = time.time() - print(f"Solving for x using the direct solver: {stop-start} seconds") - - # assert rhs_iterative is within the tolerance close to rhs, and so is rhs_direct - rhs_iterative = M1 @ x_iterative - rhs_direct = M1 @ x_direct - assert np.linalg.norm((rhs-rhs_iterative).toarray()) < tol - assert np.linalg.norm((rhs-rhs_direct).toarray()) < tol #=============================================================================== if __name__ == '__main__': From 4223d5345c205fed20d4b54eb01e9b82151d0aa5 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 15:55:23 +0200 Subject: [PATCH 58/77] some forgotten changes --- psydac/linalg/block.py | 4 ++-- psydac/linalg/kron.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/psydac/linalg/block.py b/psydac/linalg/block.py index dd9855e4f..fb2737d56 100644 --- a/psydac/linalg/block.py +++ b/psydac/linalg/block.py @@ -1336,7 +1336,7 @@ class BlockDiagonalSolver( LinearSolver ): Space of the new blocked linear solver. blocks : dict | list | tuple - (Kronecker-)LinearSolver objects (optional). + LinearSolver objects (optional). a) 'blocks' can be dictionary with . key = integer i >= 0 @@ -1450,7 +1450,7 @@ def __getitem__( self, key ): def __setitem__( self, key, value ): assert 0 <= key < self._nblocks - assert isinstance( value, LinearSolver) + assert isinstance( value, LinearSolver ) # Check domain of rhs assert value.space is self.space[key] diff --git a/psydac/linalg/kron.py b/psydac/linalg/kron.py index 9ad814284..0186f119f 100644 --- a/psydac/linalg/kron.py +++ b/psydac/linalg/kron.py @@ -380,7 +380,7 @@ class KroneckerLinearSolver(LinearSolver): ---------- V : StencilVectorSpace The space b will live in; i.e. which gives us information about - the distribution of the right-hand side. + the distribution of the right-hand sides. solvers : list of LinearSolver The components of A in each dimension. @@ -499,7 +499,7 @@ def _allocate_temps(self): else: temp2 = np.empty((self._tempsize,), dtype=self._dtype) return temp1, temp2 - + @property def space(self): """ From 43d3626b5557514ea5ee5e3409d49d294abb9185 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 16:23:30 +0200 Subject: [PATCH 59/77] solve the problem with operator from TensorSpace to TensorSpace --- psydac/feec/basis_projectors.py | 53 ++++++++++++++++++-------------- psydac/feec/global_projectors.py | 2 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index a69dbbb9e..5bf37668d 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -13,8 +13,6 @@ from psydac.fem.basic import FemField from psydac.utilities.utils import roll_edges -from sympy.core.numbers import Zero - from copy import deepcopy @@ -78,26 +76,32 @@ def __init__(self, P, V, fun, transposed=False, preproc_grid=None, dof_mat=None) self._preproc_grid = preproc_grid - if isinstance(V, TensorFemSpace): - Vspaces = [V.vector_space] - else: - Vspaces = V.vector_space - - # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(P.space, TensorFemSpace): - Wspaces = [P.space.vector_space] - else: - Wspaces = P.space.vector_space - blocks = [] - for Wspace in Wspaces: - blocks += [[]] - # input vector space (domain), column of block - for Vspace in Vspaces: - dofs_mat = StencilMatrix( - Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) - blocks[-1] += [dofs_mat] - - self._dof_operator_pre = BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) + if isinstance(V, TensorFemSpace) and isinstance(P.space, TensorFemSpace): + dofs_mat = StencilMatrix( + V.vector_space, P.space.vector_space, backend=PSYDAC_BACKEND_GPYCCEL) + self._dof_operator_pre = dofs_mat + + else : + if isinstance(V, TensorFemSpace): + Vspaces = [V.vector_space] + else: + Vspaces = V.vector_space + + # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component + if isinstance(P.space, TensorFemSpace): + Wspaces = [P.space.vector_space] + else: + Wspaces = P.space.vector_space + blocks = [] + for Wspace in Wspaces: + blocks += [[]] + # input vector space (domain), column of block + for Vspace in Vspaces: + dofs_mat = StencilMatrix( + Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) + blocks[-1] += [dofs_mat] + + self._dof_operator_pre = BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) # ============= assemble tensor-product dof matrix ======= @@ -281,7 +285,10 @@ def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): dofs_mat = dof_operator.blocks[j] else : dofs_mat = dof_operator.blocks[i][j]""" - dofs_mat = dof_operator._blocks[i, j] + if isinstance(dof_operator, BlockLinearOperator): + dofs_mat = dof_operator._blocks[i, j] + else : + dofs_mat = dof_operator _starts_in = np.array(dofs_mat.domain.starts) _ends_in = np.array(dofs_mat.domain.ends) diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index c02eb24f8..723b289bf 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -166,7 +166,7 @@ def __init__(self, space, nquads = None): self._grid_x += [block_x] self._grid_w += [block_w] - solverblocks += [KroneckerLinearSolver(tensorspaces[i].vector_space, tensorspaces[i].vector_space, solvercells)] + solverblocks += [KroneckerLinearSolver(tensorspaces[i].vector_space, solvercells)] dataslice = tuple(slice(p, -p) for p in tensorspaces[i].vector_space.pads) dofs[i] = rhsblocks[i]._data[dataslice] From a85d9f8739658a31828decb305802b97d08570d0 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 10 Oct 2023 16:41:29 +0200 Subject: [PATCH 60/77] cleaning useless variable and adding some more comments --- psydac/feec/basis_projectors.py | 55 +++++++++++++++------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py index 5bf37668d..ab794c49d 100644 --- a/psydac/feec/basis_projectors.py +++ b/psydac/feec/basis_projectors.py @@ -279,12 +279,7 @@ def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): # input vector space (domain), column of block for Vspace, V1d, f in zip(_Vspaces, _V1ds, fun_line): # instantiate cell of block matrix - """if isinstance(V, TensorFemSpace): - dofs_mat = dof_operator.blocks[i] - elif isinstance(P.space, TensorFemSpace): - dofs_mat = dof_operator.blocks[j] - else : - dofs_mat = dof_operator.blocks[i][j]""" + if isinstance(dof_operator, BlockLinearOperator): dofs_mat = dof_operator._blocks[i, j] else : @@ -299,7 +294,7 @@ def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): _pads_out = np.array(dofs_mat.codomain.pads) if isinstance(f,FemField): - + #In case of Femfield we call a special kernel using the coeffs of the FF rather than evaluating the field and then calling the kernel space_ff = f.space.vector_space Vfd = f.space.spaces _starts_c = np.array(space_ff.starts) @@ -307,10 +302,10 @@ def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): _pads_c = np.array(space_ff.pads) if preproc_grid != None : - _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = preproc_grid[i][j] + _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff = preproc_grid[i][j] else : - _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ + _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff = \ prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) _ptsG = [pts.flatten() for pts in _ptsG] @@ -328,10 +323,10 @@ def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): else : if preproc_grid != None : - _ptsG, _wtsG, _spans, _bases, _npt_pts = preproc_grid[i][j] + _ptsG, _wtsG, _spans, _bases = preproc_grid[i][j] else: - _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( V1d, W1d, _starts_out, _ends_out, nq) _ptsG = [pts.flatten() for pts in _ptsG] @@ -397,7 +392,7 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): import psydac.core.bsplines as bsp - x_grid, pts, wts, spans, bases, np_pts_cell = [], [], [], [], [], [] + x_grid, pts, wts, spans, bases = [], [], [], [], [] # Loop over direction, prepare point sets and evaluate basis functions direction = 0 @@ -414,7 +409,6 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): x_grid = greville_loc pts += [greville_loc[:, None]] wts += [np.ones(pts[-1].shape, dtype=float)] - np_pts_cell += [1] # histopolation elif space_out.basis == 'M': @@ -437,7 +431,6 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): w = global_quad_w pts += [x] wts += [w] - np_pts_cell += [nq] # Knot span indices and V-basis functions evaluated at W-point sets s, b = get_span_and_basis(pts[-1], space_in) @@ -446,18 +439,22 @@ def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): bases += [b] direction += 1 - return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(np_pts_cell) + return tuple(pts), tuple(wts), tuple(spans), tuple(bases) def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_quad=None): - '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. + '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space, + for V1d and space_ff, in order to have the two at the same points (to avoid two calls). Parameters ---------- V1d : 3-list - Three SplineSpace objects from Psydac from the input space (to be projected). + Three SplineSpace objects from the input space (to be projected). W1d : 3-list - Three SplineSpace objects from Psydac from the output space (projected onto). + Three SplineSpace objects from the output space (projected onto). + + space_ff : 3-list + Three SplineSpace objects from the coefficient space. starts_out : 3-list Global starting indices of process. @@ -485,7 +482,7 @@ def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_q import psydac.core.bsplines as bsp - x_grid, pts, wts, spans, bases, spans_c, bases_c, np_pts_cell = [], [], [], [], [], [], [], [] + x_grid, pts, wts, spans, bases, spans_c, bases_c = [], [], [], [], [], [], [] # Loop over direction, prepare point sets and evaluate basis functions direction = 0 @@ -502,7 +499,6 @@ def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_q x_grid = greville_loc pts += [greville_loc[:, None]] wts += [np.ones(pts[-1].shape, dtype=float)] - np_pts_cell += [1] # histopolation elif space_out.basis == 'M': @@ -525,7 +521,6 @@ def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_q w = global_quad_w pts += [x] wts += [w] - np_pts_cell += [nq] # Knot span indices and V-basis functions evaluated at W-point sets s, b = get_span_and_basis(pts[-1], space_in) s_c, b_c = get_span_and_basis(pts[-1], space_coeff) @@ -537,7 +532,7 @@ def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_q bases_c +=[b_c] direction += 1 - return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(spans_c), tuple(bases_c), tuple(np_pts_cell) + return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(spans_c), tuple(bases_c) def get_span_and_basis(pts, space): @@ -638,18 +633,18 @@ def preprocess_grid(P, V): _starts_out = np.array(dofs_mat.codomain.starts) _ends_out = np.array(dofs_mat.codomain.ends) - _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( V1d, W1d, _starts_out, _ends_out, nq) - line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) + line_pre.append((_ptsG, _wtsG, _spans, _bases)) preproc.append(line_pre.copy()) return preproc def preprocess_grid_with_ff(P, V, f_type): """ - Gather the results of prepare_projection_of_basis for the different SplineSpaces composing a space, + Gather the results of prepare_projection_of_basis_ff for the different SplineSpaces composing a space, the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid - computing several time the same quantities + computing several time the same quantities. Parameters ---------- @@ -709,14 +704,14 @@ def preprocess_grid_with_ff(P, V, f_type): if isinstance(f,FemField): Vfd = f.space.spaces - _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts = \ + _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff = \ prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) - line_pre.append((_ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff, _npt_pts)) + line_pre.append((_ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff)) else : - _ptsG, _wtsG, _spans, _bases, _npt_pts = prepare_projection_of_basis( + _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( V1d, W1d, _starts_out, _ends_out,nq) - line_pre.append((_ptsG, _wtsG, _spans, _bases, _npt_pts)) + line_pre.append((_ptsG, _wtsG, _spans, _bases)) preproc.append(deepcopy(line_pre)) return preproc \ No newline at end of file From d1bfea7d771dce50c0fd551ed97c77b7232c04d1 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 12 Oct 2023 09:57:33 +0200 Subject: [PATCH 61/77] remove all basis projector related changes to keep only H_vec stuff --- psydac/api/discretization.py | 26 +- psydac/api/feec.py | 21 +- psydac/feec/basis_projection_kernels.py | 1083 -------------------- psydac/feec/basis_projectors.py | 717 ------------- psydac/feec/tests/test_basis_projectors.py | 573 ----------- 5 files changed, 38 insertions(+), 2382 deletions(-) delete mode 100644 psydac/feec/basis_projection_kernels.py delete mode 100644 psydac/feec/basis_projectors.py delete mode 100644 psydac/feec/tests/test_basis_projectors.py diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 1524be7d6..fca7ccc24 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -83,20 +83,35 @@ def change_dtype(V, dtype): #============================================================================== def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): + """ + Create a discrete De Rham sequence by creating the spaces and then initiating DiscreteDerham object. + + Parameters + ---------- + + derham : sympde.topology.space.Derham + The symbolic Derham sequence + + domain_h : Geometry + Discrete domain where the spaces will be discretized + + get_vec : Bool + True to also get the "Hvec" space discretizing (H1)^n vector fields + + **kwargs : list + optional parameters for the space discretization + """ ldim = derham.shape mapping = domain_h.domain.mapping # NOTE: assuming single-patch domain! - bases = ['B'] + ldim * ['M'] spaces = [discretize_space(V, domain_h, basis=basis, **kwargs) \ for V, basis in zip(derham.spaces, bases)] if get_vec: - Vnh = spaces[0] + V0h = spaces[0] X = VectorFunctionSpace('X', domain_h.domain, kind='h1') - #Vn = Vnh.symbolic_space - #X = ProductSpace(Vn,Vn) #should fix sympde first - Xh = VectorFemSpace(Vnh, Vnh) + Xh = VectorFemSpace([V0h]*ldim) Xh.symbolic_space = X spaces.append(Xh) @@ -372,6 +387,7 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, max_coords = interior.max_coords assert len(ncells) == len(periodic) == len(degree_i) == len(multiplicity_i) == len(min_coords) == len(max_coords) + if knots is None: # Create uniform grid grids = [np.linspace(xmin, xmax, num=ne + 1) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index cf62a6995..50afd6d84 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -15,6 +15,19 @@ #============================================================================== class DiscreteDerham(BasicDiscrete): """ Represent the discrete De Rham sequence. + Should be initialized via discretize_derham function in api.discretization.py + + Parameters + ---------- + + mapping : Mapping + The mapping from the logical space to the physical space of the discrete De Rham. + + get_vec : Bool + True to also get the "Hvec" space discretizing (H1)^n vector fields + + *spaces : list of + The discrete spaces of the De Rham sequence """ def __init__(self, mapping, get_vec=False, *spaces): @@ -118,7 +131,7 @@ def derivatives_as_operators(self): return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- - def projectors(self, *, kind='global', nquads=None, get_reference=False): + def projectors(self, *, kind='global', nquads=None): if not (kind == 'global'): raise NotImplementedError('only global projectors are available') @@ -126,7 +139,7 @@ def projectors(self, *, kind='global', nquads=None, get_reference=False): if self.dim == 1: P0 = Projector_H1(self.V0) P1 = Projector_L2(self.V1, nquads) - if self.mapping and not get_reference: + if self.mapping: P0_m = lambda f: P0(pull_1d_h1(f, self.callable_mapping)) P1_m = lambda f: P1(pull_1d_l2(f, self.callable_mapping)) return P0_m, P1_m @@ -147,7 +160,7 @@ def projectors(self, *, kind='global', nquads=None, get_reference=False): if self.has_vec : Pvec = Projector_H1vec(self.Vvec, nquads) - if self.mapping and not get_reference: + if self.mapping: P0_m = lambda f: P0(pull_2d_h1(f, self.callable_mapping)) P2_m = lambda f: P2(pull_2d_l2(f, self.callable_mapping)) if kind == 'hcurl': @@ -172,7 +185,7 @@ def projectors(self, *, kind='global', nquads=None, get_reference=False): P3 = Projector_L2 (self.V3, nquads) if self.has_vec : Pvec = Projector_H1vec(self.Vvec) - if self.mapping and not get_reference: + if self.mapping: P0_m = lambda f: P0(pull_3d_h1 (f, self.callable_mapping)) P1_m = lambda f: P1(pull_3d_hcurl(f, self.callable_mapping)) P2_m = lambda f: P2(pull_3d_hdiv (f, self.callable_mapping)) diff --git a/psydac/feec/basis_projection_kernels.py b/psydac/feec/basis_projection_kernels.py deleted file mode 100644 index 2df48ef40..000000000 --- a/psydac/feec/basis_projection_kernels.py +++ /dev/null @@ -1,1083 +0,0 @@ -def assemble_dofs_for_weighted_basisfuns_1d(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', dim1_in : int, p1_out : int): - '''Kernel for assembling the matrix - - A_(i,j) = DOFS_i(fun*Lambda^in_j) , - - into the _data attribute of a StencilMatrix. - Here, DOFS_i are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_j are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. - - Parameters - ---------- - mat : 2d float array - _data attribute of StencilMatrix. - - starts_in : int - Starting index of the input space (domain) of a distributed StencilMatrix. - - ends_in : int - Ending index of the input space (domain) of a distributed StencilMatrix. - - pads_in : int - Paddings of the input space (domain) of a distributed StencilMatrix. - - starts_out : int - Starting indices of the output space (codomain) of a distributed StencilMatrix. - - ends_out : int - Ending indices of the output space (codomain) of a distributed StencilMatrix. - - pads_out : int - Paddings of the output space (codomain) of a distributed StencilMatrix. - - fun_q : 1d float array - The function evaluated at the points (nq*ii + iq), where iq a local quadrature point of interval ii. - - wts1 : 2d float array - Quadrature weights in format (ii, iq). - - span1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq). - - basis1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - - dim1_in : int - Dimension of the first direction of the input space - - p1_out : int - Spline degree of the first direction of the output space - ''' - - # Start/end indices and paddings for distributed stencil matrix of input space - # si1 = starts_in[0} - # ei1 = ends_in[0] - pi1 = pads_in[0] - - # Start/end indices for distributed stencil matrix of output space - so1 = starts_out[0] - # eo1 = ends_out[0] - po1 = pads_out[0] - - # Spline degrees of input space - p1 = basis1.shape[2] - 1 - - # number of quadrature points - nq1 = span1.shape[1] - - # Set output to zero - mat[:] = 0. - - # Dimensions of output space - dim1_out = span1.shape[0] - # Interval (either element or sub-interval thereof) - # ------------------------------------------------- - cumsub_i = 0 # Cumulative sub-interval index - for ii in range(span1.shape[0]): - i = ii - cumsub_i # local DOF index - - # Quadrature point index in interval - # ---------------------------------- - for iq in range(nq1): - - funval = fun_q[nq1*ii + iq] * wts1[ii, iq] - - # Basis function of input space: - # ------------------------------ - for b1 in range(p1 + 1): - m = (span1[ii, iq] - p1 + b1) # global index - # basis value - value = funval * basis1[ii, iq, b1] - - # Find column index for _data: - if dim1_out <= dim1_in: - cut1 = p1 - else: - cut1 = p1_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col1 - col1_tmp = m - (i + so1) - if col1_tmp > cut1: - m = m - dim1_in - elif col1_tmp < -cut1: - m = m + dim1_in - # add padding - col1 = pi1 + m - (i + so1) - - # Row index: padding + local index. - mat[po1 + i, col1] += value - - -def assemble_dofs_for_weighted_basisfuns_2d(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): - '''Kernel for assembling the matrix - - A_(ij,kl) = DOFS_ij(fun*Lambda^in_kl) , - - into the _data attribute of a StencilMatrix. - Here, DOFS_ij are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_kl are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. - - Parameters - ---------- - mat : 4d float array - _data attribute of StencilMatrix. - - starts_in : 1d int array - Starting indices of the input space (domain) of a distributed StencilMatrix. - - ends_in : 1d int array - Ending indices of the input space (domain) of a distributed StencilMatrix. - - pads_in : 1d int array - Paddings of the input space (domain) of a distributed StencilMatrix. - - starts_out : 1d int array - Starting indices of the output space (codomain) of a distributed StencilMatrix. - - ends_out : 1d int array - Ending indices of the output space (codomain) of a distributed StencilMatrix. - - pads_out : 1d int array - Paddings of the output space (codomain) of a distributed StencilMatrix. - - fun_q : 2d float array - The function evaluated at the points (nq_i*ii + iq, nq_j*jj + jq), where iq a local quadrature point of interval ii. - - wts1 : 2d float array - Quadrature weights in direction eta1 in format (ii, iq). - - wts2 : 2d float array - Quadrature weights in direction eta2 in format (jj, jq). - - span1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq). - - span2 : 2d int array - Knot span indices in direction eta2 in format (jj, jq). - - basis1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - - basis2 : 3d float array - Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). - - dim1_in : int - Dimension of the first direction of the input space - - dim2_in : int - Dimension of the second direction of the input space - - p1_out : int - Spline degree of the first direction of the output space - - p2_out : int - Spline degree of the second direction of the output space - ''' - - # Start/end indices and paddings for distributed stencil matrix of input space - # si1 = starts_in[0] - # si2 = starts_in[1] - # ei1 = ends_in[0] - # ei2 = ends_in[1] - pi1 = pads_in[0] - pi2 = pads_in[1] - - # Start/end indices for distributed stencil matrix of output space - so1 = starts_out[0] - so2 = starts_out[1] - # eo1 = ends_out[0] - # eo2 = ends_out[1] - po1 = pads_out[0] - po2 = pads_out[1] - - # Spline degrees of input space - p1 = basis1.shape[2] - 1 - p2 = basis2.shape[2] - 1 - - # number of quadrature points - nq1 = span1.shape[1] - nq2 = span2.shape[1] - - # Set output to zero - mat[:] = 0. - - # Dimensions of output space - dim1_out = span1.shape[0] - dim2_out = span2.shape[0] - - # Interval (either element or sub-interval thereof) - # ------------------------------------------------- - cumsub_i = 0 # Cumulative sub-interval index - for ii in range(span1.shape[0]): - i = ii - cumsub_i # local DOF index - - cumsub_j = 0 # Cumulative sub-interval index - for jj in range(span2.shape[0]): - j = jj - cumsub_j # local DOF index - - # Quadrature point index in interval - # ---------------------------------- - for iq in range(nq1): - for jq in range(nq2): - - funval = fun_q[nq1*ii + iq, nq2*jj + jq] * wts1[ii, iq] * wts2[jj, jq] - - # Basis function of input space: - # ------------------------------ - for b1 in range(p1 + 1): - m = (span1[ii, iq] - p1 + b1) # global index - # basis value - val1 = funval * basis1[ii, iq, b1] - - # Find column index for _data: - if dim1_out <= dim1_in: - cut1 = p1 - else: - cut1 = p1_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col1 - col1_tmp = m - (i + so1) - if col1_tmp > cut1: - m = m - dim1_in - elif col1_tmp < -cut1: - m = m + dim1_in - # add padding - col1 = pi1 + m - (i + so1) - - for b2 in range(p2 + 1): - # global index - n = (span2[jj, jq] - p2 + b2) - value = val1 * basis2[jj, jq, b2] - - # Find column index for _data: - if dim2_out <= dim2_in: - cut2 = p2 - else: - cut2 = p2_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col2 - col2_tmp = n - (j + so2) - if col2_tmp > cut2: - n = n - dim2_in - elif col2_tmp < -cut2: - n = n + dim2_in - # add padding - col2 = pi2 + n - (j + so2) - - # Row index: padding + local index. - mat[po1 + i, po2 + j, col1, col2] += value - - - -def assemble_dofs_for_weighted_basisfuns_3d(mat : 'float[:,:,:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', fun_q : 'float[:,:,:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', wts3 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', span3 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', basis3 : 'float[:,:,:]', dim1_in : int, dim2_in : int, dim3_in : int, p1_out : int, p2_out : int, p3_out : int): - '''Kernel for assembling the matrix - - A_(ijk,mno) = DOFS_ijk(fun*Lambda^in_mno) , - - into the _data attribute of a StencilMatrix. - Here, DOFS_ijk are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_mno are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. - - Parameters - ---------- - mat : 6d float array - _data attribute of StencilMatrix. - - starts_in : 1d int array - Starting indices of the input space (domain) of a distributed StencilMatrix. - - ends_in : 1d int array - Ending indices of the input space (domain) of a distributed StencilMatrix. - - pads_in : 1d int array - Paddings of the input space (domain) of a distributed StencilMatrix. - - starts_out : 1d int array - Starting indices of the output space (codomain) of a distributed StencilMatrix. - - ends_out : 1d int array - Ending indices of the output space (codomain) of a distributed StencilMatrix. - - pads_out : 1d int array - Paddings of the output space (codomain) of a distributed StencilMatrix. - - fun_q : 3d float array - The function evaluated at the points (nq_i*ii + iq, nq_j*jj + jq, nq_k*kk + kq), where iq a local quadrature point of interval ii. - - wts1 : 2d float array - Quadrature weights in direction eta1 in format (ii, iq). - - wts2 : 2d float array - Quadrature weights in direction eta2 in format (jj, jq). - - wts3 : 2d float array - Quadrature weights in direction eta3 in format (kk, kq). - - span1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq). - - span2 : 2d int array - Knot span indices in direction eta2 in format (jj, jq). - - span3 : 2d int array - Knot span indices in direction eta3 in format (kk, kq). - - basis1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - - basis2 : 3d float array - Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). - - basis3 : 3d float array - Values of p3 + 1 non-zero eta-3 basis functions at quadrature points in format (kk, kq, basis function). - - dim1_in : int - Dimension of the first direction of the input space - - dim2_in : int - Dimension of the second direction of the input space - - dim3_in : int - Dimension of the third direction of the input space - - p1_out : int - Spline degree of the first direction of the output space - - p2_out : int - Spline degree of the second direction of the output space - - p3_out : int - Spline degree of the third direction of the output space - ''' - - # Start/end indices and paddings for distributed stencil matrix of input space - # si1 = starts_in[0] - # si2 = starts_in[1] - # si3 = starts_in[2] - # ei1 = ends_in[0] - # ei2 = ends_in[1] - # ei3 = ends_in[2] - pi1 = pads_in[0] - pi2 = pads_in[1] - pi3 = pads_in[2] - - # Start/end indices for distributed stencil matrix of output space - so1 = starts_out[0] - so2 = starts_out[1] - so3 = starts_out[2] - # eo1 = ends_out[0] - # eo2 = ends_out[1] - # eo3 = ends_out[2] - po1 = pads_out[0] - po2 = pads_out[1] - po3 = pads_out[2] - - # Spline degrees of input space - p1 = basis1.shape[2] - 1 - p2 = basis2.shape[2] - 1 - p3 = basis3.shape[2] - 1 - - # number of quadrature points - nq1 = span1.shape[1] - nq2 = span2.shape[1] - nq3 = span3.shape[1] - - # Set output to zero - mat[:] = 0. - - # Dimensions of output space - dim1_out = span1.shape[0] - dim2_out = span2.shape[0] - dim3_out = span3.shape[0] - - # Interval (either element or sub-interval thereof) - # ------------------------------------------------- - cumsub_i = 0 # Cumulative sub-interval index - for ii in range(span1.shape[0]): - i = ii - cumsub_i # local DOF index - - cumsub_j = 0 # Cumulative sub-interval index - for jj in range(span2.shape[0]): - j = jj - cumsub_j # local DOF index - - cumsub_k = 0 # Cumulative sub-interval index - for kk in range(span3.shape[0]): - k = kk - cumsub_k # local DOF index - - # Quadrature point index in interval - # ---------------------------------- - for iq in range(nq1): - for jq in range(nq2): - for kq in range(nq3): - - funval = fun_q[nq1*ii + iq, nq2*jj + jq, nq3*kk + kq] * wts1[ii, iq] * wts2[jj, jq] * wts3[kk, kq] - - # Basis function of input space: - # ------------------------------ - for b1 in range(p1 + 1): - m = (span1[ii, iq] - p1 + b1) # global index - # basis value - val1 = funval * basis1[ii, iq, b1] - - # Find column index for _data: - if dim1_out <= dim1_in: - cut1 = p1 - else: - cut1 = p1_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col1 - col1_tmp = m - (i + so1) - if col1_tmp > cut1: - m = m - dim1_in - elif col1_tmp < -cut1: - m = m + dim1_in - # add padding - col1 = pi1 + m - (i + so1) - - for b2 in range(p2 + 1): - # global index - n = (span2[jj, jq] - p2 + b2) - val2 = val1 * basis2[jj, jq, b2] - - # Find column index for _data: - if dim2_out <= dim2_in: - cut2 = p2 - else: - cut2 = p2_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col2 - col2_tmp = n - (j + so2) - if col2_tmp > cut2: - n = n - dim2_in - elif col2_tmp < -cut2: - n = n + dim2_in - # add padding - col2 = pi2 + n - (j + so2) - - for b3 in range(p3 + 1): - # global index - o = (span3[kk, kq] - p3 + b3) - value = val2 * basis3[kk, kq, b3] - - # Find column index for _data: - if dim3_out <= dim3_in: - cut3 = p3 - else: - cut3 = p3_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col3 - col3_tmp = o - (k + so3) - if col3_tmp > cut3: - o = o - dim3_in - elif col3_tmp < -cut3: - o = o + dim3_in - # add padding - col3 = pi3 + o - (k + so3) - - # Row index: padding + local index. - mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value - - -def assemble_dofs_for_weighted_basisfuns_1d_ff(mat : 'float[:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', span1 : 'int[:,:]', basis1 : 'float[:,:,:]', coeffs_f : 'float[:]', span_c1 : 'int[:,:]', basis_c1 : 'float[:,:,:]', dim1_in : int, p1_out : int): - '''Kernel for assembling the matrix - - A_(i,j) = DOFS_i(fun*Lambda^in_j) , - - into the _data attribute of a StencilMatrix. - Here, DOFS_i are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_j are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. - - Parameters - ---------- - mat : 2d float array - _data attribute of StencilMatrix. - - starts_in : int - Starting index of the input space (domain) of a distributed StencilMatrix. - - ends_in : int - Ending index of the input space (domain) of a distributed StencilMatrix. - - pads_in : int - Paddings of the input space (domain) of a distributed StencilMatrix. - - starts_out : int - Starting indices of the output space (codomain) of a distributed StencilMatrix. - - ends_out : int - Ending indices of the output space (codomain) of a distributed StencilMatrix. - - pads_out : int - Paddings of the output space (codomain) of a distributed StencilMatrix. - - starts_c : 1d int array - Starting indices of the coefficient (femfield f) space. - - ends_c : 1d int array - Ending indices of the coefficient (femfield f) space. - - pads_c : 1d int array - Paddings of the coefficient (femfield f) space. - - wts1 : 2d float array - Quadrature weights in format (ii, iq). - - span1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq). - - basis1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - - coeffs_f : 3d float array - Coefficient of the femfield f - - span_c1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq) for the coefficient FemField f. - - basis_c1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions of the space of f at quadrature points in format (ii, iq, basis function). - - dim1_in : int - Dimension of the first direction of the input space - - p1_out : int - Spline degree of the first direction of the output space - ''' - - # Start/end indices and paddings for distributed stencil matrix of input space - # si1 = starts_in[0} - # ei1 = ends_in[0] - pi1 = pads_in[0] - - # Start/end indices for distributed stencil matrix of output space - so1 = starts_out[0] - # eo1 = ends_out[0] - po1 = pads_out[0] - - sc1 = starts_c[0] - # ec1 = ends_c[0] - pc1 = pads_c[0] - - # Spline degrees of input space - p1 = basis1.shape[2] - 1 - - p1_c = basis_c1.shape[2] - 1 - - # number of quadrature points - nq1 = span1.shape[1] - - # Set output to zero - mat[:] = 0. - - # Dimensions of output space - dim1_out = span1.shape[0] - - #local dof index - for i in range(span1.shape[0]): - # Quadrature point index in interval - # ---------------------------------- - for iq in range(nq1): - - f_val = 0. - - for b1 in range(p1_c + 1): - # global index - m = (span_c1[i, iq] - p1_c + b1) - #local index - m_loc = m-sc1+pc1 - f_val += basis_c1[i, iq, b1] * coeffs_f[m_loc] - - funval = wts1[i, iq] * f_val - - # Basis function of input space: - # ------------------------------ - for b1 in range(p1 + 1): - m = (span1[i, iq] - p1 + b1) # global index - # basis value - value = funval * basis1[i, iq, b1] - - # Find column index for _data: - if dim1_out <= dim1_in: - cut1 = p1 - else: - cut1 = p1_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col1 - col1_tmp = m - (i + so1) - if col1_tmp > cut1: - m = m - dim1_in - elif col1_tmp < -cut1: - m = m + dim1_in - # add padding - col1 = pi1 + m - (i + so1) - - # Row index: padding + local index. - mat[po1 + i, col1] += value - - -def assemble_dofs_for_weighted_basisfuns_2d_ff(mat : 'float[:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', coeffs_f : 'float[:,:]', span_c1 : 'int[:,:]', span_c2 : 'int[:,:]', basis_c1 : 'float[:,:,:]', basis_c2 : 'float[:,:,:]', dim1_in : int, dim2_in : int, p1_out : int, p2_out : int): - '''Kernel for assembling the matrix - - A_(ij,kl) = DOFS_ij(fun*Lambda^in_kl) , - - into the _data attribute of a StencilMatrix. - Here, DOFS_ij are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_kl are the basis functions of the input space (domain, must not be a product space), and is a FemField object. - - Parameters - ---------- - mat : 4d float array - _data attribute of StencilMatrix. - - starts_in : 1d int array - Starting indices of the input space (domain) of a distributed StencilMatrix. - - ends_in : 1d int array - Ending indices of the input space (domain) of a distributed StencilMatrix. - - pads_in : 1d int array - Paddings of the input space (domain) of a distributed StencilMatrix. - - starts_out : 1d int array - Starting indices of the output space (codomain) of a distributed StencilMatrix. - - ends_out : 1d int array - Ending indices of the output space (codomain) of a distributed StencilMatrix. - - pads_out : 1d int array - Paddings of the output space (codomain) of a distributed StencilMatrix. - - starts_c : 1d int array - Starting indices of the coefficient (femfield f) space. - - ends_c : 1d int array - Ending indices of the coefficient (femfield f) space. - - pads_c : 1d int array - Paddings of the coefficient (femfield f) space. - - wts1 : 2d float array - Quadrature weights in direction eta1 in format (ii, iq). - - wts2 : 2d float array - Quadrature weights in direction eta2 in format (jj, jq). - - span1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq). - - span2 : 2d int array - Knot span indices in direction eta2 in format (jj, jq). - - basis1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - - basis2 : 3d float array - Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). - - coeffs_f : 2d float array - Coefficient of the femfield f - - span_c1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq) for the coefficient FemField f. - - span_c2 : 2d int array - Knot span indices in direction eta2 in format (jj, jq) for the coefficient FemField f. - - basis_c1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions of the space of f at quadrature points in format (ii, iq, basis function). - - basis_c2 : 3d float array - Values of p2 + 1 non-zero eta-2 basis functions of the space of f at quadrature points in format (jj, jq, basis function). - - dim1_in : int - Dimension of the first direction of the input space - - dim2_in : int - Dimension of the second direction of the input space - - p1_out : int - Spline degree of the first direction of the output space - - p2_out : int - Spline degree of the second direction of the output space - ''' - - # Start/end indices and paddings for distributed stencil matrix of input space - # si1 = starts_in[0] - # si2 = starts_in[1] - # ei1 = ends_in[0] - # ei2 = ends_in[1] - pi1 = pads_in[0] - pi2 = pads_in[1] - - # Start/end indices for distributed stencil matrix of output space - so1 = starts_out[0] - so2 = starts_out[1] - # eo1 = ends_out[0] - # eo2 = ends_out[1] - po1 = pads_out[0] - po2 = pads_out[1] - - sc1 = starts_c[0] - sc2 = starts_c[1] - # ec1 = ends_c[0] - # ec2 = ends_c[1] - pc1 = pads_c[0] - pc2 = pads_c[1] - - # Spline degrees of input space - p1 = basis1.shape[2] - 1 - p2 = basis2.shape[2] - 1 - - p1_c = basis_c1.shape[2] - 1 - p2_c = basis_c2.shape[2] - 1 - - # number of quadrature points - nq1 = span1.shape[1] - nq2 = span2.shape[1] - - # Set output to zero - mat[:] = 0. - - # Dimensions of output space - dim1_out = span1.shape[0] - dim2_out = span2.shape[0] - - # Interval (either element or sub-interval thereof) - # ------------------------------------------------- - # local DOF index - for i in range(span1.shape[0]): - - for j in range(span2.shape[0]): - - # Quadrature point index in interval - # ---------------------------------- - for iq in range(nq1): - for jq in range(nq2): - - f_val = 0. - - for b1 in range(p1_c + 1): - # global index - m = (span_c1[i, iq] - p1_c + b1) - #local index - m_loc = m-sc1+pc1 - for b2 in range(p2_c + 1): - # global index - n = (span_c2[j, jq] - p2_c + b2) - #local index - n_loc = n-sc2+pc2 - f_val += basis_c1[i, iq, b1] * basis_c2[j, jq, b2] *coeffs_f[m_loc,n_loc] - - funval = wts1[i, iq] * wts2[j, jq] * f_val - - # Basis function of input space: - # ------------------------------ - for b1 in range(p1 + 1): - m = (span1[i, iq] - p1 + b1) # global index - # basis value - val1 = funval * basis1[i, iq, b1] - - # Find column index for _data: - if dim1_out <= dim1_in: - cut1 = p1 - else: - cut1 = p1_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col1 - col1_tmp = m - (i + so1) - if col1_tmp > cut1: - m = m - dim1_in - elif col1_tmp < -cut1: - m = m + dim1_in - # add padding - col1 = pi1 + m - (i + so1) - - for b2 in range(p2 + 1): - # global index - n = (span2[j, jq] - p2 + b2) - value = val1 * basis2[j, jq, b2] - - # Find column index for _data: - if dim2_out <= dim2_in: - cut2 = p2 - else: - cut2 = p2_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col2 - col2_tmp = n - (j + so2) - if col2_tmp > cut2: - n = n - dim2_in - elif col2_tmp < -cut2: - n = n + dim2_in - # add padding - col2 = pi2 + n - (j + so2) - - # Row index: padding + local index. - mat[po1 + i, po2 + j, col1, col2] += value - - - -def assemble_dofs_for_weighted_basisfuns_3d_ff(mat : 'float[:,:,:,:,:,:]', starts_in : 'int[:]', ends_in : 'int[:]', pads_in : 'int[:]', starts_out : 'int[:]', ends_out : 'int[:]', pads_out : 'int[:]', starts_c : 'int[:]', ends_c : 'int[:]', pads_c : 'int[:]', wts1 : 'float[:,:]', wts2 : 'float[:,:]', wts3 : 'float[:,:]', span1 : 'int[:,:]', span2 : 'int[:,:]', span3 : 'int[:,:]', basis1 : 'float[:,:,:]', basis2 : 'float[:,:,:]', basis3 : 'float[:,:,:]', coeffs_f : 'float[:,:,:]', span_c1 : 'int[:,:]', span_c2 : 'int[:,:]', span_c3 : 'int[:,:]', basis_c1 : 'float[:,:,:]', basis_c2 : 'float[:,:,:]', basis_c3 : 'float[:,:,:]', dim1_in : int, dim2_in : int, dim3_in : int, p1_out : int, p2_out : int, p3_out : int): - '''Kernel for assembling the matrix - - A_(ijk,mno) = DOFS_ijk(fun*Lambda^in_mno) , - - into the _data attribute of a StencilMatrix. - Here, DOFS_ijk are the degrees-of-freedom of the output space (codomain, must not be a product space), - Lambda^in_mno are the basis functions of the input space (domain, must not be a product space), and fun is an arbitrary function. - - Parameters - ---------- - mat : 6d float array - _data attribute of StencilMatrix. - - starts_in : 1d int array - Starting indices of the input space (domain) of a distributed StencilMatrix. - - ends_in : 1d int array - Ending indices of the input space (domain) of a distributed StencilMatrix. - - pads_in : 1d int array - Paddings of the input space (domain) of a distributed StencilMatrix. - - starts_out : 1d int array - Starting indices of the output space (codomain) of a distributed StencilMatrix. - - ends_out : 1d int array - Ending indices of the output space (codomain) of a distributed StencilMatrix. - - pads_out : 1d int array - Paddings of the output space (codomain) of a distributed StencilMatrix. - - starts_c : 1d int array - Starting indices of the coefficient (femfield f) space. - - ends_c : 1d int array - Ending indices of the coefficient (femfield f) space. - - pads_c : 1d int array - Paddings of the coefficient (femfield f) space. - - wts1 : 2d float array - Quadrature weights in direction eta1 in format (ii, iq). - - wts2 : 2d float array - Quadrature weights in direction eta2 in format (jj, jq). - - wts3 : 2d float array - Quadrature weights in direction eta3 in format (kk, kq). - - span1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq). - - span2 : 2d int array - Knot span indices in direction eta2 in format (jj, jq). - - span3 : 2d int array - Knot span indices in direction eta3 in format (kk, kq). - - basis1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions at quadrature points in format (ii, iq, basis function). - - basis2 : 3d float array - Values of p2 + 1 non-zero eta-2 basis functions at quadrature points in format (jj, jq, basis function). - - basis3 : 3d float array - Values of p3 + 1 non-zero eta-3 basis functions at quadrature points in format (kk, kq, basis function). - - coeffs_f : 3d float array - Coefficient of the femfield f - - span_c1 : 2d int array - Knot span indices in direction eta1 in format (ii, iq) for the coefficient FemField f. - - span_c2 : 2d int array - Knot span indices in direction eta2 in format (jj, jq) for the coefficient FemField f. - - span_c3 : 2d int array - Knot span indices in direction eta3 in format (jj, jq) for the coefficient FemField f. - - basis_c1 : 3d float array - Values of p1 + 1 non-zero eta-1 basis functions of the space of f at quadrature points in format (ii, iq, basis function). - - basis_c2 : 3d float array - Values of p2 + 1 non-zero eta-2 basis functions of the space of f at quadrature points in format (jj, jq, basis function). - - basis_c3 : 3d float array - Values of p3 + 1 non-zero eta-3 basis functions of the space of f at quadrature points in format (kk, kq, basis function). - - dim1_in : int - Dimension of the first direction of the input space - - dim2_in : int - Dimension of the second direction of the input space - - dim3_in : int - Dimension of the third direction of the input space - - p1_out : int - Spline degree of the first direction of the output space - - p2_out : int - Spline degree of the second direction of the output space - - p3_out : int - Spline degree of the third direction of the output space - ''' - - # Start/end indices and paddings for distributed stencil matrix of input space - # si1 = starts_in[0] - # si2 = starts_in[1] - # si3 = starts_in[2] - # ei1 = ends_in[0] - # ei2 = ends_in[1] - # ei3 = ends_in[2] - pi1 = pads_in[0] - pi2 = pads_in[1] - pi3 = pads_in[2] - - # Start/end indices for distributed stencil matrix of output space - so1 = starts_out[0] - so2 = starts_out[1] - so3 = starts_out[2] - # eo1 = ends_out[0] - # eo2 = ends_out[1] - # eo3 = ends_out[2] - po1 = pads_out[0] - po2 = pads_out[1] - po3 = pads_out[2] - - sc1 = starts_c[0] - sc2 = starts_c[1] - sc3 = starts_c[2] - # ec1 = ends_c[0] - # ec2 = ends_c[1] - # ec3 = ends_c[2] - pc1 = pads_c[0] - pc2 = pads_c[1] - pc3 = pads_c[2] - - # Spline degrees of input space - p1 = basis1.shape[2] - 1 - p2 = basis2.shape[2] - 1 - p3 = basis3.shape[2] - 1 - - p1_c = basis_c1.shape[2] - 1 - p2_c = basis_c2.shape[2] - 1 - p3_c = basis_c3.shape[2] - 1 - - # number of quadrature points - nq1 = span1.shape[1] - nq2 = span2.shape[1] - nq3 = span3.shape[1] - - # Set output to zero - mat[:] = 0. - - # Dimensions of output space - dim1_out = span1.shape[0] - dim2_out = span2.shape[0] - dim3_out = span3.shape[0] - - # Interval (either element or sub-interval thereof) - # ------------------------------------------------- - # local DOF index - for i in range(span1.shape[0]): - - for j in range(span2.shape[0]): - - for k in range(span3.shape[0]): - - # Quadrature point index in interval - # ---------------------------------- - for iq in range(nq1): - for jq in range(nq2): - for kq in range(nq3): - - f_val = 0. - - for b1 in range(p1_c + 1): - # global index - m = (span_c1[i, iq] - p1_c + b1) - #local index - m_loc = m-sc1+pc1 - for b2 in range(p2_c + 1): - # global index - n = (span_c2[j, jq] - p2_c + b2) - #local index - n_loc = n-sc2+pc2 - for b3 in range(p3_c + 1): - # global index - o = (span_c3[k, kq] - p3_c + b3) - #local index - o_loc = o-sc3+pc3 - f_val += basis_c1[i, iq, b1] * basis_c2[j, jq, b2] * basis_c3[k, kq, b3] * coeffs_f[m_loc,n_loc,o_loc] - - funval = wts1[i, iq] * wts2[j, jq] * wts3[k, kq] * f_val - - # Basis function of input space: - # ------------------------------ - for b1 in range(p1 + 1): - m = (span1[i, iq] - p1 + b1) # global index - # basis value - val1 = funval * basis1[i, iq, b1] - - # Find column index for _data: - if dim1_out <= dim1_in: - cut1 = p1 - else: - cut1 = p1_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col1 - col1_tmp = m - (i + so1) - if col1_tmp > cut1: - m = m - dim1_in - elif col1_tmp < -cut1: - m = m + dim1_in - # add padding - col1 = pi1 + m - (i + so1) - - for b2 in range(p2 + 1): - # global index - n = (span2[j, jq] - p2 + b2) - val2 = val1 * basis2[j, jq, b2] - - # Find column index for _data: - if dim2_out <= dim2_in: - cut2 = p2 - else: - cut2 = p2_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col2 - col2_tmp = n - (j + so2) - if col2_tmp > cut2: - n = n - dim2_in - elif col2_tmp < -cut2: - n = n + dim2_in - # add padding - col2 = pi2 + n - (j + so2) - - for b3 in range(p3 + 1): - # global index - o = (span3[k, kq] - p3 + b3) - value = val2 * basis3[k, kq, b3] - - # Find column index for _data: - if dim3_out <= dim3_in: - cut3 = p3 - else: - cut3 = p3_out - - # Diff of global indices, needs to be adjusted for boundary conditions --> col3 - col3_tmp = o - (k + so3) - if col3_tmp > cut3: - o = o - dim3_in - elif col3_tmp < -cut3: - o = o + dim3_in - # add padding - col3 = pi3 + o - (k + so3) - - # Row index: padding + local index. - mat[po1 + i, po2 + j, po3 + k, col1, col2, col3] += value - diff --git a/psydac/feec/basis_projectors.py b/psydac/feec/basis_projectors.py deleted file mode 100644 index ab794c49d..000000000 --- a/psydac/feec/basis_projectors.py +++ /dev/null @@ -1,717 +0,0 @@ -import numpy as np - -from psydac.linalg.stencil import StencilMatrix -from psydac.linalg.block import BlockLinearOperator -from psydac.linalg.basic import Vector -from psydac.fem.basic import FemSpace -from psydac.fem.tensor import TensorFemSpace -from psydac.feec.global_projectors import GlobalProjector -from psydac.api.settings import PSYDAC_BACKEND_GPYCCEL -from psydac.linalg.basic import LinearOperator -from psydac.feec import basis_projection_kernels -from psydac.utilities.quadratures import gauss_legendre -from psydac.fem.basic import FemField -from psydac.utilities.utils import roll_edges - -from copy import deepcopy - - - -class BasisProjectionOperator(LinearOperator): - """ - Class for "basis projection operators" PI_ijk(fun Lambda_mno) in the general form BP * P * DOF * EV^T * BV^T. - Be carefull that PI is the Projector on the reference domain and fun has to be define on the reference domain, - in other terms, this class does dot handle mappings - - Parameters - ---------- - P : psydac.feec.global_projection.GlobalProjector - Global commuting projector mapping into TensorFemSpace/ProductFemSpace W = P.space (codomain of operator). - Has to be the projection on the reference domain - - V : psydac.fem.basic.FemSpace - Finite element spline space (domain, input space). - - fun : list - Weight function(s) (callables) in a 2d list of shape corresponding to number of components of domain/codomain. - - transposed : bool - Whether to assemble the transposed operator. - """ - - def __init__(self, P, V, fun, transposed=False, preproc_grid=None, dof_mat=None): - - # only for M1 Mac users - #PSYDAC_BACKEND_GPYCCEL['flags'] = '-O3 -march=native -mtune=native -ffast-math -ffree-line-length-none' - - assert isinstance(P, GlobalProjector) - assert isinstance(V, FemSpace) - - self._P = P - self._V = V - - - self._fun = fun - self._transposed = transposed - self._dtype = V.vector_space.dtype - assert(self._dtype == float) - - # set domain and codomain symbolic names - if hasattr(P.space.symbolic_space, 'name'): - P_name = P.space.symbolic_space.name - else: - P_name = 'H1vec' - - if hasattr(V.symbolic_space, 'name'): - V_name = V.symbolic_space.name - else: - V_name = 'H1vec' - - if transposed: - self._domain_symbolic_name = P_name - self._codomain_symbolic_name = V_name - else: - self._domain_symbolic_name = V_name - self._codomain_symbolic_name = P_name - - self._preproc_grid = preproc_grid - - if isinstance(V, TensorFemSpace) and isinstance(P.space, TensorFemSpace): - dofs_mat = StencilMatrix( - V.vector_space, P.space.vector_space, backend=PSYDAC_BACKEND_GPYCCEL) - self._dof_operator_pre = dofs_mat - - else : - if isinstance(V, TensorFemSpace): - Vspaces = [V.vector_space] - else: - Vspaces = V.vector_space - - # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(P.space, TensorFemSpace): - Wspaces = [P.space.vector_space] - else: - Wspaces = P.space.vector_space - blocks = [] - for Wspace in Wspaces: - blocks += [[]] - # input vector space (domain), column of block - for Vspace in Vspaces: - dofs_mat = StencilMatrix( - Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) - blocks[-1] += [dofs_mat] - - self._dof_operator_pre = BlockLinearOperator(V.vector_space, P.space.vector_space, blocks) - - # ============= assemble tensor-product dof matrix ======= - - BasisProjectionOperator.assemble_mat( - P, V, fun, self._dof_operator_pre, self._preproc_grid) - # ======================================================== - - if transposed: - self._dof_operator = self._dof_operator_pre.transpose() - else: - self._dof_operator = self._dof_operator_pre - - # set domain and codomain - self._domain = self.dof_operator.domain - self._codomain = self.dof_operator.codomain - - # temporary vectors for dot product - self._tmp_dom = self._dof_operator.domain.zeros() - self._tmp_codom = self._dof_operator.codomain.zeros() - - @property - def domain(self): - """ Domain vector space (input) of the operator. - """ - return self._domain - - @property - def codomain(self): - """ Codomain vector space (input) of the operator. - """ - return self._codomain - - @property - def dtype(self): - """ Datatype of the operator. - """ - return self._dtype - - @property - def tosparse(self): - raise NotImplementedError() - - @property - def toarray(self): - raise NotImplementedError() - - @property - def transposed(self): - """ If the transposed operator is in play. - """ - return self._transposed - - @property - def dof_operator(self): - """ The degrees of freedom operator as composite linear operator containing polar extraction and boundary operators. - """ - return self._dof_operator - - def update_fun(self, fun): - self._fun = fun - BasisProjectionOperator.assemble_mat( - self._P, self._V, fun, self._dof_operator_pre, self._preproc_grid) - if self._transposed: - self._dof_operator = self._dof_operator_pre.transpose() - else: - self._dof_operator = self._dof_operator_pre - - def dot(self, v, out=None): - """ - Applies the basis projection operator to the FE coefficients v. - - Parameters - ---------- - v : psydac.linalg.basic.Vector - Vector the operator shall be applied to. - - out : psydac.linalg.basic.Vector, optional - If given, the output will be written in-place into this vector. - - Returns - ------- - out : psydac.linalg.basic.Vector - The output (codomain) vector. - """ - - assert isinstance(v, Vector) - assert v.space == self.domain - - if out is None: - - if self.transposed: - # 1. apply inverse transposed inter-/histopolation matrix, 2. apply transposed dof operator - out = self.dof_operator.dot(self._P.solver.solve(v, transposed=True)) - else: - # 1. apply dof operator, 2. apply inverse inter-/histopolation matrix - out = self._P.solver.solve(self.dof_operator.dot(v)) - - else: - - assert isinstance(out, Vector) - assert out.space == self.codomain - - if self.transposed: - # 1. apply inverse transposed inter-/histopolation matrix, 2. apply transposed dof operator - self._P.solver.solve(v, out=self._tmp_dom, transposed=True) - self.dof_operator.dot(self._tmp_dom, out=out) - else: - # 1. apply dof operator, 2. apply inverse inter-/histopolation matrix - self.dof_operator.dot(v, out=self._tmp_codom) - self._P.solver.solve(self._tmp_codom, out=out) - - return out - - def transpose(self, conjugate=False): - """ - Returns the transposed operator. - """ - #conjugate not implemented - if self.transposed: - return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed, preproc_grid=self._preproc_grid, dof_mat=self._dof_operator.transpose()) - else : - return BasisProjectionOperator(self._P, self._V, self._fun, not self.transposed, preproc_grid=self._preproc_grid, dof_mat=self._dof_operator) - - @staticmethod - def assemble_mat(P, V, fun, dof_operator, preproc_grid=None): - """ - Assembles the tensor-product DOF matrix sigma_i(fun*Lambda_j), where i=(i1, i2, ...) and j=(j1, j2, ...) depending on the number of spatial dimensions (1d, 2d or 3d). - - Parameters - ---------- - P : GlobalProjector - The psydac global tensor product projector defining the space onto which the input shall be projected. - - V : TensorFemSpace | ProductFemSpace - The spline space which shall be projected. - - fun : list - Weight function(s) (callables) in a 2d list of shape corresponding to number of components of domain/codomain. - - Returns - ------- - dof_mat : StencilMatrix | BlockLinearOperator - Degrees of freedom matrix in the full tensor product setting. - """ - - # input space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(V, TensorFemSpace): - _Vspaces = [V.vector_space] - _V1ds = [V.spaces] - else: - _Vspaces = V.vector_space - _V1ds = [comp.spaces for comp in V.spaces] - - # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(P.space, TensorFemSpace): - _Wspaces = [P.space.vector_space] - _W1ds = [P.space.spaces] - else: - _Wspaces = P.space.vector_space - _W1ds = [comp.spaces for comp in P.space.spaces] - - # retrieve number of quadrature points of each component (=1 for interpolation) - _nqs = [[P.grid_x[comp][direction].shape[1] - for direction in range(V.ldim)] for comp in range(len(_W1ds))] - - # blocks of dof matrix - - i=0 - # ouptut vector space (codomain), row of block - for Wspace, W1d, nq, fun_line in zip(_Wspaces, _W1ds, _nqs, fun): - - _Wdegrees = [space.degree for space in W1d] - j=0 - - # input vector space (domain), column of block - for Vspace, V1d, f in zip(_Vspaces, _V1ds, fun_line): - # instantiate cell of block matrix - - if isinstance(dof_operator, BlockLinearOperator): - dofs_mat = dof_operator._blocks[i, j] - else : - dofs_mat = dof_operator - - _starts_in = np.array(dofs_mat.domain.starts) - _ends_in = np.array(dofs_mat.domain.ends) - _pads_in = np.array(dofs_mat.domain.pads) - - _starts_out = np.array(dofs_mat.codomain.starts) - _ends_out = np.array(dofs_mat.codomain.ends) - _pads_out = np.array(dofs_mat.codomain.pads) - - if isinstance(f,FemField): - #In case of Femfield we call a special kernel using the coeffs of the FF rather than evaluating the field and then calling the kernel - space_ff = f.space.vector_space - Vfd = f.space.spaces - _starts_c = np.array(space_ff.starts) - _ends_c = np.array(space_ff.ends) - _pads_c = np.array(space_ff.pads) - - if preproc_grid != None : - _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff = preproc_grid[i][j] - - else : - _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff = \ - prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) - - _ptsG = [pts.flatten() for pts in _ptsG] - _Vnbases = [space.nbasis for space in V1d] - f_coeffs = f.coeffs._data - kernel = getattr( - basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd_ff') - - kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, - _pads_out, _starts_c, _ends_c, _pads_c, *_wtsG, *_spans, *_bases, f_coeffs, *_spans_ff, - *_bases_ff, *_Vnbases, *_Wdegrees) - - - - else : - - if preproc_grid != None : - _ptsG, _wtsG, _spans, _bases = preproc_grid[i][j] - - else: - _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( - V1d, W1d, _starts_out, _ends_out, nq) - - _ptsG = [pts.flatten() for pts in _ptsG] - _Vnbases = [space.nbasis for space in V1d] - - # Evaluate weight function at quadrature points - pts = np.meshgrid(*_ptsG, indexing='ij') - - if isinstance(f, float) or isinstance(f, int): - shape_grid = tuple([len(pts_i) for pts_i in _ptsG]) - _fun_q = np.full(shape_grid, f) - else : - f = np.vectorize(f) - _fun_q = f(*pts) - - # Call the kernel if weight function is not zero - if np.any(np.abs(_fun_q) > 1e-14): - - kernel = getattr( - basis_projection_kernels, 'assemble_dofs_for_weighted_basisfuns_' + str(V.ldim) + 'd') - - kernel(dofs_mat._data, _starts_in, _ends_in, _pads_in, _starts_out, _ends_out, - _pads_out, _fun_q, *_wtsG, *_spans, *_bases, *_Vnbases, *_Wdegrees) - - j+=1 - i+=1 - - -def prepare_projection_of_basis(V1d, W1d, starts_out, ends_out, n_quad=None): - '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space. - - Parameters - ---------- - V1d : 3-list - Three SplineSpace objects from Psydac from the input space (to be projected). - - W1d : 3-list - Three SplineSpace objects from Psydac from the output space (projected onto). - - starts_out : 3-list - Global starting indices of process. - - ends_out : 3-list - Global ending indices of process. - - n_quad : 3_list - Number of quadrature points per histpolation interval. If not given, is set to V1d.degree + 1. - - Returns - ------- - ptsG : 3-tuple of 2d float arrays - Quadrature points (or Greville points for interpolation) in each dimension in format (interval, quadrature point). - - wtsG : 3-tuple of 2d float arrays - Quadrature weights (or ones for interpolation) in each dimension in format (interval, quadrature point). - - spans : 3-tuple of 2d int arrays - Knot span indices in each direction in format (n, nq). - - bases : 3-tuple of 3d float arrays - Values of p + 1 non-zero eta basis functions at quadrature points in format (n, nq, basis).''' - - import psydac.core.bsplines as bsp - - - x_grid, pts, wts, spans, bases = [], [], [], [], [] - - # Loop over direction, prepare point sets and evaluate basis functions - direction = 0 - for space_in, space_out, s, e in zip(V1d, W1d, starts_out, ends_out): - - greville_loc = space_out.greville[s: e + 1].copy() - histopol_loc = space_out.histopolation_grid[s: e + 2].copy() - - # make sure that greville points used for interpolation are in [0, 1] - #assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) - - # interpolation - if space_out.basis == 'B': - x_grid = greville_loc - pts += [greville_loc[:, None]] - wts += [np.ones(pts[-1].shape, dtype=float)] - - # histopolation - elif space_out.basis == 'M': - - x_grid = histopol_loc #space_out.histopolation_grid - - # Gauss - Legendre quadrature points and weights - if n_quad is None: - # products of basis functions are integrated exactly - nq = space_in.degree + 1 - else: - nq = n_quad[direction] - pts_loc, wts_loc = gauss_legendre(nq-1) - pts_loc, wts_loc = pts_loc[::-1], wts_loc[::-1] - global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) - #"roll" back points to the interval to ensure that the quadrature points are - #in the domain. Probably only usefull on periodic cases - roll_edges(space_out.domain, global_quad_x) - x = global_quad_x - w = global_quad_w - pts += [x] - wts += [w] - - # Knot span indices and V-basis functions evaluated at W-point sets - s, b = get_span_and_basis(pts[-1], space_in) - - spans += [s] - bases += [b] - - direction += 1 - return tuple(pts), tuple(wts), tuple(spans), tuple(bases) - -def prepare_projection_of_basis_ff(V1d, W1d, space_ff, starts_out, ends_out, n_quad=None): - '''Obtain knot span indices and basis functions evaluated at projection point sets of a given space, - for V1d and space_ff, in order to have the two at the same points (to avoid two calls). - - Parameters - ---------- - V1d : 3-list - Three SplineSpace objects from the input space (to be projected). - - W1d : 3-list - Three SplineSpace objects from the output space (projected onto). - - space_ff : 3-list - Three SplineSpace objects from the coefficient space. - - starts_out : 3-list - Global starting indices of process. - - ends_out : 3-list - Global ending indices of process. - - n_quad : 3_list - Number of quadrature points per histpolation interval. If not given, is set to V1d.degree + 1. - - Returns - ------- - ptsG : 3-tuple of 2d float arrays - Quadrature points (or Greville points for interpolation) in each dimension in format (interval, quadrature point). - - wtsG : 3-tuple of 2d float arrays - Quadrature weights (or ones for interpolation) in each dimension in format (interval, quadrature point). - - spans : 3-tuple of 2d int arrays - Knot span indices in each direction in format (n, nq). - - bases : 3-tuple of 3d float arrays - Values of p + 1 non-zero eta basis functions at quadrature points in format (n, nq, basis).''' - - import psydac.core.bsplines as bsp - - - x_grid, pts, wts, spans, bases, spans_c, bases_c = [], [], [], [], [], [], [] - - # Loop over direction, prepare point sets and evaluate basis functions - direction = 0 - for space_in, space_out, space_coeff, s, e in zip(V1d, W1d, space_ff, starts_out, ends_out): - - greville_loc = space_out.greville[s: e + 1].copy() - histopol_loc = space_out.histopolation_grid[s: e + 2].copy() - - # make sure that greville points used for interpolation are in [0, 1] - #assert np.all(np.logical_and(greville_loc >= 0., greville_loc <= 1.)) - - # interpolation - if space_out.basis == 'B': - x_grid = greville_loc - pts += [greville_loc[:, None]] - wts += [np.ones(pts[-1].shape, dtype=float)] - - # histopolation - elif space_out.basis == 'M': - - x_grid = histopol_loc #space_out.histopolation_grid - - # Gauss - Legendre quadrature points and weights - if n_quad is None: - # products of basis functions are integrated exactly - nq = space_in.degree + 1 - else: - nq = n_quad[direction] - pts_loc, wts_loc = gauss_legendre(nq-1) - pts_loc, wts_loc = pts_loc[::-1], wts_loc[::-1] - global_quad_x, global_quad_w = bsp.quadrature_grid(x_grid, pts_loc, wts_loc) - #"roll" back points to the interval to ensure that the quadrature points are - #in the domain. Probably only usefull on periodic cases - roll_edges(space_out.domain, global_quad_x) - x = global_quad_x - w = global_quad_w - pts += [x] - wts += [w] - # Knot span indices and V-basis functions evaluated at W-point sets - s, b = get_span_and_basis(pts[-1], space_in) - s_c, b_c = get_span_and_basis(pts[-1], space_coeff) - - #set boundary weights to zero in prescribed direction - spans += [s] - bases += [b] - spans_c +=[s_c] - bases_c +=[b_c] - - direction += 1 - return tuple(pts), tuple(wts), tuple(spans), tuple(bases), tuple(spans_c), tuple(bases_c) - - -def get_span_and_basis(pts, space): - '''Compute the knot span index and the values of p + 1 basis function at each point in pts. - - Parameters - ---------- - pts : np.array - 2d array of points (interval, quadrature point). - - space : SplineSpace - Psydac object, the 1d spline space to be projected. - - Returns - ------- - span : np.array - 2d array indexed by (n, nq), where n is the interval and nq is the quadrature point in the interval. - - basis : np.array - 3d array of values of basis functions indexed by (n, nq, basis function). - ''' - - import psydac.core.bsplines as bsp - - # Extract knot vectors, degree and kind of basis - T = space.knots - p = space.degree - - span = np.zeros(pts.shape, dtype=int) - basis = np.zeros((*pts.shape, p + 1), dtype=float) - - for n in range(pts.shape[0]): - for nq in range(pts.shape[1]): - # avoid 1. --> 0. for clamped interpolation - x = pts[n, nq] #% (1. + 1e-14) - span_tmp = bsp.find_span(T, p, x) - basis[n, nq, :] = bsp.basis_funs_all_ders( - T, p, x, span_tmp, 0, normalization=space.basis) - span[n, nq] = span_tmp # % space.nbasis - - return span, basis - - -def preprocess_grid(P, V): - """ - Gather the results of prepare_projection_of_basis for the different SplineSpaces composing a space, - the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid - computing several time the same quantities - - Parameters - ---------- - P : GlobalProjector - The psydac global tensor product projector defining the space onto which the input shall be projected. - - V : TensorFemSpace | ProductFemSpace - The spline space which shall be projected. - - Returns - ------- - preproc : List of List of Tuple - List of List containing the outputs of prepare_projection_of_basis applied to the differents spaces - """ - - # input space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(V, TensorFemSpace): - _Vspaces = [V.vector_space] - _V1ds = [V.spaces] - else: - _Vspaces = V.vector_space - _V1ds = [comp.spaces for comp in V.spaces] - - # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(P.space, TensorFemSpace): - _Wspaces = [P.space.vector_space] - _W1ds = [P.space.spaces] - else: - _Wspaces = P.space.vector_space - _W1ds = [comp.spaces for comp in P.space.spaces] - - # retrieve number of quadrature points of each component (=1 for interpolation) - _nqs = [[P.grid_x[comp][direction].shape[1] - for direction in range(V.ldim)] for comp in range(len(_W1ds))] - - # blocks of dof matrix - preproc = [] - - # ouptut vector space (codomain), row of block - for Wspace, W1d, nq in zip(_Wspaces, _W1ds, _nqs): - - line_pre = [] - # input vector space (domain), column of block - for Vspace, V1d in zip(_Vspaces, _V1ds): - - # instantiate cell of block matrix - dofs_mat = StencilMatrix( - Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) - - _starts_out = np.array(dofs_mat.codomain.starts) - _ends_out = np.array(dofs_mat.codomain.ends) - - _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( - V1d, W1d, _starts_out, _ends_out, nq) - line_pre.append((_ptsG, _wtsG, _spans, _bases)) - preproc.append(line_pre.copy()) - return preproc - - -def preprocess_grid_with_ff(P, V, f_type): - """ - Gather the results of prepare_projection_of_basis_ff for the different SplineSpaces composing a space, - the result of this function can then be passed when initialyzing a BasisProjectionOperator to avoid - computing several time the same quantities. - - Parameters - ---------- - P : GlobalProjector - The psydac global tensor product projector defining the space onto which the input shall be projected. - - V : TensorFemSpace | ProductFemSpace - The spline space which shall be projected. - - f_type : None | list - Instance of the callable that will be used in the projection basis. Only used to compute the grids - if some of those callable are FemFields to preocompute the grids. - - Returns - ------- - preproc : List of List of Tuple - List of List containing the outputs of prepare_projection_of_basis applied to the differents spaces - """ - - # input space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(V, TensorFemSpace): - _Vspaces = [V.vector_space] - _V1ds = [V.spaces] - else: - _Vspaces = V.vector_space - _V1ds = [comp.spaces for comp in V.spaces] - - # output space: 3d StencilVectorSpaces and 1d SplineSpaces of each component - if isinstance(P.space, TensorFemSpace): - _Wspaces = [P.space.vector_space] - _W1ds = [P.space.spaces] - else: - _Wspaces = P.space.vector_space - _W1ds = [comp.spaces for comp in P.space.spaces] - - # retrieve number of quadrature points of each component (=1 for interpolation) - _nqs = [[P.grid_x[comp][direction].shape[1] - for direction in range(V.ldim)] for comp in range(len(_W1ds))] - - # blocks of dof matrix - preproc = [] - - # ouptut vector space (codomain), row of block - for Wspace, W1d, nq, f_line in zip(_Wspaces, _W1ds, _nqs, f_type): - line_pre = [] - # input vector space (domain), column of block - for Vspace, V1d, f in zip(_Vspaces, _V1ds, f_line): - - - # instantiate cell of block matrix - dofs_mat = StencilMatrix( - Vspace, Wspace, backend=PSYDAC_BACKEND_GPYCCEL) - - _starts_out = np.array(dofs_mat.codomain.starts) - _ends_out = np.array(dofs_mat.codomain.ends) - - if isinstance(f,FemField): - - Vfd = f.space.spaces - _ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff = \ - prepare_projection_of_basis_ff(V1d, W1d, Vfd, _starts_out, _ends_out, nq) - - line_pre.append((_ptsG, _wtsG, _spans, _bases, _spans_ff, _bases_ff)) - - else : - _ptsG, _wtsG, _spans, _bases = prepare_projection_of_basis( - V1d, W1d, _starts_out, _ends_out,nq) - line_pre.append((_ptsG, _wtsG, _spans, _bases)) - preproc.append(deepcopy(line_pre)) - return preproc \ No newline at end of file diff --git a/psydac/feec/tests/test_basis_projectors.py b/psydac/feec/tests/test_basis_projectors.py deleted file mode 100644 index f45eed4f5..000000000 --- a/psydac/feec/tests/test_basis_projectors.py +++ /dev/null @@ -1,573 +0,0 @@ -from sympde.topology import Square, Domain -from psydac.feec.multipatch.api import discretize -from sympde.topology import Derham -from psydac.feec.basis_projectors import BasisProjectionOperator -from psydac.fem.basic import FemField -import matplotlib.pyplot as plt -import numpy as np -import os - -import pytest - -try: - mesh_dir = os.environ['PSYDAC_MESH_DIR'] - -except: - base_dir = os.path.dirname(os.path.realpath(__file__)) - base_dir = os.path.join(base_dir, '..', '..', '..') - mesh_dir = os.path.join(base_dir, 'mesh') - -@pytest.mark.parametrize('nc', [4, 8, 15]) -@pytest.mark.parametrize('deg', [2,3]) -@pytest.mark.parametrize('perio', [[True, True], [True, False], [False, False]]) - -def test_basis_projector_2d(nc, deg, perio): - ### INITIALISATION ### - domain = Square() - ncells = (nc,nc) - degree = (deg,deg) - nquads = [2*(d + 1) for d in degree] - domain_h = discretize(domain, ncells=ncells, periodic=perio) - - derham = Derham(domain, ["H1", "Hdiv", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - Xh = derham_h.Vvec - - P0, P1, P2, PX = derham_h.projectors(nquads=nquads) - - #Bunch of (1,1)-periodic function for tests - f_1 = lambda x, y : x*(x-1)+3 - f_2 = lambda x, y : np.cos(2*np.pi*x) - f_3 = lambda x, y : np.sin(2*np.pi*x)*y*(y-1) - f_4 = lambda x, y : x*(x-1)*y*(y-1) - f_5 = lambda x, y : np.cos(2*np.pi*x)*np.sin(2*np.pi*y) - f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) - f_7 = lambda x, y : np.exp(y)+np.exp(1-y) - - ### TEST V0->V0 ### - fun = [[f_1]] - f_test = P0(f_2) - P0_0fv = BasisProjectionOperator(P0, V0h, fun) - sol_with_op = P0_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_1(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V0 ### - fun = [[f_3,f_4]] - f_test = P1([f_1, f_5]) - P1_0fv = BasisProjectionOperator(P0, V1h, fun) - sol_with_op = P1_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V0 ### - fun = [[f_6]] - f_test = P2(f_3) - P2_0fv = BasisProjectionOperator(P0, V2h, fun) - sol_with_op = P2_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_6(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V0 ### - fun = [[f_4,f_1]] - f_test = PX([f_4,f_5]) - PX_0fv = BasisProjectionOperator(P0, Xh, fun) - sol_with_op = PX_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0 -> V1 ### - fun = [[f_2],[f_6]] - f_test = P0(f_4) - P0_1fv = BasisProjectionOperator(P1, V0h, fun) - sol_with_op = P0_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V1 ### - fun = [[f_2,f_4],[f_5,f_1]] - f_test = P1([f_3,f_7]) - P1_1fv = BasisProjectionOperator(P1, V1h, fun) - sol_with_op = P1_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V1 ### - fun = [[f_4],[f_7]] - f_test = P2(f_1) - P2_1fv = BasisProjectionOperator(P1, V2h, fun) - sol_with_op = P2_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V1 ### - fun = [[f_3,f_6],[f_5,f_2]] - f_test = PX([f_3,f_1]) - PX_1fv = BasisProjectionOperator(P1, Xh, fun) - sol_with_op = PX_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0->V2 ### - fun = [[f_4]] - P0_2fv = BasisProjectionOperator(P2, V0h, fun) - f_test = P0(f_2) - sol_with_op = P0_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_4(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V2 ### - fun = [[f_5,f_2]] - f_test = P1([f_1, f_5]) - P1_2fv = BasisProjectionOperator(P2, V1h, fun) - sol_with_op = P1_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V2 ### - fun = [[f_1]] - f_test = P2(f_3) - P2_2fv = BasisProjectionOperator(P2, V2h, fun) - sol_with_op = P2_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_1(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V2 ### - fun = [[f_3,f_7]] - f_test = PX([f_4,f_5]) - PX_2fv = BasisProjectionOperator(P2, Xh, fun) - sol_with_op = PX_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0 -> X ### - fun = [[f_4],[f_1]] - f_test = P0(f_2) - P0_Xfv = BasisProjectionOperator(PX, V0h, fun) - sol_with_op = P0_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> X ### - fun = [[f_1,f_3],[f_5,f_7]] - f_test = P1([f_4,f_5]) - P1_Xfv = BasisProjectionOperator(PX, V1h, fun) - sol_with_op = P1_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> X ### - fun = [[f_2],[f_6]] - f_test = P2(f_1) - P2_Xfv = BasisProjectionOperator(PX, V2h, fun) - sol_with_op = P2_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> X ### - fun = [[f_1,f_2],[f_3,f_4]] - f_test = PX([f_5,f_6]) - PX_Xfv = BasisProjectionOperator(PX, Xh, fun) - sol_with_op = PX_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), - lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ###TEST WITH FemField as parameter### - ### X->V0 with V1 field ### - pf = P1([f_4,f_1]) - fun = [[pf[0],pf[1]]] - f_test = PX([f_4,f_5]) - PX_0fv = BasisProjectionOperator(P0, Xh, fun) - sol_with_op = PX_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### X->V2 with V1 field ### - pf = P1([f_2,f_6]) - fun = [[pf[0],pf[1]]] - f_test = PX([f_3,f_1]) - P2_0fv = BasisProjectionOperator(P2, Xh, fun) - sol_with_op = P2_0fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - -@pytest.mark.parametrize('nc', [4, 8, 15]) -@pytest.mark.parametrize('deg', [2,3]) -@pytest.mark.parametrize('perio', [[True, True], [True, False], [False, False]]) - -def test_basis_projector_non_unit_square_2d(nc, deg, perio): - ### INITIALISATION ### - domain = Square('Omega', bounds1 = (0,1), bounds2 = (-1,1)) - ncells = (nc,nc) - degree = (deg,deg) - nquads = [2*(d + 1) for d in degree] - domain_h = discretize(domain, ncells=ncells, periodic=perio) - - derham = Derham(domain, ["H1", "Hdiv", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - Xh = derham_h.Vvec - - P0, P1, P2, PX = derham_h.projectors(nquads=nquads) - - #Bunch of (1,1)-periodic function for tests - f_1 = lambda x, y : x*(x-1)+3 - f_2 = lambda x, y : np.cos(2*np.pi*x) - f_3 = lambda x, y : np.sin(2*np.pi*x)*y*(y-1) - f_4 = lambda x, y : x*(x-1)*y*(y-1) - f_5 = lambda x, y : np.cos(2*np.pi*x)*np.sin(2*np.pi*y) - f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) - f_7 = lambda x, y : np.exp(y)+np.exp(1-y) - - ### TEST V0->V0 ### - fun = [[f_2]] - f_test = P0(f_1) - P0_0fv = BasisProjectionOperator(P0, V0h, fun) - sol_with_op = P0_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_2(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V0 ### - fun = [[f_3,f_4]] - f_test = P1([f_5, f_1]) - P1_0fv = BasisProjectionOperator(P0, V1h, fun) - sol_with_op = P1_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V0 ### - fun = [[f_6]] - f_test = P2(f_3) - P2_0fv = BasisProjectionOperator(P0, V2h, fun) - sol_with_op = P2_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_6(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V0 ### - fun = [[f_4,f_1]] - f_test = PX([f_4,f_5]) - PX_0fv = BasisProjectionOperator(P0, Xh, fun) - sol_with_op = PX_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0 -> V1 ### - fun = [[f_2],[f_6]] - f_test = P0(f_4) - #x_array = np.linspace(-1,1,50) - #ex_array = [f_4(x,0.7) for x in x_array] - #p_array = [f_test(x,0.7) for x in x_array] - #plt.plot(x_array, ex_array) - #plt.plot(x_array, p_array) - #plt.show() - P0_1fv = BasisProjectionOperator(P1, V0h, fun) - sol_with_op = P0_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V1 ### - fun = [[f_2,f_4],[f_5,f_1]] - f_test = P1([f_3,f_7]) - P1_1fv = BasisProjectionOperator(P1, V1h, fun) - sol_with_op = P1_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V1 ### - fun = [[f_4],[f_7]] - f_test = P2(f_1) - P2_1fv = BasisProjectionOperator(P1, V2h, fun) - sol_with_op = P2_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V1 ### - fun = [[f_3,f_6],[f_5,f_2]] - f_test = PX([f_3,f_1]) - PX_1fv = BasisProjectionOperator(P1, Xh, fun) - sol_with_op = PX_1fv.dot(f_test.coeffs) - sol_no_op = P1([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0->V2 ### - fun = [[f_4]] - P0_2fv = BasisProjectionOperator(P2, V0h, fun) - f_test = P0(f_2) - sol_with_op = P0_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_4(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V2 ### - fun = [[f_5,f_2]] - f_test = P1([f_1, f_5]) - P1_2fv = BasisProjectionOperator(P2, V1h, fun) - sol_with_op = P1_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V2 ### - fun = [[f_1]] - f_test = P2(f_3) - P2_2fv = BasisProjectionOperator(P2, V2h, fun) - sol_with_op = P2_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_1(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V2 ### - fun = [[f_3,f_7]] - f_test = PX([f_4,f_5]) - PX_2fv = BasisProjectionOperator(P2, Xh, fun) - sol_with_op = PX_2fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0 -> X ### - fun = [[f_4],[f_1]] - f_test = P0(f_2) - P0_Xfv = BasisProjectionOperator(PX, V0h, fun) - sol_with_op = P0_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> X ### - fun = [[f_1,f_3],[f_5,f_7]] - f_test = P1([f_4,f_5]) - P1_Xfv = BasisProjectionOperator(PX, V1h, fun) - sol_with_op = P1_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> X ### - fun = [[f_2],[f_6]] - f_test = P2(f_1) - P2_Xfv = BasisProjectionOperator(PX, V2h, fun) - sol_with_op = P2_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> X ### - fun = [[f_1,f_2],[f_3,f_4]] - f_test = PX([f_5,f_6]) - PX_Xfv = BasisProjectionOperator(PX, Xh, fun) - sol_with_op = PX_Xfv.dot(f_test.coeffs) - sol_no_op = PX([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), - lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ###TEST WITH FemField as parameter### - ### X->V0 with V1 field ### - pf = P1([f_4,f_1]) - fun = [[pf[0],pf[1]]] - f_test = PX([f_4,f_5]) - PX_0fv = BasisProjectionOperator(P0, Xh, fun) - sol_with_op = PX_0fv.dot(f_test.coeffs) - sol_no_op = P0(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### X->V2 with V1 field ### - pf = P1([f_2,f_6]) - fun = [[pf[0],pf[1]]] - f_test = PX([f_3,f_1]) - P2_0fv = BasisProjectionOperator(P2, Xh, fun) - sol_with_op = P2_0fv.dot(f_test.coeffs) - sol_no_op = P2(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - -@pytest.mark.parametrize('deg', [2,3]) -@pytest.mark.parametrize('filename', ['identity_2d.h5', 'collela_2d.h5']) - -def test_basis_projector_2d_mapping(deg, filename): - #We cannot really test on physical domains since the FemFields can only be evaluated on the ref domain - ### INITIALISATION ### - meshname = os.path.join(mesh_dir, filename) - domain = Domain.from_file(meshname) - degree = (deg,deg) - nquads = [2*(d + 1) for d in degree] - domain_h = discretize(domain, filename=meshname) - mapping = domain.mapping - F = mapping.get_callable_mapping() - derham = Derham(domain, ["H1", "Hdiv", "L2"]) - derham_h = discretize(derham, domain_h, get_vec = True) - V0h = derham_h.V0 - V1h = derham_h.V1 - V2h = derham_h.V2 - Xh = derham_h.Vvec - - P0, P1, P2, PX = derham_h.projectors(nquads=nquads) - P0_ref, P1_ref, P2_ref, PX_ref = derham_h.projectors(nquads=nquads, get_reference=True) - - #Bunch of function for tests - f_1 = lambda x, y : x*(x-1)+3 - f_2 = lambda x, y : np.cos(2*np.pi*x) - f_3 = lambda x, y : np.sin(2*np.pi*x)*y*(y-1) - f_4 = lambda x, y : x*(x-1)*y*(y-1) - f_5 = lambda x, y : np.cos(2*np.pi*x)*np.sin(2*np.pi*y) - f_6 = lambda x, y : x*(x-1)*x*(x-1)+3*y*(y-1) - f_7 = lambda x, y : np.exp(y)+np.exp(1-y) - - ### TEST V0->V0 ### - fun = [[f_1]] - f_test = P0(f_2) - P0_0fv = BasisProjectionOperator(P0_ref, V0h, fun) - sol_with_op = P0_0fv.dot(f_test.coeffs) - sol_no_op = P0_ref(lambda x, y : f_1(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V0 ### - fun = [[f_3,f_4]] - f_test = P1([f_1, f_5]) - P1_0fv = BasisProjectionOperator(P0_ref, V1h, fun) - sol_with_op = P1_0fv.dot(f_test.coeffs) - sol_no_op = P0_ref(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V0 ### - fun = [[f_6]] - f_test = P2(f_3) - P2_0fv = BasisProjectionOperator(P0_ref, V2h, fun) - sol_with_op = P2_0fv.dot(f_test.coeffs) - sol_no_op = P0_ref(lambda x, y : f_6(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V0 ### - fun = [[f_4,f_1]] - f_test = PX([f_4,f_5]) - PX_0fv = BasisProjectionOperator(P0_ref, Xh, fun) - sol_with_op = PX_0fv.dot(f_test.coeffs) - sol_no_op = P0_ref(lambda x, y : f_4(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0 -> V1 ### - fun = [[f_2],[f_6]] - f_test = P0(f_4) - P0_1fv = BasisProjectionOperator(P1_ref, V0h, fun) - sol_with_op = P0_1fv.dot(f_test.coeffs) - sol_no_op = P1_ref([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V1 ### - fun = [[f_2,f_4],[f_5,f_1]] - f_test = P1([f_3,f_7]) - P1_1fv = BasisProjectionOperator(P1_ref, V1h, fun) - sol_with_op = P1_1fv.dot(f_test.coeffs) - sol_no_op = P1_ref([lambda x, y : f_2(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_1(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V1 ### - fun = [[f_4],[f_7]] - f_test = P2(f_1) - P2_1fv = BasisProjectionOperator(P1_ref, V2h, fun) - sol_with_op = P2_1fv.dot(f_test.coeffs) - sol_no_op = P1_ref([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_7(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V1 ### - fun = [[f_3,f_6],[f_5,f_2]] - f_test = PX([f_3,f_1]) - PX_1fv = BasisProjectionOperator(P1_ref, Xh, fun) - sol_with_op = PX_1fv.dot(f_test.coeffs) - sol_no_op = P1_ref([lambda x, y : f_3(x,y)*f_test[0](x,y)+f_6(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0->V2 ### - fun = [[f_4]] - P0_2fv = BasisProjectionOperator(P2_ref, V0h, fun) - f_test = P0(f_2) - sol_with_op = P0_2fv.dot(f_test.coeffs) - sol_no_op = P2_ref(lambda x, y : f_4(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> V2 ### - fun = [[f_5,f_2]] - f_test = P1([f_1, f_5]) - P1_2fv = BasisProjectionOperator(P2_ref, V1h, fun) - sol_with_op = P1_2fv.dot(f_test.coeffs) - sol_no_op = P2_ref(lambda x, y : f_5(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> V2 ### - fun = [[f_1]] - f_test = P2(f_3) - P2_2fv = BasisProjectionOperator(P2_ref, V2h, fun) - sol_with_op = P2_2fv.dot(f_test.coeffs) - sol_no_op = P2_ref(lambda x, y : f_1(x,y)*f_test(x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> V2 ### - fun = [[f_3,f_7]] - f_test = PX([f_4,f_5]) - PX_2fv = BasisProjectionOperator(P2_ref, Xh, fun) - sol_with_op = PX_2fv.dot(f_test.coeffs) - sol_no_op = P2_ref(lambda x, y : f_3(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V0 -> X ### - fun = [[f_4],[f_1]] - f_test = P0(f_2) - P0_Xfv = BasisProjectionOperator(PX_ref, V0h, fun) - sol_with_op = P0_Xfv.dot(f_test.coeffs) - sol_no_op = PX_ref([lambda x, y : f_4(x,y)*f_test(x,y),lambda x, y : f_1(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V1 -> X ### - fun = [[f_1,f_3],[f_5,f_7]] - f_test = P1([f_4,f_5]) - P1_Xfv = BasisProjectionOperator(PX_ref, V1h, fun) - sol_with_op = P1_Xfv.dot(f_test.coeffs) - sol_no_op = PX_ref([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_3(x,y)*f_test[1](x,y), - lambda x, y : f_5(x,y)*f_test[0](x,y)+f_7(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST V2 -> X ### - fun = [[f_2],[f_6]] - f_test = P2(f_1) - P2_Xfv = BasisProjectionOperator(PX_ref, V2h, fun) - sol_with_op = P2_Xfv.dot(f_test.coeffs) - sol_no_op = PX_ref([lambda x, y : f_2(x,y)*f_test(x,y),lambda x, y : f_6(x,y)*f_test(x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### TEST X -> X ### - fun = [[f_1,f_2],[f_3,f_4]] - f_test = PX([f_5,f_6]) - PX_Xfv = BasisProjectionOperator(PX_ref, Xh, fun) - sol_with_op = PX_Xfv.dot(f_test.coeffs) - sol_no_op = PX_ref([lambda x, y : f_1(x,y)*f_test[0](x,y)+f_2(x,y)*f_test[1](x,y), - lambda x, y : f_3(x,y)*f_test[0](x,y)+f_4(x,y)*f_test[1](x,y)]) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ###TEST WITH FemField as parameter### - ### X->V0 with V1 field ### - pf = P1([f_4,f_1]) - fun = [[pf[0],pf[1]]] - f_test = PX([f_4,f_5]) - PX_0fv = BasisProjectionOperator(P0_ref, Xh, fun) - sol_with_op = PX_0fv.dot(f_test.coeffs) - sol_no_op = P0_ref(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - - ### X->V2 with V1 field ### - pf = P1([f_2,f_6]) - fun = [[pf[0],pf[1]]] - f_test = PX([f_3,f_1]) - P2_0fv = BasisProjectionOperator(P2_ref, Xh, fun) - sol_with_op = P2_0fv.dot(f_test.coeffs) - sol_no_op = P2_ref(lambda x, y : pf[0](x,y)*f_test[0](x,y)+pf[1](x,y)*f_test[1](x,y)) - assert(np.allclose(sol_with_op.toarray(),sol_no_op.coeffs.toarray(),1e-12)) - -if __name__ == '__main__': - test_basis_projector_2d(4, 2, [False,False]) - test_basis_projector_2d_mapping(2, 'collela_2d.h5') \ No newline at end of file From d747470aa57f6e1683b42d4761b8cc59c2eb1dbe Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 12 Oct 2023 13:25:35 +0200 Subject: [PATCH 62/77] add tests for the global projectors + Hvec in any dimension + solved the issue with the symbolic space --- psydac/api/discretization.py | 4 +- psydac/feec/tests/test_global_projectors.py | 154 ++++++++++++++++++++ psydac/fem/vector.py | 3 +- 3 files changed, 157 insertions(+), 4 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index fca7ccc24..34868ac7c 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -110,9 +110,7 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): if get_vec: V0h = spaces[0] - X = VectorFunctionSpace('X', domain_h.domain, kind='h1') - Xh = VectorFemSpace([V0h]*ldim) - Xh.symbolic_space = X + Xh = VectorFemSpace(*([V0h]*ldim)) spaces.append(Xh) return DiscreteDerham(mapping, get_vec, *spaces) diff --git a/psydac/feec/tests/test_global_projectors.py b/psydac/feec/tests/test_global_projectors.py index f3b96bd88..e5a6b42ca 100644 --- a/psydac/feec/tests/test_global_projectors.py +++ b/psydac/feec/tests/test_global_projectors.py @@ -7,6 +7,10 @@ from psydac.fem.tensor import TensorFemSpace from psydac.feec.global_projectors import Projector_H1, Projector_L2 from psydac.ddm.cart import DomainDecomposition +from sympde.topology import Square, Cube +from psydac.api.discretization import discretize +from sympde.topology import element_of, Derham + #============================================================================== @pytest.mark.parametrize('domain', [(0, 2*np.pi)]) @@ -83,6 +87,146 @@ def test_L2_projector_1d(domain, ncells, degree, periodic, nquads): maxnorm_error = abs(vals_u1 - vals_f).max() print(ncells, maxnorm_error) # assert maxnorm_error <= 1e-14 + + #============================================================================== +@pytest.mark.parametrize('ncells', [200,200]) +@pytest.mark.parametrize('degree', [[2,2], [2,3], [3,3]]) +@pytest.mark.parametrize('periodic', [[False, False], [True, True]]) + +def test_derham_projector_2d_hdiv(ncells, degree, periodic): + + domain = Square('Omega', bounds1 = (0,2*np.pi), bounds2 = (0,2*np.pi)) + domain_h = discretize(domain, ncells=ncells, periodic=periodic) + + derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + P0, P1, P2, PX = derham_h.projectors() + + # Projector onto H1 space (1D interpolation) + + # Function to project + f1 = lambda xi1, xi2 : np.sin( xi1 + 0.5 ) * np.cos( xi2 + 0.3 ) + f2 = lambda xi1, xi2 : np.cos( xi1 + 0.5 ) * np.sin( xi2 - 0.2 ) + + # Compute the projection + u0 = P0(f1) + u2 = P2(f1) + u1 = P1((f1,f2)) + ux = PX((f1,f2)) + + # Create evaluation grid, and check if u0(x) == f(x) + xgrid = np.linspace(0, 2*np.pi, num=51) + vals_u0 = np.array([[u0(x, y) for x in xgrid] for y in xgrid]) + vals_u1_1 = np.array([[u1(x, y)[0] for x in xgrid] for y in xgrid]) + vals_u2 = np.array([[u2(x, y) for x in xgrid] for y in xgrid]) + vals_ux_1 = np.array([[ux(x, y)[0] for x in xgrid] for y in xgrid]) + vals_f = np.array([[f1(x, y) for x in xgrid] for y in xgrid]) + + # Test if max-norm of error is <= TOL + maxnorm_error = abs(vals_u0 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u1_1 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u2 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_ux_1 - vals_f).max() + print(ncells, maxnorm_error) +# assert maxnorm_error <= 1e-14 + +#============================================================================== +@pytest.mark.parametrize('ncells', [200,200]) +@pytest.mark.parametrize('degree', [[2,2], [2,3], [3,3]]) +@pytest.mark.parametrize('periodic', [[False, False], [True, False] ,[True, True]]) + +def test_derham_projector_2d_hcurl(ncells, degree, periodic): + + domain = Square('Omega', bounds1 = (0,2*np.pi), bounds2 = (0,2*np.pi)) + domain_h = discretize(domain, ncells=ncells, periodic=periodic) + + derham = Derham(domain, ["H1", "Hcurl", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + P0, P1, P2, PX = derham_h.projectors() + + # Projector onto H1 space (1D interpolation) + + # Function to project + f1 = lambda xi1, xi2 : np.sin( xi1 + 0.5 ) * np.cos( xi2 + 0.3 ) + f2 = lambda xi1, xi2 : np.cos( xi1 + 0.5 ) * np.sin( xi2 - 0.2 ) + + # Compute the projection + u0 = P0(f1) + u2 = P2(f1) + u1 = P1((f1,f2)) + ux = PX((f1,f2)) + + # Create evaluation grid, and check if u0(x) == f(x) + xgrid = np.linspace(0, 2*np.pi, num=51) + vals_u0 = np.array([[u0(x, y) for x in xgrid] for y in xgrid]) + vals_u1_1 = np.array([[u1(x, y)[0] for x in xgrid] for y in xgrid]) + vals_u2 = np.array([[u2(x, y) for x in xgrid] for y in xgrid]) + vals_ux_1 = np.array([[ux(x, y)[0] for x in xgrid] for y in xgrid]) + vals_f = np.array([[f1(x, y) for x in xgrid] for y in xgrid]) + + # Test if max-norm of error is <= TOL + maxnorm_error = abs(vals_u0 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u1_1 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u2 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_ux_1 - vals_f).max() + print(ncells, maxnorm_error) +# assert maxnorm_error <= 1e-14 + +#============================================================================== +@pytest.mark.parametrize('ncells', [30,30,30]) +@pytest.mark.parametrize('degree', [[2,2,2], [2,3,2], [3,3,3]]) +@pytest.mark.parametrize('periodic', [[False, False, False], [True, True, True]]) + +def test_derham_projector_3d(ncells, degree, periodic): + + domain = Cube('Omega', bounds1 = (0,2*np.pi), bounds2 = (0,2*np.pi), bounds3 = (0,2*np.pi)) + domain_h = discretize(domain, ncells=ncells, periodic=periodic) + + derham = Derham(domain) + derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + P0, P1, P2, P3, PX = derham_h.projectors() + + # Projector onto H1 space (1D interpolation) + + # Function to project + f1 = lambda xi1, xi2, xi3 : np.sin( xi1 + 0.5 ) * np.cos( xi2 + 0.3 ) * np.sin( 2 * xi3 ) + f2 = lambda xi1, xi2, xi3 : np.cos( xi1 + 0.5 ) * np.sin( xi2 - 0.2 ) * np.cos( xi3 ) + f3 = lambda xi1, xi2, xi3 : np.cos( xi1 + 0.7 ) * np.sin( 2*xi2 - 0.2 ) * np.cos( xi3 ) + + # Compute the projection + u0 = P0(f1) + u3 = P3(f1) + u1 = P1((f1,f2,f3)) + u2 = P2((f1,f2,f3)) + ux = PX((f1,f2,f3)) + + # Create evaluation grid, and check if u0(x) == f(x) + xgrid = np.linspace(0, 2*np.pi, num=21) + vals_u0 = np.array([[[u0(x, y, z) for x in xgrid] for y in xgrid] for z in xgrid]) + vals_u1_1 = np.array([[[u1(x, y, z)[0] for x in xgrid] for y in xgrid] for z in xgrid]) + vals_u2_1 = np.array([[[u2(x, y, z)[0] for x in xgrid] for y in xgrid] for z in xgrid]) + vals_ux_1 = np.array([[[ux(x, y, z)[0] for x in xgrid] for y in xgrid] for z in xgrid]) + vals_u3 = np.array([[[u3(x, y, z) for x in xgrid] for y in xgrid] for z in xgrid]) + vals_f = np.array([[[f1(x, y, z) for x in xgrid] for y in xgrid] for z in xgrid]) + + # Test if max-norm of error is <= TOL + maxnorm_error = abs(vals_u0 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u1_1 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u2_1 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_u3 - vals_f).max() + print(ncells, maxnorm_error) + maxnorm_error = abs(vals_ux_1 - vals_f).max() + print(ncells, maxnorm_error) +# assert maxnorm_error <= 1e-14 #============================================================================== if __name__ == '__main__': @@ -98,3 +242,13 @@ def test_L2_projector_1d(domain, ncells, degree, periodic, nquads): nquads = degree for nc in ncells: test_L2_projector_1d(domain, nc, degree, periodic, nquads) + + for nc in ncells: + test_derham_projector_2d_hdiv([nc, nc], [degree, degree], [periodic, periodic]) + + for nc in ncells : + test_derham_projector_2d_hcurl([nc, nc], [degree, degree], [periodic, periodic]) + + for nc in ncells[:3] : + test_derham_projector_3d([nc, nc, nc], [degree, degree, degree], [periodic, periodic, periodic]) + diff --git a/psydac/fem/vector.py b/psydac/fem/vector.py index 4ecc896a6..bca983331 100644 --- a/psydac/fem/vector.py +++ b/psydac/fem/vector.py @@ -58,7 +58,8 @@ def __init__( self, *spaces ): self._symbolic_space = None if all(s.symbolic_space for s in spaces): - self._symbolic_space = reduce(lambda x,y:x.symbolic_space*y.symbolic_space, spaces) + symbolic_spaces = [s.symbolic_space for s in spaces] + self._symbolic_space = reduce(lambda x,y:x*y, symbolic_spaces) self._vector_space = BlockVectorSpace(*[V.vector_space for V in self.spaces]) self._refined_space = {} From ecb267b306b8e7b1d9729ee35f89fc450d4eb487 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 12 Oct 2023 13:42:06 +0200 Subject: [PATCH 63/77] we still need to provide a symbolic space because of sympde --- psydac/api/discretization.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 34868ac7c..da6c87931 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -109,12 +109,14 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): for V, basis in zip(derham.spaces, bases)] if get_vec: + X = VectorFunctionSpace('X', domain_h.domain, kind='h1') V0h = spaces[0] Xh = VectorFemSpace(*([V0h]*ldim)) + Xh.symbolic_space = X + #We still need to specify the symbolic space because of "_recursive_element_of" not implemented in sympde spaces.append(Xh) return DiscreteDerham(mapping, get_vec, *spaces) - #============================================================================== def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): """ From 5e630706b6c3674a54662d14d7905e237776a85a Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 12 Oct 2023 14:44:22 +0200 Subject: [PATCH 64/77] error in parametrization of tests --- psydac/feec/tests/test_global_projectors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/psydac/feec/tests/test_global_projectors.py b/psydac/feec/tests/test_global_projectors.py index e5a6b42ca..62aacda50 100644 --- a/psydac/feec/tests/test_global_projectors.py +++ b/psydac/feec/tests/test_global_projectors.py @@ -89,7 +89,7 @@ def test_L2_projector_1d(domain, ncells, degree, periodic, nquads): # assert maxnorm_error <= 1e-14 #============================================================================== -@pytest.mark.parametrize('ncells', [200,200]) +@pytest.mark.parametrize('ncells', [[200,200]]) @pytest.mark.parametrize('degree', [[2,2], [2,3], [3,3]]) @pytest.mark.parametrize('periodic', [[False, False], [True, True]]) @@ -134,7 +134,7 @@ def test_derham_projector_2d_hdiv(ncells, degree, periodic): # assert maxnorm_error <= 1e-14 #============================================================================== -@pytest.mark.parametrize('ncells', [200,200]) +@pytest.mark.parametrize('ncells', [[200,200]]) @pytest.mark.parametrize('degree', [[2,2], [2,3], [3,3]]) @pytest.mark.parametrize('periodic', [[False, False], [True, False] ,[True, True]]) @@ -179,7 +179,7 @@ def test_derham_projector_2d_hcurl(ncells, degree, periodic): # assert maxnorm_error <= 1e-14 #============================================================================== -@pytest.mark.parametrize('ncells', [30,30,30]) +@pytest.mark.parametrize('ncells', [[30,30,30]]) @pytest.mark.parametrize('degree', [[2,2,2], [2,3,2], [3,3,3]]) @pytest.mark.parametrize('periodic', [[False, False, False], [True, True, True]]) From d713e5f5d9d2d2c8a246e60a63ecfa3b8b9303a7 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 13 Oct 2023 11:59:41 +0200 Subject: [PATCH 65/77] update name after review --- psydac/api/discretization.py | 6 +++--- psydac/api/feec.py | 12 ++++++------ psydac/feec/pull_push.py | 9 ++++----- psydac/feec/tests/test_global_projectors.py | 6 +++--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index da6c87931..1a6fbc576 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -82,7 +82,7 @@ def change_dtype(V, dtype): return V #============================================================================== -def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): +def discretize_derham(derham, domain_h, get_H1vec_space = False, *args, **kwargs): """ Create a discrete De Rham sequence by creating the spaces and then initiating DiscreteDerham object. @@ -108,7 +108,7 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): spaces = [discretize_space(V, domain_h, basis=basis, **kwargs) \ for V, basis in zip(derham.spaces, bases)] - if get_vec: + if get_H1vec_space: X = VectorFunctionSpace('X', domain_h.domain, kind='h1') V0h = spaces[0] Xh = VectorFemSpace(*([V0h]*ldim)) @@ -116,7 +116,7 @@ def discretize_derham(derham, domain_h, get_vec = False, *args, **kwargs): #We still need to specify the symbolic space because of "_recursive_element_of" not implemented in sympde spaces.append(Xh) - return DiscreteDerham(mapping, get_vec, *spaces) + return DiscreteDerham(mapping, get_H1vec_space, *spaces) #============================================================================== def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): """ diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 50afd6d84..9a0f14600 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -7,8 +7,8 @@ from psydac.feec.global_projectors import Projector_H1, Projector_Hcurl, Projector_H1vec from psydac.feec.global_projectors import Projector_Hdiv, Projector_L2 from psydac.feec.pull_push import pull_1d_h1, pull_1d_l2 -from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_v -from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_v +from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_vec +from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_vec __all__ = ('DiscreteDerham',) @@ -29,11 +29,11 @@ class DiscreteDerham(BasicDiscrete): *spaces : list of The discrete spaces of the De Rham sequence """ - def __init__(self, mapping, get_vec=False, *spaces): + def __init__(self, mapping, get_H1vec_space=False, *spaces): assert (mapping is None) or isinstance(mapping, Mapping) - self.has_vec = get_vec + self.has_vec = get_H1vec_space if self.has_vec : dim = len(spaces) - 2 @@ -168,7 +168,7 @@ def projectors(self, *, kind='global', nquads=None): elif kind == 'hdiv': P1_m = lambda f: P1(pull_2d_hdiv(f, self.callable_mapping)) if self.has_vec : - Pvec_m = lambda f: Pvec(pull_2d_v(f, self.callable_mapping)) + Pvec_m = lambda f: Pvec(pull_2d_vec(f, self.callable_mapping)) return P0_m, P1_m, P2_m, Pvec_m else : return P0_m, P1_m, P2_m @@ -191,7 +191,7 @@ def projectors(self, *, kind='global', nquads=None): P2_m = lambda f: P2(pull_3d_hdiv (f, self.callable_mapping)) P3_m = lambda f: P3(pull_3d_l2 (f, self.callable_mapping)) if self.has_vec : - Pvec_m = lambda f: Pvec(pull_3d_v(f, self.callable_mapping)) + Pvec_m = lambda f: Pvec(pull_3d_vec(f, self.callable_mapping)) return P0_m, P1_m, P2_m, P3_m, Pvec_m else : return P0_m, P1_m, P2_m, P3_m diff --git a/psydac/feec/pull_push.py b/psydac/feec/pull_push.py index 4f3b9b92f..3afd6b6b5 100644 --- a/psydac/feec/pull_push.py +++ b/psydac/feec/pull_push.py @@ -8,12 +8,12 @@ # ------------------- 'pull_1d_h1', 'pull_1d_l2', - 'pull_2d_v', + 'pull_2d_vec', 'pull_2d_h1', 'pull_2d_hcurl', 'pull_2d_hdiv', 'pull_2d_l2', - 'pull_3d_v', # NOTE: what is this used for? + 'pull_3d_vec', # NOTE: what is this used for? 'pull_3d_h1', 'pull_3d_hcurl', 'pull_3d_hdiv', @@ -65,8 +65,7 @@ def f_logical(eta1): #============================================================================== # 2D PULL-BACKS #============================================================================== -def pull_2d_v(f, F): - #We should check if the metric terms are really the good ones! +def pull_2d_vec(f, F): assert isinstance(F, BasicCallableMapping) assert F.ldim == 2 @@ -192,7 +191,7 @@ def f_logical(eta1, eta2): # TODO [YG 05.10.2022]: # Remove? But it makes sense to return a vector-valued function... -def pull_3d_v(f, F): +def pull_3d_vec(f, F): assert isinstance(F, BasicCallableMapping) assert F.ldim == 3 diff --git a/psydac/feec/tests/test_global_projectors.py b/psydac/feec/tests/test_global_projectors.py index 62aacda50..273b7b05d 100644 --- a/psydac/feec/tests/test_global_projectors.py +++ b/psydac/feec/tests/test_global_projectors.py @@ -99,7 +99,7 @@ def test_derham_projector_2d_hdiv(ncells, degree, periodic): domain_h = discretize(domain, ncells=ncells, periodic=periodic) derham = Derham(domain, ["H1", "Hdiv", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + derham_h = discretize(derham, domain_h, degree=degree, get_H1vec_space = True) P0, P1, P2, PX = derham_h.projectors() # Projector onto H1 space (1D interpolation) @@ -144,7 +144,7 @@ def test_derham_projector_2d_hcurl(ncells, degree, periodic): domain_h = discretize(domain, ncells=ncells, periodic=periodic) derham = Derham(domain, ["H1", "Hcurl", "L2"]) - derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + derham_h = discretize(derham, domain_h, degree=degree, get_H1vec_space = True) P0, P1, P2, PX = derham_h.projectors() # Projector onto H1 space (1D interpolation) @@ -189,7 +189,7 @@ def test_derham_projector_3d(ncells, degree, periodic): domain_h = discretize(domain, ncells=ncells, periodic=periodic) derham = Derham(domain) - derham_h = discretize(derham, domain_h, degree=degree, get_vec = True) + derham_h = discretize(derham, domain_h, degree=degree, get_H1vec_space = True) P0, P1, P2, P3, PX = derham_h.projectors() # Projector onto H1 space (1D interpolation) From d3e7b66fb421f28002c7adbaf9d7f8d283611615 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Fri, 13 Oct 2023 12:00:33 +0200 Subject: [PATCH 66/77] forgot one update --- psydac/api/discretization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 1a6fbc576..47bf5b620 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -95,7 +95,7 @@ def discretize_derham(derham, domain_h, get_H1vec_space = False, *args, **kwargs domain_h : Geometry Discrete domain where the spaces will be discretized - get_vec : Bool + get_H1vec_space : Bool True to also get the "Hvec" space discretizing (H1)^n vector fields **kwargs : list From 9166d7d31e7185232b30a3843137692f3702caf5 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 16 Oct 2023 10:27:17 +0200 Subject: [PATCH 67/77] answering Yaman review --- psydac/api/discretization.py | 2 +- psydac/api/feec.py | 19 +++++++++---------- psydac/feec/global_projectors.py | 7 +++++++ psydac/feec/pull_push.py | 10 +++++----- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index 47bf5b620..0d74f3178 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -116,7 +116,7 @@ def discretize_derham(derham, domain_h, get_H1vec_space = False, *args, **kwargs #We still need to specify the symbolic space because of "_recursive_element_of" not implemented in sympde spaces.append(Xh) - return DiscreteDerham(mapping, get_H1vec_space, *spaces) + return DiscreteDerham(mapping, *spaces) #============================================================================== def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): """ diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 9a0f14600..7917f98eb 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -9,6 +9,8 @@ from psydac.feec.pull_push import pull_1d_h1, pull_1d_l2 from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_vec from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_vec +from psydac.fem.vector import VectorFemSpace + __all__ = ('DiscreteDerham',) @@ -22,23 +24,20 @@ class DiscreteDerham(BasicDiscrete): mapping : Mapping The mapping from the logical space to the physical space of the discrete De Rham. - - get_vec : Bool - True to also get the "Hvec" space discretizing (H1)^n vector fields *spaces : list of The discrete spaces of the De Rham sequence """ - def __init__(self, mapping, get_H1vec_space=False, *spaces): + def __init__(self, mapping, *spaces): assert (mapping is None) or isinstance(mapping, Mapping) - self.has_vec = get_H1vec_space + self.has_vec = isinstance(spaces[-1], VectorFemSpace) if self.has_vec : dim = len(spaces) - 2 self._spaces = spaces[:-1] - self._Vvec = spaces[-1] + self._H1vec = spaces[-1] else : dim = len(spaces) - 1 @@ -106,9 +105,9 @@ def V3(self): return self._spaces[3] @property - def Vvec(self): + def H1vec(self): assert self.has_vec - return self._Vvec + return self._H1vec @property def spaces(self): @@ -158,7 +157,7 @@ def projectors(self, *, kind='global', nquads=None): raise TypeError('projector of space type {} is not available'.format(kind)) if self.has_vec : - Pvec = Projector_H1vec(self.Vvec, nquads) + Pvec = Projector_H1vec(self.H1vec, nquads) if self.mapping: P0_m = lambda f: P0(pull_2d_h1(f, self.callable_mapping)) @@ -184,7 +183,7 @@ def projectors(self, *, kind='global', nquads=None): P2 = Projector_Hdiv (self.V2, nquads) P3 = Projector_L2 (self.V3, nquads) if self.has_vec : - Pvec = Projector_H1vec(self.Vvec) + Pvec = Projector_H1vec(self.H1vec) if self.mapping: P0_m = lambda f: P0(pull_3d_h1 (f, self.callable_mapping)) P1_m = lambda f: P1(pull_3d_hcurl(f, self.callable_mapping)) diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index 723b289bf..caec7c60f 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -577,11 +577,16 @@ class Projector_H1vec(GlobalProjector): This is a global projector constructed over a tensor-product grid in the logical domain. The vertices of this grid are obtained as the tensor product of the 1D splines' Greville points along each direction. + Parameters ---------- H1vec : ProductFemSpace H1 x H1 x H1-conforming finite element space, codomain of the projection operator. + + nquads : list(int) | tuple(int) + Number of quadrature points along each direction, to be used in Gauss + quadrature rule for computing the (approximated) degrees of freedom. """ def _structure(self, dim): if dim == 3: @@ -609,6 +614,7 @@ def __call__(self, fun): r""" Project vector function onto the H1 x H1 x H1-conforming finite element space. This happens in the logical domain $\hat{\Omega}$. + Parameters ---------- fun : list/tuple of callables @@ -617,6 +623,7 @@ def __call__(self, fun): point in the logical domain. These correspond to the coefficients of a vector-field. $fun_i : \hat{\Omega} \mapsto \mathbb{R}$ with i = 1, ..., N. + Returns ------- field : FemField diff --git a/psydac/feec/pull_push.py b/psydac/feec/pull_push.py index 3afd6b6b5..f4acb65f6 100644 --- a/psydac/feec/pull_push.py +++ b/psydac/feec/pull_push.py @@ -79,7 +79,7 @@ def f1_logical(eta1, eta2): a2_phys = f2(x, y) J_inv_value = F.jacobian_inv(eta1, eta2) - value_1 = J_inv_value[0,0]*a1_phys + J_inv_value[0,1]*a2_phys + value_1 = J_inv_value[0, 0] * a1_phys + J_inv_value[0, 1] * a2_phys return value_1 def f2_logical(eta1, eta2): @@ -89,7 +89,7 @@ def f2_logical(eta1, eta2): a2_phys = f2(x, y) J_inv_value = F.jacobian_inv(eta1, eta2) - value_2 = J_inv_value[1,0]*a1_phys + J_inv_value[1,1]*a2_phys + value_2 = J_inv_value[1, 0] * a1_phys + J_inv_value[1, 1] * a2_phys return value_2 return f1_logical, f2_logical @@ -206,7 +206,7 @@ def f1_logical(eta1, eta2, eta3): a3_phys = f3(x, y, z) J_inv_value = F.jacobian_inv(eta1, eta2, eta3) - value_1 = J_inv_value[0,0]*a1_phys + J_inv_value[0,1]*a2_phys + J_inv_value[0,2]*a3_phys + value_1 = J_inv_value[0, 0] * a1_phys + J_inv_value[0, 1] * a2_phys + J_inv_value[0, 2] * a3_phys return value_1 def f2_logical(eta1, eta2, eta3): @@ -217,7 +217,7 @@ def f2_logical(eta1, eta2, eta3): a3_phys = f3(x, y, z) J_inv_value = F.jacobian_inv(eta1, eta2, eta3) - value_2 = J_inv_value[1,0]*a1_phys + J_inv_value[1,1]*a2_phys + J_inv_value[1,2]*a3_phys + value_2 = J_inv_value[1, 0] * a1_phys + J_inv_value[1, 1] * a2_phys + J_inv_value[1, 2] * a3_phys return value_2 def f3_logical(eta1, eta2, eta3): @@ -228,7 +228,7 @@ def f3_logical(eta1, eta2, eta3): a3_phys = f3(x, y, z) J_inv_value = F.jacobian_inv(eta1, eta2, eta3) - value_2 = J_inv_value[2,0]*a1_phys + J_inv_value[2,1]*a2_phys + J_inv_value[2,2]*a3_phys + value_2 = J_inv_value[2, 0] * a1_phys + J_inv_value[2, 1] * a2_phys + J_inv_value[2, 2] * a3_phys return value_2 return f1_logical, f2_logical, f3_logical From c8b187bb0c72418f5fbf31d8f160d5a6798f0f1a Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 16 Oct 2023 17:01:30 +0200 Subject: [PATCH 68/77] pb in Discrete Derham docstring and useless lines --- psydac/api/feec.py | 2 +- psydac/feec/global_projectors.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 7917f98eb..4e2e270cf 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -25,7 +25,7 @@ class DiscreteDerham(BasicDiscrete): mapping : Mapping The mapping from the logical space to the physical space of the discrete De Rham. - *spaces : list of + *spaces : list of FemSpace The discrete spaces of the De Rham sequence """ def __init__(self, mapping, *spaces): diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index caec7c60f..f615b9870 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -887,8 +887,6 @@ def evaluate_dofs_3d_vec( ): # evaluate input functions at interpolation points (make sure that points are in [0, 1]) - - n1, n2, n3 = F1.shape for i1 in range(n1): for i2 in range(n2): From 1729b1a16eff8966f6b80994f47d5657a4e866fa Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 16 Oct 2023 17:07:55 +0200 Subject: [PATCH 69/77] change names for pull-push --- psydac/api/feec.py | 8 ++++---- psydac/feec/pull_push.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 4e2e270cf..07acc0b4d 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -7,8 +7,8 @@ from psydac.feec.global_projectors import Projector_H1, Projector_Hcurl, Projector_H1vec from psydac.feec.global_projectors import Projector_Hdiv, Projector_L2 from psydac.feec.pull_push import pull_1d_h1, pull_1d_l2 -from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_vec -from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_vec +from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_h1vec +from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_h1vec from psydac.fem.vector import VectorFemSpace @@ -167,7 +167,7 @@ def projectors(self, *, kind='global', nquads=None): elif kind == 'hdiv': P1_m = lambda f: P1(pull_2d_hdiv(f, self.callable_mapping)) if self.has_vec : - Pvec_m = lambda f: Pvec(pull_2d_vec(f, self.callable_mapping)) + Pvec_m = lambda f: Pvec(pull_2d_h1vec(f, self.callable_mapping)) return P0_m, P1_m, P2_m, Pvec_m else : return P0_m, P1_m, P2_m @@ -190,7 +190,7 @@ def projectors(self, *, kind='global', nquads=None): P2_m = lambda f: P2(pull_3d_hdiv (f, self.callable_mapping)) P3_m = lambda f: P3(pull_3d_l2 (f, self.callable_mapping)) if self.has_vec : - Pvec_m = lambda f: Pvec(pull_3d_vec(f, self.callable_mapping)) + Pvec_m = lambda f: Pvec(pull_3d_h1vec(f, self.callable_mapping)) return P0_m, P1_m, P2_m, P3_m, Pvec_m else : return P0_m, P1_m, P2_m, P3_m diff --git a/psydac/feec/pull_push.py b/psydac/feec/pull_push.py index f4acb65f6..dba0391f5 100644 --- a/psydac/feec/pull_push.py +++ b/psydac/feec/pull_push.py @@ -8,12 +8,12 @@ # ------------------- 'pull_1d_h1', 'pull_1d_l2', - 'pull_2d_vec', + 'pull_2d_h1vec', 'pull_2d_h1', 'pull_2d_hcurl', 'pull_2d_hdiv', 'pull_2d_l2', - 'pull_3d_vec', # NOTE: what is this used for? + 'pull_3d_h1vec', # NOTE: what is this used for? 'pull_3d_h1', 'pull_3d_hcurl', 'pull_3d_hdiv', @@ -65,7 +65,7 @@ def f_logical(eta1): #============================================================================== # 2D PULL-BACKS #============================================================================== -def pull_2d_vec(f, F): +def pull_2d_h1vec(f, F): assert isinstance(F, BasicCallableMapping) assert F.ldim == 2 @@ -191,7 +191,7 @@ def f_logical(eta1, eta2): # TODO [YG 05.10.2022]: # Remove? But it makes sense to return a vector-valued function... -def pull_3d_vec(f, F): +def pull_3d_h1vec(f, F): assert isinstance(F, BasicCallableMapping) assert F.ldim == 3 From 5d521ee89c5dbd57a60d301a366774f28e92ba17 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Tue, 17 Oct 2023 09:32:05 +0200 Subject: [PATCH 70/77] update tests and comment on roll edges --- psydac/feec/global_projectors.py | 4 +- psydac/feec/tests/test_global_projectors.py | 72 +++++++++++++++++++-- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/psydac/feec/global_projectors.py b/psydac/feec/global_projectors.py index f615b9870..7ecb67a7d 100644 --- a/psydac/feec/global_projectors.py +++ b/psydac/feec/global_projectors.py @@ -150,7 +150,9 @@ def __init__(self, space, nquads = None): u, w = uw[j] global_quad_x, global_quad_w = quadrature_grid(V.histopolation_grid, u, w) #"roll" back points to the interval to ensure that the quadrature points are - #in the domain. Probably only usefull on periodic cases + #in the domain. Only usefull in th eperiodic case (else do nothing) + #if not used then you will have quadrature points outside of the domain which + #might cause problem when your function is only defined inside the domain roll_edges(V.domain, global_quad_x) quad_x[j] = global_quad_x[s:e+1] quad_w[j] = global_quad_w[s:e+1] diff --git a/psydac/feec/tests/test_global_projectors.py b/psydac/feec/tests/test_global_projectors.py index 273b7b05d..f7a3c0cf0 100644 --- a/psydac/feec/tests/test_global_projectors.py +++ b/psydac/feec/tests/test_global_projectors.py @@ -46,7 +46,7 @@ def test_H1_projector_1d(domain, ncells, degree, periodic): # Test if max-norm of error is <= TOL maxnorm_error = abs(vals_u0 - vals_f).max() print(ncells, maxnorm_error) -# assert maxnorm_error <= 1e-14 + assert maxnorm_error <= 1e-9 #============================================================================== @pytest.mark.parametrize('domain', [(0, 2*np.pi)]) @@ -86,9 +86,9 @@ def test_L2_projector_1d(domain, ncells, degree, periodic, nquads): # Test if max-norm of error is <= TOL maxnorm_error = abs(vals_u1 - vals_f).max() print(ncells, maxnorm_error) -# assert maxnorm_error <= 1e-14 + assert maxnorm_error <= 1e-3 - #============================================================================== +#============================================================================== @pytest.mark.parametrize('ncells', [[200,200]]) @pytest.mark.parametrize('degree', [[2,2], [2,3], [3,3]]) @pytest.mark.parametrize('periodic', [[False, False], [True, True]]) @@ -125,13 +125,65 @@ def test_derham_projector_2d_hdiv(ncells, degree, periodic): # Test if max-norm of error is <= TOL maxnorm_error = abs(vals_u0 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 + maxnorm_error = abs(vals_u1_1 - vals_f).max() + print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 + maxnorm_error = abs(vals_u2 - vals_f).max() + print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 + maxnorm_error = abs(vals_ux_1 - vals_f).max() + print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 + +#============================================================================== +@pytest.mark.parametrize('ncells', [[200,200]]) +@pytest.mark.parametrize('degree', [[2,2], [2,3], [3,3]]) +@pytest.mark.parametrize('periodic', [[False, False], [True, True]]) + +def test_derham_projector_2d_hdiv_2(ncells, degree, periodic): + + domain = Square('Omega', bounds1 = (0,1), bounds2 = (0,1)) + domain_h = discretize(domain, ncells=ncells, periodic=periodic) + + derham = Derham(domain, ["H1", "Hdiv", "L2"]) + derham_h = discretize(derham, domain_h, degree=degree, get_H1vec_space = True) + P0, P1, P2, PX = derham_h.projectors() + + # Projector onto H1 space (1D interpolation) + + # Function to project + f1 = lambda xi1, xi2 : xi1**2*(xi1-1.)**2 + #function C0 restricted to [0,1] with periodic BC (0 at x1=0 and x1=1) + f2 = lambda xi1, xi2 : xi2**2*(xi2-1.)**2 + + # Compute the projection + u0 = P0(f1) + u2 = P2(f1) + u1 = P1((f1,f2)) + ux = PX((f1,f2)) + + # Create evaluation grid, and check if u0(x) == f(x) + xgrid = np.linspace(0, 1, num=51) + vals_u0 = np.array([[u0(x, y) for x in xgrid] for y in xgrid]) + vals_u1_1 = np.array([[u1(x, y)[0] for x in xgrid] for y in xgrid]) + vals_u2 = np.array([[u2(x, y) for x in xgrid] for y in xgrid]) + vals_ux_1 = np.array([[ux(x, y)[0] for x in xgrid] for y in xgrid]) + vals_f = np.array([[f1(x, y) for x in xgrid] for y in xgrid]) + + # Test if max-norm of error is <= TOL + maxnorm_error = abs(vals_u0 - vals_f).max() + print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 maxnorm_error = abs(vals_u1_1 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 maxnorm_error = abs(vals_u2 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 maxnorm_error = abs(vals_ux_1 - vals_f).max() print(ncells, maxnorm_error) -# assert maxnorm_error <= 1e-14 + assert maxnorm_error <= 1e-3 #============================================================================== @pytest.mark.parametrize('ncells', [[200,200]]) @@ -170,13 +222,16 @@ def test_derham_projector_2d_hcurl(ncells, degree, periodic): # Test if max-norm of error is <= TOL maxnorm_error = abs(vals_u0 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 maxnorm_error = abs(vals_u1_1 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 maxnorm_error = abs(vals_u2 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 1e-3 maxnorm_error = abs(vals_ux_1 - vals_f).max() print(ncells, maxnorm_error) -# assert maxnorm_error <= 1e-14 + assert maxnorm_error <= 1e-3 #============================================================================== @pytest.mark.parametrize('ncells', [[30,30,30]]) @@ -218,15 +273,19 @@ def test_derham_projector_3d(ncells, degree, periodic): # Test if max-norm of error is <= TOL maxnorm_error = abs(vals_u0 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 2e-2 maxnorm_error = abs(vals_u1_1 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 2e-2 maxnorm_error = abs(vals_u2_1 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 2e-2 maxnorm_error = abs(vals_u3 - vals_f).max() print(ncells, maxnorm_error) + assert maxnorm_error <= 2e-2 maxnorm_error = abs(vals_ux_1 - vals_f).max() print(ncells, maxnorm_error) -# assert maxnorm_error <= 1e-14 + assert maxnorm_error <= 2e-2 #============================================================================== if __name__ == '__main__': @@ -244,6 +303,7 @@ def test_derham_projector_3d(ncells, degree, periodic): test_L2_projector_1d(domain, nc, degree, periodic, nquads) for nc in ncells: + test_derham_projector_2d_hdiv_2([nc, nc], [degree, degree], [periodic, periodic]) test_derham_projector_2d_hdiv([nc, nc], [degree, degree], [periodic, periodic]) for nc in ncells : From cc2fa3f2932ef90f50b2c5245ec651fd62a16dba Mon Sep 17 00:00:00 2001 From: vcarlier Date: Thu, 19 Oct 2023 07:58:28 +0200 Subject: [PATCH 71/77] add some first comments --- psydac/api/feec.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 07acc0b4d..d931a8032 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -86,31 +86,43 @@ def __init__(self, mapping, *spaces): #-------------------------------------------------------------------------- @property def dim(self): + """dimension of the ambient space""" return self._dim @property def V0(self): + """First space of the De Rham sequence : H1 space""" return self._spaces[0] @property def V1(self): + """Second space of the De Rham sequence : + 1d : L2 space + 2d : either Hdiv or Hcurl space + 3d : Hcurl space""" return self._spaces[1] @property def V2(self): + """Third space of the De Rham sequence : + 2d : L2 space + 3d : Hdiv space""" return self._spaces[2] @property def V3(self): + """Fourth space of the De Rham sequence : L2 space in 3d""" return self._spaces[3] @property def H1vec(self): + """Vectorial H1 space built has cartesian product of V0""" assert self.has_vec return self._H1vec @property def spaces(self): + """Spaces of the proper de Rham sequence (excluding Hvec)""" return self._spaces @property From 3973ce5701538e0b69ad10919296b5acbdd09eb0 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 25 Oct 2023 10:04:06 +0200 Subject: [PATCH 72/77] little change on dimension --- psydac/api/feec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 0da38d461..9fa136f92 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -86,7 +86,7 @@ def __init__(self, mapping, *spaces): #-------------------------------------------------------------------------- @property def dim(self): - """dimension of the ambient space""" + """dimension of the physical and logical space""" return self._dim @property From 8e2067f9b456019809436057608fee7a063ff421 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Mon, 30 Oct 2023 15:34:49 +0100 Subject: [PATCH 73/77] modifications after Martin's review --- psydac/api/feec.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 9fa136f92..ced6efc7f 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -86,7 +86,7 @@ def __init__(self, mapping, *spaces): #-------------------------------------------------------------------------- @property def dim(self): - """dimension of the physical and logical space""" + """dimension of the physical and logical domains, which are assumed to be the same""" return self._dim @property @@ -116,7 +116,7 @@ def V3(self): @property def H1vec(self): - """Vectorial H1 space built has cartesian product of V0""" + """Vector H1 space built as cartesian product of V0 n times with n = dimension of (logical) domain""" assert self.has_vec return self._H1vec @@ -137,12 +137,14 @@ def callable_mapping(self): @property def derivatives_as_matrices(self): - """Differential operators of the De Rham sequence as BlockLinearOperator""" + """Differential operators of the De Rham sequence as LinearOperator""" return tuple(V.diff.matrix for V in self.spaces[:-1]) @property def derivatives_as_operators(self): - """Differential operators of the De Rham sequence as DiffOperator""" + """Differential operators of the De Rham sequence as DiffOperator objects. + Those are objects with domain and codomain properties that are FemSpace, + they act on FemField (they take a FemField of their domain as input and return a FemField of their codomain.""" return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- @@ -154,7 +156,7 @@ def projectors(self, *, kind='global', nquads=None): kind : str Type of the projection : at the moment, only global is accepted and - returns commuting projectors based on interpolation/histopolation + returns geometric commuting projectors based on interpolation/histopolation for the De Rham sequence (GlobalProjector objects) nquads : list(int) | tuple(int) From 49ed53eec0a2ac1e5117de0e818720027b2a6e36 Mon Sep 17 00:00:00 2001 From: vcarlier Date: Wed, 29 Nov 2023 20:14:20 +0100 Subject: [PATCH 74/77] changes to fix last discussions --- psydac/api/feec.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index ced6efc7f..e232f13a8 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -16,8 +16,9 @@ #============================================================================== class DiscreteDerham(BasicDiscrete): - """ Represent the discrete De Rham sequence. - Should be initialized via discretize_derham function in api.discretization.py + """ Represent the discrete De Rham sequence in the case of a single patch geometry. + For the multipatch counterpart please see `MultipatchDiscreteDerham` in `psydac.feec.multipatch.api.py` + Should be initialized via `discretize_derham` function in `api.discretization.py` Parameters ---------- @@ -116,7 +117,7 @@ def V3(self): @property def H1vec(self): - """Vector H1 space built as cartesian product of V0 n times with n = dimension of (logical) domain""" + """Vector-valued H1 space built as cartesian product of V0 n times with n = dimension of (logical) domain""" assert self.has_vec return self._H1vec @@ -141,15 +142,17 @@ def derivatives_as_matrices(self): return tuple(V.diff.matrix for V in self.spaces[:-1]) @property - def derivatives_as_operators(self): - """Differential operators of the De Rham sequence as DiffOperator objects. - Those are objects with domain and codomain properties that are FemSpace, - they act on FemField (they take a FemField of their domain as input and return a FemField of their codomain.""" + def derivatives(self): + """Differential operators of the De Rham sequence as `DiffOperator` objects. + Those are objects with `domain` and `codomain` properties that are `FemSpace`, + they act on `FemField` (they take a `FemField` of their `domain` as input and return + a `FemField` of their `codomain`.""" return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- def projectors(self, *, kind='global', nquads=None): - """Projectors mapping callable to FemFields of the De Rham sequence. + """Projectors mapping callable functions of the physical coordinates to a + corresponding `FemField` object in the De Rham sequence. Parameters ---------- From 95caa9ca13de927fb98a0adbbfcb17d179431e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Fri, 1 Dec 2023 11:07:15 +0100 Subject: [PATCH 75/77] Improve docstrings in psydac/api/discretization.py --- psydac/api/discretization.py | 60 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/psydac/api/discretization.py b/psydac/api/discretization.py index e6b77d7cb..93c46ebad 100644 --- a/psydac/api/discretization.py +++ b/psydac/api/discretization.py @@ -43,27 +43,37 @@ from psydac.linalg.stencil import StencilVectorSpace from psydac.linalg.block import BlockVectorSpace -__all__ = ('discretize', 'discretize_derham', 'reduce_space_degrees', 'discretize_space', 'discretize_domain') - +__all__ = ( + 'discretize', + 'discretize_derham', + 'reduce_space_degrees', + 'discretize_space', + 'discretize_domain' +) #============================================================================== def change_dtype(V, dtype): """ - This function take a FemSpace V and create a new vector_space for it with the data type required. + Given a FemSpace V, change its underlying vector_space (i.e. the space of + its coefficients) so that it matches the required data type. Parameters ---------- - Vh : FemSpace - The FEM space. + The FEM space, which is modified in place. + + dtype : float or complex + Datatype of the new vector_space. - dtype : Data Type - float or complex + Returns + ------- + FemSpace + The same FEM space passed as input, which was modified in place. """ if not V.vector_space.dtype == dtype: if isinstance(V.vector_space, BlockVectorSpace): # Recreate the BlockVectorSpace - new_spaces=[] + new_spaces = [] for v in V.spaces: change_dtype(v, dtype) new_spaces.append(v.vector_space) @@ -84,28 +94,30 @@ def change_dtype(V, dtype): #============================================================================== def discretize_derham(derham, domain_h, get_H1vec_space = False, *args, **kwargs): """ - Create a discrete De Rham sequence by creating the spaces and then initiating DiscreteDerham object. + Create a discrete De Rham sequence from a symbolic one. + + This function creates the discrete spaces from the symbolic ones, and then + creates a DiscreteDerham object from them. Parameters ---------- - derham : sympde.topology.space.Derham - The symbolic Derham sequence + The symbolic Derham sequence. - domain_h : Geometry - Discrete domain where the spaces will be discretized + domain_h : Geometry + Discrete domain where the spaces will be discretized. get_H1vec_space : Bool - True to also get the "Hvec" space discretizing (H1)^n vector fields + True to also get the "Hvec" space discretizing (H1)^n vector fields. **kwargs : list - optional parameters for the space discretization + Optional parameters for the space discretization. Returns ------- - : DiscreteDerham + DiscreteDerham The discrete De Rham sequence containing the discrete spaces, - differential operators and projectors + differential operators and projectors. """ ldim = derham.shape @@ -123,6 +135,7 @@ def discretize_derham(derham, domain_h, get_H1vec_space = False, *args, **kwargs spaces.append(Xh) return DiscreteDerham(mapping, *spaces) + #============================================================================== def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): """ @@ -174,7 +187,7 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): The symbolic space. Vh : TensorFemSpace - The tensor product fem space. + The tensor product FEM space. basis: str The basis function of the reduced spaces, it can be either 'B' for @@ -191,7 +204,7 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): Returns ------- Wh : TensorFemSpace, VectorFemSpace - The reduced space + The reduced space. """ multiplicity = Vh.multiplicity @@ -256,7 +269,6 @@ def reduce_space_degrees(V, Vh, *, basis='B', sequence='DR'): return Wh - #============================================================================== # TODO knots def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, nquads=None, basis='B', sequence='DR'): @@ -265,12 +277,11 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, Parameters ---------- - V : - the symbolic space + The symbolic space. domain_h : - the discretized domain + The discretized domain. degree : list | dict The degree of the h1 space in each direction. @@ -312,8 +323,7 @@ def discretize_space(V, domain_h, *, degree=None, multiplicity=None, knots=None, Returns ------- Vh : - represents the discrete fem space - + The discrete FEM space. """ # we have two cases, the case where we have a geometry file, From 2336051f3466e017dd975d04a7d93e325cdb1bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Fri, 1 Dec 2023 11:35:49 +0100 Subject: [PATCH 76/77] Improve docstrings in psydac/api/feec.py --- psydac/api/feec.py | 75 ++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index e232f13a8..174ca4d16 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -9,30 +9,39 @@ from psydac.feec.pull_push import pull_1d_h1, pull_1d_l2 from psydac.feec.pull_push import pull_2d_h1, pull_2d_hcurl, pull_2d_hdiv, pull_2d_l2, pull_2d_h1vec from psydac.feec.pull_push import pull_3d_h1, pull_3d_hcurl, pull_3d_hdiv, pull_3d_l2, pull_3d_h1vec +from psydac.fem.basic import FemSpace from psydac.fem.vector import VectorFemSpace - __all__ = ('DiscreteDerham',) #============================================================================== class DiscreteDerham(BasicDiscrete): - """ Represent the discrete De Rham sequence in the case of a single patch geometry. - For the multipatch counterpart please see `MultipatchDiscreteDerham` in `psydac.feec.multipatch.api.py` - Should be initialized via `discretize_derham` function in `api.discretization.py` - + """ A discrete de Rham sequence built over a single-patch geometry. + Parameters ---------- + mapping : Mapping or None + Symbolic mapping from the logical space to the physical space, if any. - mapping : Mapping - The mapping from the logical space to the physical space of the discrete De Rham. - *spaces : list of FemSpace - The discrete spaces of the De Rham sequence + The discrete spaces of the de Rham sequence. + + Notes + ----- + - The basic type Mapping is defined in module sympde.topology.mapping. + A discrete mapping (spline or NURBS) may be attached to it. + + - This constructor should not be called directly, but rather from the + `discretize_derham` function in `psydac.api.discretization`. + + - For the multipatch counterpart of this class please see + `MultipatchDiscreteDerham` in `psydac.feec.multipatch.api`. """ def __init__(self, mapping, *spaces): assert (mapping is None) or isinstance(mapping, Mapping) - + assert all(isinstance(space, FemSpace)) + self.has_vec = isinstance(spaces[-1], VectorFemSpace) if self.has_vec : @@ -87,92 +96,94 @@ def __init__(self, mapping, *spaces): #-------------------------------------------------------------------------- @property def dim(self): - """dimension of the physical and logical domains, which are assumed to be the same""" + """Dimension of the physical and logical domains, which are assumed to be the same.""" return self._dim @property def V0(self): - """First space of the De Rham sequence : H1 space""" + """First space of the de Rham sequence : H1 space""" return self._spaces[0] @property def V1(self): - """Second space of the De Rham sequence : - 1d : L2 space - 2d : either Hdiv or Hcurl space - 3d : Hcurl space""" + """Second space of the de Rham sequence : + - 1d : L2 space + - 2d : either Hdiv or Hcurl space + - 3d : Hcurl space""" return self._spaces[1] @property def V2(self): - """Third space of the De Rham sequence : - 2d : L2 space - 3d : Hdiv space""" + """Third space of the de Rham sequence : + - 2d : L2 space + - 3d : Hdiv space""" return self._spaces[2] @property def V3(self): - """Fourth space of the De Rham sequence : L2 space in 3d""" + """Fourth space of the de Rham sequence : L2 space in 3d""" return self._spaces[3] @property def H1vec(self): - """Vector-valued H1 space built as cartesian product of V0 n times with n = dimension of (logical) domain""" + """Vector-valued H1 space built as the Cartesian product of N copies of V0, + where N is the dimension of the (logical) domain.""" assert self.has_vec return self._H1vec @property def spaces(self): - """Spaces of the proper de Rham sequence (excluding Hvec)""" + """Spaces of the proper de Rham sequence (excluding Hvec).""" return self._spaces @property def mapping(self): - """The mapping from the logical space to the physical space of the discrete De Rham.""" + """The mapping from the logical space to the physical space.""" return self._mapping @property def callable_mapping(self): - """The mapping as a callable""" + """The mapping as a callable.""" return self._callable_mapping @property def derivatives_as_matrices(self): - """Differential operators of the De Rham sequence as LinearOperator""" + """Differential operators of the De Rham sequence as LinearOperator objects.""" return tuple(V.diff.matrix for V in self.spaces[:-1]) @property def derivatives(self): """Differential operators of the De Rham sequence as `DiffOperator` objects. + Those are objects with `domain` and `codomain` properties that are `FemSpace`, they act on `FemField` (they take a `FemField` of their `domain` as input and return - a `FemField` of their `codomain`.""" + a `FemField` of their `codomain`. + """ return tuple(V.diff for V in self.spaces[:-1]) #-------------------------------------------------------------------------- def projectors(self, *, kind='global', nquads=None): """Projectors mapping callable functions of the physical coordinates to a corresponding `FemField` object in the De Rham sequence. - + Parameters ---------- - kind : str Type of the projection : at the moment, only global is accepted and returns geometric commuting projectors based on interpolation/histopolation - for the De Rham sequence (GlobalProjector objects) - + for the De Rham sequence (GlobalProjector objects). + nquads : list(int) | tuple(int) Number of quadrature points along each direction, to be used in Gauss quadrature rule for computing the (approximated) degrees of freedom. - + Returns ------- P0, ..., Pn : callables Projectors that can be called on any callable function that maps from the physical space to R (scalar case) or R^d (vector case) and returns a FemField belonging to the i-th space of the De Rham sequence - """ + """ if not (kind == 'global'): raise NotImplementedError('only global projectors are available') From 90647cd345440c908744359bca9b90f7dc81bceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaman=20G=C3=BC=C3=A7l=C3=BC?= Date: Fri, 1 Dec 2023 15:35:19 +0100 Subject: [PATCH 77/77] Fix bug in psydac/api/feec.py --- psydac/api/feec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psydac/api/feec.py b/psydac/api/feec.py index 174ca4d16..a0039b734 100644 --- a/psydac/api/feec.py +++ b/psydac/api/feec.py @@ -40,7 +40,7 @@ class DiscreteDerham(BasicDiscrete): def __init__(self, mapping, *spaces): assert (mapping is None) or isinstance(mapping, Mapping) - assert all(isinstance(space, FemSpace)) + assert all(isinstance(space, FemSpace) for space in spaces) self.has_vec = isinstance(spaces[-1], VectorFemSpace)