From ebdc6c029a8bb9ed677497e26bcaa75ea42879ce Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 2 Jun 2024 17:27:30 -0700 Subject: [PATCH 01/75] Create recurrence.py - Feedback requested --- sumpy/recurrence.py | 296 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 sumpy/recurrence.py diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py new file mode 100644 index 000000000..1bf0519ff --- /dev/null +++ b/sumpy/recurrence.py @@ -0,0 +1,296 @@ +__copyright__ = """ +Copyright (C) 2024 Hirish Chandrasekaran +Copyright (C) 2024 Andreas Kloeckner +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from collections import namedtuple +from pyrsistent import pmap +from pytools import memoize +from sumpy.tools import add_mi +from itertools import accumulate +import sumpy.symbolic as sym +import logging +from typing import List +import sympy as sp +from sumpy.expansion.diff_op import LinearPDESystemOperator +from pytools.obj_array import make_obj_array + +#A similar function exists in sumpy.symbolic +def make_sympy_vec(name, n): + return make_obj_array([sp.Symbol(f"{name}{i}") for i in range(n)]) + + +__doc__ = """ +.. autoclass:: Recurrence +.. automodule:: sumpy.recurrence +""" + +#CREATE LAPLACE_3D +DerivativeIdentifier = namedtuple("DerivativeIdentifier", ["mi", "vec_idx"]) +partial2_x = DerivativeIdentifier((2,0,0), 0) +partial2_y = DerivativeIdentifier((0,2,0), 0) +partial2_z = DerivativeIdentifier((0,0,2), 0) +#Coefficients +list_pde_dict_3d = {partial2_x: 1, partial2_y: 1, partial2_z: 1} +laplace_3d = LinearPDESystemOperator(3,list_pde_dict_3d) + +#CREATE LAPLACE_2D +partial2_x = DerivativeIdentifier((2,0), 0) +partial2_y = DerivativeIdentifier((0,2), 0) +#Coefficients +list_pde_dict = {partial2_x: 1, partial2_y: 1} +laplace_2d = LinearPDESystemOperator(2,list_pde_dict) + +#CREATE HELMHOLTZ_2D +func_val = DerivativeIdentifier((0,0), 0) +#Coefficients +list_pde_dict = {partial2_x: 1, partial2_y: 1, func_val: 1} +helmholtz_2d = LinearPDESystemOperator(2,list_pde_dict) + +''' +get_pde_in_recurrence_form +Input: + - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such that assert(len(pde.eqs) == 1) + is true. +Output: + - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) satisfies away from the origin. + Note: to represent f, f_r, f_{rr}, we use the sympy variables f_{r0}, f_{r1}, .... So ode_in_r is a linear + combination of the sympy variables f_{r0}, f_{r1}, .... + - var, represents the variables for the input space: [x0, x1, ...] + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present + (the reason this is called n_derivs since if we have a second order PDE for example + then we might see f, f_{r}, f_{rr} in our ODE in r, which is technically 3 terms since we count + the 0th order derivative f as a "derivative." If this doesn't make sense just know that n_derivs + is the order the of the input sumpy PDE + 1) + +Description: We assume we are handed a system of 1 sumpy PDE (pde) and output the +pde in a way that allows us to easily replace derivatives with respect to r. In other words we output +a linear combination of sympy variables f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) +to represent our ODE in r for the point potential. +''' +def get_pde_in_recurrence_form(laplace): + dim = laplace.dim + n_derivs = laplace.order + assert(len(laplace.eqs) == 1) + ops = len(laplace.eqs[0]) + derivs = [] + coeffs = [] + for i in laplace.eqs[0]: + derivs.append(i.mi) + coeffs.append(laplace.eqs[0][i]) + var = make_sympy_vec("x", dim) + r = sp.sqrt(sum(var**2)) + + eps = sp.symbols("epsilon") + rval = r + eps + + f = sp.Function("f") + f_derivs = [sp.diff(f(rval),eps,i) for i in range(n_derivs+1)] + + def compute_term(a, t): + term = a + for i in range(len(t)): + term = term.diff(var[i], t[i]) + return term + + pde = 0 + for i in range(ops): + pde += coeffs[i] * compute_term(f(rval), derivs[i]) + + n_derivs = len(f_derivs) + f_r_derivs = make_sympy_vec("f_r", n_derivs) + + for i in range(n_derivs): + pde = pde.subs(f_derivs[i], f_r_derivs[i]) + + return pde, var, n_derivs + + +ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace_2d) + + +''' +generate_ND_derivative_relations +Input: + - var, a sympy vector of variables called [x0, x1, ...] + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present +Output: + - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... using the chain rule + (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and + f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) + +Description: Using the chain rule outputs a vector that tells us how to write f, f_r, f_{rr}, ... as a linear +combination of f, f_x, f_{xx}, ... +''' +def generate_ND_derivative_relations(var, n_derivs): + f_r_derivs = make_sympy_vec("f_r", n_derivs) + f_x_derivs = make_sympy_vec("f_x", n_derivs) + f = sp.Function("f") + eps = sp.symbols("epsilon") + rval = sp.sqrt(sum(var**2)) + eps + f_derivs_x = [sp.diff(f(rval),var[0],i) for i in range(n_derivs)] + f_derivs = [sp.diff(f(rval),eps,i) for i in range(n_derivs)] + for i in range(len(f_derivs_x)): + for j in range(len(f_derivs)): + f_derivs_x[i] = f_derivs_x[i].subs(f_derivs[j], f_r_derivs[j]) + system = [f_x_derivs[i] - f_derivs_x[i] for i in range(n_derivs)] + + return sp.solve(system, *f_r_derivs, dict=True)[0] + + +''' +ode_in_r_to_x +Input: + - ode_in_r, a linear combination of f, f_r, f_{rr}, ... (in code represented as f_{r0}, f_{r1}, f_{r2}) + with coefficients as RATIONAL functions in var[0], var[1], ... + - var, array of sympy variables [x_0, x_1, ...] + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present +Output: + - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as rational + functions in var[0], var[1], ... + +Description: Translates an ode in the variable r into an ode in the variable x by substituting f, f_r, f_{rr}, ... + as a linear combination of f, f_x, f_{xx}, ... using the chain rule +''' +def ode_in_r_to_x(ode_in_r, var, n_derivs): + subme = generate_ND_derivative_relations(var, n_derivs) + ode_in_x = ode_in_r + f_r_derivs = make_sympy_vec("f_r", n_derivs) + for i in range(n_derivs): + ode_in_x = ode_in_x.subs(f_r_derivs[i], subme[f_r_derivs[i]]) + return ode_in_x + + +ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() +ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() + +delta_x = sp.symbols("delta_x") +c_vec = make_sympy_vec("c", len(var)) + +''' +compute_poly_in_deriv +Input: + - ode_in_x_cleared, an ode in x, i.e. a linear combination of f, f_x, f_{xx}, ... + (in code represented as f_{x0}, f_{x1}, f_{x2}) with coefficients as POLYNOMIALS in var[0], var[1], ... + (i.e. not rational functions) + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present +Output: + - a polynomial in f, f_x, f_{xx}, ... (in code represented as f_{x0}, f_{x1}, f_{x2}) with coefficients + as polynomials in \delta_x where \delta_x = x_0 - c_0 that represents the ''shifted ODE'' - i.e. the ODE + where we substitute all occurences of \delta_x with x_0 - c_0 + +Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., with coefficients as polynomials +in \delta_x = x_0 - c_0. +''' +def compute_poly_in_deriv(ode_in_x_cleared, n_derivs): + #Note that generate_ND_derivative_relations will at worst put some power of $x_0^order$ in the denominator. To clear + #the denominator we can probably? just multiply by x_0^order. + ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() + + ode_in_x_shifted = ode_in_x_cleared.subs(var[0], delta_x + c_vec[0]).simplify() + + f_x_derivs = make_sympy_vec("f_x", n_derivs) + poly = sp.Poly(ode_in_x_shifted, *f_x_derivs) + + return poly + +poly = compute_poly_in_deriv(ode_in_x, n_derivs) + +''' +compute_coefficients_of_poly +Input: + - poly, a polynomial in sympy variables f_{x0}, f_{x1}, ..., + (recall that this corresponds to f_0, f_x, f_{xx}, ...) with coefficients that are polynomials in \delta_x + where poly represents the ''shifted ODE''- i.e. we substitute all occurences of \delta_x with x_0 - c_0 +Output: + - a 2d array, each row giving the coefficient of f_0, f_x, f_{xx}, ..., + each entry in the row giving the coefficients of the polynomial in \delta_x + +Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are polynomials in \delta_x +and outputs a 2d array for easy access to the coefficients based on their degree as a polynomial in \delta_x. +''' +def compute_coefficients_of_poly(poly, n_derivs): + #Returns coefficients in lexographic order. So lowest order first + def tup(i,n=n_derivs): + a = [] + for j in range(n): + if j != i: + a.append(0) + else: + a.append(1) + return tuple(a) + + coeffs = [] + for deriv_ind in range(n_derivs): + coeffs.append(sp.Poly(poly.coeff_monomial(tup(deriv_ind)), delta_x).all_coeffs()) + + return coeffs + +coeffs = compute_coefficients_of_poly(poly, n_derivs) + +i = sp.symbols("i") +s = sp.Function("s") + +''' +compute_recurrence_relation +Input: + - coeffs a 2d array that gives access to the coefficients of poly, where poly represents the coefficients of + the ''shifted ODE'' (''shifted ODE'' = we substitute all occurences of \delta_x with x_0 - c_0) + based on their degree as a polynomial in \delta_x + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present +Output: + - a recurrence statement that equals 0 where s(i) is the ith coefficient of the Taylor polynomial for + our point potential. + +Description: Takes in coeffs which represents our ``shifted ode in x" (i.e. ode_in_x with coefficients in \delta_x) +and outputs a recurrence relation for the point potential. +''' + +def compute_recurrence_relation(coeffs, n_derivs): + #Compute symbolic derivative + def hc_diff(i, n): + retMe = 1 + for j in range(n): + retMe *= (i-j) + return retMe + + #We are differentiating deriv_ind, which shifts down deriv_ind. Do this for one deriv_ind + r = 0 + for deriv_ind in range(n_derivs): + part_of_r = 0 + pow_delta = 0 + for j in range(len(coeffs[deriv_ind])-1, -1, -1): + shift = pow_delta - deriv_ind + 1 + pow_delta += 1 + temp = coeffs[deriv_ind][j] * s(i) * hc_diff(i, deriv_ind) + part_of_r += temp.subs(i, i-shift) + r += part_of_r + + for j in range(1, len(var)): + r = r.subs(var[j], c_vec[j]) + + return r.simplify() + +r = compute_recurrence_relation(coeffs, n_derivs) + + From d5aac5ec8d7bc53274682280213c8ac0c20cc3ee Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 3 Jun 2024 16:07:17 -0500 Subject: [PATCH 02/75] Hackin with Andreas --- sumpy/recurrence.py | 111 +++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 1bf0519ff..7586cffa9 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -24,13 +24,6 @@ """ from collections import namedtuple -from pyrsistent import pmap -from pytools import memoize -from sumpy.tools import add_mi -from itertools import accumulate -import sumpy.symbolic as sym -import logging -from typing import List import sympy as sp from sumpy.expansion.diff_op import LinearPDESystemOperator from pytools.obj_array import make_obj_array @@ -45,50 +38,29 @@ def make_sympy_vec(name, n): .. automodule:: sumpy.recurrence """ -#CREATE LAPLACE_3D -DerivativeIdentifier = namedtuple("DerivativeIdentifier", ["mi", "vec_idx"]) -partial2_x = DerivativeIdentifier((2,0,0), 0) -partial2_y = DerivativeIdentifier((0,2,0), 0) -partial2_z = DerivativeIdentifier((0,0,2), 0) -#Coefficients -list_pde_dict_3d = {partial2_x: 1, partial2_y: 1, partial2_z: 1} -laplace_3d = LinearPDESystemOperator(3,list_pde_dict_3d) - -#CREATE LAPLACE_2D -partial2_x = DerivativeIdentifier((2,0), 0) -partial2_y = DerivativeIdentifier((0,2), 0) -#Coefficients -list_pde_dict = {partial2_x: 1, partial2_y: 1} -laplace_2d = LinearPDESystemOperator(2,list_pde_dict) - -#CREATE HELMHOLTZ_2D -func_val = DerivativeIdentifier((0,0), 0) -#Coefficients -list_pde_dict = {partial2_x: 1, partial2_y: 1, func_val: 1} -helmholtz_2d = LinearPDESystemOperator(2,list_pde_dict) -''' -get_pde_in_recurrence_form -Input: - - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such that assert(len(pde.eqs) == 1) - is true. -Output: - - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) satisfies away from the origin. - Note: to represent f, f_r, f_{rr}, we use the sympy variables f_{r0}, f_{r1}, .... So ode_in_r is a linear - combination of the sympy variables f_{r0}, f_{r1}, .... - - var, represents the variables for the input space: [x0, x1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present - (the reason this is called n_derivs since if we have a second order PDE for example - then we might see f, f_{r}, f_{rr} in our ODE in r, which is technically 3 terms since we count - the 0th order derivative f as a "derivative." If this doesn't make sense just know that n_derivs - is the order the of the input sumpy PDE + 1) - -Description: We assume we are handed a system of 1 sumpy PDE (pde) and output the -pde in a way that allows us to easily replace derivatives with respect to r. In other words we output -a linear combination of sympy variables f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) -to represent our ODE in r for the point potential. -''' def get_pde_in_recurrence_form(laplace): + ''' + get_pde_in_recurrence_form + Input: + - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such that assert(len(pde.eqs) == 1) + is true. + Output: + - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) satisfies away from the origin. + Note: to represent f, f_r, f_{rr}, we use the sympy variables f_{r0}, f_{r1}, .... So ode_in_r is a linear + combination of the sympy variables f_{r0}, f_{r1}, .... + - var, represents the variables for the input space: [x0, x1, ...] + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present + (the reason this is called n_derivs since if we have a second order PDE for example + then we might see f, f_{r}, f_{rr} in our ODE in r, which is technically 3 terms since we count + the 0th order derivative f as a "derivative." If this doesn't make sense just know that n_derivs + is the order the of the input sumpy PDE + 1) + + Description: We assume we are handed a system of 1 sumpy PDE (pde) and output the + pde in a way that allows us to easily replace derivatives with respect to r. In other words we output + a linear combination of sympy variables f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) + to represent our ODE in r for the point potential. + ''' dim = laplace.dim n_derivs = laplace.order assert(len(laplace.eqs) == 1) @@ -113,20 +85,33 @@ def compute_term(a, t): term = term.diff(var[i], t[i]) return term - pde = 0 + ode_in_r = 0 for i in range(ops): - pde += coeffs[i] * compute_term(f(rval), derivs[i]) + ode_in_r += coeffs[i] * compute_term(f(rval), derivs[i]) n_derivs = len(f_derivs) f_r_derivs = make_sympy_vec("f_r", n_derivs) for i in range(n_derivs): - pde = pde.subs(f_derivs[i], f_r_derivs[i]) + ode_in_r = ode_in_r.subs(f_derivs[i], f_r_derivs[i]) - return pde, var, n_derivs + return ode_in_r, var, n_derivs + + +def test_recurrence_finder(): + from sumpy.expansion.diff_op import make_identity_diff_op, laplacian + w = make_identity_diff_op(2) + laplace2d = laplacian(w) + print(get_pde_in_recurrence_form(laplace2d)) + + assert 1 == 1 + + + + -ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace_2d) +# ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace_2d) ''' @@ -181,11 +166,11 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): return ode_in_x -ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() -ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() +# ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() +# ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() -delta_x = sp.symbols("delta_x") -c_vec = make_sympy_vec("c", len(var)) +# delta_x = sp.symbols("delta_x") +# c_vec = make_sympy_vec("c", len(var)) ''' compute_poly_in_deriv @@ -214,7 +199,7 @@ def compute_poly_in_deriv(ode_in_x_cleared, n_derivs): return poly -poly = compute_poly_in_deriv(ode_in_x, n_derivs) +# poly = compute_poly_in_deriv(ode_in_x, n_derivs) ''' compute_coefficients_of_poly @@ -246,10 +231,10 @@ def tup(i,n=n_derivs): return coeffs -coeffs = compute_coefficients_of_poly(poly, n_derivs) +# coeffs = compute_coefficients_of_poly(poly, n_derivs) -i = sp.symbols("i") -s = sp.Function("s") +# i = sp.symbols("i") +# s = sp.Function("s") ''' compute_recurrence_relation @@ -291,6 +276,6 @@ def hc_diff(i, n): return r.simplify() -r = compute_recurrence_relation(coeffs, n_derivs) +# r = compute_recurrence_relation(coeffs, n_derivs) From da538df0817de98d4b8935662f40d5c6ee668a13 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 3 Jun 2024 20:01:16 -0700 Subject: [PATCH 03/75] Fix all flake8 issues --- sumpy/recurrence.py | 270 ++++++++++++++++++++++---------------------- 1 file changed, 133 insertions(+), 137 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 7586cffa9..227ada25f 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -23,12 +23,11 @@ THE SOFTWARE. """ -from collections import namedtuple import sympy as sp -from sumpy.expansion.diff_op import LinearPDESystemOperator from pytools.obj_array import make_obj_array -#A similar function exists in sumpy.symbolic + +#A similar function exists in sumpy.symbolic def make_sympy_vec(name, n): return make_obj_array([sp.Symbol(f"{name}{i}") for i in range(n)]) @@ -42,28 +41,33 @@ def make_sympy_vec(name, n): def get_pde_in_recurrence_form(laplace): ''' get_pde_in_recurrence_form - Input: - - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such that assert(len(pde.eqs) == 1) + Input: + - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such + that assert(len(pde.eqs) == 1) is true. - Output: - - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) satisfies away from the origin. - Note: to represent f, f_r, f_{rr}, we use the sympy variables f_{r0}, f_{r1}, .... So ode_in_r is a linear - combination of the sympy variables f_{r0}, f_{r1}, .... + Output: + - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) + satisfies away from the origin. + Note: to represent f, f_r, f_{rr}, we use the sympy variables + f_{r0}, f_{r1}, .... So ode_in_r is a linear combination of the sympy + variables f_{r0}, f_{r1}, .... - var, represents the variables for the input space: [x0, x1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present - (the reason this is called n_derivs since if we have a second order PDE for example - then we might see f, f_{r}, f_{rr} in our ODE in r, which is technically 3 terms since we count - the 0th order derivative f as a "derivative." If this doesn't make sense just know that n_derivs - is the order the of the input sumpy PDE + 1) - - Description: We assume we are handed a system of 1 sumpy PDE (pde) and output the - pde in a way that allows us to easily replace derivatives with respect to r. In other words we output - a linear combination of sympy variables f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) + - n_derivs, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present (the reason this is called n_derivs + since if we have a second order PDE for example then we might see f, f_{r}, + f_{rr} in our ODE in r, which is technically 3 terms since we count + the 0th order derivative f as a "derivative." If this doesn't make sense + just know that n_derivs is the order the of the input sumpy PDE + 1) + + Description: We assume we are handed a system of 1 sumpy PDE (pde) and output + the pde in a way that allows us to easily replace derivatives with respect to r. + In other words we output a linear combination of sympy variables + f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) to represent our ODE in r for the point potential. ''' dim = laplace.dim n_derivs = laplace.order - assert(len(laplace.eqs) == 1) + assert (len(laplace.eqs) == 1) ops = len(laplace.eqs[0]) derivs = [] coeffs = [] @@ -75,10 +79,9 @@ def get_pde_in_recurrence_form(laplace): eps = sp.symbols("epsilon") rval = r + eps - f = sp.Function("f") - f_derivs = [sp.diff(f(rval),eps,i) for i in range(n_derivs+1)] - + f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs+1)] + def compute_term(a, t): term = a for i in range(len(t)): @@ -88,76 +91,63 @@ def compute_term(a, t): ode_in_r = 0 for i in range(ops): ode_in_r += coeffs[i] * compute_term(f(rval), derivs[i]) - n_derivs = len(f_derivs) f_r_derivs = make_sympy_vec("f_r", n_derivs) for i in range(n_derivs): ode_in_r = ode_in_r.subs(f_derivs[i], f_r_derivs[i]) - return ode_in_r, var, n_derivs -def test_recurrence_finder(): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian - w = make_identity_diff_op(2) - laplace2d = laplacian(w) - print(get_pde_in_recurrence_form(laplace2d)) - - assert 1 == 1 - - - - - - -# ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace_2d) - - -''' -generate_ND_derivative_relations -Input: +def generate_ND_derivative_relations(var, n_derivs): + ''' + generate_ND_derivative_relations + Input: - var, a sympy vector of variables called [x0, x1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present -Output: - - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... using the chain rule + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of + f that may be present + Output: + - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... + using the chain rule (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) -Description: Using the chain rule outputs a vector that tells us how to write f, f_r, f_{rr}, ... as a linear -combination of f, f_x, f_{xx}, ... -''' -def generate_ND_derivative_relations(var, n_derivs): + Description: Using the chain rule outputs a vector that tells us how to + write f, f_r, f_{rr}, ... as a linear + combination of f, f_x, f_{xx}, ... + ''' f_r_derivs = make_sympy_vec("f_r", n_derivs) f_x_derivs = make_sympy_vec("f_x", n_derivs) f = sp.Function("f") eps = sp.symbols("epsilon") rval = sp.sqrt(sum(var**2)) + eps - f_derivs_x = [sp.diff(f(rval),var[0],i) for i in range(n_derivs)] - f_derivs = [sp.diff(f(rval),eps,i) for i in range(n_derivs)] + f_derivs_x = [sp.diff(f(rval), var[0], i) for i in range(n_derivs)] + f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs)] for i in range(len(f_derivs_x)): for j in range(len(f_derivs)): f_derivs_x[i] = f_derivs_x[i].subs(f_derivs[j], f_r_derivs[j]) system = [f_x_derivs[i] - f_derivs_x[i] for i in range(n_derivs)] - return sp.solve(system, *f_r_derivs, dict=True)[0] -''' -ode_in_r_to_x -Input: - - ode_in_r, a linear combination of f, f_r, f_{rr}, ... (in code represented as f_{r0}, f_{r1}, f_{r2}) +def ode_in_r_to_x(ode_in_r, var, n_derivs): + ''' + ode_in_r_to_x + Input: + - ode_in_r, a linear combination of f, f_r, f_{rr}, ... + (in code represented as f_{r0}, f_{r1}, f_{r2}) with coefficients as RATIONAL functions in var[0], var[1], ... - var, array of sympy variables [x_0, x_1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present -Output: - - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as rational - functions in var[0], var[1], ... - -Description: Translates an ode in the variable r into an ode in the variable x by substituting f, f_r, f_{rr}, ... - as a linear combination of f, f_x, f_{xx}, ... using the chain rule -''' -def ode_in_r_to_x(ode_in_r, var, n_derivs): + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of + f that may be present + Output: + - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as + rational functions in var[0], var[1], ... + + Description: Translates an ode in the variable r into an ode in the variable x + by substituting f, f_r, f_{rr}, ... as a linear combination of + f, f_x, f_{xx}, ... using the chain rule + ''' subme = generate_ND_derivative_relations(var, n_derivs) ode_in_x = ode_in_r f_r_derivs = make_sympy_vec("f_r", n_derivs) @@ -166,57 +156,54 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): return ode_in_x -# ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() -# ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() - -# delta_x = sp.symbols("delta_x") -# c_vec = make_sympy_vec("c", len(var)) - -''' -compute_poly_in_deriv -Input: - - ode_in_x_cleared, an ode in x, i.e. a linear combination of f, f_x, f_{xx}, ... - (in code represented as f_{x0}, f_{x1}, f_{x2}) with coefficients as POLYNOMIALS in var[0], var[1], ... - (i.e. not rational functions) - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present -Output: - - a polynomial in f, f_x, f_{xx}, ... (in code represented as f_{x0}, f_{x1}, f_{x2}) with coefficients - as polynomials in \delta_x where \delta_x = x_0 - c_0 that represents the ''shifted ODE'' - i.e. the ODE - where we substitute all occurences of \delta_x with x_0 - c_0 - -Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., with coefficients as polynomials -in \delta_x = x_0 - c_0. -''' -def compute_poly_in_deriv(ode_in_x_cleared, n_derivs): - #Note that generate_ND_derivative_relations will at worst put some power of $x_0^order$ in the denominator. To clear +def compute_poly_in_deriv(ode_in_x, n_derivs, var): + ''' + compute_poly_in_deriv + Input: + - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as + rational functions in var[0], var[1], ... + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives + of f that may be present + Output: + - a polynomial in f, f_x, f_{xx}, ... (in code represented as f_{x0}, f_{x1}, + f_{x2}) with coefficients as polynomials in delta_x where delta_x = x_0 - c_0 + that represents the ''shifted ODE'' - i.e. the ODE where we substitute all + occurences of delta_x with x_0 - c_0 + + Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., + with coefficients as polynomials in delta_x = x_0 - c_0. + ''' + #Note that generate_ND_derivative_relations will at worst put some power of + #$x_0^order$ in the denominator. To clear #the denominator we can probably? just multiply by x_0^order. + delta_x = sp.symbols("delta_x") + c_vec = make_sympy_vec("c", len(var)) ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() - ode_in_x_shifted = ode_in_x_cleared.subs(var[0], delta_x + c_vec[0]).simplify() - f_x_derivs = make_sympy_vec("f_x", n_derivs) poly = sp.Poly(ode_in_x_shifted, *f_x_derivs) - return poly -# poly = compute_poly_in_deriv(ode_in_x, n_derivs) - -''' -compute_coefficients_of_poly -Input: - - poly, a polynomial in sympy variables f_{x0}, f_{x1}, ..., - (recall that this corresponds to f_0, f_x, f_{xx}, ...) with coefficients that are polynomials in \delta_x - where poly represents the ''shifted ODE''- i.e. we substitute all occurences of \delta_x with x_0 - c_0 -Output: - - a 2d array, each row giving the coefficient of f_0, f_x, f_{xx}, ..., - each entry in the row giving the coefficients of the polynomial in \delta_x - -Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are polynomials in \delta_x -and outputs a 2d array for easy access to the coefficients based on their degree as a polynomial in \delta_x. -''' + def compute_coefficients_of_poly(poly, n_derivs): + ''' + compute_coefficients_of_poly + Input: + - poly, a polynomial in sympy variables f_{x0}, f_{x1}, ..., + (recall that this corresponds to f_0, f_x, f_{xx}, ...) with coefficients + that are polynomials in delta_x where poly represents the ''shifted ODE'' + - i.e. we substitute all occurences of delta_x with x_0 - c_0 + Output: + - a 2d array, each row giving the coefficient of f_0, f_x, f_{xx}, ..., + each entry in the row giving the coefficients of the polynomial in delta_x + Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are + polynomials in delta_x and outputs a 2d array for easy access to the + coefficients based on their degree as a polynomial in delta_x. + ''' + delta_x = sp.symbols("delta_x") + #Returns coefficients in lexographic order. So lowest order first - def tup(i,n=n_derivs): + def tup(i, n=n_derivs): a = [] for j in range(n): if j != i: @@ -224,42 +211,46 @@ def tup(i,n=n_derivs): else: a.append(1) return tuple(a) - + coeffs = [] for deriv_ind in range(n_derivs): - coeffs.append(sp.Poly(poly.coeff_monomial(tup(deriv_ind)), delta_x).all_coeffs()) - + coeffs.append( + sp.Poly(poly.coeff_monomial(tup(deriv_ind)), delta_x).all_coeffs()) + return coeffs -# coeffs = compute_coefficients_of_poly(poly, n_derivs) - -# i = sp.symbols("i") -# s = sp.Function("s") - -''' -compute_recurrence_relation -Input: - - coeffs a 2d array that gives access to the coefficients of poly, where poly represents the coefficients of - the ''shifted ODE'' (''shifted ODE'' = we substitute all occurences of \delta_x with x_0 - c_0) - based on their degree as a polynomial in \delta_x - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present -Output: - - a recurrence statement that equals 0 where s(i) is the ith coefficient of the Taylor polynomial for - our point potential. - -Description: Takes in coeffs which represents our ``shifted ode in x" (i.e. ode_in_x with coefficients in \delta_x) -and outputs a recurrence relation for the point potential. -''' - -def compute_recurrence_relation(coeffs, n_derivs): + +def compute_recurrence_relation(coeffs, n_derivs, var): + ''' + compute_recurrence_relation + Input: + - coeffs a 2d array that gives access to the coefficients of poly, where poly + represents the coefficients of the ''shifted ODE'' + (''shifted ODE'' = we substitute all occurences of delta_x with x_0 - c_0) + based on their degree as a polynomial in delta_x + - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives + of f that may be present + Output: + - a recurrence statement that equals 0 where s(i) is the ith coefficient of + the Taylor polynomial for our point potential. + + Description: Takes in coeffs which represents our ``shifted ode in x" + (i.e. ode_in_x with coefficients in delta_x) and outputs a recurrence relation + for the point potential. + ''' + i = sp.symbols("i") + s = sp.Function("s") + c_vec = make_sympy_vec("c", len(var)) + #Compute symbolic derivative def hc_diff(i, n): retMe = 1 for j in range(n): retMe *= (i-j) return retMe - - #We are differentiating deriv_ind, which shifts down deriv_ind. Do this for one deriv_ind + + #We are differentiating deriv_ind, which shifts down deriv_ind. + #Do this for one deriv_ind r = 0 for deriv_ind in range(n_derivs): part_of_r = 0 @@ -270,12 +261,17 @@ def hc_diff(i, n): temp = coeffs[deriv_ind][j] * s(i) * hc_diff(i, deriv_ind) part_of_r += temp.subs(i, i-shift) r += part_of_r - + for j in range(1, len(var)): r = r.subs(var[j], c_vec[j]) - + return r.simplify() -# r = compute_recurrence_relation(coeffs, n_derivs) +def test_recurrence_finder(): + from sumpy.expansion.diff_op import make_identity_diff_op, laplacian + w = make_identity_diff_op(2) + laplace2d = laplacian(w) + print(get_pde_in_recurrence_form(laplace2d)) + assert 1 == 1 From a172fb9628b37f294da41d8a7a98963dbc0b66ca Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 4 Jun 2024 10:14:27 -0700 Subject: [PATCH 04/75] Flake8 Issues --- sumpy/recurrence.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 227ada25f..defcebf29 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -99,9 +99,9 @@ def compute_term(a, t): return ode_in_r, var, n_derivs -def generate_ND_derivative_relations(var, n_derivs): +def generate_nd_derivative_relations(var, n_derivs): ''' - generate_ND_derivative_relations + generate_nd_derivative_relations Input: - var, a sympy vector of variables called [x0, x1, ...] - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of @@ -148,7 +148,7 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): by substituting f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... using the chain rule ''' - subme = generate_ND_derivative_relations(var, n_derivs) + subme = generate_nd_derivative_relations(var, n_derivs) ode_in_x = ode_in_r f_r_derivs = make_sympy_vec("f_r", n_derivs) for i in range(n_derivs): @@ -173,7 +173,7 @@ def compute_poly_in_deriv(ode_in_x, n_derivs, var): Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., with coefficients as polynomials in delta_x = x_0 - c_0. ''' - #Note that generate_ND_derivative_relations will at worst put some power of + #Note that generate_nd_derivative_relations will at worst put some power of #$x_0^order$ in the denominator. To clear #the denominator we can probably? just multiply by x_0^order. delta_x = sp.symbols("delta_x") @@ -244,10 +244,10 @@ def compute_recurrence_relation(coeffs, n_derivs, var): #Compute symbolic derivative def hc_diff(i, n): - retMe = 1 + retme = 1 for j in range(n): - retMe *= (i-j) - return retMe + retme *= (i-j) + return retme #We are differentiating deriv_ind, which shifts down deriv_ind. #Do this for one deriv_ind From 1ef4f3ce54efcede8dda7b582cd68bd6f548807d Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Fri, 7 Jun 2024 11:58:31 -0700 Subject: [PATCH 05/75] Flake8 Docstring Issue --- sumpy/recurrence.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index defcebf29..47b247bc4 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -39,7 +39,7 @@ def make_sympy_vec(name, n): def get_pde_in_recurrence_form(laplace): - ''' + """ get_pde_in_recurrence_form Input: - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such @@ -64,7 +64,7 @@ def get_pde_in_recurrence_form(laplace): In other words we output a linear combination of sympy variables f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) to represent our ODE in r for the point potential. - ''' + """ dim = laplace.dim n_derivs = laplace.order assert (len(laplace.eqs) == 1) @@ -100,7 +100,7 @@ def compute_term(a, t): def generate_nd_derivative_relations(var, n_derivs): - ''' + """ generate_nd_derivative_relations Input: - var, a sympy vector of variables called [x0, x1, ...] @@ -115,7 +115,7 @@ def generate_nd_derivative_relations(var, n_derivs): Description: Using the chain rule outputs a vector that tells us how to write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... - ''' + """ f_r_derivs = make_sympy_vec("f_r", n_derivs) f_x_derivs = make_sympy_vec("f_x", n_derivs) f = sp.Function("f") @@ -131,7 +131,7 @@ def generate_nd_derivative_relations(var, n_derivs): def ode_in_r_to_x(ode_in_r, var, n_derivs): - ''' + """ ode_in_r_to_x Input: - ode_in_r, a linear combination of f, f_r, f_{rr}, ... @@ -147,7 +147,7 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): Description: Translates an ode in the variable r into an ode in the variable x by substituting f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... using the chain rule - ''' + """ subme = generate_nd_derivative_relations(var, n_derivs) ode_in_x = ode_in_r f_r_derivs = make_sympy_vec("f_r", n_derivs) @@ -157,7 +157,7 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): def compute_poly_in_deriv(ode_in_x, n_derivs, var): - ''' + """ compute_poly_in_deriv Input: - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as @@ -172,7 +172,7 @@ def compute_poly_in_deriv(ode_in_x, n_derivs, var): Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., with coefficients as polynomials in delta_x = x_0 - c_0. - ''' + """ #Note that generate_nd_derivative_relations will at worst put some power of #$x_0^order$ in the denominator. To clear #the denominator we can probably? just multiply by x_0^order. @@ -186,7 +186,7 @@ def compute_poly_in_deriv(ode_in_x, n_derivs, var): def compute_coefficients_of_poly(poly, n_derivs): - ''' + """ compute_coefficients_of_poly Input: - poly, a polynomial in sympy variables f_{x0}, f_{x1}, ..., @@ -199,7 +199,7 @@ def compute_coefficients_of_poly(poly, n_derivs): Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are polynomials in delta_x and outputs a 2d array for easy access to the coefficients based on their degree as a polynomial in delta_x. - ''' + """ delta_x = sp.symbols("delta_x") #Returns coefficients in lexographic order. So lowest order first @@ -221,7 +221,7 @@ def tup(i, n=n_derivs): def compute_recurrence_relation(coeffs, n_derivs, var): - ''' + """ compute_recurrence_relation Input: - coeffs a 2d array that gives access to the coefficients of poly, where poly @@ -237,7 +237,7 @@ def compute_recurrence_relation(coeffs, n_derivs, var): Description: Takes in coeffs which represents our ``shifted ode in x" (i.e. ode_in_x with coefficients in delta_x) and outputs a recurrence relation for the point potential. - ''' + """ i = sp.symbols("i") s = sp.Function("s") c_vec = make_sympy_vec("c", len(var)) From 75be400c745f05890ccc5cd985620e74c95e0a68 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Fri, 7 Jun 2024 12:59:46 -0700 Subject: [PATCH 06/75] Fix pylint issues --- sumpy/recurrence.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 47b247bc4..7befcb76b 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -80,6 +80,7 @@ def get_pde_in_recurrence_form(laplace): eps = sp.symbols("epsilon") rval = r + eps f = sp.Function("f") + # pylint: disable=not-callable f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs+1)] def compute_term(a, t): @@ -121,8 +122,10 @@ def generate_nd_derivative_relations(var, n_derivs): f = sp.Function("f") eps = sp.symbols("epsilon") rval = sp.sqrt(sum(var**2)) + eps + # pylint: disable=not-callable f_derivs_x = [sp.diff(f(rval), var[0], i) for i in range(n_derivs)] f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs)] + # pylint: disable=not-callable for i in range(len(f_derivs_x)): for j in range(len(f_derivs)): f_derivs_x[i] = f_derivs_x[i].subs(f_derivs[j], f_r_derivs[j]) @@ -258,6 +261,7 @@ def hc_diff(i, n): for j in range(len(coeffs[deriv_ind])-1, -1, -1): shift = pow_delta - deriv_ind + 1 pow_delta += 1 + # pylint: disable=not-callable temp = coeffs[deriv_ind][j] * s(i) * hc_diff(i, deriv_ind) part_of_r += temp.subs(i, i-shift) r += part_of_r From 5116612abc3daeefe49a5169918683547c598e25 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 17 Jun 2024 15:41:09 -0700 Subject: [PATCH 07/75] Add test for Laplace2D --- sumpy/recurrence.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 7befcb76b..f42208e86 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -25,7 +25,8 @@ import sympy as sp from pytools.obj_array import make_obj_array - +from sumpy.expansion.diff_op import make_identity_diff_op, laplacian +import math #A similar function exists in sumpy.symbolic def make_sympy_vec(name, n): @@ -272,10 +273,31 @@ def hc_diff(i, n): return r.simplify() -def test_recurrence_finder(): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian +def test_recurrence_finder_laplace(): + """ + test_recurrence_finder_laplace + Description: Checks that the recurrence finder works correctly for the Laplace + 2D point potential. + """ w = make_identity_diff_op(2) laplace2d = laplacian(w) - print(get_pde_in_recurrence_form(laplace2d)) - - assert 1 == 1 + ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace2d) + ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() + poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) + coeffs = compute_coefficients_of_poly(poly, n_derivs) + i = sp.symbols("i") + s = sp.Function("s") + r = compute_recurrence_relation(coeffs, n_derivs, var) + + def coeff_laplace(i): + x, y = sp.symbols("x,y") + c_vec = make_sympy_vec("c", 2) + true_f = sp.log(sp.sqrt(x**2 + y**2)) + return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( + y, c_vec[1])/math.factorial(i) + d = 6 + # pylint: disable=not-callable + val = r.subs(i, d).subs(s(d+1),coeff_laplace(d+1)).subs( + s(d), coeff_laplace(d)).subs(s(d-1), coeff_laplace(d-1)).subs( + s(d-2), coeff_laplace(d-2)).simplify() + assert val == 0 From 63531c0e609944f76e1f4a7fc4e41d9cf8c830ae Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 18 Jun 2024 08:54:51 -0700 Subject: [PATCH 08/75] Add test Laplace3D --- sumpy/recurrence.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index f42208e86..347737aaf 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -301,3 +301,37 @@ def coeff_laplace(i): s(d), coeff_laplace(d)).subs(s(d-1), coeff_laplace(d-1)).subs( s(d-2), coeff_laplace(d-2)).simplify() assert val == 0 + + +def test_recurrence_finder_laplace_three_d(): + """ + test_recurrence_finder_laplace_three_d + Description: Checks that the recurrence finder works correctly for the Laplace + 3D point potential. + """ + w = make_identity_diff_op(3) + laplace3d = laplacian(w) + print(laplace3d) + ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace3d) + ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() + poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) + coeffs = compute_coefficients_of_poly(poly, n_derivs) + i = sp.symbols("i") + s = sp.Function("s") + r = compute_recurrence_relation(coeffs, n_derivs, var) + + def coeff_laplace_three_d(i): + x, y, z = sp.symbols("x,y,z") + c_vec = make_sympy_vec("c", 3) + true_f = 1/(sp.sqrt(x**2 + y**2 + z**2)) + return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( + y, c_vec[1]).subs(z, c_vec[2])/math.factorial(i) + + + d = 6 + # pylint: disable=not-callable + val = r.subs(i, d).subs(s(d+1),coeff_laplace_three_d(d+1)).subs( + s(d), coeff_laplace_three_d(d)).subs(s(d-1), coeff_laplace_three_d(d-1)).subs( + s(d-2), coeff_laplace_three_d(d-2)).simplify() + + assert val == 0 \ No newline at end of file From a85dade2e78cc27ed568d7b5bfc94171d5315e2a Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 18 Jun 2024 13:55:44 -0700 Subject: [PATCH 09/75] Flake8 --- sumpy/recurrence.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 347737aaf..7ae522c25 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -23,10 +23,11 @@ THE SOFTWARE. """ +import math import sympy as sp from pytools.obj_array import make_obj_array from sumpy.expansion.diff_op import make_identity_diff_op, laplacian -import math + #A similar function exists in sumpy.symbolic def make_sympy_vec(name, n): @@ -297,7 +298,7 @@ def coeff_laplace(i): y, c_vec[1])/math.factorial(i) d = 6 # pylint: disable=not-callable - val = r.subs(i, d).subs(s(d+1),coeff_laplace(d+1)).subs( + val = r.subs(i, d).subs(s(d+1), coeff_laplace(d+1)).subs( s(d), coeff_laplace(d)).subs(s(d-1), coeff_laplace(d-1)).subs( s(d-2), coeff_laplace(d-2)).simplify() assert val == 0 @@ -327,11 +328,11 @@ def coeff_laplace_three_d(i): return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( y, c_vec[1]).subs(z, c_vec[2])/math.factorial(i) - d = 6 # pylint: disable=not-callable - val = r.subs(i, d).subs(s(d+1),coeff_laplace_three_d(d+1)).subs( - s(d), coeff_laplace_three_d(d)).subs(s(d-1), coeff_laplace_three_d(d-1)).subs( + val = r.subs(i, d).subs(s(d+1), coeff_laplace_three_d(d+1)).subs( + s(d), coeff_laplace_three_d(d)).subs(s(d-1), + coeff_laplace_three_d(d-1)).subs( s(d-2), coeff_laplace_three_d(d-2)).simplify() assert val == 0 \ No newline at end of file From 412e1febe49e6fdd99fd08bea8199fb7c512c185 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 24 Jun 2024 15:34:51 -0500 Subject: [PATCH 10/75] Work on improving docs --- doc/expansion.rst | 5 +++ sumpy/recurrence.py | 74 +++++++++++++++++++++++++-------------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/doc/expansion.rst b/doc/expansion.rst index 5d72d735a..ea2680340 100644 --- a/doc/expansion.rst +++ b/doc/expansion.rst @@ -27,3 +27,8 @@ Estimating Expansion Orders --------------------------- .. automodule:: sumpy.expansion.level_to_order + +Recurrences +----------- + +.. automodule:: sumpy.recurrence diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 7ae522c25..7e0b8636a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -1,3 +1,12 @@ +""" +.. autofunction:: get_pde_in_recurrence_form +.. autofunction:: generate_nd_derivative_relations +.. autofunction:: ode_in_r_to_x +.. autofunction:: compute_poly_in_deriv +.. autofunction:: compute_coefficients_of_poly +.. autofunction:: compute_recurrence_relation +""" + __copyright__ = """ Copyright (C) 2024 Hirish Chandrasekaran Copyright (C) 2024 Andreas Kloeckner @@ -23,35 +32,32 @@ THE SOFTWARE. """ +import numpy as np import math import sympy as sp +from typing import Tuple from pytools.obj_array import make_obj_array -from sumpy.expansion.diff_op import make_identity_diff_op, laplacian +from sumpy.expansion.diff_op import ( + make_identity_diff_op, laplacian,LinearPDESystemOperator) -#A similar function exists in sumpy.symbolic -def make_sympy_vec(name, n): +# similar to make_sym_vector in sumpy.symbolic, but returns an object array +# instead of a sympy.Matrix. +def _make_sympy_vec(name, n): return make_obj_array([sp.Symbol(f"{name}{i}") for i in range(n)]) -__doc__ = """ -.. autoclass:: Recurrence -.. automodule:: sumpy.recurrence -""" - - -def get_pde_in_recurrence_form(laplace): +def get_pde_in_recurrence_form(pde: LinearPDESystemOperator) -> Tuple[ + sp.Expr, np.ndarray, int + ]: """ - get_pde_in_recurrence_form Input: - - pde, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` pde such - that assert(len(pde.eqs) == 1) - is true. + - *pde*, representing a scalar PDE. Output: - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) satisfies away from the origin. Note: to represent f, f_r, f_{rr}, we use the sympy variables - f_{r0}, f_{r1}, .... So ode_in_r is a linear combination of the sympy + :math:`f_{r0}`, f_{r1}, .... So ode_in_r is a linear combination of the sympy variables f_{r0}, f_{r1}, .... - var, represents the variables for the input space: [x0, x1, ...] - n_derivs, the order of the original PDE + 1, i.e. the number of @@ -67,16 +73,19 @@ def get_pde_in_recurrence_form(laplace): f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) to represent our ODE in r for the point potential. """ - dim = laplace.dim - n_derivs = laplace.order - assert (len(laplace.eqs) == 1) - ops = len(laplace.eqs[0]) + if len(pde.eqs) != 1: + raise ValueError("PDE must be scalar") + + dim = pde.dim + n_derivs = pde.order + assert (len(pde.eqs) == 1) + ops = len(pde.eqs[0]) derivs = [] coeffs = [] - for i in laplace.eqs[0]: + for i in pde.eqs[0]: derivs.append(i.mi) - coeffs.append(laplace.eqs[0][i]) - var = make_sympy_vec("x", dim) + coeffs.append(pde.eqs[0][i]) + var = _make_sympy_vec("x", dim) r = sp.sqrt(sum(var**2)) eps = sp.symbols("epsilon") @@ -95,7 +104,7 @@ def compute_term(a, t): for i in range(ops): ode_in_r += coeffs[i] * compute_term(f(rval), derivs[i]) n_derivs = len(f_derivs) - f_r_derivs = make_sympy_vec("f_r", n_derivs) + f_r_derivs = _make_sympy_vec("f_r", n_derivs) for i in range(n_derivs): ode_in_r = ode_in_r.subs(f_derivs[i], f_r_derivs[i]) @@ -109,6 +118,7 @@ def generate_nd_derivative_relations(var, n_derivs): - var, a sympy vector of variables called [x0, x1, ...] - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present + Output: - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... using the chain rule @@ -119,8 +129,8 @@ def generate_nd_derivative_relations(var, n_derivs): write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... """ - f_r_derivs = make_sympy_vec("f_r", n_derivs) - f_x_derivs = make_sympy_vec("f_x", n_derivs) + f_r_derivs = _make_sympy_vec("f_r", n_derivs) + f_x_derivs = _make_sympy_vec("f_x", n_derivs) f = sp.Function("f") eps = sp.symbols("epsilon") rval = sp.sqrt(sum(var**2)) + eps @@ -155,7 +165,7 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): """ subme = generate_nd_derivative_relations(var, n_derivs) ode_in_x = ode_in_r - f_r_derivs = make_sympy_vec("f_r", n_derivs) + f_r_derivs = _make_sympy_vec("f_r", n_derivs) for i in range(n_derivs): ode_in_x = ode_in_x.subs(f_r_derivs[i], subme[f_r_derivs[i]]) return ode_in_x @@ -182,10 +192,10 @@ def compute_poly_in_deriv(ode_in_x, n_derivs, var): #$x_0^order$ in the denominator. To clear #the denominator we can probably? just multiply by x_0^order. delta_x = sp.symbols("delta_x") - c_vec = make_sympy_vec("c", len(var)) + c_vec = _make_sympy_vec("c", len(var)) ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() ode_in_x_shifted = ode_in_x_cleared.subs(var[0], delta_x + c_vec[0]).simplify() - f_x_derivs = make_sympy_vec("f_x", n_derivs) + f_x_derivs = _make_sympy_vec("f_x", n_derivs) poly = sp.Poly(ode_in_x_shifted, *f_x_derivs) return poly @@ -245,7 +255,7 @@ def compute_recurrence_relation(coeffs, n_derivs, var): """ i = sp.symbols("i") s = sp.Function("s") - c_vec = make_sympy_vec("c", len(var)) + c_vec = _make_sympy_vec("c", len(var)) #Compute symbolic derivative def hc_diff(i, n): @@ -292,7 +302,7 @@ def test_recurrence_finder_laplace(): def coeff_laplace(i): x, y = sp.symbols("x,y") - c_vec = make_sympy_vec("c", 2) + c_vec = _make_sympy_vec("c", 2) true_f = sp.log(sp.sqrt(x**2 + y**2)) return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( y, c_vec[1])/math.factorial(i) @@ -323,7 +333,7 @@ def test_recurrence_finder_laplace_three_d(): def coeff_laplace_three_d(i): x, y, z = sp.symbols("x,y,z") - c_vec = make_sympy_vec("c", 3) + c_vec = _make_sympy_vec("c", 3) true_f = 1/(sp.sqrt(x**2 + y**2 + z**2)) return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( y, c_vec[1]).subs(z, c_vec[2])/math.factorial(i) @@ -335,4 +345,4 @@ def coeff_laplace_three_d(i): coeff_laplace_three_d(d-1)).subs( s(d-2), coeff_laplace_three_d(d-2)).simplify() - assert val == 0 \ No newline at end of file + assert val == 0 From 6476f0b01c18d7b616c4b46c32ee5aa95397e5fb Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 25 Jun 2024 13:48:13 -0700 Subject: [PATCH 11/75] Make function for producing recurrence from pde --- sumpy/recurrence.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 7e0b8636a..476f34434 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -284,6 +284,24 @@ def hc_diff(i, n): return r.simplify() +def get_recurrence_from_pde(pde): + """ + Input: + - *pde*, representing a scalar PDE. + + Output: + - r, a recurrence relation for a Line-Taylor expansion. + + Description: Takes in a pde, outputs a recurrence. + """ + ode_in_r, var, n_derivs = get_pde_in_recurrence_form(pde) + ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() + poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) + coeffs = compute_coefficients_of_poly(poly, n_derivs) + r = compute_recurrence_relation(coeffs, n_derivs, var) + return r + + def test_recurrence_finder_laplace(): """ test_recurrence_finder_laplace @@ -292,13 +310,9 @@ def test_recurrence_finder_laplace(): """ w = make_identity_diff_op(2) laplace2d = laplacian(w) - ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace2d) - ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() - poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) - coeffs = compute_coefficients_of_poly(poly, n_derivs) + r = get_recurrence_from_pde(laplace2d) i = sp.symbols("i") s = sp.Function("s") - r = compute_recurrence_relation(coeffs, n_derivs, var) def coeff_laplace(i): x, y = sp.symbols("x,y") @@ -323,13 +337,9 @@ def test_recurrence_finder_laplace_three_d(): w = make_identity_diff_op(3) laplace3d = laplacian(w) print(laplace3d) - ode_in_r, var, n_derivs = get_pde_in_recurrence_form(laplace3d) - ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() - poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) - coeffs = compute_coefficients_of_poly(poly, n_derivs) + r = get_recurrence_from_pde(laplace3d) i = sp.symbols("i") s = sp.Function("s") - r = compute_recurrence_relation(coeffs, n_derivs, var) def coeff_laplace_three_d(i): x, y, z = sp.symbols("x,y,z") From 19b5a39fa893e36dc440e4b726804527a18a39d2 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 26 Jun 2024 16:00:24 -0700 Subject: [PATCH 12/75] Update documentation --- sumpy/recurrence.py | 137 +++++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 476f34434..3aefab1f0 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -31,14 +31,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - -import numpy as np import math -import sympy as sp from typing import Tuple +import numpy as np +import sympy as sp from pytools.obj_array import make_obj_array from sumpy.expansion.diff_op import ( - make_identity_diff_op, laplacian,LinearPDESystemOperator) + make_identity_diff_op, laplacian, LinearPDESystemOperator) # similar to make_sym_vector in sumpy.symbolic, but returns an object array @@ -49,23 +48,26 @@ def _make_sympy_vec(name, n): def get_pde_in_recurrence_form(pde: LinearPDESystemOperator) -> Tuple[ sp.Expr, np.ndarray, int - ]: +]: """ Input: - *pde*, representing a scalar PDE. + Output: - - ode_in_r, an ode in r which the POINT-POTENTIAL (has radial symmetry) - satisfies away from the origin. - Note: to represent f, f_r, f_{rr}, we use the sympy variables - :math:`f_{r0}`, f_{r1}, .... So ode_in_r is a linear combination of the sympy - variables f_{r0}, f_{r1}, .... + - ode_in_r, an ode in r which the point-potential corresponding to the PDE + satisfies away from the origin. We assume that the point-potential has + radial symmetry. + Note: to represent :math:`f, f_r, f_{rr}`, we use the sympy variables + f_{r0}, f_{r1}, ... So ode_in_r is a linear combination of the + sympy variables f_{r0}, f_{r1}, ... - var, represents the variables for the input space: [x0, x1, ...] - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present (the reason this is called n_derivs - since if we have a second order PDE for example then we might see f, f_{r}, - f_{rr} in our ODE in r, which is technically 3 terms since we count - the 0th order derivative f as a "derivative." If this doesn't make sense - just know that n_derivs is the order the of the input sumpy PDE + 1) + since if we have a second order PDE for example then we might see + :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms + since we count the 0th order derivative f as a "derivative." If this + doesn't make sense just know that n_derivs is the order the of the input + sumpy PDE + 1) Description: We assume we are handed a system of 1 sumpy PDE (pde) and output the pde in a way that allows us to easily replace derivatives with respect to r. @@ -111,23 +113,21 @@ def compute_term(a, t): return ode_in_r, var, n_derivs -def generate_nd_derivative_relations(var, n_derivs): +def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: """ - generate_nd_derivative_relations Input: - - var, a sympy vector of variables called [x0, x1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of - f that may be present + - *var*, a sympy vector of variables called [x0, x1, ...] + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present Output: - - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... - using the chain rule - (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and - f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) + - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... + using the chain rule + (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and + f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) Description: Using the chain rule outputs a vector that tells us how to - write f, f_r, f_{rr}, ... as a linear - combination of f, f_x, f_{xx}, ... + write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... """ f_r_derivs = _make_sympy_vec("f_r", n_derivs) f_x_derivs = _make_sympy_vec("f_x", n_derivs) @@ -145,19 +145,19 @@ def generate_nd_derivative_relations(var, n_derivs): return sp.solve(system, *f_r_derivs, dict=True)[0] -def ode_in_r_to_x(ode_in_r, var, n_derivs): +def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: """ - ode_in_r_to_x Input: - - ode_in_r, a linear combination of f, f_r, f_{rr}, ... - (in code represented as f_{r0}, f_{r1}, f_{r2}) - with coefficients as RATIONAL functions in var[0], var[1], ... - - var, array of sympy variables [x_0, x_1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives of - f that may be present + - *ode_in_r*, a linear combination of f, f_r, f_{rr}, ... + (in code represented as f_{r0}, f_{r1}, f_{r2}) + with coefficients as RATIONAL functions in var[0], var[1], ... + - *var*, array of sympy variables [x_0, x_1, ...] + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + Output: - - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as - rational functions in var[0], var[1], ... + - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as + rational functions in var[0], var[1], ... Description: Translates an ode in the variable r into an ode in the variable x by substituting f, f_r, f_{rr}, ... as a linear combination of @@ -171,19 +171,22 @@ def ode_in_r_to_x(ode_in_r, var, n_derivs): return ode_in_x -def compute_poly_in_deriv(ode_in_x, n_derivs, var): +def compute_poly_in_deriv(ode_in_x: sp.Expr, n_derivs: int, var: + np.ndarray) -> sp.polys.polytools.Poly: """ - compute_poly_in_deriv Input: - - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as - rational functions in var[0], var[1], ... - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives - of f that may be present + - *ode_in_x*, a linear combination of f, f_x, f_{xx}, ... with coefficients + as rational functions in var[0], var[1], ... + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + Output: - - a polynomial in f, f_x, f_{xx}, ... (in code represented as f_{x0}, f_{x1}, - f_{x2}) with coefficients as polynomials in delta_x where delta_x = x_0 - c_0 - that represents the ''shifted ODE'' - i.e. the ODE where we substitute all - occurences of delta_x with x_0 - c_0 + - a polynomial in math:`f, f_x, f_{xx}, ...` (in code represented as + math:`f_{x0}, f_{x1}, f_{x2}`) with coefficients as polynomials in + math:`\\delta_x` where + ammath:`delta_x = x_0 - c_0` that represents the ''shifted ODE'' - i.e. + the ODE where we substitute all occurences of math:`delta_x` with + math:`x_0 - c_0` Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., with coefficients as polynomials in delta_x = x_0 - c_0. @@ -200,17 +203,21 @@ def compute_poly_in_deriv(ode_in_x, n_derivs, var): return poly -def compute_coefficients_of_poly(poly, n_derivs): +def compute_coefficients_of_poly(poly: sp.polys.polytools.Poly, + n_derivs: int) -> list: """ - compute_coefficients_of_poly Input: - - poly, a polynomial in sympy variables f_{x0}, f_{x1}, ..., - (recall that this corresponds to f_0, f_x, f_{xx}, ...) with coefficients - that are polynomials in delta_x where poly represents the ''shifted ODE'' - - i.e. we substitute all occurences of delta_x with x_0 - c_0 + - *poly*, a polynomial in sympy variables math:`f_{x0}, f_{x1}, ...`, + (recall that this corresponds to math:`f_0, f_x, f_{xx}, ...`) with + coefficients that are polynomials in delta_x where poly represents the + ''shifted ODE'' i.e. we substitute all occurences of math:`\\delta_x` + with math:`x_0 - c_0` + Output: - - a 2d array, each row giving the coefficient of f_0, f_x, f_{xx}, ..., - each entry in the row giving the coefficients of the polynomial in delta_x + - coeffs, a 2d array, each row giving the coefficient of + math:`f_0, f_x, f_{xx}, ...`, each entry in the row giving the + coefficients of the polynomial in math:`\\delta_x` + Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are polynomials in delta_x and outputs a 2d array for easy access to the coefficients based on their degree as a polynomial in delta_x. @@ -237,17 +244,17 @@ def tup(i, n=n_derivs): def compute_recurrence_relation(coeffs, n_derivs, var): """ - compute_recurrence_relation Input: - - coeffs a 2d array that gives access to the coefficients of poly, where poly - represents the coefficients of the ''shifted ODE'' - (''shifted ODE'' = we substitute all occurences of delta_x with x_0 - c_0) - based on their degree as a polynomial in delta_x - - n_derivs, the order of the original PDE + 1, i.e. the number of derivatives - of f that may be present + - *coeffs* a 2d array that gives access to the coefficients of poly, where + poly represents the coefficients of the ''shifted ODE'' + (''shifted ODE'' = we substitute all occurences of delta_x with x_0 - c_0) + based on their degree as a polynomial in delta_x) + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + Output: - - a recurrence statement that equals 0 where s(i) is the ith coefficient of - the Taylor polynomial for our point potential. + - r, a recurrence statement that equals 0 where s(i) is the ith coefficient + of the Taylor polynomial for our point potential. Description: Takes in coeffs which represents our ``shifted ode in x" (i.e. ode_in_x with coefficients in delta_x) and outputs a recurrence relation @@ -290,7 +297,8 @@ def get_recurrence_from_pde(pde): - *pde*, representing a scalar PDE. Output: - - r, a recurrence relation for a Line-Taylor expansion. + - r, a recurrence relation for a coefficients of a Line-Taylor expansion of + the point potential. Description: Takes in a pde, outputs a recurrence. """ @@ -336,7 +344,6 @@ def test_recurrence_finder_laplace_three_d(): """ w = make_identity_diff_op(3) laplace3d = laplacian(w) - print(laplace3d) r = get_recurrence_from_pde(laplace3d) i = sp.symbols("i") s = sp.Function("s") @@ -355,4 +362,4 @@ def coeff_laplace_three_d(i): coeff_laplace_three_d(d-1)).subs( s(d-2), coeff_laplace_three_d(d-2)).simplify() - assert val == 0 + assert val == 0 \ No newline at end of file From 3417a8d98a86f5319b05a7cdf1d7ef4adc346411 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Thu, 27 Jun 2024 13:17:15 -0700 Subject: [PATCH 13/75] List comprehension --- sumpy/recurrence.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 3aefab1f0..f50a106aa 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -234,10 +234,8 @@ def tup(i, n=n_derivs): a.append(1) return tuple(a) - coeffs = [] - for deriv_ind in range(n_derivs): - coeffs.append( - sp.Poly(poly.coeff_monomial(tup(deriv_ind)), delta_x).all_coeffs()) + coeffs = [sp.Poly(poly.coeff_monomial(tup(deriv_ind)), delta_x).all_coeffs() + for deriv_ind in range(n_derivs)] return coeffs From 2195ed0e357d308af1702c64bb42189c7c797a93 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Thu, 27 Jun 2024 14:02:08 -0700 Subject: [PATCH 14/75] From hardcode to loop --- sumpy/recurrence.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index f50a106aa..c4fe6a739 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -355,9 +355,9 @@ def coeff_laplace_three_d(i): d = 6 # pylint: disable=not-callable - val = r.subs(i, d).subs(s(d+1), coeff_laplace_three_d(d+1)).subs( - s(d), coeff_laplace_three_d(d)).subs(s(d-1), - coeff_laplace_three_d(d-1)).subs( - s(d-2), coeff_laplace_three_d(d-2)).simplify() + r_sub = r.subs(i, d) + for i in range(d-2, d+2): + r_sub = r_sub.subs(s(i), coeff_laplace_three_d(i)) + r_sub = r_sub.simplify() - assert val == 0 \ No newline at end of file + assert r_sub == 0 From c0e3f30633c17bb804b59d529374dcddd77f9dbe Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 30 Jun 2024 17:31:00 -0700 Subject: [PATCH 15/75] Added get_recurrence_order --- sumpy/recurrence.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index c4fe6a739..3b58017f3 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -289,6 +289,23 @@ def hc_diff(i, n): return r.simplify() +def get_recurrence_order(coeffs): + """ + Input: + - *coeffs*, represents coefficients of a scalar ODE. + + Output: + - true_order, the order of the recurrence relation that will be produced. + """ + orders = [] + for i in range(len(coeffs)): + for j in range(len(coeffs[i])): + if coeffs[i][j] != 0: + orders.append(i - j) + true_order = (max(orders)-min(orders)+1) + return true_order + + def get_recurrence_from_pde(pde): """ Input: From a8142840b8940b058caeb202b91becda831127cf Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 1 Jul 2024 10:49:35 -0700 Subject: [PATCH 16/75] Unit test for order --- sumpy/recurrence.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 3b58017f3..05a21de63 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -322,7 +322,7 @@ def get_recurrence_from_pde(pde): poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) coeffs = compute_coefficients_of_poly(poly, n_derivs) r = compute_recurrence_relation(coeffs, n_derivs, var) - return r + return r, get_recurrence_order(coeffs) def test_recurrence_finder_laplace(): @@ -333,7 +333,7 @@ def test_recurrence_finder_laplace(): """ w = make_identity_diff_op(2) laplace2d = laplacian(w) - r = get_recurrence_from_pde(laplace2d) + r, order = get_recurrence_from_pde(laplace2d) i = sp.symbols("i") s = sp.Function("s") @@ -348,6 +348,7 @@ def coeff_laplace(i): val = r.subs(i, d).subs(s(d+1), coeff_laplace(d+1)).subs( s(d), coeff_laplace(d)).subs(s(d-1), coeff_laplace(d-1)).subs( s(d-2), coeff_laplace(d-2)).simplify() + assert order == 4 assert val == 0 @@ -359,7 +360,7 @@ def test_recurrence_finder_laplace_three_d(): """ w = make_identity_diff_op(3) laplace3d = laplacian(w) - r = get_recurrence_from_pde(laplace3d) + r, order = get_recurrence_from_pde(laplace3d) i = sp.symbols("i") s = sp.Function("s") @@ -376,5 +377,5 @@ def coeff_laplace_three_d(i): for i in range(d-2, d+2): r_sub = r_sub.subs(s(i), coeff_laplace_three_d(i)) r_sub = r_sub.simplify() - - assert r_sub == 0 + assert order == 4 + assert r_sub == 0 \ No newline at end of file From c4dd7c9298d4bb0cd1f57ea751144e7dfde79a6d Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 1 Jul 2024 10:54:22 -0700 Subject: [PATCH 17/75] Flake8 --- sumpy/recurrence.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 05a21de63..ccf280953 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -117,7 +117,7 @@ def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: """ Input: - *var*, a sympy vector of variables called [x0, x1, ...] - - *n_derivs*, the order of the original PDE + 1, i.e. the number of + - *n_derivs*, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present Output: @@ -214,8 +214,8 @@ def compute_coefficients_of_poly(poly: sp.polys.polytools.Poly, with math:`x_0 - c_0` Output: - - coeffs, a 2d array, each row giving the coefficient of - math:`f_0, f_x, f_{xx}, ...`, each entry in the row giving the + - coeffs, a 2d array, each row giving the coefficient of + math:`f_0, f_x, f_{xx}, ...`, each entry in the row giving the coefficients of the polynomial in math:`\\delta_x` Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are @@ -243,15 +243,15 @@ def tup(i, n=n_derivs): def compute_recurrence_relation(coeffs, n_derivs, var): """ Input: - - *coeffs* a 2d array that gives access to the coefficients of poly, where + - *coeffs* a 2d array that gives access to the coefficients of poly, where poly represents the coefficients of the ''shifted ODE'' (''shifted ODE'' = we substitute all occurences of delta_x with x_0 - c_0) based on their degree as a polynomial in delta_x) - - *n_derivs*, the order of the original PDE + 1, i.e. the number of + - *n_derivs*, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present Output: - - r, a recurrence statement that equals 0 where s(i) is the ith coefficient + - r, a recurrence statement that equals 0 where s(i) is the ith coefficient of the Taylor polynomial for our point potential. Description: Takes in coeffs which represents our ``shifted ode in x" @@ -312,7 +312,7 @@ def get_recurrence_from_pde(pde): - *pde*, representing a scalar PDE. Output: - - r, a recurrence relation for a coefficients of a Line-Taylor expansion of + - r, a recurrence relation for a coefficients of a Line-Taylor expansion of the point potential. Description: Takes in a pde, outputs a recurrence. @@ -327,9 +327,8 @@ def get_recurrence_from_pde(pde): def test_recurrence_finder_laplace(): """ - test_recurrence_finder_laplace - Description: Checks that the recurrence finder works correctly for the Laplace - 2D point potential. + Description: Test the recurrence relation produced for the Laplace 2D point + potential. """ w = make_identity_diff_op(2) laplace2d = laplacian(w) @@ -354,7 +353,6 @@ def coeff_laplace(i): def test_recurrence_finder_laplace_three_d(): """ - test_recurrence_finder_laplace_three_d Description: Checks that the recurrence finder works correctly for the Laplace 3D point potential. """ @@ -378,4 +376,4 @@ def coeff_laplace_three_d(i): r_sub = r_sub.subs(s(i), coeff_laplace_three_d(i)) r_sub = r_sub.simplify() assert order == 4 - assert r_sub == 0 \ No newline at end of file + assert r_sub == 0 From f095401b8b4741e1275a0d5fbabfef39fc70f1b2 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 1 Jul 2024 14:07:09 -0700 Subject: [PATCH 18/75] Update get_recurrence_order --- sumpy/recurrence.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index ccf280953..c77fec574 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -32,7 +32,7 @@ THE SOFTWARE. """ import math -from typing import Tuple +from typing import Sequence, Tuple import numpy as np import sympy as sp from pytools.obj_array import make_obj_array @@ -289,21 +289,24 @@ def hc_diff(i, n): return r.simplify() -def get_recurrence_order(coeffs): +def get_recurrence_order(coeffs: Sequence[Sequence[sp.Expr]]) -> int: """ Input: - - *coeffs*, represents coefficients of a scalar ODE. - + - *coeffs*, represents coefficients of the normalized, + center-shifted ODE (se above) + with the outer sequence reflecting the order of the derivative, + and the second expansion reflecting expansion in the shift + $\delta_x$ Output: - true_order, the order of the recurrence relation that will be produced. """ - orders = [] - for i in range(len(coeffs)): - for j in range(len(coeffs[i])): - if coeffs[i][j] != 0: - orders.append(i - j) - true_order = (max(orders)-min(orders)+1) - return true_order + orders = { + i - j + for i, deriv_order_coeff in enumerate(coeffs) + for j, shift_coeff in enumerate(deriv_order_coeff) + if shift_coeff + } + return max(orders)-min(orders)+1 def get_recurrence_from_pde(pde): From 77776f93a39cf8b56b87f9f29d2cd205be262e82 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 9 Jul 2024 21:10:30 -0700 Subject: [PATCH 19/75] Add parametric recurrence finder --- sumpy/recurrence.py | 107 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index c77fec574..d4ce2d7ad 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -5,6 +5,10 @@ .. autofunction:: compute_poly_in_deriv .. autofunction:: compute_coefficients_of_poly .. autofunction:: compute_recurrence_relation +.. autofunction:: get_recurrence_parametric_from_pde +.. autofunction:: get_recurrence_parametric_from_coeffs +.. autofunction:: auto_product_rule_single_term +.. autofunction:: compute_coefficients_of_poly_parametric """ __copyright__ = """ @@ -293,10 +297,10 @@ def get_recurrence_order(coeffs: Sequence[Sequence[sp.Expr]]) -> int: """ Input: - *coeffs*, represents coefficients of the normalized, - center-shifted ODE (se above) + center-shifted ODE (see above) with the outer sequence reflecting the order of the derivative, and the second expansion reflecting expansion in the shift - $\delta_x$ + math:`\\delta_x` Output: - true_order, the order of the recurrence relation that will be produced. """ @@ -328,6 +332,101 @@ def get_recurrence_from_pde(pde): return r, get_recurrence_order(coeffs) +def compute_coefficients_of_poly_parametric(poly, n_derivs, var): + """ + Input: + - *poly*, a polynomial in sympy variables math:`f_{x0}, f_{x1}, ...`, + (recall that this corresponds to math:`f_0, f_x, f_{xx}, ...`) with + coefficients that are polynomials in math:`x_0` where poly represents the + TRUE ODE. + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + - *var*, array of sympy variables [x_0, x_1, ...] + + Output: + - coeffs, a 2d array, each row giving the coefficient of + math:`f_0, f_x, f_{xx}, ...`, each entry in the row giving the + coefficients of the polynomial in math:`x_0` + + Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are + polynomials in math:`x_0` and outputs a 2d array for easy access to the + coefficients based on their degree as a polynomial in math:`x_0`. + """ + def tup(i, n=n_derivs): + a = [] + for j in range(n): + if j != i: + a.append(0) + else: + a.append(1) + return tuple(a) + + coeffs = [] + for deriv_ind in range(n_derivs): + coeffs.append(sp.Poly(poly.coeff_monomial(tup(deriv_ind)), + var[0]).all_coeffs()[::-1]) + + return coeffs + + +def auto_product_rule_single_term(p, m, var): + """ + Input: + - *p*, degree of monomial + - *m*, order of derivative + + Output: + - recurrence relation for ODE math:`x_0^p f^(m)(x_0)` + """ + n = sp.symbols("n") + s = sp.Function("s") + result = 0 + for i in range(p+1): + temp = 1 + for j in range(i): + temp *= (n - j) + # pylint: disable=not-callable + temp *= math.comb(p, i) * s(n-i+m) * var[0]**(p-i) + result += temp + return result + + +def get_recurrence_parametric_from_coeffs(coeffs, var): + """ + Input: + - *coeffs* + + Output: + - recurrence relation for full ODE + """ + final_recurrence = 0 + for m, _ in enumerate(coeffs): + for p, _ in enumerate(coeffs[m]): + final_recurrence += coeffs[m][p] * auto_product_rule_single_term(p, + m, var) + return final_recurrence + + +def get_recurrence_parametric_from_pde(pde): + """ + Input: + - *pde*, representing a scalar PDE. + + Output: + - r, a recurrence relation for a coefficients of a Line-Taylor expansion of + the point potential. + + Description: Takes in a pde, outputs a recurrence. + """ + ode_in_r, var, n_derivs = get_pde_in_recurrence_form(pde) + ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() + ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() + f_x_derivs = _make_sympy_vec("f_x", n_derivs) + poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) + coeffs = compute_coefficients_of_poly_parametric(poly, n_derivs, var) + return get_recurrence_parametric_from_coeffs(coeffs, var) + + def test_recurrence_finder_laplace(): """ Description: Test the recurrence relation produced for the Laplace 2D point @@ -361,7 +460,7 @@ def test_recurrence_finder_laplace_three_d(): """ w = make_identity_diff_op(3) laplace3d = laplacian(w) - r, order = get_recurrence_from_pde(laplace3d) + r, _ = get_recurrence_from_pde(laplace3d) i = sp.symbols("i") s = sp.Function("s") @@ -378,5 +477,5 @@ def coeff_laplace_three_d(i): for i in range(d-2, d+2): r_sub = r_sub.subs(s(i), coeff_laplace_three_d(i)) r_sub = r_sub.simplify() - assert order == 4 + #assert order == 4 assert r_sub == 0 From ba6d40840d14361af33400ee97f2087fa8518dfe Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 09:30:45 -0700 Subject: [PATCH 20/75] Update recurrence.py --- sumpy/recurrence.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index d4ce2d7ad..f4173da8a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -377,6 +377,7 @@ def auto_product_rule_single_term(p, m, var): Output: - recurrence relation for ODE math:`x_0^p f^(m)(x_0)` + s(i) """ n = sp.symbols("n") s = sp.Function("s") @@ -394,12 +395,14 @@ def auto_product_rule_single_term(p, m, var): def get_recurrence_parametric_from_coeffs(coeffs, var): """ Input: - - *coeffs* + - *coeffs*, take the ODE Output: - recurrence relation for full ODE """ final_recurrence = 0 + #Outer loop is derivative direction + #Inner is polynomial order of x_0 for m, _ in enumerate(coeffs): for p, _ in enumerate(coeffs[m]): final_recurrence += coeffs[m][p] * auto_product_rule_single_term(p, From 01a4d06f1d9a9f977603b11ca36d5b60a3293770 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 10:09:04 -0700 Subject: [PATCH 21/75] Added tests for parametric --- sumpy/recurrence.py | 205 +++++--------------------------------------- 1 file changed, 23 insertions(+), 182 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index f4173da8a..5a1383cb9 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -2,9 +2,6 @@ .. autofunction:: get_pde_in_recurrence_form .. autofunction:: generate_nd_derivative_relations .. autofunction:: ode_in_r_to_x -.. autofunction:: compute_poly_in_deriv -.. autofunction:: compute_coefficients_of_poly -.. autofunction:: compute_recurrence_relation .. autofunction:: get_recurrence_parametric_from_pde .. autofunction:: get_recurrence_parametric_from_coeffs .. autofunction:: auto_product_rule_single_term @@ -36,7 +33,7 @@ THE SOFTWARE. """ import math -from typing import Sequence, Tuple +from typing import Tuple import numpy as np import sympy as sp from pytools.obj_array import make_obj_array @@ -175,163 +172,6 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: return ode_in_x -def compute_poly_in_deriv(ode_in_x: sp.Expr, n_derivs: int, var: - np.ndarray) -> sp.polys.polytools.Poly: - """ - Input: - - *ode_in_x*, a linear combination of f, f_x, f_{xx}, ... with coefficients - as rational functions in var[0], var[1], ... - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present - - Output: - - a polynomial in math:`f, f_x, f_{xx}, ...` (in code represented as - math:`f_{x0}, f_{x1}, f_{x2}`) with coefficients as polynomials in - math:`\\delta_x` where - ammath:`delta_x = x_0 - c_0` that represents the ''shifted ODE'' - i.e. - the ODE where we substitute all occurences of math:`delta_x` with - math:`x_0 - c_0` - - Description: Converts an ode in x, to a polynomial in f, f_x, f_{xx}, ..., - with coefficients as polynomials in delta_x = x_0 - c_0. - """ - #Note that generate_nd_derivative_relations will at worst put some power of - #$x_0^order$ in the denominator. To clear - #the denominator we can probably? just multiply by x_0^order. - delta_x = sp.symbols("delta_x") - c_vec = _make_sympy_vec("c", len(var)) - ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() - ode_in_x_shifted = ode_in_x_cleared.subs(var[0], delta_x + c_vec[0]).simplify() - f_x_derivs = _make_sympy_vec("f_x", n_derivs) - poly = sp.Poly(ode_in_x_shifted, *f_x_derivs) - return poly - - -def compute_coefficients_of_poly(poly: sp.polys.polytools.Poly, - n_derivs: int) -> list: - """ - Input: - - *poly*, a polynomial in sympy variables math:`f_{x0}, f_{x1}, ...`, - (recall that this corresponds to math:`f_0, f_x, f_{xx}, ...`) with - coefficients that are polynomials in delta_x where poly represents the - ''shifted ODE'' i.e. we substitute all occurences of math:`\\delta_x` - with math:`x_0 - c_0` - - Output: - - coeffs, a 2d array, each row giving the coefficient of - math:`f_0, f_x, f_{xx}, ...`, each entry in the row giving the - coefficients of the polynomial in math:`\\delta_x` - - Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are - polynomials in delta_x and outputs a 2d array for easy access to the - coefficients based on their degree as a polynomial in delta_x. - """ - delta_x = sp.symbols("delta_x") - - #Returns coefficients in lexographic order. So lowest order first - def tup(i, n=n_derivs): - a = [] - for j in range(n): - if j != i: - a.append(0) - else: - a.append(1) - return tuple(a) - - coeffs = [sp.Poly(poly.coeff_monomial(tup(deriv_ind)), delta_x).all_coeffs() - for deriv_ind in range(n_derivs)] - - return coeffs - - -def compute_recurrence_relation(coeffs, n_derivs, var): - """ - Input: - - *coeffs* a 2d array that gives access to the coefficients of poly, where - poly represents the coefficients of the ''shifted ODE'' - (''shifted ODE'' = we substitute all occurences of delta_x with x_0 - c_0) - based on their degree as a polynomial in delta_x) - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present - - Output: - - r, a recurrence statement that equals 0 where s(i) is the ith coefficient - of the Taylor polynomial for our point potential. - - Description: Takes in coeffs which represents our ``shifted ode in x" - (i.e. ode_in_x with coefficients in delta_x) and outputs a recurrence relation - for the point potential. - """ - i = sp.symbols("i") - s = sp.Function("s") - c_vec = _make_sympy_vec("c", len(var)) - - #Compute symbolic derivative - def hc_diff(i, n): - retme = 1 - for j in range(n): - retme *= (i-j) - return retme - - #We are differentiating deriv_ind, which shifts down deriv_ind. - #Do this for one deriv_ind - r = 0 - for deriv_ind in range(n_derivs): - part_of_r = 0 - pow_delta = 0 - for j in range(len(coeffs[deriv_ind])-1, -1, -1): - shift = pow_delta - deriv_ind + 1 - pow_delta += 1 - # pylint: disable=not-callable - temp = coeffs[deriv_ind][j] * s(i) * hc_diff(i, deriv_ind) - part_of_r += temp.subs(i, i-shift) - r += part_of_r - - for j in range(1, len(var)): - r = r.subs(var[j], c_vec[j]) - - return r.simplify() - - -def get_recurrence_order(coeffs: Sequence[Sequence[sp.Expr]]) -> int: - """ - Input: - - *coeffs*, represents coefficients of the normalized, - center-shifted ODE (see above) - with the outer sequence reflecting the order of the derivative, - and the second expansion reflecting expansion in the shift - math:`\\delta_x` - Output: - - true_order, the order of the recurrence relation that will be produced. - """ - orders = { - i - j - for i, deriv_order_coeff in enumerate(coeffs) - for j, shift_coeff in enumerate(deriv_order_coeff) - if shift_coeff - } - return max(orders)-min(orders)+1 - - -def get_recurrence_from_pde(pde): - """ - Input: - - *pde*, representing a scalar PDE. - - Output: - - r, a recurrence relation for a coefficients of a Line-Taylor expansion of - the point potential. - - Description: Takes in a pde, outputs a recurrence. - """ - ode_in_r, var, n_derivs = get_pde_in_recurrence_form(pde) - ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() - poly = compute_poly_in_deriv(ode_in_x, n_derivs, var) - coeffs = compute_coefficients_of_poly(poly, n_derivs) - r = compute_recurrence_relation(coeffs, n_derivs, var) - return r, get_recurrence_order(coeffs) - - def compute_coefficients_of_poly_parametric(poly, n_derivs, var): """ Input: @@ -437,23 +277,25 @@ def test_recurrence_finder_laplace(): """ w = make_identity_diff_op(2) laplace2d = laplacian(w) - r, order = get_recurrence_from_pde(laplace2d) - i = sp.symbols("i") + r = get_recurrence_parametric_from_pde(laplace2d) + n = sp.symbols("n") s = sp.Function("s") - def coeff_laplace(i): + def deriv_laplace(i): x, y = sp.symbols("x,y") - c_vec = _make_sympy_vec("c", 2) + var = _make_sympy_vec("x", 2) true_f = sp.log(sp.sqrt(x**2 + y**2)) - return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( - y, c_vec[1])/math.factorial(i) + return sp.diff(true_f, x, i).subs(x, var[0]).subs( + y, var[1]) d = 6 # pylint: disable=not-callable - val = r.subs(i, d).subs(s(d+1), coeff_laplace(d+1)).subs( - s(d), coeff_laplace(d)).subs(s(d-1), coeff_laplace(d-1)).subs( - s(d-2), coeff_laplace(d-2)).simplify() - assert order == 4 - assert val == 0 + + r_sub = r.subs(n, d) + for i in range(d-1, d+3): + r_sub = r_sub.subs(s(i), deriv_laplace(i)) + r_sub = r_sub.simplify() + + assert r_sub == 0 def test_recurrence_finder_laplace_three_d(): @@ -463,22 +305,21 @@ def test_recurrence_finder_laplace_three_d(): """ w = make_identity_diff_op(3) laplace3d = laplacian(w) - r, _ = get_recurrence_from_pde(laplace3d) - i = sp.symbols("i") + r = get_recurrence_parametric_from_pde(laplace3d) + n = sp.symbols("n") s = sp.Function("s") - def coeff_laplace_three_d(i): + def deriv_laplace_three_d(i): x, y, z = sp.symbols("x,y,z") - c_vec = _make_sympy_vec("c", 3) + var = _make_sympy_vec("x", 3) true_f = 1/(sp.sqrt(x**2 + y**2 + z**2)) - return sp.diff(true_f, x, i).subs(x, c_vec[0]).subs( - y, c_vec[1]).subs(z, c_vec[2])/math.factorial(i) + return sp.diff(true_f, x, i).subs(x, var[0]).subs( + y, var[1]).subs(z, var[2]) d = 6 # pylint: disable=not-callable - r_sub = r.subs(i, d) - for i in range(d-2, d+2): - r_sub = r_sub.subs(s(i), coeff_laplace_three_d(i)) + r_sub = r.subs(n, d) + for i in range(d-1, d+3): + r_sub = r_sub.subs(s(i), deriv_laplace_three_d(i)) r_sub = r_sub.simplify() - #assert order == 4 assert r_sub == 0 From 2c912b9d50545d48fdd70f6991d507839ee9dd6b Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 10:22:27 -0700 Subject: [PATCH 22/75] Add function skeletons --- sumpy/recurrence.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 5a1383cb9..34ba4129c 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -172,7 +172,8 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: return ode_in_x -def compute_coefficients_of_poly_parametric(poly, n_derivs, var): +def compute_coefficients_of_poly_parametric(poly: sp.Poly, n_derivs: int, + var: np.ndarray) -> list: """ Input: - *poly*, a polynomial in sympy variables math:`f_{x0}, f_{x1}, ...`, @@ -209,7 +210,7 @@ def tup(i, n=n_derivs): return coeffs -def auto_product_rule_single_term(p, m, var): +def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: """ Input: - *p*, degree of monomial @@ -232,12 +233,12 @@ def auto_product_rule_single_term(p, m, var): return result -def get_recurrence_parametric_from_coeffs(coeffs, var): +def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.Expr: """ - Input: + ## Input: - *coeffs*, take the ODE - Output: + ## Output: - recurrence relation for full ODE """ final_recurrence = 0 @@ -250,7 +251,7 @@ def get_recurrence_parametric_from_coeffs(coeffs, var): return final_recurrence -def get_recurrence_parametric_from_pde(pde): +def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: """ Input: - *pde*, representing a scalar PDE. From 92d3bec0920a8a3e24c84f1c3e85c3d77f349c4f Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 11:23:09 -0700 Subject: [PATCH 23/75] Remove all documentation --- sumpy/recurrence.py | 111 -------------------------------------------- 1 file changed, 111 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 34ba4129c..561fa24c3 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -50,32 +50,6 @@ def _make_sympy_vec(name, n): def get_pde_in_recurrence_form(pde: LinearPDESystemOperator) -> Tuple[ sp.Expr, np.ndarray, int ]: - """ - Input: - - *pde*, representing a scalar PDE. - - Output: - - ode_in_r, an ode in r which the point-potential corresponding to the PDE - satisfies away from the origin. We assume that the point-potential has - radial symmetry. - Note: to represent :math:`f, f_r, f_{rr}`, we use the sympy variables - f_{r0}, f_{r1}, ... So ode_in_r is a linear combination of the - sympy variables f_{r0}, f_{r1}, ... - - var, represents the variables for the input space: [x0, x1, ...] - - n_derivs, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present (the reason this is called n_derivs - since if we have a second order PDE for example then we might see - :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms - since we count the 0th order derivative f as a "derivative." If this - doesn't make sense just know that n_derivs is the order the of the input - sumpy PDE + 1) - - Description: We assume we are handed a system of 1 sumpy PDE (pde) and output - the pde in a way that allows us to easily replace derivatives with respect to r. - In other words we output a linear combination of sympy variables - f_{r0}, f_{r1}, ... (which represents f, f_r, f_{rr} respectively) - to represent our ODE in r for the point potential. - """ if len(pde.eqs) != 1: raise ValueError("PDE must be scalar") @@ -115,21 +89,6 @@ def compute_term(a, t): def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: - """ - Input: - - *var*, a sympy vector of variables called [x0, x1, ...] - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present - - Output: - - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... - using the chain rule - (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and - f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) - - Description: Using the chain rule outputs a vector that tells us how to - write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... - """ f_r_derivs = _make_sympy_vec("f_r", n_derivs) f_x_derivs = _make_sympy_vec("f_x", n_derivs) f = sp.Function("f") @@ -147,23 +106,6 @@ def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: - """ - Input: - - *ode_in_r*, a linear combination of f, f_r, f_{rr}, ... - (in code represented as f_{r0}, f_{r1}, f_{r2}) - with coefficients as RATIONAL functions in var[0], var[1], ... - - *var*, array of sympy variables [x_0, x_1, ...] - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present - - Output: - - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as - rational functions in var[0], var[1], ... - - Description: Translates an ode in the variable r into an ode in the variable x - by substituting f, f_r, f_{rr}, ... as a linear combination of - f, f_x, f_{xx}, ... using the chain rule - """ subme = generate_nd_derivative_relations(var, n_derivs) ode_in_x = ode_in_r f_r_derivs = _make_sympy_vec("f_r", n_derivs) @@ -174,25 +116,6 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: def compute_coefficients_of_poly_parametric(poly: sp.Poly, n_derivs: int, var: np.ndarray) -> list: - """ - Input: - - *poly*, a polynomial in sympy variables math:`f_{x0}, f_{x1}, ...`, - (recall that this corresponds to math:`f_0, f_x, f_{xx}, ...`) with - coefficients that are polynomials in math:`x_0` where poly represents the - TRUE ODE. - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present - - *var*, array of sympy variables [x_0, x_1, ...] - - Output: - - coeffs, a 2d array, each row giving the coefficient of - math:`f_0, f_x, f_{xx}, ...`, each entry in the row giving the - coefficients of the polynomial in math:`x_0` - - Description: Takes in a polynomial in f_{x0}, f_{x1}, ..., w/coeffs that are - polynomials in math:`x_0` and outputs a 2d array for easy access to the - coefficients based on their degree as a polynomial in math:`x_0`. - """ def tup(i, n=n_derivs): a = [] for j in range(n): @@ -211,15 +134,6 @@ def tup(i, n=n_derivs): def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: - """ - Input: - - *p*, degree of monomial - - *m*, order of derivative - - Output: - - recurrence relation for ODE math:`x_0^p f^(m)(x_0)` - s(i) - """ n = sp.symbols("n") s = sp.Function("s") result = 0 @@ -234,13 +148,6 @@ def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.Expr: - """ - ## Input: - - *coeffs*, take the ODE - - ## Output: - - recurrence relation for full ODE - """ final_recurrence = 0 #Outer loop is derivative direction #Inner is polynomial order of x_0 @@ -252,16 +159,6 @@ def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.E def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: - """ - Input: - - *pde*, representing a scalar PDE. - - Output: - - r, a recurrence relation for a coefficients of a Line-Taylor expansion of - the point potential. - - Description: Takes in a pde, outputs a recurrence. - """ ode_in_r, var, n_derivs = get_pde_in_recurrence_form(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() @@ -272,10 +169,6 @@ def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: def test_recurrence_finder_laplace(): - """ - Description: Test the recurrence relation produced for the Laplace 2D point - potential. - """ w = make_identity_diff_op(2) laplace2d = laplacian(w) r = get_recurrence_parametric_from_pde(laplace2d) @@ -300,10 +193,6 @@ def deriv_laplace(i): def test_recurrence_finder_laplace_three_d(): - """ - Description: Checks that the recurrence finder works correctly for the Laplace - 3D point potential. - """ w = make_identity_diff_op(3) laplace3d = laplacian(w) r = get_recurrence_parametric_from_pde(laplace3d) From 8f6e2487bfed9c9a2c0d752eb2688db65a10f424 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 12:08:38 -0700 Subject: [PATCH 24/75] Added documentation --- sumpy/recurrence.py | 96 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 561fa24c3..04d7f489a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -50,6 +50,27 @@ def _make_sympy_vec(name, n): def get_pde_in_recurrence_form(pde: LinearPDESystemOperator) -> Tuple[ sp.Expr, np.ndarray, int ]: + """ + ## Input + - *pde*, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` such that + pde.eqs == 1 + ## Output + - ode_in_r, an ODE that the point-potential satifies w/respect to radial variable + - var, an array representing the input coordinates + - n_derivs, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present (the reason this is called n_derivs + since if we have a second order PDE for example then we might see + :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms + since we count the 0th order derivative f as a "derivative." If this + doesn't make sense just know that n_derivs is the order the of the input + sumpy PDE + 1) + ## Description + Takes as input a scalar pde represented as the type + :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator`. Assumes that the scalar + pde has coefficients that are polynomial in the input coordinates. Then assumes + that the PDE is satisfied by a point-potential with radial symmetry and comes up + with an ODE in the radial variable that the point-potential satisfies. + """ if len(pde.eqs) != 1: raise ValueError("PDE must be scalar") @@ -89,6 +110,20 @@ def compute_term(a, t): def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: + """ + ## Input + - *var*, a sympy vector of variables called [x0, x1, ...] + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + ## Output + - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... + using the chain rule + (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and + f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) + ## Description + Using the chain rule outputs a vector that tells us how to + write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... + """ f_r_derivs = _make_sympy_vec("f_r", n_derivs) f_x_derivs = _make_sympy_vec("f_x", n_derivs) f = sp.Function("f") @@ -106,6 +141,22 @@ def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: + """ + ## Input + - *ode_in_r*, a linear combination of f, f_r, f_{rr}, ... + (in code represented as f_{r0}, f_{r1}, f_{r2}) + with coefficients as RATIONAL functions in var[0], var[1], ... + - *var*, array of sympy variables [x_0, x_1, ...] + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + ## Output + - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as + rational functions in var[0], var[1], ... + ## Description + Translates an ode in the variable r into an ode in the variable x + by substituting f, f_r, f_{rr}, ... as a linear combination of + f, f_x, f_{xx}, ... using the chain rule. + """ subme = generate_nd_derivative_relations(var, n_derivs) ode_in_x = ode_in_r f_r_derivs = _make_sympy_vec("f_r", n_derivs) @@ -116,6 +167,21 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: def compute_coefficients_of_poly_parametric(poly: sp.Poly, n_derivs: int, var: np.ndarray) -> list: + """ + ## Input + - *poly*, the original ODE for our point-potential as a polynomial + in f_{x0}, f_{x1}, f_{x2}, etc. + - *n_derivs*, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present + - *var*, array of sympy variables [x_0, x_1, ...] + ## Output + - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as + rational functions in var[0], var[1], ... + ## Description + Translates an ode in the variable r into an ode in the variable x + by substituting f, f_r, f_{rr}, ... as a linear combination of + f, f_x, f_{xx}, ... using the chain rule. + """ def tup(i, n=n_derivs): a = [] for j in range(n): @@ -148,6 +214,20 @@ def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.Expr: + """ + ## Input + - *coeffs*, a 2D array with elements :math:`b_{ij}`. + If we write the coefficients of our ODE for the point-potential as a + polynomial w/respect to f_{x0}, f_{x1}, f_{x2}, ... we can call these + coefficients :math:`a_0, a_1, a_2, ...` Since each coefficient :math:`a_i` is a + polynomial in :math:`x_0`, we can write a_i as a polynomial in :math:`x_0^j`, + and call these coefficients :math:`b_{ij}`. + + - *var*, array of sympy variables [x_0, x_1, ...] + ## Output + - final_recurrence, the recurrence relation for derivatives of our + point-potential. + """ final_recurrence = 0 #Outer loop is derivative direction #Inner is polynomial order of x_0 @@ -159,6 +239,14 @@ def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.E def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: + """ + ## Input + - *pde*, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` such that + pde.eqs == 1 + ## Output + - final_recurrence, the recurrence relation for derivatives of our + point-potential. + """ ode_in_r, var, n_derivs = get_pde_in_recurrence_form(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() @@ -169,6 +257,10 @@ def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: def test_recurrence_finder_laplace(): + """ + ## Description + Tests our recurrence relation generator for Lapalace 2D. + """ w = make_identity_diff_op(2) laplace2d = laplacian(w) r = get_recurrence_parametric_from_pde(laplace2d) @@ -193,6 +285,10 @@ def deriv_laplace(i): def test_recurrence_finder_laplace_three_d(): + """ + ## Description + Tests our recurrence relation generator for Laplace 3D. + """ w = make_identity_diff_op(3) laplace3d = laplacian(w) r = get_recurrence_parametric_from_pde(laplace3d) From d9b6777460c33e64488cee86ffa86d22faae3519 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 12:28:16 -0700 Subject: [PATCH 25/75] Update recurrence.py --- sumpy/recurrence.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 04d7f489a..2bccd7a4a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -216,17 +216,17 @@ def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.Expr: """ ## Input - - *coeffs*, a 2D array with elements :math:`b_{ij}`. - If we write the coefficients of our ODE for the point-potential as a - polynomial w/respect to f_{x0}, f_{x1}, f_{x2}, ... we can call these - coefficients :math:`a_0, a_1, a_2, ...` Since each coefficient :math:`a_i` is a - polynomial in :math:`x_0`, we can write a_i as a polynomial in :math:`x_0^j`, - and call these coefficients :math:`b_{ij}`. - + - *coeffs*, + Consider an ODE obeyed by a function f that can be expressed in the following + form: :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \\cdots) \\partial_{x_0}^0 f + + (b_{10} x_0^0 + b_{11} x_0^1 +\\cdots) \\partial_x^1 f`. coeffs is a sequence + of sequences, with the outer sequence iterating over derivative orders, and + each inner sequence iterating over powers of :math:`x_0`, so that, in terms of + the above form, coeffs is [[b_00, b_01, ...], [b_10, b_11, ...], ...] - *var*, array of sympy variables [x_0, x_1, ...] ## Output - - final_recurrence, the recurrence relation for derivatives of our - point-potential. + - final_recurrence, the recurrence relation for derivatives of our + point-potential. """ final_recurrence = 0 #Outer loop is derivative direction From 7102511da14defb5a4e71e2974b428f0767befd4 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 12:40:37 -0700 Subject: [PATCH 26/75] Update recurrence.py --- sumpy/recurrence.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 2bccd7a4a..f0f7b85d5 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -200,6 +200,23 @@ def tup(i, n=n_derivs): def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: + """ + ## Description + We assume that we are given the expression :math:`x_0^p f^(m)(x_0)`. We then + output the nth order derivative of the expression where n is a symbolic variable. + We let :math:`s(i)` represent the ith order derivative of f when + we output the final result. + ## Input + - *p*, see description + - *m*, see description + - *var*, array of sympy variables [x_0, x_1, ...] + ## Output + - A sympy expression is output. + We let :math:`s(i)` represent the ith order derivative of f when + we output the final result. We let n represent a symbolic variable + corresponding to how many derivatives of the original expression were + taken. + """ n = sp.symbols("n") s = sp.Function("s") result = 0 From b3d74f892b060c39ccc7551d16fe3894b1b25e1d Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 10 Jul 2024 12:42:05 -0700 Subject: [PATCH 27/75] Update recurrence.py --- sumpy/recurrence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index f0f7b85d5..e45ea3858 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -211,7 +211,8 @@ def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: - *m*, see description - *var*, array of sympy variables [x_0, x_1, ...] ## Output - - A sympy expression is output. + - A sympy expression is output corresponding to the nth order derivative of the + input expression. We let :math:`s(i)` represent the ith order derivative of f when we output the final result. We let n represent a symbolic variable corresponding to how many derivatives of the original expression were From c2432dd868354987bf11e978f971e51b37a180e8 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Fri, 12 Jul 2024 14:26:45 -0700 Subject: [PATCH 28/75] Slight mistake in documentation --- sumpy/recurrence.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index e45ea3858..4788eabc1 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -145,7 +145,8 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: ## Input - *ode_in_r*, a linear combination of f, f_r, f_{rr}, ... (in code represented as f_{r0}, f_{r1}, f_{r2}) - with coefficients as RATIONAL functions in var[0], var[1], ... + with coefficients that are polynomials in var[0], var[1], ... + divided by some power of var[0] - *var*, array of sympy variables [x_0, x_1, ...] - *n_derivs*, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present @@ -170,7 +171,8 @@ def compute_coefficients_of_poly_parametric(poly: sp.Poly, n_derivs: int, """ ## Input - *poly*, the original ODE for our point-potential as a polynomial - in f_{x0}, f_{x1}, f_{x2}, etc. + in f_{x0}, f_{x1}, f_{x2}, etc. with polynomial coefficients + in var[0], var[1], ... - *n_derivs*, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present - *var*, array of sympy variables [x_0, x_1, ...] From a174f462154124320174e4f5a5856b4d26bb3eda Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 14 Jul 2024 14:43:41 -0700 Subject: [PATCH 29/75] Added narrative --- sumpy/recurrence.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 4788eabc1..3d233ba21 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -46,6 +46,20 @@ def _make_sympy_vec(name, n): return make_obj_array([sp.Symbol(f"{name}{i}") for i in range(n)]) +""" +Overall Narrative: +First we take an elliptic PDE represented as a sumpy +:class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` type. We then +use get_pde_in_recurrence_form to get an ODE in the radial variable +that the point-potential satisfies assuming radial symmetry of the point-potential. + +We then take the ODE in the radial variable that we get and use the chain-rule to +convert it into a ODE in a single spatial variable using ode_in_r_to_x. We then +collect the coefficients of the ODE using compute_coefficients_of_poly_parametric +and then use these coefficients to finally compute the recurrence relation via +get_recurrence_parametric_from_pde. +""" + def get_pde_in_recurrence_form(pde: LinearPDESystemOperator) -> Tuple[ sp.Expr, np.ndarray, int From 0d3728b0ca236a4dc4b5a3c5a1ead94d2c06779a Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 14 Jul 2024 14:44:42 -0700 Subject: [PATCH 30/75] Flake - narrative --- sumpy/recurrence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 3d233ba21..f29d9fa96 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -46,16 +46,17 @@ def _make_sympy_vec(name, n): return make_obj_array([sp.Symbol(f"{name}{i}") for i in range(n)]) + """ Overall Narrative: -First we take an elliptic PDE represented as a sumpy +First we take an elliptic PDE represented as a sumpy :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` type. We then use get_pde_in_recurrence_form to get an ODE in the radial variable that the point-potential satisfies assuming radial symmetry of the point-potential. We then take the ODE in the radial variable that we get and use the chain-rule to convert it into a ODE in a single spatial variable using ode_in_r_to_x. We then -collect the coefficients of the ODE using compute_coefficients_of_poly_parametric +collect the coefficients of the ODE using compute_coefficients_of_poly_parametric and then use these coefficients to finally compute the recurrence relation via get_recurrence_parametric_from_pde. """ From 175cbe97aa243f56458521ff52098b56552fa200 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 14 Jul 2024 23:23:22 -0700 Subject: [PATCH 31/75] Added helmholtz unit test --- sumpy/recurrence.py | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index f29d9fa96..608ba99d1 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -344,3 +344,58 @@ def deriv_laplace_three_d(i): r_sub = r_sub.subs(s(i), deriv_laplace_three_d(i)) r_sub = r_sub.simplify() assert r_sub == 0 + + +def test_recurrence_finder_helmholtz_three_d(): + """ + ## Description + Tests our recurrence relation generator for Helmhotlz 3D. + """ + #We are creating the recurrence relation for helmholtz3d which + #seems to be an order 5 recurrence relation + w = make_identity_diff_op(3) + helmholtz3d = laplacian(w) + w + r = get_recurrence_parametric_from_pde(helmholtz3d) + + #We create that function that gives the derivatives of the point + # potential for helmholtz + #Remember! Our point-source was placed at the origin and we + # were performing a LT expansion at x_0 + def deriv_helmholtz_three_d(i, s_loc): + s_x = s_loc[0] + s_y = s_loc[1] + s_z = s_loc[2] + x, y, z = sp.symbols("x,y,z") + true_f = sp.exp(1j * sp.sqrt(x**2 + y**2 + z**2) + ) / (sp.sqrt(x**2 + y**2 + z**2)) + return sp.diff(true_f, x, i).subs(x, s_x).subs( + y, s_y).subs(z, s_z) + + #Create relevant symbols + var = _make_sympy_vec("x", 3) + n = sp.symbols("n") + s = sp.Function("s") + + #Create random source location + s_loc = np.random.rand(3) + + #Create random order to check + d = np.random.randint(0, 5) + + #Substitute random location into recurrence relation and value of n = d + r_loc = r.subs(var[0], s_loc[0]) + r_loc = r_loc.subs(var[1], s_loc[1]) + r_loc = r_loc.subs(var[2], s_loc[2]) + r_sub = r_loc.subs(n, d) + + #Checking that the recurrence holds to some machine epsilon + for i in range(max(d-3, 0), d+3): + # pylint: disable=not-callable + r_sub = r_sub.subs(s(i), deriv_helmholtz_three_d(i, s_loc)) + err = abs(abs(r_sub).evalf()) + print(err) + assert err <= 1e-10 + + + + From d35e3811a769df47d8a06786175648324fe52966 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 15 Jul 2024 16:05:02 -0500 Subject: [PATCH 32/75] Documentation tweaks --- sumpy/recurrence.py | 107 +++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 608ba99d1..ba4fd4616 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -1,13 +1,34 @@ -""" -.. autofunction:: get_pde_in_recurrence_form -.. autofunction:: generate_nd_derivative_relations +r""" +With the functionality in this module, we aim to compute a recurrence for +one-dimensional derivatives of functions :math:`f:\mathbb R^n \to \mathbb R` +for functions :math:`f` satisfying two assumptions: + +- :math:`f` satisfies a PDE is linear and has coefficients polynomial + in the coordinates. +- :math:`f` only depends on the radius :math:`r`, + i.e. :math:`f(\boldsymbol x)=f(|\boldsymbol x|_2)`. + +This process proceeds in multiple steps: + +- Convert from the PDE to an ODE in :math:`r`, using :func:`pde_to_ode_in_r`. +- Convert from an ODE in :math:`r` to one in :math:`x`, using :func:`ode_in_r_to_x`. +- Sort general-form ODE in :math:`x` into a coefficient array, using + :func:`ode_in_x_to_coeff_array`. +- Finally, get an expression for the recurrence, using + :func:`recurrence_from_coeff_array`. + +The whole process can be automated using :func:`recurrence_from_pde`. + +.. autofunction:: pde_to_ode_in_r .. autofunction:: ode_in_r_to_x -.. autofunction:: get_recurrence_parametric_from_pde -.. autofunction:: get_recurrence_parametric_from_coeffs -.. autofunction:: auto_product_rule_single_term -.. autofunction:: compute_coefficients_of_poly_parametric +.. autofunction:: ode_in_x_to_coeff_array +.. autofunction:: recurrence_from_coeff_array +.. autofunction:: recurrence_from_pde """ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2024 Hirish Chandrasekaran Copyright (C) 2024 Andreas Kloeckner @@ -33,7 +54,6 @@ THE SOFTWARE. """ import math -from typing import Tuple import numpy as np import sympy as sp from pytools.obj_array import make_obj_array @@ -47,44 +67,29 @@ def _make_sympy_vec(name, n): return make_obj_array([sp.Symbol(f"{name}{i}") for i in range(n)]) -""" -Overall Narrative: -First we take an elliptic PDE represented as a sumpy -:class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` type. We then -use get_pde_in_recurrence_form to get an ODE in the radial variable -that the point-potential satisfies assuming radial symmetry of the point-potential. - -We then take the ODE in the radial variable that we get and use the chain-rule to -convert it into a ODE in a single spatial variable using ode_in_r_to_x. We then -collect the coefficients of the ODE using compute_coefficients_of_poly_parametric -and then use these coefficients to finally compute the recurrence relation via -get_recurrence_parametric_from_pde. -""" - - -def get_pde_in_recurrence_form(pde: LinearPDESystemOperator) -> Tuple[ +def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ sp.Expr, np.ndarray, int ]: """ - ## Input - - *pde*, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` such that - pde.eqs == 1 - ## Output - - ode_in_r, an ODE that the point-potential satifies w/respect to radial variable - - var, an array representing the input coordinates - - n_derivs, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present (the reason this is called n_derivs - since if we have a second order PDE for example then we might see - :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms - since we count the 0th order derivative f as a "derivative." If this - doesn't make sense just know that n_derivs is the order the of the input - sumpy PDE + 1) - ## Description Takes as input a scalar pde represented as the type :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator`. Assumes that the scalar pde has coefficients that are polynomial in the input coordinates. Then assumes that the PDE is satisfied by a point-potential with radial symmetry and comes up with an ODE in the radial variable that the point-potential satisfies. + + :arg pde: must satisfy ``pde.eqs == 1``` + + :returns: a tuple ``(ode_in_r, var, n_derivs)``, where + - *ode_in_r* is the ODE satisfied by :math:`f`. + - var, an array representing the input coordinates + (maybe give an example?) + - n_derivs, the order of the original PDE + 1, i.e. the number of + derivatives of f that may be present (the reason this is called n_derivs + since if we have a second order PDE for example then we might see + :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms + since we count the 0th order derivative f as a "derivative." If this + doesn't make sense just know that n_derivs is the order the of the input + sumpy PDE + 1) """ if len(pde.eqs) != 1: raise ValueError("PDE must be scalar") @@ -124,7 +129,7 @@ def compute_term(a, t): return ode_in_r, var, n_derivs -def generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: +def _generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: """ ## Input - *var*, a sympy vector of variables called [x0, x1, ...] @@ -173,7 +178,7 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: by substituting f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... using the chain rule. """ - subme = generate_nd_derivative_relations(var, n_derivs) + subme = _generate_nd_derivative_relations(var, n_derivs) ode_in_x = ode_in_r f_r_derivs = _make_sympy_vec("f_r", n_derivs) for i in range(n_derivs): @@ -181,7 +186,7 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: return ode_in_x -def compute_coefficients_of_poly_parametric(poly: sp.Poly, n_derivs: int, +def ode_in_x_to_coeff_array(poly: sp.Poly, n_derivs: int, var: np.ndarray) -> list: """ ## Input @@ -216,7 +221,7 @@ def tup(i, n=n_derivs): return coeffs -def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: +def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: """ ## Description We assume that we are given the expression :math:`x_0^p f^(m)(x_0)`. We then @@ -248,7 +253,7 @@ def auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: return result -def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.Expr: +def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: """ ## Input - *coeffs*, @@ -268,12 +273,12 @@ def get_recurrence_parametric_from_coeffs(coeffs: list, var: np.ndarray) -> sp.E #Inner is polynomial order of x_0 for m, _ in enumerate(coeffs): for p, _ in enumerate(coeffs[m]): - final_recurrence += coeffs[m][p] * auto_product_rule_single_term(p, + final_recurrence += coeffs[m][p] * _auto_product_rule_single_term(p, m, var) return final_recurrence -def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: +def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: """ ## Input - *pde*, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` such that @@ -282,13 +287,13 @@ def get_recurrence_parametric_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: - final_recurrence, the recurrence relation for derivatives of our point-potential. """ - ode_in_r, var, n_derivs = get_pde_in_recurrence_form(pde) + ode_in_r, var, n_derivs = pde_to_ode_in_r(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() f_x_derivs = _make_sympy_vec("f_x", n_derivs) poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) - coeffs = compute_coefficients_of_poly_parametric(poly, n_derivs, var) - return get_recurrence_parametric_from_coeffs(coeffs, var) + coeffs = ode_in_x_to_coeff_array(poly, n_derivs, var) + return recurrence_from_coeff_array(coeffs, var) def test_recurrence_finder_laplace(): @@ -298,7 +303,7 @@ def test_recurrence_finder_laplace(): """ w = make_identity_diff_op(2) laplace2d = laplacian(w) - r = get_recurrence_parametric_from_pde(laplace2d) + r = recurrence_from_pde(laplace2d) n = sp.symbols("n") s = sp.Function("s") @@ -326,7 +331,7 @@ def test_recurrence_finder_laplace_three_d(): """ w = make_identity_diff_op(3) laplace3d = laplacian(w) - r = get_recurrence_parametric_from_pde(laplace3d) + r = recurrence_from_pde(laplace3d) n = sp.symbols("n") s = sp.Function("s") @@ -355,7 +360,7 @@ def test_recurrence_finder_helmholtz_three_d(): #seems to be an order 5 recurrence relation w = make_identity_diff_op(3) helmholtz3d = laplacian(w) + w - r = get_recurrence_parametric_from_pde(helmholtz3d) + r = recurrence_from_pde(helmholtz3d) #We create that function that gives the derivatives of the point # potential for helmholtz From c0ffbf78fd48703a2c5bbafd2960b32ff8428844 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 15 Jul 2024 16:12:39 -0700 Subject: [PATCH 33/75] Documentation --- sumpy/recurrence.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index ba4fd4616..ac5136bf9 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -81,9 +81,8 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ :returns: a tuple ``(ode_in_r, var, n_derivs)``, where - *ode_in_r* is the ODE satisfied by :math:`f`. - - var, an array representing the input coordinates - (maybe give an example?) - - n_derivs, the order of the original PDE + 1, i.e. the number of + - *var*, represents the sympy vec [x0, x1, ...] corresponding to coordinates + - *n_derivs*, the order of the original PDE + 1, i.e. the number of derivatives of f that may be present (the reason this is called n_derivs since if we have a second order PDE for example then we might see :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms From c13027c4c99623c1215114ee458e547a03436a99 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Jul 2024 10:52:08 -0500 Subject: [PATCH 34/75] Code clarity fixes --- sumpy/recurrence.py | 62 +++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index ac5136bf9..e06000eea 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -58,7 +58,7 @@ import sympy as sp from pytools.obj_array import make_obj_array from sumpy.expansion.diff_op import ( - make_identity_diff_op, laplacian, LinearPDESystemOperator) + DerivativeIdentifier, make_identity_diff_op, laplacian, LinearPDESystemOperator) # similar to make_sym_vector in sumpy.symbolic, but returns an object array @@ -70,61 +70,51 @@ def _make_sympy_vec(name, n): def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ sp.Expr, np.ndarray, int ]: - """ - Takes as input a scalar pde represented as the type - :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator`. Assumes that the scalar - pde has coefficients that are polynomial in the input coordinates. Then assumes - that the PDE is satisfied by a point-potential with radial symmetry and comes up - with an ODE in the radial variable that the point-potential satisfies. + r""" + Returns an ODE satisfied by the radial derivatives of a function + :math:`f:\mathbb R^n \to \mathbb R` satisfying + :math:`f(\boldsymbol x)=f(|\boldsymbol x|_2)` and *pde*. - :arg pde: must satisfy ``pde.eqs == 1``` + :arg pde: must satisfy ``pde.eqs == 1``` and have polynomial coefficients. :returns: a tuple ``(ode_in_r, var, n_derivs)``, where - - *ode_in_r* is the ODE satisfied by :math:`f`. - - *var*, represents the sympy vec [x0, x1, ...] corresponding to coordinates - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present (the reason this is called n_derivs - since if we have a second order PDE for example then we might see - :math:`f, f_{r}, f_{rr}` in our ODE in r, which is technically 3 terms - since we count the 0th order derivative f as a "derivative." If this - doesn't make sense just know that n_derivs is the order the of the input - sumpy PDE + 1) + - *ode_in_r* with derivatives given as :class:`sympy.Derivative`. + - *var* is an object array of :class:`sympy.Symbol`, with successive + variables representing the Cartesian coordinate directions. """ if len(pde.eqs) != 1: raise ValueError("PDE must be scalar") + # FIXME remove n_derivs dim = pde.dim n_derivs = pde.order - assert (len(pde.eqs) == 1) - ops = len(pde.eqs[0]) - derivs = [] - coeffs = [] - for i in pde.eqs[0]: - derivs.append(i.mi) - coeffs.append(pde.eqs[0][i]) + pde_eqn, = pde.eqs + var = _make_sympy_vec("x", dim) r = sp.sqrt(sum(var**2)) - eps = sp.symbols("epsilon") rval = r + eps f = sp.Function("f") - # pylint: disable=not-callable - f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs+1)] - def compute_term(a, t): - term = a - for i in range(len(t)): - term = term.diff(var[i], t[i]) - return term + def apply_deriv_id(expr: sp.Expr, deriv_id: DerivativeIdentifier) -> sp.Expr: + for i, nderivs in enumerate(deriv_id.mi): + expr = expr.diff(var[i], nderivs) + return expr + + ode_in_r = sum( + coeff * apply_deriv_id(f(rval), deriv_id) + for deriv_id, coeff in pde_eqn.items() + ) - ode_in_r = 0 - for i in range(ops): - ode_in_r += coeffs[i] * compute_term(f(rval), derivs[i]) - n_derivs = len(f_derivs) f_r_derivs = _make_sympy_vec("f_r", n_derivs) + # pylint: disable-next=not-callable + f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs+1)] + n_derivs = len(f_derivs) + # FIXME: Is this bulletproof? I.e. can non-r derivatives survive? for i in range(n_derivs): ode_in_r = ode_in_r.subs(f_derivs[i], f_r_derivs[i]) + return ode_in_r, var, n_derivs From c7e2ac7de5abe35b1abc5c851f400043dd1030c6 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 17 Jul 2024 14:51:06 -0700 Subject: [PATCH 35/75] Replaced n_derivs with ode_order --- sumpy/recurrence.py | 69 ++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index e06000eea..4dfb38f73 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -77,17 +77,17 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ :arg pde: must satisfy ``pde.eqs == 1``` and have polynomial coefficients. - :returns: a tuple ``(ode_in_r, var, n_derivs)``, where + :returns: a tuple ``(ode_in_r, var, ode_order)``, where - *ode_in_r* with derivatives given as :class:`sympy.Derivative`. - *var* is an object array of :class:`sympy.Symbol`, with successive variables representing the Cartesian coordinate directions. + - *ode_order* the order of ODE that is returned """ if len(pde.eqs) != 1: raise ValueError("PDE must be scalar") - # FIXME remove n_derivs dim = pde.dim - n_derivs = pde.order + ode_order = pde.order pde_eqn, = pde.eqs var = _make_sympy_vec("x", dim) @@ -100,30 +100,28 @@ def apply_deriv_id(expr: sp.Expr, deriv_id: DerivativeIdentifier) -> sp.Expr: for i, nderivs in enumerate(deriv_id.mi): expr = expr.diff(var[i], nderivs) return expr - + # pylint: disable-next=not-callable ode_in_r = sum( coeff * apply_deriv_id(f(rval), deriv_id) for deriv_id, coeff in pde_eqn.items() ) - f_r_derivs = _make_sympy_vec("f_r", n_derivs) + f_r_derivs = _make_sympy_vec("f_r", ode_order+1) # pylint: disable-next=not-callable - f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs+1)] - n_derivs = len(f_derivs) + f_derivs = [sp.diff(f(rval), eps, i) for i in range(ode_order+1)] - # FIXME: Is this bulletproof? I.e. can non-r derivatives survive? - for i in range(n_derivs): + # PDE ORDER = ODE ORDER + for i in range(ode_order+1): ode_in_r = ode_in_r.subs(f_derivs[i], f_r_derivs[i]) - return ode_in_r, var, n_derivs + return ode_in_r, var, ode_order -def _generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: +def _generate_nd_derivative_relations(var: np.ndarray, ode_order: int) -> dict: """ ## Input - *var*, a sympy vector of variables called [x0, x1, ...] - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present + - *ode_order*, the order of the ODE that we will be translating ## Output - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... using the chain rule @@ -133,23 +131,23 @@ def _generate_nd_derivative_relations(var: np.ndarray, n_derivs: int) -> dict: Using the chain rule outputs a vector that tells us how to write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... """ - f_r_derivs = _make_sympy_vec("f_r", n_derivs) - f_x_derivs = _make_sympy_vec("f_x", n_derivs) + f_r_derivs = _make_sympy_vec("f_r", ode_order+1) + f_x_derivs = _make_sympy_vec("f_x", ode_order+1) f = sp.Function("f") eps = sp.symbols("epsilon") rval = sp.sqrt(sum(var**2)) + eps # pylint: disable=not-callable - f_derivs_x = [sp.diff(f(rval), var[0], i) for i in range(n_derivs)] - f_derivs = [sp.diff(f(rval), eps, i) for i in range(n_derivs)] + f_derivs_x = [sp.diff(f(rval), var[0], i) for i in range(ode_order+1)] + f_derivs = [sp.diff(f(rval), eps, i) for i in range(ode_order+1)] # pylint: disable=not-callable for i in range(len(f_derivs_x)): for j in range(len(f_derivs)): f_derivs_x[i] = f_derivs_x[i].subs(f_derivs[j], f_r_derivs[j]) - system = [f_x_derivs[i] - f_derivs_x[i] for i in range(n_derivs)] + system = [f_x_derivs[i] - f_derivs_x[i] for i in range(ode_order+1)] return sp.solve(system, *f_r_derivs, dict=True)[0] -def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: +def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr: """ ## Input - *ode_in_r*, a linear combination of f, f_r, f_{rr}, ... @@ -157,8 +155,7 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: with coefficients that are polynomials in var[0], var[1], ... divided by some power of var[0] - *var*, array of sympy variables [x_0, x_1, ...] - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present + - *ode_order*, the order of the input ODE ## Output - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as rational functions in var[0], var[1], ... @@ -167,23 +164,22 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, n_derivs: int) -> sp.Expr: by substituting f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... using the chain rule. """ - subme = _generate_nd_derivative_relations(var, n_derivs) + subme = _generate_nd_derivative_relations(var, ode_order+1) ode_in_x = ode_in_r - f_r_derivs = _make_sympy_vec("f_r", n_derivs) - for i in range(n_derivs): + f_r_derivs = _make_sympy_vec("f_r", ode_order+1) + for i in range(ode_order+1): ode_in_x = ode_in_x.subs(f_r_derivs[i], subme[f_r_derivs[i]]) return ode_in_x -def ode_in_x_to_coeff_array(poly: sp.Poly, n_derivs: int, +def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: np.ndarray) -> list: """ ## Input - *poly*, the original ODE for our point-potential as a polynomial in f_{x0}, f_{x1}, f_{x2}, etc. with polynomial coefficients in var[0], var[1], ... - - *n_derivs*, the order of the original PDE + 1, i.e. the number of - derivatives of f that may be present + - *ode_order*, the order of input ODE - *var*, array of sympy variables [x_0, x_1, ...] ## Output - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as @@ -193,7 +189,7 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, n_derivs: int, by substituting f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... using the chain rule. """ - def tup(i, n=n_derivs): + def tup(i, n=ode_order+1): a = [] for j in range(n): if j != i: @@ -203,7 +199,7 @@ def tup(i, n=n_derivs): return tuple(a) coeffs = [] - for deriv_ind in range(n_derivs): + for deriv_ind in range(ode_order+1): coeffs.append(sp.Poly(poly.coeff_monomial(tup(deriv_ind)), var[0]).all_coeffs()[::-1]) @@ -276,12 +272,12 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: - final_recurrence, the recurrence relation for derivatives of our point-potential. """ - ode_in_r, var, n_derivs = pde_to_ode_in_r(pde) - ode_in_x = ode_in_r_to_x(ode_in_r, var, n_derivs).simplify() - ode_in_x_cleared = (ode_in_x * var[0]**n_derivs).simplify() - f_x_derivs = _make_sympy_vec("f_x", n_derivs) + ode_in_r, var, ode_order = pde_to_ode_in_r(pde) + ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() + ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() + f_x_derivs = _make_sympy_vec("f_x", ode_order+1) poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) - coeffs = ode_in_x_to_coeff_array(poly, n_derivs, var) + coeffs = ode_in_x_to_coeff_array(poly, ode_order, var) return recurrence_from_coeff_array(coeffs, var) @@ -390,6 +386,7 @@ def deriv_helmholtz_three_d(i, s_loc): print(err) assert err <= 1e-10 - - +test_recurrence_finder_laplace() +test_recurrence_finder_laplace_three_d() +test_recurrence_finder_helmholtz_three_d() From 99a658fcf24f8187839ee9abd22dd161efa36fa9 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 17 Jul 2024 15:31:38 -0700 Subject: [PATCH 36/75] Update documentation for sphinx --- sumpy/recurrence.py | 54 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 4dfb38f73..d8308e464 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -75,13 +75,13 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ :math:`f:\mathbb R^n \to \mathbb R` satisfying :math:`f(\boldsymbol x)=f(|\boldsymbol x|_2)` and *pde*. - :arg pde: must satisfy ``pde.eqs == 1``` and have polynomial coefficients. + :arg pde: must satisfy ``pde.eqs == 1`` and have polynomial coefficients. :returns: a tuple ``(ode_in_r, var, ode_order)``, where - - *ode_in_r* with derivatives given as :class:`sympy.Derivative`. - - *var* is an object array of :class:`sympy.Symbol`, with successive - variables representing the Cartesian coordinate directions. - - *ode_order* the order of ODE that is returned + - *ode_in_r* with derivatives given as :class:`sympy.Derivative`. + - *var* is an object array of :class:`sympy.Symbol`, with successive variables + representing the Cartesian coordinate directions. + - *ode_order* the order of ODE that is returned """ if len(pde.eqs) != 1: raise ValueError("PDE must be scalar") @@ -118,18 +118,13 @@ def apply_deriv_id(expr: sp.Expr, deriv_id: DerivativeIdentifier) -> sp.Expr: def _generate_nd_derivative_relations(var: np.ndarray, ode_order: int) -> dict: - """ - ## Input - - *var*, a sympy vector of variables called [x0, x1, ...] - - *ode_order*, the order of the ODE that we will be translating - ## Output - - a vector that gives [f, f_r, f_{rr}, ...] in terms of f, f_x, f_{xx}, ... - using the chain rule - (f, f_x, f_{xx}, ... in code is represented as f_{x0}, f_{x1}, f_{x2} and - f, f_r, f_{rr}, ... in code is represented as f_{r0}, f_{r1}, f_{r2}) - ## Description - Using the chain rule outputs a vector that tells us how to - write f, f_r, f_{rr}, ... as a linear combination of f, f_x, f_{xx}, ... + r""" + Using the chain rule outputs a vector that gives in each component respectively + :math:`[f(r), f'(r), \dots, f^{(ode_order)}(r)]` as a linear combination of + :math:`[f(x), f'(x), \dots, f^{(ode_order)}(x)]` + + :arg var: array of sympy variables math:`[x_0, x_1, \dots]` + :arg ode_order: the order of the ODE that we will be translating """ f_r_derivs = _make_sympy_vec("f_r", ode_order+1) f_x_derivs = _make_sympy_vec("f_x", ode_order+1) @@ -148,21 +143,18 @@ def _generate_nd_derivative_relations(var: np.ndarray, ode_order: int) -> dict: def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr: - """ - ## Input - - *ode_in_r*, a linear combination of f, f_r, f_{rr}, ... - (in code represented as f_{r0}, f_{r1}, f_{r2}) - with coefficients that are polynomials in var[0], var[1], ... - divided by some power of var[0] - - *var*, array of sympy variables [x_0, x_1, ...] - - *ode_order*, the order of the input ODE - ## Output - - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as - rational functions in var[0], var[1], ... - ## Description + r""" Translates an ode in the variable r into an ode in the variable x - by substituting f, f_r, f_{rr}, ... as a linear combination of - f, f_x, f_{xx}, ... using the chain rule. + by replcaing the terms :math:`f, f_r, f_{rr}, \dots` as a linear combinations of + :math:`f, f_x, f_{xx}, \dots` using the chain rule. + + :arg ode_in_r: a linear combination of :math:`f, f_r, f_{rr}, \dots` represented + by the sympy variables :math:`f_{r0}, f_{r1}, f_{r1}, f_{r2}, \dots` + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` + :arg ode_order: the order of the input ODE + + :returns: *ode_in_x* a linear combination of :math:`f, f_x, f_{xx}, \dots` with + coefficients as rational functions in :math:`x_0, x_1, \dots` """ subme = _generate_nd_derivative_relations(var, ode_order+1) ode_in_x = ode_in_r From a6b03afd581bc378fc789677ae93b535854763c1 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 17 Jul 2024 20:53:21 -0700 Subject: [PATCH 37/75] Format documentation for ode_in_x_to_coeff_array --- sumpy/recurrence.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index d8308e464..52477c00c 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -166,20 +166,18 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: np.ndarray) -> list: - """ - ## Input - - *poly*, the original ODE for our point-potential as a polynomial - in f_{x0}, f_{x1}, f_{x2}, etc. with polynomial coefficients - in var[0], var[1], ... - - *ode_order*, the order of input ODE - - *var*, array of sympy variables [x_0, x_1, ...] - ## Output - - ode_in_x, a linear combination of f, f_x, f_{xx}, ... with coefficients as - rational functions in var[0], var[1], ... - ## Description - Translates an ode in the variable r into an ode in the variable x - by substituting f, f_r, f_{rr}, ... as a linear combination of - f, f_x, f_{xx}, ... using the chain rule. + r""" + Organizes the coefficients of an ODE in the :math:`x_0` variable into a 2D array. + + :arg poly: :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \cdots) \partial_{x_0}^0 f + + (b_{10} x_0^0 + b_{11} x_0^1 +\cdots) \partial_x^1 f` + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` + :arg ode_order: the order of the input ODE we return a sequence + + :returns: *coeffs* a sequence of of sequences, with the outer sequence iterating + over derivative orders, and each inner sequence iterating over powers of :math:`x_0`, + so that, in terms of the above form, coeffs is + :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` """ def tup(i, n=ode_order+1): a = [] From 66ce1601085e3f3ba02888227d4209bb4dd2e5cd Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 17 Jul 2024 21:12:20 -0700 Subject: [PATCH 38/75] Re-request tmrw mrning --- sumpy/recurrence.py | 75 +++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 52477c00c..68969ba74 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -77,10 +77,10 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ :arg pde: must satisfy ``pde.eqs == 1`` and have polynomial coefficients. - :returns: a tuple ``(ode_in_r, var, ode_order)``, where - - *ode_in_r* with derivatives given as :class:`sympy.Derivative`. + :returns: a tuple ``(ode_in_r, var, ode_order)``, where + - *ode_in_r* with derivatives given as :class:`sympy.Derivative` - *var* is an object array of :class:`sympy.Symbol`, with successive variables - representing the Cartesian coordinate directions. + representing the Cartesian coordinate directions. - *ode_order* the order of ODE that is returned """ if len(pde.eqs) != 1: @@ -149,12 +149,13 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr :math:`f, f_x, f_{xx}, \dots` using the chain rule. :arg ode_in_r: a linear combination of :math:`f, f_r, f_{rr}, \dots` represented - by the sympy variables :math:`f_{r0}, f_{r1}, f_{r1}, f_{r2}, \dots` + by the sympy variables :math:`f_{r0}, f_{r1}, f_{r2}, \dots` :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE - :returns: *ode_in_x* a linear combination of :math:`f, f_x, f_{xx}, \dots` with - coefficients as rational functions in :math:`x_0, x_1, \dots` + :returns: *ode_in_x* a linear combination of :math:`f, f_x, f_{xx}, \dots` + represented by the sympy variables :math:`f_{x0}, f_{x1}, f_{x2}, \dots` + with coefficients as rational functions in :math:`x_0, x_1, \dots` """ subme = _generate_nd_derivative_relations(var, ode_order+1) ode_in_x = ode_in_r @@ -169,7 +170,9 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, r""" Organizes the coefficients of an ODE in the :math:`x_0` variable into a 2D array. - :arg poly: :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \cdots) \partial_{x_0}^0 f + + :arg poly: a sympy polynomial in + :math:`\partial_{x_0}^0 f, \partial_{x_0}^1 f,\cdots` of the form + :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \cdots) \partial_{x_0}^0 f + (b_{10} x_0^0 + b_{11} x_0^1 +\cdots) \partial_x^1 f` :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE we return a sequence @@ -197,23 +200,15 @@ def tup(i, n=ode_order+1): def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: - """ - ## Description + r""" We assume that we are given the expression :math:`x_0^p f^(m)(x_0)`. We then - output the nth order derivative of the expression where n is a symbolic variable. + output the nth order derivative of the expression where :math:`n` is a symbolic + variable. We let :math:`s(i)` represent the ith order derivative of f when we output the final result. - ## Input - - *p*, see description - - *m*, see description - - *var*, array of sympy variables [x_0, x_1, ...] - ## Output - - A sympy expression is output corresponding to the nth order derivative of the - input expression. - We let :math:`s(i)` represent the ith order derivative of f when - we output the final result. We let n represent a symbolic variable - corresponding to how many derivatives of the original expression were - taken. + :arg p: see description + :arg m: see description + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ n = sp.symbols("n") s = sp.Function("s") @@ -229,19 +224,15 @@ def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: - """ - ## Input - - *coeffs*, - Consider an ODE obeyed by a function f that can be expressed in the following - form: :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \\cdots) \\partial_{x_0}^0 f + - (b_{10} x_0^0 + b_{11} x_0^1 +\\cdots) \\partial_x^1 f`. coeffs is a sequence - of sequences, with the outer sequence iterating over derivative orders, and - each inner sequence iterating over powers of :math:`x_0`, so that, in terms of - the above form, coeffs is [[b_00, b_01, ...], [b_10, b_11, ...], ...] - - *var*, array of sympy variables [x_0, x_1, ...] - ## Output - - final_recurrence, the recurrence relation for derivatives of our - point-potential. + r""" + A function that takes in as input an organized 2D coefficient array (see above) + and outputs a recurrence relation. + + :arg coeffs: a sequence of of sequences, with the outer sequence iterating + over derivative orders, and each inner sequence iterating over powers of + :math:`x_0`, so that, in terms of the above form, coeffs is + :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ final_recurrence = 0 #Outer loop is derivative direction @@ -254,13 +245,12 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: - """ - ## Input - - *pde*, a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` such that - pde.eqs == 1 - ## Output - - final_recurrence, the recurrence relation for derivatives of our - point-potential. + r""" + A function that takes in as input a sympy PDE and outputs a recurrence relation. + + :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` + that must satisfy ``pde.eqs == 1`` and have polynomial coefficients. + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ ode_in_r, var, ode_order = pde_to_ode_in_r(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() @@ -273,7 +263,6 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: def test_recurrence_finder_laplace(): """ - ## Description Tests our recurrence relation generator for Lapalace 2D. """ w = make_identity_diff_op(2) @@ -301,7 +290,6 @@ def deriv_laplace(i): def test_recurrence_finder_laplace_three_d(): """ - ## Description Tests our recurrence relation generator for Laplace 3D. """ w = make_identity_diff_op(3) @@ -328,7 +316,6 @@ def deriv_laplace_three_d(i): def test_recurrence_finder_helmholtz_three_d(): """ - ## Description Tests our recurrence relation generator for Helmhotlz 3D. """ #We are creating the recurrence relation for helmholtz3d which From 471342b2f923babddfbe5d5a944c9fd0642ca8c9 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Thu, 18 Jul 2024 17:20:05 -0700 Subject: [PATCH 39/75] Flake 8/pylint --- sumpy/recurrence.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 68969ba74..dd464f28f 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -77,7 +77,7 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ :arg pde: must satisfy ``pde.eqs == 1`` and have polynomial coefficients. - :returns: a tuple ``(ode_in_r, var, ode_order)``, where + :returns: a tuple ``(ode_in_r, var, ode_order)``, where - *ode_in_r* with derivatives given as :class:`sympy.Derivative` - *var* is an object array of :class:`sympy.Symbol`, with successive variables representing the Cartesian coordinate directions. @@ -100,8 +100,9 @@ def apply_deriv_id(expr: sp.Expr, deriv_id: DerivativeIdentifier) -> sp.Expr: for i, nderivs in enumerate(deriv_id.mi): expr = expr.diff(var[i], nderivs) return expr - # pylint: disable-next=not-callable + ode_in_r = sum( + # pylint: disable-next=not-callable coeff * apply_deriv_id(f(rval), deriv_id) for deriv_id, coeff in pde_eqn.items() ) @@ -153,8 +154,8 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE - :returns: *ode_in_x* a linear combination of :math:`f, f_x, f_{xx}, \dots` - represented by the sympy variables :math:`f_{x0}, f_{x1}, f_{x2}, \dots` + :returns: *ode_in_x* a linear combination of :math:`f, f_x, f_{xx}, \dots` + represented by the sympy variables :math:`f_{x0}, f_{x1}, f_{x2}, \dots` with coefficients as rational functions in :math:`x_0, x_1, \dots` """ subme = _generate_nd_derivative_relations(var, ode_order+1) @@ -169,17 +170,16 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: np.ndarray) -> list: r""" Organizes the coefficients of an ODE in the :math:`x_0` variable into a 2D array. - - :arg poly: a sympy polynomial in + :arg poly: a sympy polynomial in :math:`\partial_{x_0}^0 f, \partial_{x_0}^1 f,\cdots` of the form :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \cdots) \partial_{x_0}^0 f + (b_{10} x_0^0 + b_{11} x_0^1 +\cdots) \partial_x^1 f` :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE we return a sequence - :returns: *coeffs* a sequence of of sequences, with the outer sequence iterating - over derivative orders, and each inner sequence iterating over powers of :math:`x_0`, - so that, in terms of the above form, coeffs is + :returns: *coeffs* a sequence of of sequences, with the outer sequence iterating + over derivative orders, and each inner sequence iterating over powers of + :math:`x_0`, so that, in terms of the above form, coeffs is :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` """ def tup(i, n=ode_order+1): @@ -248,7 +248,7 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: r""" A function that takes in as input a sympy PDE and outputs a recurrence relation. - :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` + :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` that must satisfy ``pde.eqs == 1`` and have polynomial coefficients. :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ @@ -318,16 +318,12 @@ def test_recurrence_finder_helmholtz_three_d(): """ Tests our recurrence relation generator for Helmhotlz 3D. """ - #We are creating the recurrence relation for helmholtz3d which + #We are creating the recurrence relation for helmholtz3d which #seems to be an order 5 recurrence relation w = make_identity_diff_op(3) helmholtz3d = laplacian(w) + w r = recurrence_from_pde(helmholtz3d) - #We create that function that gives the derivatives of the point - # potential for helmholtz - #Remember! Our point-source was placed at the origin and we - # were performing a LT expansion at x_0 def deriv_helmholtz_three_d(i, s_loc): s_x = s_loc[0] s_y = s_loc[1] @@ -337,7 +333,6 @@ def deriv_helmholtz_three_d(i, s_loc): ) / (sp.sqrt(x**2 + y**2 + z**2)) return sp.diff(true_f, x, i).subs(x, s_x).subs( y, s_y).subs(z, s_z) - #Create relevant symbols var = _make_sympy_vec("x", 3) n = sp.symbols("n") @@ -362,8 +357,3 @@ def deriv_helmholtz_three_d(i, s_loc): err = abs(abs(r_sub).evalf()) print(err) assert err <= 1e-10 - -test_recurrence_finder_laplace() -test_recurrence_finder_laplace_three_d() -test_recurrence_finder_helmholtz_three_d() - From ffff8658e6450077731b1f9da2d7733aa46718ec Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 21 Jul 2024 19:12:05 -0700 Subject: [PATCH 40/75] Typos and clarification to docs --- sumpy/recurrence.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index dd464f28f..75c060b5c 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -1,9 +1,9 @@ r""" With the functionality in this module, we aim to compute a recurrence for one-dimensional derivatives of functions :math:`f:\mathbb R^n \to \mathbb R` -for functions :math:`f` satisfying two assumptions: +for functions satisfying two assumptions: -- :math:`f` satisfies a PDE is linear and has coefficients polynomial +- :math:`f` satisfies a PDE that is linear and has coefficients polynomial in the coordinates. - :math:`f` only depends on the radius :math:`r`, i.e. :math:`f(\boldsymbol x)=f(|\boldsymbol x|_2)`. @@ -146,7 +146,7 @@ def _generate_nd_derivative_relations(var: np.ndarray, ode_order: int) -> dict: def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr: r""" Translates an ode in the variable r into an ode in the variable x - by replcaing the terms :math:`f, f_r, f_{rr}, \dots` as a linear combinations of + by replacing the terms :math:`f, f_r, f_{rr}, \dots` as a linear combinations of :math:`f, f_x, f_{xx}, \dots` using the chain rule. :arg ode_in_r: a linear combination of :math:`f, f_r, f_{rr}, \dots` represented @@ -170,10 +170,12 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: np.ndarray) -> list: r""" Organizes the coefficients of an ODE in the :math:`x_0` variable into a 2D array. + :arg poly: a sympy polynomial in - :math:`\partial_{x_0}^0 f, \partial_{x_0}^1 f,\cdots` of the form - :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \cdots) \partial_{x_0}^0 f + - (b_{10} x_0^0 + b_{11} x_0^1 +\cdots) \partial_x^1 f` + :math:`\partial_{x_0}^0 f, \partial_{x_0}^1 f,\cdots` of the form + :math:`(b_{00} x_0^0 + b_{01} x_0^1 + \cdots) \partial_{x_0}^0 f + + (b_{10} x_0^0 + b_{11} x_0^1 +\cdots) \partial_x^1 f` + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE we return a sequence @@ -228,10 +230,8 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: A function that takes in as input an organized 2D coefficient array (see above) and outputs a recurrence relation. - :arg coeffs: a sequence of of sequences, with the outer sequence iterating - over derivative orders, and each inner sequence iterating over powers of - :math:`x_0`, so that, in terms of the above form, coeffs is - :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` + :arg coeffs: a sequence of of sequences, described in + :func:`ode_in_x_to_coeff_array` :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ final_recurrence = 0 @@ -249,7 +249,8 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: A function that takes in as input a sympy PDE and outputs a recurrence relation. :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` - that must satisfy ``pde.eqs == 1`` and have polynomial coefficients. + that must satisfy ``pde.eqs == 1`` and have polynomial coefficients + in the coordinates. :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ ode_in_r, var, ode_order = pde_to_ode_in_r(pde) From 4e09ed09b7cb93a1e4cebf06a92a1c3b8f314619 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 22 Jul 2024 16:04:33 -0500 Subject: [PATCH 41/75] Review: code quality, denominator clearing --- sumpy/recurrence.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 75c060b5c..9ae8eaa9e 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -184,21 +184,12 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, :math:`x_0`, so that, in terms of the above form, coeffs is :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` """ - def tup(i, n=ode_order+1): - a = [] - for j in range(n): - if j != i: - a.append(0) - else: - a.append(1) - return tuple(a) + def kronecker(i, n=ode_order+1): + return tuple(1 if i == j else 0 for j in range(n)) - coeffs = [] - for deriv_ind in range(ode_order+1): - coeffs.append(sp.Poly(poly.coeff_monomial(tup(deriv_ind)), - var[0]).all_coeffs()[::-1]) - - return coeffs + return [ + sp.Poly(poly.coeff_monomial(kronecker(deriv_ind)), var[0]).all_coeffs()[::-1] + for deriv_ind in range(ode_order+1) def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: @@ -256,6 +247,9 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: ode_in_r, var, ode_order = pde_to_ode_in_r(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() + + assert is_actually_cleared() + f_x_derivs = _make_sympy_vec("f_x", ode_order+1) poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) coeffs = ode_in_x_to_coeff_array(poly, ode_order, var) From bfa8372c9ae75dba401cbe82dcc2244deb7300aa Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 23 Jul 2024 19:57:27 -0700 Subject: [PATCH 42/75] Check if ode_in_x is truly cleared --- sumpy/recurrence.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 9ae8eaa9e..f422c09d5 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -187,9 +187,8 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, def kronecker(i, n=ode_order+1): return tuple(1 if i == j else 0 for j in range(n)) - return [ - sp.Poly(poly.coeff_monomial(kronecker(deriv_ind)), var[0]).all_coeffs()[::-1] - for deriv_ind in range(ode_order+1) + return [sp.Poly(poly.coeff_monomial(kronecker(deriv_ind)), + var[0]).all_coeffs()[::-1] for deriv_ind in range(ode_order+1)] def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: @@ -247,9 +246,8 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: ode_in_r, var, ode_order = pde_to_ode_in_r(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() - - assert is_actually_cleared() - + #ode_in_x_cleared shouldn't have rational function coefficients in the coord. + assert sp.together(ode_in_x_cleared) == ode_in_x_cleared f_x_derivs = _make_sympy_vec("f_x", ode_order+1) poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) coeffs = ode_in_x_to_coeff_array(poly, ode_order, var) From e363248e23f8da05e1360b7eecc62b7e52be1b95 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 29 Jul 2024 16:05:49 -0500 Subject: [PATCH 43/75] Hacking during meeting --- sumpy/recurrence.py | 82 +++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index f422c09d5..5bd5a9a3b 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -28,6 +28,8 @@ from __future__ import annotations +from typing import TypeVar + __copyright__ = """ Copyright (C) 2024 Hirish Chandrasekaran @@ -54,11 +56,18 @@ THE SOFTWARE. """ import math + import numpy as np import sympy as sp + from pytools.obj_array import make_obj_array + from sumpy.expansion.diff_op import ( - DerivativeIdentifier, make_identity_diff_op, laplacian, LinearPDESystemOperator) + DerivativeIdentifier, + LinearPDESystemOperator, + laplacian, + make_identity_diff_op, +) # similar to make_sym_vector in sumpy.symbolic, but returns an object array @@ -166,8 +175,11 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr return ode_in_x +ODECoefficients = list[list[sp.Expr]] + + def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, - var: np.ndarray) -> list: + var: np.ndarray) -> ODECoefficients: r""" Organizes the coefficients of an ODE in the :math:`x_0` variable into a 2D array. @@ -184,11 +196,26 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, :math:`x_0`, so that, in terms of the above form, coeffs is :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` """ - def kronecker(i, n=ode_order+1): - return tuple(1 if i == j else 0 for j in range(n)) + return [ + # recast ODE coefficient obtained below as polynomial in x0 + sp.Poly( + # get coefficient of deriv_ind'th derivative + poly.coeff_monomial(poly.gens[deriv_ind]), + + var[0]) + # get poly coefficients in /ascending/ order + .all_coeffs()[::-1] + for deriv_ind in range(ode_order+1)] + - return [sp.Poly(poly.coeff_monomial(kronecker(deriv_ind)), - var[0]).all_coeffs()[::-1] for deriv_ind in range(ode_order+1)] +NumberT = TypeVar("NumberT", int, float, complex) + + +def _falling_factorial(arg: NumberT, num_terms: int) -> NumberT: + result = 1 + for i in range(num_terms): + result = result * (arg - i) + return result def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: @@ -198,21 +225,15 @@ def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: variable. We let :math:`s(i)` represent the ith order derivative of f when we output the final result. - :arg p: see description - :arg m: see description :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ n = sp.symbols("n") s = sp.Function("s") - result = 0 - for i in range(p+1): - temp = 1 - for j in range(i): - temp *= (n - j) - # pylint: disable=not-callable - temp *= math.comb(p, i) * s(n-i+m) * var[0]**(p-i) - result += temp - return result + return sum( + _falling_factorial(n, i) + * math.comb(p, i) * s(n-i+m) * var[0]**(p-i) + for i in range(p+1) + ) def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: @@ -225,8 +246,8 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ final_recurrence = 0 - #Outer loop is derivative direction - #Inner is polynomial order of x_0 + # Outer loop is derivative direction + # Inner is polynomial order of x_0 for m, _ in enumerate(coeffs): for p, _ in enumerate(coeffs[m]): final_recurrence += coeffs[m][p] * _auto_product_rule_single_term(p, @@ -246,7 +267,7 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: ode_in_r, var, ode_order = pde_to_ode_in_r(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() - #ode_in_x_cleared shouldn't have rational function coefficients in the coord. + # ode_in_x_cleared shouldn't have rational function coefficients in the coord. assert sp.together(ode_in_x_cleared) == ode_in_x_cleared f_x_derivs = _make_sympy_vec("f_x", ode_order+1) poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) @@ -311,8 +332,8 @@ def test_recurrence_finder_helmholtz_three_d(): """ Tests our recurrence relation generator for Helmhotlz 3D. """ - #We are creating the recurrence relation for helmholtz3d which - #seems to be an order 5 recurrence relation + # We are creating the recurrence relation for helmholtz3d which + # seems to be an order 5 recurrence relation w = make_identity_diff_op(3) helmholtz3d = laplacian(w) + w r = recurrence_from_pde(helmholtz3d) @@ -326,24 +347,27 @@ def deriv_helmholtz_three_d(i, s_loc): ) / (sp.sqrt(x**2 + y**2 + z**2)) return sp.diff(true_f, x, i).subs(x, s_x).subs( y, s_y).subs(z, s_z) - #Create relevant symbols + # Create relevant symbols var = _make_sympy_vec("x", 3) n = sp.symbols("n") s = sp.Function("s") - #Create random source location - s_loc = np.random.rand(3) + rng = np.random.default_rng() + + # Create random source location + s_loc = rng.uniform(size=3) - #Create random order to check - d = np.random.randint(0, 5) + # Create random order to check + from random import randrange + d = randrange(0, 5) - #Substitute random location into recurrence relation and value of n = d + # Substitute random location into recurrence relation and value of n = d r_loc = r.subs(var[0], s_loc[0]) r_loc = r_loc.subs(var[1], s_loc[1]) r_loc = r_loc.subs(var[2], s_loc[2]) r_sub = r_loc.subs(n, d) - #Checking that the recurrence holds to some machine epsilon + # Checking that the recurrence holds to some machine epsilon for i in range(max(d-3, 0), d+3): # pylint: disable=not-callable r_sub = r_sub.subs(s(i), deriv_helmholtz_three_d(i, s_loc)) From 1f305672379f66cd33fc6ec2a1a6ba4a54461257 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 6 Aug 2024 00:54:30 -0700 Subject: [PATCH 44/75] Flake8 --- sumpy/recurrence.py | 65 ++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 5bd5a9a3b..81836c42c 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -11,7 +11,8 @@ This process proceeds in multiple steps: - Convert from the PDE to an ODE in :math:`r`, using :func:`pde_to_ode_in_r`. -- Convert from an ODE in :math:`r` to one in :math:`x`, using :func:`ode_in_r_to_x`. +- Convert from an ODE in :math:`r` to one in :math:`x`, +using :func:`ode_in_r_to_x`. - Sort general-form ODE in :math:`x` into a coefficient array, using :func:`ode_in_x_to_coeff_array`. - Finally, get an expression for the recurrence, using @@ -88,7 +89,8 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ :returns: a tuple ``(ode_in_r, var, ode_order)``, where - *ode_in_r* with derivatives given as :class:`sympy.Derivative` - - *var* is an object array of :class:`sympy.Symbol`, with successive variables + - *var* is an object array of :class:`sympy.Symbol`, with successive + variables representing the Cartesian coordinate directions. - *ode_order* the order of ODE that is returned """ @@ -105,7 +107,8 @@ def pde_to_ode_in_r(pde: LinearPDESystemOperator) -> tuple[ rval = r + eps f = sp.Function("f") - def apply_deriv_id(expr: sp.Expr, deriv_id: DerivativeIdentifier) -> sp.Expr: + def apply_deriv_id(expr: sp.Expr, + deriv_id: DerivativeIdentifier) -> sp.Expr: for i, nderivs in enumerate(deriv_id.mi): expr = expr.diff(var[i], nderivs) return expr @@ -129,7 +132,8 @@ def apply_deriv_id(expr: sp.Expr, deriv_id: DerivativeIdentifier) -> sp.Expr: def _generate_nd_derivative_relations(var: np.ndarray, ode_order: int) -> dict: r""" - Using the chain rule outputs a vector that gives in each component respectively + Using the chain rule outputs a vector that gives in each component + respectively :math:`[f(r), f'(r), \dots, f^{(ode_order)}(r)]` as a linear combination of :math:`[f(x), f'(x), \dots, f^{(ode_order)}(x)]` @@ -152,20 +156,23 @@ def _generate_nd_derivative_relations(var: np.ndarray, ode_order: int) -> dict: return sp.solve(system, *f_r_derivs, dict=True)[0] -def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr: +def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, + ode_order: int) -> sp.Expr: r""" Translates an ode in the variable r into an ode in the variable x - by replacing the terms :math:`f, f_r, f_{rr}, \dots` as a linear combinations of + by replacing the terms :math:`f, f_r, f_{rr}, \dots` as a linear + combinations of :math:`f, f_x, f_{xx}, \dots` using the chain rule. - :arg ode_in_r: a linear combination of :math:`f, f_r, f_{rr}, \dots` represented - by the sympy variables :math:`f_{r0}, f_{r1}, f_{r2}, \dots` + :arg ode_in_r: a linear combination of :math:`f, f_r, f_{rr}, \dots` + represented by the sympy variables :math:`f_{r0}, f_{r1}, f_{r2}, \dots` :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE :returns: *ode_in_x* a linear combination of :math:`f, f_x, f_{xx}, \dots` - represented by the sympy variables :math:`f_{x0}, f_{x1}, f_{x2}, \dots` - with coefficients as rational functions in :math:`x_0, x_1, \dots` + represented by the sympy variables :math:`f_{x0}, f_{x1}, f_{x2}, + \dots` with coefficients as rational functions in + :math:`x_0, x_1, \dots` """ subme = _generate_nd_derivative_relations(var, ode_order+1) ode_in_x = ode_in_r @@ -178,10 +185,11 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ode_order: int) -> sp.Expr ODECoefficients = list[list[sp.Expr]] -def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, - var: np.ndarray) -> ODECoefficients: +def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: + np.ndarray) -> ODECoefficients: r""" - Organizes the coefficients of an ODE in the :math:`x_0` variable into a 2D array. + Organizes the coefficients of an ODE in the :math:`x_0` variable into a + 2D array. :arg poly: a sympy polynomial in :math:`\partial_{x_0}^0 f, \partial_{x_0}^1 f,\cdots` of the form @@ -191,10 +199,10 @@ def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` :arg ode_order: the order of the input ODE we return a sequence - :returns: *coeffs* a sequence of of sequences, with the outer sequence iterating - over derivative orders, and each inner sequence iterating over powers of - :math:`x_0`, so that, in terms of the above form, coeffs is - :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` + :returns: *coeffs* a sequence of of sequences, with the outer sequence + iterating over derivative orders, and each inner sequence iterating + over powers of :math:`x_0`, so that, in terms of the above form, + coeffs is :math:`[[b_{00}, b_{01}, ...], [b_{10}, b_{11}, ...], ...]` """ return [ # recast ODE coefficient obtained below as polynomial in x0 @@ -220,16 +228,18 @@ def _falling_factorial(arg: NumberT, num_terms: int) -> NumberT: def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: r""" - We assume that we are given the expression :math:`x_0^p f^(m)(x_0)`. We then - output the nth order derivative of the expression where :math:`n` is a symbolic - variable. + We assume that we are given the expression :math:`x_0^p f^(m)(x_0)`. We + then output the nth order derivative of the expression where :math:`n` is + a symbolic variable. We let :math:`s(i)` represent the ith order derivative of f when we output the final result. :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` """ n = sp.symbols("n") s = sp.Function("s") + return sum( + # pylint: disable=not-callable _falling_factorial(n, i) * math.comb(p, i) * s(n-i+m) * var[0]**(p-i) for i in range(p+1) @@ -238,8 +248,8 @@ def _auto_product_rule_single_term(p: int, m: int, var: np.ndarray) -> sp.Expr: def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: r""" - A function that takes in as input an organized 2D coefficient array (see above) - and outputs a recurrence relation. + A function that takes in as input an organized 2D coefficient array (see + above) and outputs a recurrence relation. :arg coeffs: a sequence of of sequences, described in :func:`ode_in_x_to_coeff_array` @@ -250,14 +260,15 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: # Inner is polynomial order of x_0 for m, _ in enumerate(coeffs): for p, _ in enumerate(coeffs[m]): - final_recurrence += coeffs[m][p] * _auto_product_rule_single_term(p, - m, var) + final_recurrence += coeffs[m][p] * _auto_product_rule_single_term( + p, m, var) return final_recurrence def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: r""" - A function that takes in as input a sympy PDE and outputs a recurrence relation. + A function that takes in as input a sympy PDE and outputs a recurrence + relation. :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` that must satisfy ``pde.eqs == 1`` and have polynomial coefficients @@ -267,7 +278,7 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: ode_in_r, var, ode_order = pde_to_ode_in_r(pde) ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() - # ode_in_x_cleared shouldn't have rational function coefficients in the coord. + # ode_in_x_cleared shouldn't have rational function coefficients assert sp.together(ode_in_x_cleared) == ode_in_x_cleared f_x_derivs = _make_sympy_vec("f_x", ode_order+1) poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) @@ -373,4 +384,4 @@ def deriv_helmholtz_three_d(i, s_loc): r_sub = r_sub.subs(s(i), deriv_helmholtz_three_d(i, s_loc)) err = abs(abs(r_sub).evalf()) print(err) - assert err <= 1e-10 + assert err <= 1e-10 \ No newline at end of file From eef4e78c53119f98802bf993b17eca06827f6304 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Tue, 6 Aug 2024 00:57:55 -0700 Subject: [PATCH 45/75] Pylint/Flake8 --- sumpy/recurrence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 81836c42c..722f75892 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -58,11 +58,13 @@ """ import math +from random import randrange import numpy as np import sympy as sp from pytools.obj_array import make_obj_array + from sumpy.expansion.diff_op import ( DerivativeIdentifier, LinearPDESystemOperator, @@ -369,7 +371,6 @@ def deriv_helmholtz_three_d(i, s_loc): s_loc = rng.uniform(size=3) # Create random order to check - from random import randrange d = randrange(0, 5) # Substitute random location into recurrence relation and value of n = d @@ -384,4 +385,4 @@ def deriv_helmholtz_three_d(i, s_loc): r_sub = r_sub.subs(s(i), deriv_helmholtz_three_d(i, s_loc)) err = abs(abs(r_sub).evalf()) print(err) - assert err <= 1e-10 \ No newline at end of file + assert err <= 1e-10 From 52b38526e9229af6275b0a61f566d043faab27e1 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 7 Aug 2024 13:58:54 -0700 Subject: [PATCH 46/75] Update recurrence.py --- sumpy/recurrence.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 722f75892..41e54dc21 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -386,3 +386,8 @@ def deriv_helmholtz_three_d(i, s_loc): err = abs(abs(r_sub).evalf()) print(err) assert err <= 1e-10 + +w = make_identity_diff_op(2) +laplace2d = laplacian(w) +r = recurrence_from_pde(laplace2d) +print(r) \ No newline at end of file From ee23f651c2602dc43f654aef594650928366e98a Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 7 Aug 2024 13:59:49 -0700 Subject: [PATCH 47/75] Update recurrence.py --- sumpy/recurrence.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 41e54dc21..722f75892 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -386,8 +386,3 @@ def deriv_helmholtz_three_d(i, s_loc): err = abs(abs(r_sub).evalf()) print(err) assert err <= 1e-10 - -w = make_identity_diff_op(2) -laplace2d = laplacian(w) -r = recurrence_from_pde(laplace2d) -print(r) \ No newline at end of file From 6df8870b07d15d3a736c90a2ecb32a55d9514a71 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 12 Aug 2024 12:42:46 -0700 Subject: [PATCH 48/75] Added function to process recurrence relation --- sumpy/recurrence.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 722f75892..ec4b93af5 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -267,6 +267,58 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: return final_recurrence +def process_recurrence_relation(r: sp.Expr) -> tuple[int, sp.Expr]: + r""" + A function that takes in as input a recurrence and outputs a recurrence + relation that has the nth term in terms of the n-1th, n-2th etc. + Also returns the order of the recurrence relation. + + :arg recurrence: a recurrence relation in :math:`s(n)` + """ + terms = list(r.atoms(sp.Function)) + terms = np.array(terms) + + # Sort terms and create idx_l + idx_l = [] + for i in range(len(terms)): + tms = list(terms[i].atoms(sp.Number)) + if len(tms) == 1: + idx_l.append(tms[0]) + else: + idx_l.append(0) + idx_l = np.array(idx_l, dtype='int') + idx_sort = idx_l.argsort() + idx_l = idx_l[idx_sort] + terms = terms[idx_sort] + + # Order is the max difference between highest/lowest in idx_l + order = max(idx_l) - min(idx_l) + 1 + + # How much do we need to shift the recurrence relation + shift_idx = max(idx_l) + + # Get the respective coefficients in the recurrence relation from r + n = sp.symbols("n") + s = sp.Function("s") + coeffs = sp.poly(r, list(terms)).coeffs() + + # Re-arrange the recurrence relation so we get s(n) = ____ + # in terms of s(n-1), ... + true_recurrence = sum([coeffs[i]/coeffs[-1] * terms[i] + for i in range(0, len(terms)-1)]) + true_recurrence1 = true_recurrence.subs(n, n-shift_idx) + + # Replace s(n-1) with snm_1, s(n-2) with snm_2 etc. + # because pymbolic.substitute won't recognize it + last_syms = [sp.Symbol(f"snm{i+1}") for i in range(order-1)] + # pylint: disable=not-callable + true_recurrence2 = true_recurrence1.subs(s(n-1), last_syms[0]) + true_recurrence2 = true_recurrence2.subs(s(n-2), last_syms[1]) + true_recurrence2 = true_recurrence2.subs(s(n-3), last_syms[2]) + + return order, true_recurrence2 + + def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: r""" A function that takes in as input a sympy PDE and outputs a recurrence From 05a46abad947ee534c01e45d197dcca716cadc4e Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 2 Sep 2024 16:07:33 -0500 Subject: [PATCH 49/75] Shift recurrence so origin at expansion center --- sumpy/recurrence.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index ec4b93af5..b19c1b6d9 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -340,6 +340,19 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: return recurrence_from_coeff_array(coeffs, var) +def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: + r""" + A function that "shifts" the recurrence so it's center is placed + at the origin and source is the input for the recurrence generated. + + :arg recurrence: a recurrence relation in :math:`s(n)` + """ + r0 = r + for i in range(len(var)): + r0 = r0.subs(var[i], -var[i]) + return r0 + + def test_recurrence_finder_laplace(): """ Tests our recurrence relation generator for Lapalace 2D. From 846983576f2b2fb6aaf7e3e6ce11dc8eecce21a7 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 9 Sep 2024 10:18:09 -0500 Subject: [PATCH 50/75] Added flag to process_recurrence_relation, removed hardcode --- sumpy/recurrence.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index b19c1b6d9..06892fdc5 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -267,7 +267,8 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: return final_recurrence -def process_recurrence_relation(r: sp.Expr) -> tuple[int, sp.Expr]: +def process_recurrence_relation(r: sp.Expr, + replace=True) -> tuple[int, sp.Expr]: r""" A function that takes in as input a recurrence and outputs a recurrence relation that has the nth term in terms of the n-1th, n-2th etc. @@ -308,15 +309,18 @@ def process_recurrence_relation(r: sp.Expr) -> tuple[int, sp.Expr]: for i in range(0, len(terms)-1)]) true_recurrence1 = true_recurrence.subs(n, n-shift_idx) - # Replace s(n-1) with snm_1, s(n-2) with snm_2 etc. - # because pymbolic.substitute won't recognize it - last_syms = [sp.Symbol(f"snm{i+1}") for i in range(order-1)] - # pylint: disable=not-callable - true_recurrence2 = true_recurrence1.subs(s(n-1), last_syms[0]) - true_recurrence2 = true_recurrence2.subs(s(n-2), last_syms[1]) - true_recurrence2 = true_recurrence2.subs(s(n-3), last_syms[2]) + if replace: + # Replace s(n-1) with snm_1, s(n-2) with snm_2 etc. + # because pymbolic.substitute won't recognize it + last_syms = [sp.Symbol(f"anm{i+1}") for i in range(order-1)] + # pylint: disable=not-callable + # Assumes order > 1 + true_recurrence2 = true_recurrence1.subs(s(n-1), last_syms[0]) + for i in range(2, order): + true_recurrence2 = true_recurrence2.subs(s(n-i), last_syms[i-1]) + return order, true_recurrence2 - return order, true_recurrence2 + return order, true_recurrence1 def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: From aa1b651258481414409be32ebfc9024f48a1edf5 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 9 Sep 2024 12:50:35 -0500 Subject: [PATCH 51/75] Added 2 additional functions for generating hardcoded expressions --- sumpy/recurrence.py | 93 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 13 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 06892fdc5..b53adabf9 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -274,6 +274,10 @@ def process_recurrence_relation(r: sp.Expr, relation that has the nth term in terms of the n-1th, n-2th etc. Also returns the order of the recurrence relation. + If replace=True then the recurrence is output in a form that is ideal + for pymbolic processing. If replace=False then a standard recurrence + is output. + :arg recurrence: a recurrence relation in :math:`s(n)` """ terms = list(r.atoms(sp.Function)) @@ -322,6 +326,62 @@ def process_recurrence_relation(r: sp.Expr, return order, true_recurrence1 +def __check_neg_ind(r_n): + terms = list(r_n.atoms(sp.Function)) + terms = np.array(terms) + + idx_l = [] + for i in range(len(terms)): + tms = list(terms[i].atoms(sp.Number)) + if len(tms) == 1: + idx_l.append(tms[0]) + else: + idx_l.append(0) + idx_l = np.array(idx_l, dtype='int') + idx_sort = idx_l.argsort() + idx_l = idx_l[idx_sort] + terms = terms[idx_sort] + + return np.any(idx_l < 0) + + +def get_lower_order_expressions(p, recurrence): + r""" + A function that takes in as input an order of expansion + and a recurrence relation and outputs an array of hardcoded recurrence + expressions for each order. If an expression for a certain order + doesn't exist 0 is output. Also returns the number of initial conditions + needed. + + :arg recurrence: a recurrence relation in :math:`s(n)` + :arg p: number of orders needed for recurrence expressions + """ + p = 5 + initial_c = 0 + recur_arr = [0] * p + n = sp.symbols("n") + for i in range(p): + r_c = recurrence.subs(n, i) + if __check_neg_ind(r_c): + recur_arr[i] = 0 + initial_c = i + else: + recur_arr[i] = r_c + return initial_c, recur_arr + + +def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: + r""" + A function that "shifts" the recurrence so it's center is placed + at the origin and source is the input for the recurrence generated. + + :arg recurrence: a recurrence relation in :math:`s(n)` + """ + r0 = r + for i in range(len(var)): + r0 = r0.subs(var[i], -var[i]) + return r0 + def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: r""" @@ -344,19 +404,6 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: return recurrence_from_coeff_array(coeffs, var) -def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: - r""" - A function that "shifts" the recurrence so it's center is placed - at the origin and source is the input for the recurrence generated. - - :arg recurrence: a recurrence relation in :math:`s(n)` - """ - r0 = r - for i in range(len(var)): - r0 = r0.subs(var[i], -var[i]) - return r0 - - def test_recurrence_finder_laplace(): """ Tests our recurrence relation generator for Lapalace 2D. @@ -455,3 +502,23 @@ def deriv_helmholtz_three_d(i, s_loc): err = abs(abs(r_sub).evalf()) print(err) assert err <= 1e-10 + + +def test_get_lower_order_expressions_laplace_2D(): + """ + Tests our expression generator for Laplace 2D. + """ + + w = make_identity_diff_op(2) + laplace2d = laplacian(w) + r = recurrence_from_pde(laplace2d) + var = _make_sympy_vec("x", 2) + r = shift_recurrence(r, var) + _, r_processed = process_recurrence_relation(r, False) + + _, recur_arr = get_lower_order_expressions(5, r_processed) + + print(recur_arr) + + +test_get_lower_order_expressions_laplace_2D() \ No newline at end of file From 4ded696808e37903e2ba60ba91cbd71eb902449f Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Thu, 12 Sep 2024 15:57:48 -0500 Subject: [PATCH 52/75] sp.cancel --- sumpy/recurrence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index b53adabf9..c34247aa5 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -366,7 +366,7 @@ def get_lower_order_expressions(p, recurrence): recur_arr[i] = 0 initial_c = i else: - recur_arr[i] = r_c + recur_arr[i] = sp.cancel(r_c) return initial_c, recur_arr From 2e615f0d73513fafff3c82a15335b98c0f40418e Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 28 Oct 2024 14:31:25 -0500 Subject: [PATCH 53/75] Added recurrence+qbx code --- sumpy/recurrence.py | 361 ++++++++++++++++++++++++---------------- test/test_recurrence.py | 83 +++++++++ 2 files changed, 305 insertions(+), 139 deletions(-) create mode 100644 test/test_recurrence.py diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index c34247aa5..e75c19689 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -67,9 +67,7 @@ from sumpy.expansion.diff_op import ( DerivativeIdentifier, - LinearPDESystemOperator, - laplacian, - make_identity_diff_op, + LinearPDESystemOperator ) @@ -267,6 +265,27 @@ def recurrence_from_coeff_array(coeffs: list, var: np.ndarray) -> sp.Expr: return final_recurrence +def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: + r""" + A function that takes in as input a sympy PDE and outputs a recurrence + relation. + + :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` + that must satisfy ``pde.eqs == 1`` and have polynomial coefficients + in the coordinates. + :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` + """ + ode_in_r, var, ode_order = pde_to_ode_in_r(pde) + ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() + ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() + # ode_in_x_cleared shouldn't have rational function coefficients + assert sp.together(ode_in_x_cleared) == ode_in_x_cleared + f_x_derivs = _make_sympy_vec("f_x", ode_order+1) + poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) + coeffs = ode_in_x_to_coeff_array(poly, ode_order, var) + return recurrence_from_coeff_array(coeffs, var) + + def process_recurrence_relation(r: sp.Expr, replace=True) -> tuple[int, sp.Expr]: r""" @@ -326,10 +345,19 @@ def process_recurrence_relation(r: sp.Expr, return order, true_recurrence1 -def __check_neg_ind(r_n): - terms = list(r_n.atoms(sp.Function)) + +def extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, + np.ndarray]: + r""" + Given a recurrence extracts the variables in the recurrence + as well as the indexes in sorted order. + + :arg r: recurrence to extract terms from + """ + terms = list(r.atoms(sp.Function)) terms = np.array(terms) + idx_l = [] for i in range(len(terms)): tms = list(terms[i].atoms(sp.Number)) @@ -342,32 +370,32 @@ def __check_neg_ind(r_n): idx_l = idx_l[idx_sort] terms = terms[idx_sort] - return np.any(idx_l < 0) + return idx_l, terms -def get_lower_order_expressions(p, recurrence): +def __check_neg_ind(r_n): r""" - A function that takes in as input an order of expansion - and a recurrence relation and outputs an array of hardcoded recurrence - expressions for each order. If an expression for a certain order - doesn't exist 0 is output. Also returns the number of initial conditions - needed. + Simply checks if a negative index exists in a recurrence relation. + """ - :arg recurrence: a recurrence relation in :math:`s(n)` - :arg p: number of orders needed for recurrence expressions + idx_l, _ = extract_idx_terms_from_recurrence(r_n) + + return np.any(idx_l < 0) + + +def __get_initial_c(recurrence): + r""" + For a given recurrence checks how many initial conditions by + checking for non-negative indexed terms. """ - p = 5 - initial_c = 0 - recur_arr = [0] * p n = sp.symbols("n") - for i in range(p): + + i = 0 + r_c = recurrence.subs(n, i) + while __check_neg_ind(r_c): + i += 1 r_c = recurrence.subs(n, i) - if __check_neg_ind(r_c): - recur_arr[i] = 0 - initial_c = i - else: - recur_arr[i] = sp.cancel(r_c) - return initial_c, recur_arr + return i def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: @@ -377,148 +405,203 @@ def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: :arg recurrence: a recurrence relation in :math:`s(n)` """ - r0 = r - for i in range(len(var)): - r0 = r0.subs(var[i], -var[i]) - return r0 + idx_l, terms = extract_idx_terms_from_recurrence(r) + r_ret = r -def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: + n = sp.symbols('n') + for i in range(len(idx_l)): + r_ret = r_ret.subs(terms[i], (-1)**(n+idx_l[i])*terms[i]) + + return r_ret*((-1)**(n+1)) + + +def get_processed_recurrence_from_pde_shift(pde, ndim) -> tuple[int, int, + sp.Expr]: r""" - A function that takes in as input a sympy PDE and outputs a recurrence - relation. + A function that "shifts" the recurrence so the expansion center is placed + at the origin and source is the input for the recurrence generated. - :arg pde: a :class:`sumpy.expansion.diff_op.LinearSystemPDEOperator` - that must satisfy ``pde.eqs == 1`` and have polynomial coefficients - in the coordinates. - :arg var: array of sympy variables :math:`[x_0, x_1, \dots]` + :arg recurrence: a recurrence relation in :math:`s(n)` """ - ode_in_r, var, ode_order = pde_to_ode_in_r(pde) - ode_in_x = ode_in_r_to_x(ode_in_r, var, ode_order).simplify() - ode_in_x_cleared = (ode_in_x * var[0]**(ode_order+1)).simplify() - # ode_in_x_cleared shouldn't have rational function coefficients - assert sp.together(ode_in_x_cleared) == ode_in_x_cleared - f_x_derivs = _make_sympy_vec("f_x", ode_order+1) - poly = sp.Poly(ode_in_x_cleared, *f_x_derivs) - coeffs = ode_in_x_to_coeff_array(poly, ode_order, var) - return recurrence_from_coeff_array(coeffs, var) + r = recurrence_from_pde(pde) + var = _make_sympy_vec("x", ndim) + order, r_p = process_recurrence_relation(r, False) + n_initial = __get_initial_c(r_p) + r_s = shift_recurrence(r_p, var) + return n_initial, order, r_s -def test_recurrence_finder_laplace(): - """ - Tests our recurrence relation generator for Lapalace 2D. - """ - w = make_identity_diff_op(2) - laplace2d = laplacian(w) - r = recurrence_from_pde(laplace2d) - n = sp.symbols("n") - s = sp.Function("s") +# ================ Transform/Rotate ================= +def __produce_orthogonal_basis(normals): + ndim, ncenters = normals.shape + orth_coordsys = [normals] + for i in range(1, ndim): + v = np.random.rand(ndim, ncenters) + v = v/np.linalg.norm(v, 2, axis=0) + for j in range(i): + v = v - np.einsum("dc,dc->c", v, orth_coordsys[j]) * orth_coordsys[j] + v = v/np.linalg.norm(v, 2, axis=0) + orth_coordsys.append(v) - def deriv_laplace(i): - x, y = sp.symbols("x,y") - var = _make_sympy_vec("x", 2) - true_f = sp.log(sp.sqrt(x**2 + y**2)) - return sp.diff(true_f, x, i).subs(x, var[0]).subs( - y, var[1]) - d = 6 - # pylint: disable=not-callable + return orth_coordsys - r_sub = r.subs(n, d) - for i in range(d-1, d+3): - r_sub = r_sub.subs(s(i), deriv_laplace(i)) - r_sub = r_sub.simplify() - assert r_sub == 0 +def __compute_rotated_shifted_coordinates(sources, centers, normals): + cts = sources[:, None] - centers[:, :, None] + orth_coordsys = __produce_orthogonal_basis(normals) + cts_rotated_shifted = np.einsum("idc,dcs->ics", orth_coordsys, cts) -def test_recurrence_finder_laplace_three_d(): - """ - Tests our recurrence relation generator for Laplace 3D. + return cts_rotated_shifted + + +# ================ Recurrence LP Eval ================= +def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, + p) -> np.ndarray: + r""" + A function that computes a single-layer potential using a recurrence. + + :arg sources: a (ndim, nsources) array of source locations + :arg centers: a (ndim, ncenters) array of center locations + :arg normals: a (ndim, ncenters) array of normals + :arg strengths: array corresponding to quadrature weight multiplied by + density + :arg radius: expansion radius + :arg pde: pde that we are computing layer potential for + :arg g_x_y: a green's function in (x0, x1, ...) source and + (t0, t1, ...) target + :arg p: order of expansion computed """ - w = make_identity_diff_op(3) - laplace3d = laplacian(w) - r = recurrence_from_pde(laplace3d) - n = sp.symbols("n") - s = sp.Function("s") - def deriv_laplace_three_d(i): - x, y, z = sp.symbols("x,y,z") - var = _make_sympy_vec("x", 3) - true_f = 1/(sp.sqrt(x**2 + y**2 + z**2)) - return sp.diff(true_f, x, i).subs(x, var[0]).subs( - y, var[1]).subs(z, var[2]) + #------------- 2. Compute rotated/shifted coordinates + cts_r_s = __compute_rotated_shifted_coordinates(sources, centers, normals) - d = 6 - # pylint: disable=not-callable - r_sub = r.subs(n, d) - for i in range(d-1, d+3): - r_sub = r_sub.subs(s(i), deriv_laplace_three_d(i)) - r_sub = r_sub.simplify() - assert r_sub == 0 + #------------- 4. Compute green's function expression + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + + #------------ 5. Compute recurrence + n_initial, order, recurrence = get_processed_recurrence_from_pde_shift(pde, ndim=2) + + #------------ 6. Set order p = 5 + n_p = sources.shape[1] + storage = [np.zeros((n_p,n_p))] * order -def test_recurrence_finder_helmholtz_three_d(): - """ - Tests our recurrence relation generator for Helmhotlz 3D. - """ - # We are creating the recurrence relation for helmholtz3d which - # seems to be an order 5 recurrence relation - w = make_identity_diff_op(3) - helmholtz3d = laplacian(w) + w - r = recurrence_from_pde(helmholtz3d) - - def deriv_helmholtz_three_d(i, s_loc): - s_x = s_loc[0] - s_y = s_loc[1] - s_z = s_loc[2] - x, y, z = sp.symbols("x,y,z") - true_f = sp.exp(1j * sp.sqrt(x**2 + y**2 + z**2) - ) / (sp.sqrt(x**2 + y**2 + z**2)) - return sp.diff(true_f, x, i).subs(x, s_x).subs( - y, s_y).subs(z, s_z) - # Create relevant symbols - var = _make_sympy_vec("x", 3) - n = sp.symbols("n") s = sp.Function("s") + r,n = sp.symbols("r,n") + + def generate_lamb_expr(i, n_initial): + arg_list = [] + for j in range(order,0,-1): + arg_list.append(s(i-j)) + arg_list.append(var[0]) + arg_list.append(var[1]) + arg_list.append(r) + + if i < n_initial: + lamb_expr = sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) + else: + lamb_expr = recurrence.subs(n, i) + return sp.lambdify(arg_list, lamb_expr) - rng = np.random.default_rng() + interactions_2d = 0 + for i in range(p+1): + lamb_expr = generate_lamb_expr(i, n_initial) + a = storage[-4:] + [cts_r_s[0],cts_r_s[1],radius] + s_new = lamb_expr(*a) + interactions_2d += s_new * radius**i/math.factorial(i) - # Create random source location - s_loc = rng.uniform(size=3) + storage.pop(0) + storage.append(s_new) - # Create random order to check - d = randrange(0, 5) + exp_res = (interactions_2d * strengths[None, :]).sum(axis=1) - # Substitute random location into recurrence relation and value of n = d - r_loc = r.subs(var[0], s_loc[0]) - r_loc = r_loc.subs(var[1], s_loc[1]) - r_loc = r_loc.subs(var[2], s_loc[2]) - r_sub = r_loc.subs(n, d) + return exp_res - # Checking that the recurrence holds to some machine epsilon - for i in range(max(d-3, 0), d+3): - # pylint: disable=not-callable - r_sub = r_sub.subs(s(i), deriv_helmholtz_three_d(i, s_loc)) - err = abs(abs(r_sub).evalf()) - print(err) - assert err <= 1e-10 +# TEST CODE +from sumpy.expansion.diff_op import ( + laplacian, + make_identity_diff_op, +) -def test_get_lower_order_expressions_laplace_2D(): - """ - Tests our expression generator for Laplace 2D. - """ - +import numpy as np +from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 +from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion + + +actx_factory = _acf +expn_class = LineTaylorLocalExpansion + +actx = actx_factory() + +from sumpy.kernel import LaplaceKernel +lknl = LaplaceKernel(2) + +from sumpy.qbx import LayerPotential + +def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): + lpot = LayerPotential(actx.context, + expansion=expn_class(lknl, order), + target_kernels=(lknl,), + source_kernels=(lknl,)) + + #print(lpot.get_kernel()) + expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) + sources = actx.from_numpy(sources) + targets = actx.from_numpy(targets) + centers = actx.from_numpy(centers) + + strengths = (strengths,) + + _evt, (result_qbx,) = lpot( + actx.queue, + targets, sources, centers, strengths, + expansion_radii=expansion_radii) + result_qbx = actx.to_numpy(result_qbx) + + return result_qbx + +def create_ellipse(n_p): + h = 9.688 / n_p + radius = 7*h + t = np.linspace(0, 2 * np.pi, n_p, endpoint=False) + + unit_circle_param = np.exp(1j * t) + unit_circle = np.array([2 * unit_circle_param.real, unit_circle_param.imag]) + + sources = unit_circle + normals = np.array([unit_circle_param.real, 2*unit_circle_param.imag]) + normals = normals / np.linalg.norm(normals, axis=0) + centers = sources - normals * radius + + mode_nr = 25 + density = np.cos(mode_nr * t) + + return sources, centers, normals, density, h, radius + +def test_recurrence_laplace_2d_ellipse(): + + #------------- 1. Define PDE, Green's Function w = make_identity_diff_op(2) laplace2d = laplacian(w) - r = recurrence_from_pde(laplace2d) - var = _make_sympy_vec("x", 2) - r = shift_recurrence(r, var) - _, r_processed = process_recurrence_relation(r, False) - _, recur_arr = get_lower_order_expressions(5, r_processed) - - print(recur_arr) + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) + + p = 4 + err = [] + for n_p in range(200, 1001, 200): + sources, centers, normals, density, h, radius = create_ellipse(n_p) + strengths = h * density + exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) + qbx_res = qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) + #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) + err.append(np.max(exp_res - qbx_res)) + print(err) -test_get_lower_order_expressions_laplace_2D() \ No newline at end of file +test_recurrence_laplace_2d_ellipse() \ No newline at end of file diff --git a/test/test_recurrence.py b/test/test_recurrence.py new file mode 100644 index 000000000..aa9e01293 --- /dev/null +++ b/test/test_recurrence.py @@ -0,0 +1,83 @@ +from sumpy.expansion.diff_op import ( + laplacian, + make_identity_diff_op, +) + +import numpy as np +from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 +from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion + + +actx_factory = _acf +expn_class = LineTaylorLocalExpansion + +actx = actx_factory() + +from sumpy.kernel import LaplaceKernel +lknl = LaplaceKernel(2) + +from sumpy.qbx import LayerPotential +from sumpy.recurrence import recurrence_qbx_lp, _make_sympy_vec + +def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): + lpot = LayerPotential(actx.context, + expansion=expn_class(lknl, order), + target_kernels=(lknl,), + source_kernels=(lknl,)) + + #print(lpot.get_kernel()) + expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) + sources = actx.from_numpy(sources) + targets = actx.from_numpy(targets) + centers = actx.from_numpy(centers) + + strengths = (strengths,) + + _evt, (result_qbx,) = lpot( + actx.queue, + targets, sources, centers, strengths, + expansion_radii=expansion_radii) + result_qbx = actx.to_numpy(result_qbx) + + return result_qbx + +def create_ellipse(n_p): + h = 9.688 / n_p + radius = 7*h + t = np.linspace(0, 2 * np.pi, n_p, endpoint=False) + + unit_circle_param = np.exp(1j * t) + unit_circle = np.array([2 * unit_circle_param.real, unit_circle_param.imag]) + + sources = unit_circle + normals = np.array([unit_circle_param.real, 2*unit_circle_param.imag]) + normals = normals / np.linalg.norm(normals, axis=0) + centers = sources - normals * radius + + mode_nr = 25 + density = np.cos(mode_nr * t) + + return sources, centers, normals, density, h, radius + +def test_recurrence_laplace_2d_ellipse(): + + #------------- 1. Define PDE, Green's Function + w = make_identity_diff_op(2) + laplace2d = laplacian(w) + + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) + + p = 4 + err = [] + for n_p in range(200, 1001, 200): + sources, centers, normals, density, h, radius = create_ellipse(n_p) + strengths = h * density + exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) + qbx_res = qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) + #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) + err.append(np.max(exp_res - qbx_res)) + + print(err) + From ab46c104e95c1915e5cedc2525483181fb743100 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Wed, 30 Oct 2024 13:36:47 -0500 Subject: [PATCH 54/75] Separate file/move to test file --- sumpy/recurrence.py | 173 ---------------------------------------- sumpy/recurrenceqbx.py | 100 +++++++++++++++++++++++ test/test_recurrence.py | 3 +- 3 files changed, 102 insertions(+), 174 deletions(-) create mode 100644 sumpy/recurrenceqbx.py diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index e75c19689..d3d195ab7 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -432,176 +432,3 @@ def get_processed_recurrence_from_pde_shift(pde, ndim) -> tuple[int, int, return n_initial, order, r_s -# ================ Transform/Rotate ================= -def __produce_orthogonal_basis(normals): - ndim, ncenters = normals.shape - orth_coordsys = [normals] - for i in range(1, ndim): - v = np.random.rand(ndim, ncenters) - v = v/np.linalg.norm(v, 2, axis=0) - for j in range(i): - v = v - np.einsum("dc,dc->c", v, orth_coordsys[j]) * orth_coordsys[j] - v = v/np.linalg.norm(v, 2, axis=0) - orth_coordsys.append(v) - - return orth_coordsys - - -def __compute_rotated_shifted_coordinates(sources, centers, normals): - - cts = sources[:, None] - centers[:, :, None] - orth_coordsys = __produce_orthogonal_basis(normals) - cts_rotated_shifted = np.einsum("idc,dcs->ics", orth_coordsys, cts) - - return cts_rotated_shifted - - -# ================ Recurrence LP Eval ================= -def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, - p) -> np.ndarray: - r""" - A function that computes a single-layer potential using a recurrence. - - :arg sources: a (ndim, nsources) array of source locations - :arg centers: a (ndim, ncenters) array of center locations - :arg normals: a (ndim, ncenters) array of normals - :arg strengths: array corresponding to quadrature weight multiplied by - density - :arg radius: expansion radius - :arg pde: pde that we are computing layer potential for - :arg g_x_y: a green's function in (x0, x1, ...) source and - (t0, t1, ...) target - :arg p: order of expansion computed - """ - - #------------- 2. Compute rotated/shifted coordinates - cts_r_s = __compute_rotated_shifted_coordinates(sources, centers, normals) - - - #------------- 4. Compute green's function expression - var = _make_sympy_vec("x", 2) - var_t = _make_sympy_vec("t", 2) - - #------------ 5. Compute recurrence - n_initial, order, recurrence = get_processed_recurrence_from_pde_shift(pde, ndim=2) - - #------------ 6. Set order p = 5 - n_p = sources.shape[1] - storage = [np.zeros((n_p,n_p))] * order - - s = sp.Function("s") - r,n = sp.symbols("r,n") - - def generate_lamb_expr(i, n_initial): - arg_list = [] - for j in range(order,0,-1): - arg_list.append(s(i-j)) - arg_list.append(var[0]) - arg_list.append(var[1]) - arg_list.append(r) - - if i < n_initial: - lamb_expr = sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) - else: - lamb_expr = recurrence.subs(n, i) - return sp.lambdify(arg_list, lamb_expr) - - interactions_2d = 0 - for i in range(p+1): - lamb_expr = generate_lamb_expr(i, n_initial) - a = storage[-4:] + [cts_r_s[0],cts_r_s[1],radius] - s_new = lamb_expr(*a) - interactions_2d += s_new * radius**i/math.factorial(i) - - storage.pop(0) - storage.append(s_new) - - exp_res = (interactions_2d * strengths[None, :]).sum(axis=1) - - return exp_res - - -# TEST CODE -from sumpy.expansion.diff_op import ( - laplacian, - make_identity_diff_op, -) - -import numpy as np -from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 -from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion - - -actx_factory = _acf -expn_class = LineTaylorLocalExpansion - -actx = actx_factory() - -from sumpy.kernel import LaplaceKernel -lknl = LaplaceKernel(2) - -from sumpy.qbx import LayerPotential - -def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): - lpot = LayerPotential(actx.context, - expansion=expn_class(lknl, order), - target_kernels=(lknl,), - source_kernels=(lknl,)) - - #print(lpot.get_kernel()) - expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) - sources = actx.from_numpy(sources) - targets = actx.from_numpy(targets) - centers = actx.from_numpy(centers) - - strengths = (strengths,) - - _evt, (result_qbx,) = lpot( - actx.queue, - targets, sources, centers, strengths, - expansion_radii=expansion_radii) - result_qbx = actx.to_numpy(result_qbx) - - return result_qbx - -def create_ellipse(n_p): - h = 9.688 / n_p - radius = 7*h - t = np.linspace(0, 2 * np.pi, n_p, endpoint=False) - - unit_circle_param = np.exp(1j * t) - unit_circle = np.array([2 * unit_circle_param.real, unit_circle_param.imag]) - - sources = unit_circle - normals = np.array([unit_circle_param.real, 2*unit_circle_param.imag]) - normals = normals / np.linalg.norm(normals, axis=0) - centers = sources - normals * radius - - mode_nr = 25 - density = np.cos(mode_nr * t) - - return sources, centers, normals, density, h, radius - -def test_recurrence_laplace_2d_ellipse(): - - #------------- 1. Define PDE, Green's Function - w = make_identity_diff_op(2) - laplace2d = laplacian(w) - - var = _make_sympy_vec("x", 2) - var_t = _make_sympy_vec("t", 2) - g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) - - p = 4 - err = [] - for n_p in range(200, 1001, 200): - sources, centers, normals, density, h, radius = create_ellipse(n_p) - strengths = h * density - exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) - qbx_res = qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) - #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) - err.append(np.max(exp_res - qbx_res)) - - print(err) - -test_recurrence_laplace_2d_ellipse() \ No newline at end of file diff --git a/sumpy/recurrenceqbx.py b/sumpy/recurrenceqbx.py new file mode 100644 index 000000000..800d18e02 --- /dev/null +++ b/sumpy/recurrenceqbx.py @@ -0,0 +1,100 @@ +r""" +With the functionality in this module, we aim to compute layer potentials +using a recurrence for one-dimensional derivatives of the corresponding +Green's function. See recurrence.py. + +.. autofunction:: recurrence_qbx_lp +""" +import numpy as np +import sympy as sp +from sumpy.recurrence import ( + _make_sympy_vec, + get_processed_recurrence_from_pde_shift) + +# ================ Transform/Rotate ================= +def __produce_orthogonal_basis(normals): + ndim, ncenters = normals.shape + orth_coordsys = [normals] + for i in range(1, ndim): + v = np.random.rand(ndim, ncenters) + v = v/np.linalg.norm(v, 2, axis=0) + for j in range(i): + v = v - np.einsum("dc,dc->c", v, orth_coordsys[j]) * orth_coordsys[j] + v = v/np.linalg.norm(v, 2, axis=0) + orth_coordsys.append(v) + + return orth_coordsys + + +def __compute_rotated_shifted_coordinates(sources, centers, normals): + + cts = sources[:, None] - centers[:, :, None] + orth_coordsys = __produce_orthogonal_basis(normals) + cts_rotated_shifted = np.einsum("idc,dcs->ics", orth_coordsys, cts) + + return cts_rotated_shifted + + +# ================ Recurrence LP Eval ================= +def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, + p) -> np.ndarray: + r""" + A function that computes a single-layer potential using a recurrence. + + :arg sources: a (ndim, nsources) array of source locations + :arg centers: a (ndim, ncenters) array of center locations + :arg normals: a (ndim, ncenters) array of normals + :arg strengths: array corresponding to quadrature weight multiplied by + density + :arg radius: expansion radius + :arg pde: pde that we are computing layer potential for + :arg g_x_y: a green's function in (x0, x1, ...) source and + (t0, t1, ...) target + :arg p: order of expansion computed + """ + + #------------- 2. Compute rotated/shifted coordinates + cts_r_s = __compute_rotated_shifted_coordinates(sources, centers, normals) + + + #------------- 4. Compute green's function expression + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + + #------------ 5. Compute recurrence + n_initial, order, recurrence = get_processed_recurrence_from_pde_shift(pde, ndim=2) + + #------------ 6. Set order p = 5 + n_p = sources.shape[1] + storage = [np.zeros((n_p,n_p))] * order + + s = sp.Function("s") + r,n = sp.symbols("r,n") + + def generate_lamb_expr(i, n_initial): + arg_list = [] + for j in range(order,0,-1): + arg_list.append(s(i-j)) + arg_list.append(var[0]) + arg_list.append(var[1]) + arg_list.append(r) + + if i < n_initial: + lamb_expr = sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) + else: + lamb_expr = recurrence.subs(n, i) + return sp.lambdify(arg_list, lamb_expr) + + interactions_2d = 0 + for i in range(p+1): + lamb_expr = generate_lamb_expr(i, n_initial) + a = storage[-4:] + [cts_r_s[0],cts_r_s[1],radius] + s_new = lamb_expr(*a) + interactions_2d += s_new * radius**i/math.factorial(i) + + storage.pop(0) + storage.append(s_new) + + exp_res = (interactions_2d * strengths[None, :]).sum(axis=1) + + return exp_res \ No newline at end of file diff --git a/test/test_recurrence.py b/test/test_recurrence.py index aa9e01293..bff616941 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -2,6 +2,7 @@ laplacian, make_identity_diff_op, ) +from sumpy.recurrenceqbx import recurrence_qbx_lp, _make_sympy_vec import numpy as np from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 @@ -17,7 +18,7 @@ lknl = LaplaceKernel(2) from sumpy.qbx import LayerPotential -from sumpy.recurrence import recurrence_qbx_lp, _make_sympy_vec + def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): lpot = LayerPotential(actx.context, From aa1dab02c9ce2230f2818a7fa7a5e88f5bd1dd67 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Thu, 31 Oct 2024 03:45:53 -0500 Subject: [PATCH 55/75] Remove outdated code recurrence --- sumpy/recurrence.py | 46 +++++++++------------------------------------ 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index d3d195ab7..2f1cc0ff8 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -25,6 +25,9 @@ .. autofunction:: ode_in_x_to_coeff_array .. autofunction:: recurrence_from_coeff_array .. autofunction:: recurrence_from_pde +.. autofunction:: process_recurrence_relation +.. autofunction:: shift_recurrence + """ from __future__ import annotations @@ -293,28 +296,9 @@ def process_recurrence_relation(r: sp.Expr, relation that has the nth term in terms of the n-1th, n-2th etc. Also returns the order of the recurrence relation. - If replace=True then the recurrence is output in a form that is ideal - for pymbolic processing. If replace=False then a standard recurrence - is output. - :arg recurrence: a recurrence relation in :math:`s(n)` """ - terms = list(r.atoms(sp.Function)) - terms = np.array(terms) - - # Sort terms and create idx_l - idx_l = [] - for i in range(len(terms)): - tms = list(terms[i].atoms(sp.Number)) - if len(tms) == 1: - idx_l.append(tms[0]) - else: - idx_l.append(0) - idx_l = np.array(idx_l, dtype='int') - idx_sort = idx_l.argsort() - idx_l = idx_l[idx_sort] - terms = terms[idx_sort] - + idx_l, terms = _extract_idx_terms_from_recurrence(r) # Order is the max difference between highest/lowest in idx_l order = max(idx_l) - min(idx_l) + 1 @@ -332,21 +316,10 @@ def process_recurrence_relation(r: sp.Expr, for i in range(0, len(terms)-1)]) true_recurrence1 = true_recurrence.subs(n, n-shift_idx) - if replace: - # Replace s(n-1) with snm_1, s(n-2) with snm_2 etc. - # because pymbolic.substitute won't recognize it - last_syms = [sp.Symbol(f"anm{i+1}") for i in range(order-1)] - # pylint: disable=not-callable - # Assumes order > 1 - true_recurrence2 = true_recurrence1.subs(s(n-1), last_syms[0]) - for i in range(2, order): - true_recurrence2 = true_recurrence2.subs(s(n-i), last_syms[i-1]) - return order, true_recurrence2 - return order, true_recurrence1 -def extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, +def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, np.ndarray]: r""" Given a recurrence extracts the variables in the recurrence @@ -398,7 +371,7 @@ def __get_initial_c(recurrence): return i -def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: +def shift_recurrence(r: sp.Expr) -> sp.Expr: r""" A function that "shifts" the recurrence so it's center is placed at the origin and source is the input for the recurrence generated. @@ -416,7 +389,7 @@ def shift_recurrence(r: sp.Expr, var: np.ndarray) -> sp.Expr: return r_ret*((-1)**(n+1)) -def get_processed_recurrence_from_pde_shift(pde, ndim) -> tuple[int, int, +def get_processed_and_shifted_recurrence(pde) -> tuple[int, int, sp.Expr]: r""" A function that "shifts" the recurrence so the expansion center is placed @@ -425,10 +398,9 @@ def get_processed_recurrence_from_pde_shift(pde, ndim) -> tuple[int, int, :arg recurrence: a recurrence relation in :math:`s(n)` """ r = recurrence_from_pde(pde) - var = _make_sympy_vec("x", ndim) - order, r_p = process_recurrence_relation(r, False) + order, r_p = process_recurrence_relation(r) n_initial = __get_initial_c(r_p) - r_s = shift_recurrence(r_p, var) + r_s = shift_recurrence(r_p) return n_initial, order, r_s From 52a859d89f54a6f74bdb39c2a322c3b8eaba6b2f Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Thu, 31 Oct 2024 04:06:30 -0500 Subject: [PATCH 56/75] Renamed function for clarity --- sumpy/recurrence.py | 2 +- sumpy/recurrenceqbx.py | 6 +++--- test/test_recurrenceqbx.py | 0 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 test/test_recurrenceqbx.py diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 2f1cc0ff8..0696ba365 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -27,7 +27,7 @@ .. autofunction:: recurrence_from_pde .. autofunction:: process_recurrence_relation .. autofunction:: shift_recurrence - +.. autofunction:: get_processed_and_shifted_recurrence """ from __future__ import annotations diff --git a/sumpy/recurrenceqbx.py b/sumpy/recurrenceqbx.py index 800d18e02..b620c6fb2 100644 --- a/sumpy/recurrenceqbx.py +++ b/sumpy/recurrenceqbx.py @@ -9,7 +9,7 @@ import sympy as sp from sumpy.recurrence import ( _make_sympy_vec, - get_processed_recurrence_from_pde_shift) + get_processed_and_shifted_recurrence) # ================ Transform/Rotate ================= def __produce_orthogonal_basis(normals): @@ -57,12 +57,12 @@ def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, cts_r_s = __compute_rotated_shifted_coordinates(sources, centers, normals) - #------------- 4. Compute green's function expression + #------------- 4. Define input variables for green's function expression var = _make_sympy_vec("x", 2) var_t = _make_sympy_vec("t", 2) #------------ 5. Compute recurrence - n_initial, order, recurrence = get_processed_recurrence_from_pde_shift(pde, ndim=2) + n_initial, order, recurrence = get_processed_and_shifted_recurrence(pde) #------------ 6. Set order p = 5 n_p = sources.shape[1] diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py new file mode 100644 index 000000000..e69de29bb From 473d714722dc1fc5cc10e9dfa28196d5ac04e9bf Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Fri, 1 Nov 2024 18:13:42 -0500 Subject: [PATCH 57/75] Update recurrence.py --- sumpy/recurrence.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 0696ba365..e8a9c0fbf 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -289,8 +289,7 @@ def recurrence_from_pde(pde: LinearPDESystemOperator) -> sp.Expr: return recurrence_from_coeff_array(coeffs, var) -def process_recurrence_relation(r: sp.Expr, - replace=True) -> tuple[int, sp.Expr]: +def process_recurrence_relation(r: sp.Expr) -> tuple[int, sp.Expr]: r""" A function that takes in as input a recurrence and outputs a recurrence relation that has the nth term in terms of the n-1th, n-2th etc. @@ -307,7 +306,6 @@ def process_recurrence_relation(r: sp.Expr, # Get the respective coefficients in the recurrence relation from r n = sp.symbols("n") - s = sp.Function("s") coeffs = sp.poly(r, list(terms)).coeffs() # Re-arrange the recurrence relation so we get s(n) = ____ @@ -351,7 +349,7 @@ def __check_neg_ind(r_n): Simply checks if a negative index exists in a recurrence relation. """ - idx_l, _ = extract_idx_terms_from_recurrence(r_n) + idx_l, _ = _extract_idx_terms_from_recurrence(r_n) return np.any(idx_l < 0) @@ -378,7 +376,7 @@ def shift_recurrence(r: sp.Expr) -> sp.Expr: :arg recurrence: a recurrence relation in :math:`s(n)` """ - idx_l, terms = extract_idx_terms_from_recurrence(r) + idx_l, terms = _extract_idx_terms_from_recurrence(r) r_ret = r @@ -404,3 +402,4 @@ def get_processed_and_shifted_recurrence(pde) -> tuple[int, int, return n_initial, order, r_s +print(_generate_nd_derivative_relations(_make_sympy_vec("x", 2), 3)) \ No newline at end of file From cdd85adc0136c26b79aa4dad2793062739c60b4d Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sat, 2 Nov 2024 20:52:13 -0500 Subject: [PATCH 58/75] Added 1 test --- sumpy/recurrence.py | 5 +-- test/playground.ipynb | 0 test/test_recurrence.py | 92 +++++++++----------------------------- test/test_recurrenceqbx.py | 84 ++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 75 deletions(-) create mode 100644 test/playground.ipynb diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index e8a9c0fbf..6468a794a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -399,7 +399,4 @@ def get_processed_and_shifted_recurrence(pde) -> tuple[int, int, order, r_p = process_recurrence_relation(r) n_initial = __get_initial_c(r_p) r_s = shift_recurrence(r_p) - return n_initial, order, r_s - - -print(_generate_nd_derivative_relations(_make_sympy_vec("x", 2), 3)) \ No newline at end of file + return n_initial, order, r_s \ No newline at end of file diff --git a/test/playground.ipynb b/test/playground.ipynb new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_recurrence.py b/test/test_recurrence.py index bff616941..270314de1 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -1,84 +1,34 @@ +from sumpy.recurrence import get_processed_and_shifted_recurrence, _make_sympy_vec +import sympy as sp +import numpy as np + from sumpy.expansion.diff_op import ( laplacian, make_identity_diff_op, ) -from sumpy.recurrenceqbx import recurrence_qbx_lp, _make_sympy_vec - -import numpy as np -from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 -from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion - - -actx_factory = _acf -expn_class = LineTaylorLocalExpansion - -actx = actx_factory() - -from sumpy.kernel import LaplaceKernel -lknl = LaplaceKernel(2) - -from sumpy.qbx import LayerPotential - - -def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): - lpot = LayerPotential(actx.context, - expansion=expn_class(lknl, order), - target_kernels=(lknl,), - source_kernels=(lknl,)) - - #print(lpot.get_kernel()) - expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) - sources = actx.from_numpy(sources) - targets = actx.from_numpy(targets) - centers = actx.from_numpy(centers) - strengths = (strengths,) - - _evt, (result_qbx,) = lpot( - actx.queue, - targets, sources, centers, strengths, - expansion_radii=expansion_radii) - result_qbx = actx.to_numpy(result_qbx) - - return result_qbx - -def create_ellipse(n_p): - h = 9.688 / n_p - radius = 7*h - t = np.linspace(0, 2 * np.pi, n_p, endpoint=False) - - unit_circle_param = np.exp(1j * t) - unit_circle = np.array([2 * unit_circle_param.real, unit_circle_param.imag]) - - sources = unit_circle - normals = np.array([unit_circle_param.real, 2*unit_circle_param.imag]) - normals = normals / np.linalg.norm(normals, axis=0) - centers = sources - normals * radius - - mode_nr = 25 - density = np.cos(mode_nr * t) - - return sources, centers, normals, density, h, radius - -def test_recurrence_laplace_2d_ellipse(): - - #------------- 1. Define PDE, Green's Function +def test_laplace_2D(): w = make_identity_diff_op(2) laplace2d = laplacian(w) + _,_, r = get_processed_and_shifted_recurrence(laplace2d) + + n = sp.symbols("n") + s = sp.Function("s") var = _make_sympy_vec("x", 2) var_t = _make_sympy_vec("t", 2) - g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) + g_x_y = sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) + derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) for i in range(6)] + + check_2_s = r.subs(n, 2).subs(s(1), derivs[1]) - derivs[2] + check_3_s = r.subs(n, 3).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] + check_4_s = r.subs(n, 4).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] + check_5_s = r.subs(n, 5).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] - p = 4 - err = [] - for n_p in range(200, 1001, 200): - sources, centers, normals, density, h, radius = create_ellipse(n_p) - strengths = h * density - exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) - qbx_res = qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) - #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) - err.append(np.max(exp_res - qbx_res)) + assert abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-15 + assert abs(check_3_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-14 + assert abs(check_4_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-12 + assert abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-12 - print(err) +test_laplace_2D() \ No newline at end of file diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index e69de29bb..bff616941 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -0,0 +1,84 @@ +from sumpy.expansion.diff_op import ( + laplacian, + make_identity_diff_op, +) +from sumpy.recurrenceqbx import recurrence_qbx_lp, _make_sympy_vec + +import numpy as np +from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 +from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion + + +actx_factory = _acf +expn_class = LineTaylorLocalExpansion + +actx = actx_factory() + +from sumpy.kernel import LaplaceKernel +lknl = LaplaceKernel(2) + +from sumpy.qbx import LayerPotential + + +def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): + lpot = LayerPotential(actx.context, + expansion=expn_class(lknl, order), + target_kernels=(lknl,), + source_kernels=(lknl,)) + + #print(lpot.get_kernel()) + expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) + sources = actx.from_numpy(sources) + targets = actx.from_numpy(targets) + centers = actx.from_numpy(centers) + + strengths = (strengths,) + + _evt, (result_qbx,) = lpot( + actx.queue, + targets, sources, centers, strengths, + expansion_radii=expansion_radii) + result_qbx = actx.to_numpy(result_qbx) + + return result_qbx + +def create_ellipse(n_p): + h = 9.688 / n_p + radius = 7*h + t = np.linspace(0, 2 * np.pi, n_p, endpoint=False) + + unit_circle_param = np.exp(1j * t) + unit_circle = np.array([2 * unit_circle_param.real, unit_circle_param.imag]) + + sources = unit_circle + normals = np.array([unit_circle_param.real, 2*unit_circle_param.imag]) + normals = normals / np.linalg.norm(normals, axis=0) + centers = sources - normals * radius + + mode_nr = 25 + density = np.cos(mode_nr * t) + + return sources, centers, normals, density, h, radius + +def test_recurrence_laplace_2d_ellipse(): + + #------------- 1. Define PDE, Green's Function + w = make_identity_diff_op(2) + laplace2d = laplacian(w) + + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) + + p = 4 + err = [] + for n_p in range(200, 1001, 200): + sources, centers, normals, density, h, radius = create_ellipse(n_p) + strengths = h * density + exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) + qbx_res = qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) + #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) + err.append(np.max(exp_res - qbx_res)) + + print(err) + From 8beb851c2274926cce8cdb0836b5338249ec0eb3 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sat, 2 Nov 2024 20:52:37 -0500 Subject: [PATCH 59/75] Update playground.ipynb --- test/playground.ipynb | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/playground.ipynb b/test/playground.ipynb index e69de29bb..8b7fa717d 100644 --- a/test/playground.ipynb +++ b/test/playground.ipynb @@ -0,0 +1,47 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sympy as sp\n", + "import numpy as np\n", + "\n", + "from sumpy.expansion.diff_op import (\n", + " laplacian,\n", + " make_identity_diff_op,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 47844754c9ab6a92ef036f32822219510c7861fd Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sat, 2 Nov 2024 23:03:26 -0500 Subject: [PATCH 60/75] Added helmholtz3d test --- test/test_recurrence.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 270314de1..1001d61b0 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -7,6 +7,34 @@ make_identity_diff_op, ) + + +def test_helmholtz_3D(): + w = make_identity_diff_op(3) + helmholtz3d = laplacian(w) + w + _,_, r = get_processed_and_shifted_recurrence(helmholtz3d) + + n = sp.symbols("n") + s = sp.Function("s") + + var = _make_sympy_vec("x", 3) + var_t = _make_sympy_vec("t", 3) + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2 + (var[2]-var_t[2])**2) + g_x_y = sp.exp(1j * abs_dist) / abs_dist + derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) for i in range(6)] + + + check_2_s = r.subs(n, 2).subs(s(1), derivs[1]).subs(s(0), derivs[0]) - derivs[2] + assert abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand())) <= 1e-12 + + +test_helmholtz_3D() + + + + + + def test_laplace_2D(): w = make_identity_diff_op(2) laplace2d = laplacian(w) @@ -31,4 +59,3 @@ def test_laplace_2D(): assert abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-12 -test_laplace_2D() \ No newline at end of file From c7f7be75b5a0c306d1ebd44666b8fb63ac1097cb Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 3 Nov 2024 13:16:12 -0600 Subject: [PATCH 61/75] Laplace3D test --- test/test_recurrence.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 1001d61b0..048890b36 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -7,12 +7,35 @@ make_identity_diff_op, ) +def test_laplace_3D(): + w = make_identity_diff_op(3) + laplace3d = laplacian(w) + _, _, r = get_processed_and_shifted_recurrence(laplace3d) + n = sp.symbols("n") + s = sp.Function("s") + + var = _make_sympy_vec("x", 3) + var_t = _make_sympy_vec("t", 3) + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2 + (var[2]-var_t[2])**2) + g_x_y = 1/abs_dist + derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) for i in range(6)] + + check_2_s = r.subs(n, 2).subs(s(0), derivs[0]).subs(s(1), derivs[1]) - derivs[2] + check_3_s = r.subs(n, 3).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] + check_4_s = r.subs(n, 4).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] + check_5_s = r.subs(n, 5).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] + + assert abs(abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-15 + assert abs(abs(check_3_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-14 + assert abs(abs(check_4_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 + #print(abs(abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand())))) + assert abs(abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 def test_helmholtz_3D(): w = make_identity_diff_op(3) helmholtz3d = laplacian(w) + w - _,_, r = get_processed_and_shifted_recurrence(helmholtz3d) + _, _, r = get_processed_and_shifted_recurrence(helmholtz3d) n = sp.symbols("n") s = sp.Function("s") @@ -24,11 +47,18 @@ def test_helmholtz_3D(): derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) for i in range(6)] - check_2_s = r.subs(n, 2).subs(s(1), derivs[1]).subs(s(0), derivs[0]) - derivs[2] - assert abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand())) <= 1e-12 + check_2_s = r.subs(n, 2).subs(s(0), derivs[0]).subs(s(1), derivs[1]) - derivs[2] + check_3_s = r.subs(n, 3).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] + check_4_s = r.subs(n, 4).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] + check_5_s = r.subs(n, 5).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] + + assert abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand())) <= 1e-15 + assert abs(abs(check_3_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-14 + assert abs(abs(check_4_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 + assert abs(abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 -test_helmholtz_3D() + From 3bbd2f3a126482adddd8e842da218e0e121212df Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 3 Nov 2024 17:58:28 -0600 Subject: [PATCH 62/75] Check Helmholtz2D --- test/playground.ipynb | 112 +++++++++++++++++++++++++++++++++++++++- test/test_recurrence.py | 26 +++++++++- 2 files changed, 134 insertions(+), 4 deletions(-) diff --git a/test/playground.ipynb b/test/playground.ipynb index 8b7fa717d..6fffa849b 100644 --- a/test/playground.ipynb +++ b/test/playground.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -12,7 +12,115 @@ "from sumpy.expansion.diff_op import (\n", " laplacian,\n", " make_identity_diff_op,\n", - ")\n" + ")\n", + "\n", + "from sumpy.recurrence import _make_sympy_vec\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "#Create Hankel Function\n", + "\n", + "from sympy import hankel1\n", + "z = sp.symbols(\"z\")\n", + "f = hankel1(0, z)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{H^{(1)}_{-1}\\left(z\\right)}{2} - \\frac{H^{(1)}_{1}\\left(z\\right)}{2}$" + ], + "text/plain": [ + "hankel1(-1, z)/2 - hankel1(1, z)/2" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f.diff(z)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "var = _make_sympy_vec(\"x\", 2)\n", + "var_t = _make_sympy_vec(\"t\", 2)\n", + "k = 1\n", + "abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)\n", + "g_x_y = (1j/4) * hankel1(0, k * abs_dist)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 0.25 i H^{(1)}_{0}\\left(\\sqrt{\\left(- t_{0} + x_{0}\\right)^{2} + \\left(- t_{1} + x_{1}\\right)^{2}}\\right)$" + ], + "text/plain": [ + "0.25*I*hankel1(0, sqrt((-t0 + x0)**2 + (-t1 + x1)**2))" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g_x_y" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) for i in range(6)]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.25*I*hankel1(0, sqrt(x0**2 + x1**2)),\n", + " -0.25*I*x0*(hankel1(-1, sqrt(x0**2 + x1**2))/2 - hankel1(1, sqrt(x0**2 + x1**2))/2)/sqrt(x0**2 + x1**2),\n", + " 0.0625*I*(x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2)),\n", + " 0.03125*I*x0*(4*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - (x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2)),\n", + " -0.25*I*(-9*x0**4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(4*(x0**2 + x1**2)**3) + 15*x0**4*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(2*(x0**2 + x1**2)**(7/2)) + 9*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(4*(x0**2 + x1**2)**2) + x0**2*(4*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 4*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 12*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + (-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - (-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 12*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/(16*sqrt(x0**2 + x1**2)) + 3*x0**2*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(8*(x0**2 + x1**2)**(3/2)) - 9*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 3*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(8*sqrt(x0**2 + x1**2)) + 3*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(2*(x0**2 + x1**2)**(3/2))),\n", + " 0.0078125*I*x0*(480*x0**4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**4 - 1680*x0**4*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(9/2) - 576*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**3 - 8*x0**2*(4*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 4*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 12*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + (-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - (-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 12*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/(x0**2 + x1**2)**(3/2) - 72*x0**2*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(5/2) + 2400*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(7/2) + 96*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 8*(4*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 4*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 12*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + (-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - (-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 12*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) - (36*x0**4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**3 - 36*x0**4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**3 - 120*x0**4*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(7/2) + 120*x0**4*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(7/2) - 36*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 36*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + x0**2*(-4*x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 4*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 12*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 12*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 4*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - (-x0**2*(hankel1(-5, sqrt(x0**2 + x1**2)) - 2*hankel1(-3, sqrt(x0**2 + x1**2)) + hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - hankel1(-2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-4, sqrt(x0**2 + x1**2)) - hankel1(-2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + (-x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - 12*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 12*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) - x0**2*(-4*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 4*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 12*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 12*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 4*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - (-x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + (-x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - 2*hankel1(3, sqrt(x0**2 + x1**2)) + hankel1(5, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(2, sqrt(x0**2 + x1**2)) - hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(2, sqrt(x0**2 + x1**2)) - hankel1(4, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - 12*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 12*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) + 6*x0**2*(-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(3/2) - 6*x0**2*(-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(3/2) + 144*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 144*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 6*(-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 6*(-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - 24*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 24*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) + 72*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(3/2) - 720*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2))]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "derivs" ] }, { diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 048890b36..41051b27d 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -1,7 +1,7 @@ from sumpy.recurrence import get_processed_and_shifted_recurrence, _make_sympy_vec import sympy as sp import numpy as np - +from sympy import hankel1 from sumpy.expansion.diff_op import ( laplacian, make_identity_diff_op, @@ -59,11 +59,33 @@ def test_helmholtz_3D(): +def test_helmholtz_2D(): + w = make_identity_diff_op(2) + laplace2d = laplacian(w) + w + _,_, r = get_processed_and_shifted_recurrence(laplace2d) + n = sp.symbols("n") + s = sp.Function("s") + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + k = 1 + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2) + g_x_y = (1j/4) * hankel1(0, k * abs_dist) + x_coord = np.random.rand() + y_coord = np.random.rand() + derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) for i in range(6)] + derivs = [derivs[i].subs(var[0], x_coord).subs(var[1], y_coord).evalf() for i in range(6)] + check_2_s = r.subs(n, 2).subs(s(0), derivs[0]).subs(s(1), derivs[1]) - derivs[2] + check_3_s = r.subs(n, 3).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] + check_4_s = r.subs(n, 4).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] + check_5_s = r.subs(n, 5).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] - + assert abs(check_2_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 + assert abs(check_3_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 + assert abs(check_4_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 + assert abs(check_5_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 def test_laplace_2D(): w = make_identity_diff_op(2) From a33bd51af49ded958c05f51e5994b6f7ee283568 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 3 Nov 2024 18:05:19 -0600 Subject: [PATCH 63/75] Added helmholtz2D test --- test/test_recurrenceqbx.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index bff616941..5fdeeebe9 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -1,26 +1,22 @@ +import numpy as np + from sumpy.expansion.diff_op import ( laplacian, make_identity_diff_op, ) from sumpy.recurrenceqbx import recurrence_qbx_lp, _make_sympy_vec - -import numpy as np from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion - +from sumpy.kernel import LaplaceKernel +from sumpy.qbx import LayerPotential actx_factory = _acf expn_class = LineTaylorLocalExpansion actx = actx_factory() - -from sumpy.kernel import LaplaceKernel lknl = LaplaceKernel(2) -from sumpy.qbx import LayerPotential - - -def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): +def _qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): lpot = LayerPotential(actx.context, expansion=expn_class(lknl, order), target_kernels=(lknl,), @@ -42,7 +38,7 @@ def qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): return result_qbx -def create_ellipse(n_p): +def _create_ellipse(n_p): h = 9.688 / n_p radius = 7*h t = np.linspace(0, 2 * np.pi, n_p, endpoint=False) @@ -73,10 +69,10 @@ def test_recurrence_laplace_2d_ellipse(): p = 4 err = [] for n_p in range(200, 1001, 200): - sources, centers, normals, density, h, radius = create_ellipse(n_p) + sources, centers, normals, density, h, radius = _create_ellipse(n_p) strengths = h * density exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) - qbx_res = qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) + qbx_res = _qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) err.append(np.max(exp_res - qbx_res)) From 310df005801775f3a1e2a080049b9743b9f61346 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 3 Nov 2024 18:30:24 -0600 Subject: [PATCH 64/75] Make recurrenceqbx general --- sumpy/recurrenceqbx.py | 29 +++++++++++++++++------------ test/test_recurrenceqbx.py | 9 +++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/sumpy/recurrenceqbx.py b/sumpy/recurrenceqbx.py index b620c6fb2..a316f8a7e 100644 --- a/sumpy/recurrenceqbx.py +++ b/sumpy/recurrenceqbx.py @@ -7,12 +7,14 @@ """ import numpy as np import sympy as sp +import math from sumpy.recurrence import ( _make_sympy_vec, get_processed_and_shifted_recurrence) + # ================ Transform/Rotate ================= -def __produce_orthogonal_basis(normals): +def _produce_orthogonal_basis(normals): ndim, ncenters = normals.shape orth_coordsys = [normals] for i in range(1, ndim): @@ -26,10 +28,10 @@ def __produce_orthogonal_basis(normals): return orth_coordsys -def __compute_rotated_shifted_coordinates(sources, centers, normals): +def _compute_rotated_shifted_coordinates(sources, centers, normals): cts = sources[:, None] - centers[:, :, None] - orth_coordsys = __produce_orthogonal_basis(normals) + orth_coordsys = _produce_orthogonal_basis(normals) cts_rotated_shifted = np.einsum("idc,dcs->ics", orth_coordsys, cts) return cts_rotated_shifted @@ -37,7 +39,7 @@ def __compute_rotated_shifted_coordinates(sources, centers, normals): # ================ Recurrence LP Eval ================= def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, - p) -> np.ndarray: + ndim, p) -> np.ndarray: r""" A function that computes a single-layer potential using a recurrence. @@ -50,16 +52,17 @@ def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, :arg pde: pde that we are computing layer potential for :arg g_x_y: a green's function in (x0, x1, ...) source and (t0, t1, ...) target + :arg ndim: number of spatial variables :arg p: order of expansion computed """ #------------- 2. Compute rotated/shifted coordinates - cts_r_s = __compute_rotated_shifted_coordinates(sources, centers, normals) + cts_r_s = _compute_rotated_shifted_coordinates(sources, centers, normals) #------------- 4. Define input variables for green's function expression - var = _make_sympy_vec("x", 2) - var_t = _make_sympy_vec("t", 2) + var = _make_sympy_vec("x", ndim) + var_t = _make_sympy_vec("t", ndim) #------------ 5. Compute recurrence n_initial, order, recurrence = get_processed_and_shifted_recurrence(pde) @@ -80,21 +83,23 @@ def generate_lamb_expr(i, n_initial): arg_list.append(r) if i < n_initial: - lamb_expr = sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) + lamb_expr = sp.diff(g_x_y, var_t[0], i) + for j in range(ndim): + lamb_expr = lamb_expr.subs(var_t[j], 0) else: lamb_expr = recurrence.subs(n, i) return sp.lambdify(arg_list, lamb_expr) - interactions_2d = 0 + interactions = 0 for i in range(p+1): lamb_expr = generate_lamb_expr(i, n_initial) - a = storage[-4:] + [cts_r_s[0],cts_r_s[1],radius] + a = storage + [cts_r_s[0],cts_r_s[1],radius] s_new = lamb_expr(*a) - interactions_2d += s_new * radius**i/math.factorial(i) + interactions += s_new * radius**i/math.factorial(i) storage.pop(0) storage.append(s_new) - exp_res = (interactions_2d * strengths[None, :]).sum(axis=1) + exp_res = (interactions * strengths[None, :]).sum(axis=1) return exp_res \ No newline at end of file diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index 5fdeeebe9..4d219cb86 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -1,4 +1,5 @@ import numpy as np +import sympy as sp from sumpy.expansion.diff_op import ( laplacian, @@ -71,10 +72,10 @@ def test_recurrence_laplace_2d_ellipse(): for n_p in range(200, 1001, 200): sources, centers, normals, density, h, radius = _create_ellipse(n_p) strengths = h * density - exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, p) + exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, 2, p) qbx_res = _qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) - err.append(np.max(exp_res - qbx_res)) - - print(err) + err.append(np.max(np.abs(exp_res - qbx_res))) + assert np.max(err) <= 1e-13 +test_recurrence_laplace_2d_ellipse() From e08fd824cb46408230d3ca7dc0ddf94eb8ddf5d2 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 3 Nov 2024 18:52:06 -0600 Subject: [PATCH 65/75] Helmholtz not checked --- test/test_recurrence.py | 4 +-- test/test_recurrenceqbx.py | 54 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 41051b27d..1914ab549 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -61,8 +61,8 @@ def test_helmholtz_3D(): def test_helmholtz_2D(): w = make_identity_diff_op(2) - laplace2d = laplacian(w) + w - _,_, r = get_processed_and_shifted_recurrence(laplace2d) + helmholtz2d = laplacian(w) + w + _,_, r = get_processed_and_shifted_recurrence(helmholtz2d) n = sp.symbols("n") s = sp.Function("s") diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index 4d219cb86..0e19faf3c 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -1,5 +1,6 @@ import numpy as np import sympy as sp +from sympy import hankel1 from sumpy.expansion.diff_op import ( laplacian, @@ -8,7 +9,7 @@ from sumpy.recurrenceqbx import recurrence_qbx_lp, _make_sympy_vec from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion -from sumpy.kernel import LaplaceKernel +from sumpy.kernel import LaplaceKernel, HelmholtzKernel from sumpy.qbx import LayerPotential actx_factory = _acf @@ -16,6 +17,31 @@ actx = actx_factory() lknl = LaplaceKernel(2) +hlknl = HelmholtzKernel(2, "k") + +def _qbx_lp_helmholtz_general(sources,targets,centers,radius,strengths,order): + lpot = LayerPotential(actx.context, + expansion=expn_class(hlknl, order), + target_kernels=(hlknl,), + source_kernels=(hlknl,)) + + #print(lpot.get_kernel()) + expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) + sources = actx.from_numpy(sources) + targets = actx.from_numpy(targets) + centers = actx.from_numpy(centers) + + strengths = (strengths,) + extra_kernel_kwargs={"k": 1} + _evt, (result_qbx,) = lpot( + actx.queue, + targets, sources, centers, strengths, + expansion_radii=expansion_radii, + kwargs=extra_kernel_kwargs) + result_qbx = actx.to_numpy(result_qbx) + + return result_qbx + def _qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): lpot = LayerPotential(actx.context, @@ -78,4 +104,28 @@ def test_recurrence_laplace_2d_ellipse(): err.append(np.max(np.abs(exp_res - qbx_res))) assert np.max(err) <= 1e-13 -test_recurrence_laplace_2d_ellipse() + +def test_recurrence_helmholtz_2d_ellipse(): + + #------------- 1. Define PDE, Green's Function + w = make_identity_diff_op(2) + helmholtz2d = laplacian(w) + w + + var = _make_sympy_vec("x", 2) + var_t = _make_sympy_vec("t", 2) + k = 1 + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2) + g_x_y = (1j/4) * hankel1(0, k * abs_dist) + + p = 4 + err = [] + for n_p in range(200, 1001, 200): + sources, centers, normals, density, h, radius = _create_ellipse(n_p) + strengths = h * density + exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, helmholtz2d, g_x_y, 2, p) + #qbx_res = _qbx_lp_helmholtz_general(sources, sources, centers, radius, strengths, p) + #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) + #err.append(np.max(np.abs(exp_res - qbx_res))) + #assert np.max(err) <= 1e-13 + +test_recurrence_helmholtz_2d_ellipse() From dcd96e76052ea1ebd65777b8d62723b1658b2fef Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Sun, 3 Nov 2024 19:39:14 -0600 Subject: [PATCH 66/75] Ruff formatting --- sumpy/recurrence.py | 21 ++++++++---------- sumpy/recurrenceqbx.py | 48 ++++++++++++++++++++++++------------------ 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index 6468a794a..e77ad901a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -61,16 +61,14 @@ """ import math -from random import randrange import numpy as np import sympy as sp from pytools.obj_array import make_obj_array - from sumpy.expansion.diff_op import ( DerivativeIdentifier, - LinearPDESystemOperator + LinearPDESystemOperator, ) @@ -188,7 +186,7 @@ def ode_in_r_to_x(ode_in_r: sp.Expr, var: np.ndarray, ODECoefficients = list[list[sp.Expr]] -def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: +def ode_in_x_to_coeff_array(poly: sp.Poly, ode_order: int, var: np.ndarray) -> ODECoefficients: r""" Organizes the coefficients of an ODE in the :math:`x_0` variable into a @@ -310,15 +308,15 @@ def process_recurrence_relation(r: sp.Expr) -> tuple[int, sp.Expr]: # Re-arrange the recurrence relation so we get s(n) = ____ # in terms of s(n-1), ... - true_recurrence = sum([coeffs[i]/coeffs[-1] * terms[i] - for i in range(0, len(terms)-1)]) + true_recurrence = sum(coeffs[i]/coeffs[-1] * terms[i] + for i in range(0, len(terms)-1)) true_recurrence1 = true_recurrence.subs(n, n-shift_idx) return order, true_recurrence1 def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, - np.ndarray]: + np.ndarray]: r""" Given a recurrence extracts the variables in the recurrence as well as the indexes in sorted order. @@ -328,7 +326,6 @@ def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, terms = list(r.atoms(sp.Function)) terms = np.array(terms) - idx_l = [] for i in range(len(terms)): tms = list(terms[i].atoms(sp.Number)) @@ -336,7 +333,7 @@ def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, idx_l.append(tms[0]) else: idx_l.append(0) - idx_l = np.array(idx_l, dtype='int') + idx_l = np.array(idx_l, dtype="int") idx_sort = idx_l.argsort() idx_l = idx_l[idx_sort] terms = terms[idx_sort] @@ -380,7 +377,7 @@ def shift_recurrence(r: sp.Expr) -> sp.Expr: r_ret = r - n = sp.symbols('n') + n = sp.symbols("n") for i in range(len(idx_l)): r_ret = r_ret.subs(terms[i], (-1)**(n+idx_l[i])*terms[i]) @@ -388,7 +385,7 @@ def shift_recurrence(r: sp.Expr) -> sp.Expr: def get_processed_and_shifted_recurrence(pde) -> tuple[int, int, - sp.Expr]: + sp.Expr]: r""" A function that "shifts" the recurrence so the expansion center is placed at the origin and source is the input for the recurrence generated. @@ -399,4 +396,4 @@ def get_processed_and_shifted_recurrence(pde) -> tuple[int, int, order, r_p = process_recurrence_relation(r) n_initial = __get_initial_c(r_p) r_s = shift_recurrence(r_p) - return n_initial, order, r_s \ No newline at end of file + return n_initial, order, r_s diff --git a/sumpy/recurrenceqbx.py b/sumpy/recurrenceqbx.py index a316f8a7e..083a28fbf 100644 --- a/sumpy/recurrenceqbx.py +++ b/sumpy/recurrenceqbx.py @@ -1,16 +1,21 @@ r""" With the functionality in this module, we aim to compute layer potentials -using a recurrence for one-dimensional derivatives of the corresponding +using a recurrence for one-dimensional derivatives of the corresponding Green's function. See recurrence.py. .. autofunction:: recurrence_qbx_lp """ +from __future__ import annotations # noqa: I001 + +import math + import numpy as np import sympy as sp -import math + from sumpy.recurrence import ( - _make_sympy_vec, - get_processed_and_shifted_recurrence) + _make_sympy_vec, + get_processed_and_shifted_recurrence +) # ================ Transform/Rotate ================= @@ -18,10 +23,11 @@ def _produce_orthogonal_basis(normals): ndim, ncenters = normals.shape orth_coordsys = [normals] for i in range(1, ndim): - v = np.random.rand(ndim, ncenters) + v = np.random.rand(ndim, ncenters) # noqa: NPY002 v = v/np.linalg.norm(v, 2, axis=0) for j in range(i): - v = v - np.einsum("dc,dc->c", v, orth_coordsys[j]) * orth_coordsys[j] + v = v - np.einsum("dc,dc->c", v, + orth_coordsys[j]) * orth_coordsys[j] v = v/np.linalg.norm(v, 2, axis=0) orth_coordsys.append(v) @@ -39,61 +45,61 @@ def _compute_rotated_shifted_coordinates(sources, centers, normals): # ================ Recurrence LP Eval ================= def recurrence_qbx_lp(sources, centers, normals, strengths, radius, pde, g_x_y, - ndim, p) -> np.ndarray: + ndim, p) -> np.ndarray: r""" A function that computes a single-layer potential using a recurrence. :arg sources: a (ndim, nsources) array of source locations :arg centers: a (ndim, ncenters) array of center locations :arg normals: a (ndim, ncenters) array of normals - :arg strengths: array corresponding to quadrature weight multiplied by + :arg strengths: array corresponding to quadrature weight multiplied by density :arg radius: expansion radius :arg pde: pde that we are computing layer potential for - :arg g_x_y: a green's function in (x0, x1, ...) source and + :arg g_x_y: a green's function in (x0, x1, ...) source and (t0, t1, ...) target :arg ndim: number of spatial variables :arg p: order of expansion computed """ - #------------- 2. Compute rotated/shifted coordinates + # ------------- 2. Compute rotated/shifted coordinates cts_r_s = _compute_rotated_shifted_coordinates(sources, centers, normals) - - #------------- 4. Define input variables for green's function expression + # ------------- 4. Define input variables for green's function expression var = _make_sympy_vec("x", ndim) var_t = _make_sympy_vec("t", ndim) - #------------ 5. Compute recurrence + # ------------ 5. Compute recurrence n_initial, order, recurrence = get_processed_and_shifted_recurrence(pde) - #------------ 6. Set order p = 5 + # ------------ 6. Set order p = 5 n_p = sources.shape[1] - storage = [np.zeros((n_p,n_p))] * order + storage = [np.zeros((n_p, n_p))] * order s = sp.Function("s") - r,n = sp.symbols("r,n") + r, n = sp.symbols("r,n") def generate_lamb_expr(i, n_initial): arg_list = [] - for j in range(order,0,-1): + for j in range(order, 0, -1): + # pylint: disable-next=not-callable arg_list.append(s(i-j)) arg_list.append(var[0]) arg_list.append(var[1]) arg_list.append(r) - + if i < n_initial: lamb_expr = sp.diff(g_x_y, var_t[0], i) for j in range(ndim): lamb_expr = lamb_expr.subs(var_t[j], 0) else: lamb_expr = recurrence.subs(n, i) - return sp.lambdify(arg_list, lamb_expr) + return sp.lambdify(arg_list, lamb_expr) interactions = 0 for i in range(p+1): lamb_expr = generate_lamb_expr(i, n_initial) - a = storage + [cts_r_s[0],cts_r_s[1],radius] + a = [*storage, cts_r_s[0], cts_r_s[1], radius] s_new = lamb_expr(*a) interactions += s_new * radius**i/math.factorial(i) @@ -102,4 +108,4 @@ def generate_lamb_expr(i, n_initial): exp_res = (interactions * strengths[None, :]).sum(axis=1) - return exp_res \ No newline at end of file + return exp_res From f79b6d9220008748af552d902d8ea232576ac66f Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 12:10:26 -0600 Subject: [PATCH 67/75] Formatting --- test/test_recurrence.py | 194 ++++++++++++++++++++++++------------- test/test_recurrenceqbx.py | 128 +++++++++++++----------- 2 files changed, 202 insertions(+), 120 deletions(-) diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 1914ab549..5331604e2 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -1,13 +1,28 @@ -from sumpy.recurrence import get_processed_and_shifted_recurrence, _make_sympy_vec -import sympy as sp +r""" +With the functionality in this module, we aim to test recurrence +code. + +.. autofunction:: test_laplace3d +.. autofunction:: test_helmholtz3d +.. autofunction:: test_laplace2d +""" +from __future__ import annotations + import numpy as np -from sympy import hankel1 +import sympy as sp + +# from sympy import hankel1 from sumpy.expansion.diff_op import ( laplacian, make_identity_diff_op, ) +from sumpy.recurrence import _make_sympy_vec, get_processed_and_shifted_recurrence + -def test_laplace_3D(): +def test_laplace3d(): + r""" + Tests recurrence code for orders up to 6 laplace3d. + """ w = make_identity_diff_op(3) laplace3d = laplacian(w) _, _, r = get_processed_and_shifted_recurrence(laplace3d) @@ -16,23 +31,41 @@ def test_laplace_3D(): var = _make_sympy_vec("x", 3) var_t = _make_sympy_vec("t", 3) - abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2 + (var[2]-var_t[2])**2) + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + + (var[1]-var_t[1])**2 + (var[2]-var_t[2])**2) g_x_y = 1/abs_dist - derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) for i in range(6)] - - check_2_s = r.subs(n, 2).subs(s(0), derivs[0]).subs(s(1), derivs[1]) - derivs[2] - check_3_s = r.subs(n, 3).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] - check_4_s = r.subs(n, 4).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] - check_5_s = r.subs(n, 5).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] - - assert abs(abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-15 - assert abs(abs(check_3_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-14 - assert abs(abs(check_4_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 - #print(abs(abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand())))) - assert abs(abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 - - -def test_helmholtz_3D(): + derivs = [sp.diff(g_x_y, + var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) + for i in range(6)] + + # pylint: disable-next=not-callable + subs_dict = {s(0): derivs[0], s(1): derivs[1]} + check_2_s = r.subs(n, 2).subs(subs_dict) - derivs[2] + # pylint: disable-next=not-callable + subs_dict[s(2)] = derivs[2] + check_3_s = r.subs(n, 3).subs(subs_dict) - derivs[3] + # pylint: disable-next=not-callable + subs_dict[s(3)] = derivs[3] + check_4_s = r.subs(n, 4).subs(subs_dict) - derivs[4] + # pylint: disable-next=not-callable + subs_dict[s(4)] = derivs[4] + check_5_s = r.subs(n, 5).subs(subs_dict) - derivs[5] + + x_coord = np.random.rand() # noqa: NPY002 + y_coord = np.random.rand() # noqa: NPY002 + z_coord = np.random.rand() # noqa: NPY002 + coord_dict = {var[0]: x_coord, var[1]: y_coord, var[2]: z_coord} + + assert abs(check_2_s.subs(coord_dict)) <= 1e-15 + assert abs(check_3_s.subs(coord_dict)) <= 1e-14 + assert abs(check_4_s.subs(coord_dict)) <= 1e-12 + assert abs(check_5_s.subs(coord_dict)) <= 1e-12 + + +def test_helmholtz3d(): + r""" + Tests recurrence code for orders up to 6 helmholtz3d. + """ w = make_identity_diff_op(3) helmholtz3d = laplacian(w) + w _, _, r = get_processed_and_shifted_recurrence(helmholtz3d) @@ -42,27 +75,43 @@ def test_helmholtz_3D(): var = _make_sympy_vec("x", 3) var_t = _make_sympy_vec("t", 3) - abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2 + (var[2]-var_t[2])**2) + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + + (var[1]-var_t[1])**2 + (var[2]-var_t[2])**2) g_x_y = sp.exp(1j * abs_dist) / abs_dist - derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) for i in range(6)] - - - check_2_s = r.subs(n, 2).subs(s(0), derivs[0]).subs(s(1), derivs[1]) - derivs[2] - check_3_s = r.subs(n, 3).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] - check_4_s = r.subs(n, 4).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] - check_5_s = r.subs(n, 5).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] - - assert abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand())) <= 1e-15 - assert abs(abs(check_3_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-14 - assert abs(abs(check_4_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 - assert abs(abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand()).subs(var[2], np.random.rand()))) <= 1e-12 - - - -def test_helmholtz_2D(): + derivs = [sp.diff(g_x_y, + var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0).subs(var_t[2], 0) + for i in range(6)] + + # pylint: disable-next=not-callable + subs_dict = {s(0): derivs[0], s(1): derivs[1]} + check_2_s = r.subs(n, 2).subs(subs_dict) - derivs[2] + # pylint: disable-next=not-callable + subs_dict[s(2)] = derivs[2] + check_3_s = r.subs(n, 3).subs(subs_dict) - derivs[3] + # pylint: disable-next=not-callable + subs_dict[s(3)] = derivs[3] + check_4_s = r.subs(n, 4).subs(subs_dict) - derivs[4] + # pylint: disable-next=not-callable + subs_dict[s(4)] = derivs[4] + check_5_s = r.subs(n, 5).subs(subs_dict) - derivs[5] + + x_coord = np.random.rand() # noqa: NPY002 + y_coord = np.random.rand() # noqa: NPY002 + z_coord = np.random.rand() # noqa: NPY002 + coord_dict = {var[0]: x_coord, var[1]: y_coord, var[2]: z_coord} + + assert abs(abs(check_2_s.subs(coord_dict))) <= 1e-15 + assert abs(abs(check_3_s.subs(coord_dict))) <= 1e-14 + assert abs(abs(check_4_s.subs(coord_dict))) <= 1e-12 + assert abs(abs(check_5_s.subs(coord_dict))) <= 1e-12 + + +def test_helmholtz2d(): + r""" + Tests recurrence code for orders up to 6 helmholtz2d. w = make_identity_diff_op(2) helmholtz2d = laplacian(w) + w - _,_, r = get_processed_and_shifted_recurrence(helmholtz2d) + _, _, r = get_processed_and_shifted_recurrence(helmholtz2d) n = sp.symbols("n") s = sp.Function("s") @@ -74,23 +123,21 @@ def test_helmholtz_2D(): g_x_y = (1j/4) * hankel1(0, k * abs_dist) x_coord = np.random.rand() y_coord = np.random.rand() - derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) for i in range(6)] - derivs = [derivs[i].subs(var[0], x_coord).subs(var[1], y_coord).evalf() for i in range(6)] - - check_2_s = r.subs(n, 2).subs(s(0), derivs[0]).subs(s(1), derivs[1]) - derivs[2] - check_3_s = r.subs(n, 3).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] - check_4_s = r.subs(n, 4).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] - check_5_s = r.subs(n, 5).subs(s(0), derivs[0]).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] - - assert abs(check_2_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 - assert abs(check_3_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 - assert abs(check_4_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 - assert abs(check_5_s.subs(var[0],x_coord).subs(var[1],y_coord)) <= 1e-12 - -def test_laplace_2D(): + derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) + for i in range(6)] + derivs = [derivs[i].subs(var[0], x_coord).subs(var[1], y_coord).evalf() + for i in range(6)] + """ + print("HELLO!") + + +def test_laplace2d(): + r""" + Tests recurrence code for orders up to 6 laplace2d. + """ w = make_identity_diff_op(2) laplace2d = laplacian(w) - _,_, r = get_processed_and_shifted_recurrence(laplace2d) + _, _, r = get_processed_and_shifted_recurrence(laplace2d) n = sp.symbols("n") s = sp.Function("s") @@ -98,16 +145,31 @@ def test_laplace_2D(): var = _make_sympy_vec("x", 2) var_t = _make_sympy_vec("t", 2) g_x_y = sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) - derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) for i in range(6)] - - check_2_s = r.subs(n, 2).subs(s(1), derivs[1]) - derivs[2] - check_3_s = r.subs(n, 3).subs(s(1), derivs[1]).subs(s(2), derivs[2]) - derivs[3] - check_4_s = r.subs(n, 4).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]) - derivs[4] - check_5_s = r.subs(n, 5).subs(s(1), derivs[1]).subs(s(2), derivs[2]).subs(s(3), derivs[3]).subs(s(4), derivs[4]) - derivs[5] - - assert abs(check_2_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-15 - assert abs(check_3_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-14 - assert abs(check_4_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-12 - assert abs(check_5_s.subs(var[0], np.random.rand()).subs(var[1], np.random.rand())) <= 1e-12 - - + derivs = [sp.diff(g_x_y, + var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) + for i in range(6)] + + # pylint: disable-next=not-callable + subs_dict = {s(0): derivs[0], s(1): derivs[1]} + check_2_s = r.subs(n, 2).subs(subs_dict) - derivs[2] + # pylint: disable-next=not-callable + subs_dict[s(2)] = derivs[2] + check_3_s = r.subs(n, 3).subs(subs_dict) - derivs[3] + # pylint: disable-next=not-callable + subs_dict[s(3)] = derivs[3] + check_4_s = r.subs(n, 4).subs(subs_dict) - derivs[4] + # pylint: disable-next=not-callable + subs_dict[s(4)] = derivs[4] + check_5_s = r.subs(n, 5).subs(subs_dict) - derivs[5] + + x_coord = np.random.rand() # noqa: NPY002 + y_coord = np.random.rand() # noqa: NPY002 + coord_dict = {var[0]: x_coord, var[1]: y_coord} + + assert abs(abs(check_2_s.subs(coord_dict))) <= 1e-15 + assert abs(abs(check_3_s.subs(coord_dict))) <= 1e-14 + assert abs(abs(check_4_s.subs(coord_dict))) <= 1e-12 + assert abs(abs(check_5_s.subs(coord_dict))) <= 1e-12 + + +test_laplace2d() diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index 0e19faf3c..6ca09f2e2 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -1,69 +1,78 @@ +r""" +With the functionality in this module, we aim to test recurrence +code. +""" +from __future__ import annotations + import numpy as np import sympy as sp -from sympy import hankel1 +# from sympy import hankel1 +from sumpy.array_context import _acf from sumpy.expansion.diff_op import ( laplacian, make_identity_diff_op, ) -from sumpy.recurrenceqbx import recurrence_qbx_lp, _make_sympy_vec -from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf # noqa: F401 -from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion -from sumpy.kernel import LaplaceKernel, HelmholtzKernel +from sumpy.expansion.local import LineTaylorLocalExpansion +from sumpy.kernel import HelmholtzKernel, LaplaceKernel from sumpy.qbx import LayerPotential +from sumpy.recurrenceqbx import _make_sympy_vec, recurrence_qbx_lp + actx_factory = _acf -expn_class = LineTaylorLocalExpansion +ExpnClass = LineTaylorLocalExpansion actx = actx_factory() lknl = LaplaceKernel(2) hlknl = HelmholtzKernel(2, "k") -def _qbx_lp_helmholtz_general(sources,targets,centers,radius,strengths,order): - lpot = LayerPotential(actx.context, - expansion=expn_class(hlknl, order), - target_kernels=(hlknl,), - source_kernels=(hlknl,)) - #print(lpot.get_kernel()) - expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) - sources = actx.from_numpy(sources) - targets = actx.from_numpy(targets) - centers = actx.from_numpy(centers) +def _qbx_lp_helmholtz_general(sources, targets, centers, radius, strengths, order): + lpot = LayerPotential(actx.context, + expansion=ExpnClass(hlknl, order), + target_kernels=(hlknl,), + source_kernels=(hlknl,)) + + # print(lpot.get_kernel()) + expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) + sources = actx.from_numpy(sources) + targets = actx.from_numpy(targets) + centers = actx.from_numpy(centers) - strengths = (strengths,) - extra_kernel_kwargs={"k": 1} - _evt, (result_qbx,) = lpot( - actx.queue, - targets, sources, centers, strengths, - expansion_radii=expansion_radii, - kwargs=extra_kernel_kwargs) - result_qbx = actx.to_numpy(result_qbx) + strengths = (strengths,) + extra_kernel_kwargs = {"k": 1} + _evt, (result_qbx,) = lpot( + actx.queue, + targets, sources, centers, strengths, + expansion_radii=expansion_radii, + kwargs=extra_kernel_kwargs) + result_qbx = actx.to_numpy(result_qbx) - return result_qbx + return result_qbx -def _qbx_lp_laplace_general(sources,targets,centers,radius,strengths,order): - lpot = LayerPotential(actx.context, - expansion=expn_class(lknl, order), - target_kernels=(lknl,), - source_kernels=(lknl,)) +def _qbx_lp_laplace_general(sources, targets, centers, radius, strengths, order): + lpot = LayerPotential(actx.context, + expansion=ExpnClass(lknl, order), + target_kernels=(lknl,), + source_kernels=(lknl,)) - #print(lpot.get_kernel()) - expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) - sources = actx.from_numpy(sources) - targets = actx.from_numpy(targets) - centers = actx.from_numpy(centers) + # print(lpot.get_kernel()) + expansion_radii = actx.from_numpy(radius * np.ones(sources.shape[1])) + sources = actx.from_numpy(sources) + targets = actx.from_numpy(targets) + centers = actx.from_numpy(centers) - strengths = (strengths,) + strengths = (strengths,) - _evt, (result_qbx,) = lpot( - actx.queue, - targets, sources, centers, strengths, - expansion_radii=expansion_radii) - result_qbx = actx.to_numpy(result_qbx) + _evt, (result_qbx,) = lpot( + actx.queue, + targets, sources, centers, strengths, + expansion_radii=expansion_radii) + result_qbx = actx.to_numpy(result_qbx) + + return result_qbx - return result_qbx def _create_ellipse(n_p): h = 9.688 / n_p @@ -83,31 +92,40 @@ def _create_ellipse(n_p): return sources, centers, normals, density, h, radius + def test_recurrence_laplace_2d_ellipse(): + r""" + Tests recurrence code for orders up to 6 laplace3d. + """ - #------------- 1. Define PDE, Green's Function + # ------------- 1. Define PDE, Green's Function w = make_identity_diff_op(2) laplace2d = laplacian(w) var = _make_sympy_vec("x", 2) var_t = _make_sympy_vec("t", 2) - g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)) + g_x_y = (-1/(2*np.pi)) * sp.log(sp.sqrt((var[0]-var_t[0])**2 + + (var[1]-var_t[1])**2)) p = 4 err = [] for n_p in range(200, 1001, 200): sources, centers, normals, density, h, radius = _create_ellipse(n_p) strengths = h * density - exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, laplace2d, g_x_y, 2, p) - qbx_res = _qbx_lp_laplace_general(sources, sources, centers, radius, strengths, p) - #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) + exp_res = recurrence_qbx_lp(sources, centers, normals, + strengths, radius, laplace2d, + g_x_y, 2, p) + qbx_res = _qbx_lp_laplace_general(sources, sources, centers, + radius, strengths, p) + # qbx_res,_ = lpot_eval_circle(sources.shape[1], p) err.append(np.max(np.abs(exp_res - qbx_res))) assert np.max(err) <= 1e-13 def test_recurrence_helmholtz_2d_ellipse(): - - #------------- 1. Define PDE, Green's Function + r""" + Tests recurrence code for orders up to 6 laplace3d. + # ------------- 1. Define PDE, Green's Function w = make_identity_diff_op(2) helmholtz2d = laplacian(w) + w @@ -118,14 +136,16 @@ def test_recurrence_helmholtz_2d_ellipse(): g_x_y = (1j/4) * hankel1(0, k * abs_dist) p = 4 - err = [] + # err = [] for n_p in range(200, 1001, 200): sources, centers, normals, density, h, radius = _create_ellipse(n_p) strengths = h * density - exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, helmholtz2d, g_x_y, 2, p) - #qbx_res = _qbx_lp_helmholtz_general(sources, sources, centers, radius, strengths, p) + exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, + radius, helmholtz2d, g_x_y, 2, p) + #qbx_res = _qbx_lp_helmholtz_general(sources, sources, centers, + # radius, strengths, p) #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) #err.append(np.max(np.abs(exp_res - qbx_res))) #assert np.max(err) <= 1e-13 - -test_recurrence_helmholtz_2d_ellipse() + """ + print("Hello") From b0a6c0fba98a7f533c65877d2d64d4623dafbe20 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 12:13:55 -0600 Subject: [PATCH 68/75] Delete playground.ipynb --- test/playground.ipynb | 155 ------------------------------------------ 1 file changed, 155 deletions(-) delete mode 100644 test/playground.ipynb diff --git a/test/playground.ipynb b/test/playground.ipynb deleted file mode 100644 index 6fffa849b..000000000 --- a/test/playground.ipynb +++ /dev/null @@ -1,155 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "import sympy as sp\n", - "import numpy as np\n", - "\n", - "from sumpy.expansion.diff_op import (\n", - " laplacian,\n", - " make_identity_diff_op,\n", - ")\n", - "\n", - "from sumpy.recurrence import _make_sympy_vec\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "#Create Hankel Function\n", - "\n", - "from sympy import hankel1\n", - "z = sp.symbols(\"z\")\n", - "f = hankel1(0, z)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\frac{H^{(1)}_{-1}\\left(z\\right)}{2} - \\frac{H^{(1)}_{1}\\left(z\\right)}{2}$" - ], - "text/plain": [ - "hankel1(-1, z)/2 - hankel1(1, z)/2" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "f.diff(z)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "var = _make_sympy_vec(\"x\", 2)\n", - "var_t = _make_sympy_vec(\"t\", 2)\n", - "k = 1\n", - "abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2)\n", - "g_x_y = (1j/4) * hankel1(0, k * abs_dist)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle 0.25 i H^{(1)}_{0}\\left(\\sqrt{\\left(- t_{0} + x_{0}\\right)^{2} + \\left(- t_{1} + x_{1}\\right)^{2}}\\right)$" - ], - "text/plain": [ - "0.25*I*hankel1(0, sqrt((-t0 + x0)**2 + (-t1 + x1)**2))" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g_x_y" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) for i in range(6)]" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[0.25*I*hankel1(0, sqrt(x0**2 + x1**2)),\n", - " -0.25*I*x0*(hankel1(-1, sqrt(x0**2 + x1**2))/2 - hankel1(1, sqrt(x0**2 + x1**2))/2)/sqrt(x0**2 + x1**2),\n", - " 0.0625*I*(x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2)),\n", - " 0.03125*I*x0*(4*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - (x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2)),\n", - " -0.25*I*(-9*x0**4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(4*(x0**2 + x1**2)**3) + 15*x0**4*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(2*(x0**2 + x1**2)**(7/2)) + 9*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(4*(x0**2 + x1**2)**2) + x0**2*(4*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 4*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 12*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + (-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - (-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 12*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/(16*sqrt(x0**2 + x1**2)) + 3*x0**2*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(8*(x0**2 + x1**2)**(3/2)) - 9*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 3*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(8*sqrt(x0**2 + x1**2)) + 3*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(2*(x0**2 + x1**2)**(3/2))),\n", - " 0.0078125*I*x0*(480*x0**4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**4 - 1680*x0**4*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(9/2) - 576*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**3 - 8*x0**2*(4*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 4*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 12*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + (-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - (-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 12*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/(x0**2 + x1**2)**(3/2) - 72*x0**2*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(5/2) + 2400*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(7/2) + 96*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 8*(4*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 4*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 - 12*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 12*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + (-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - (-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 12*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 12*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) - (36*x0**4*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**3 - 36*x0**4*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**3 - 120*x0**4*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(7/2) + 120*x0**4*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(7/2) - 36*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 36*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + x0**2*(-4*x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 4*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 12*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 12*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 4*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - (-x0**2*(hankel1(-5, sqrt(x0**2 + x1**2)) - 2*hankel1(-3, sqrt(x0**2 + x1**2)) + hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - hankel1(-2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-4, sqrt(x0**2 + x1**2)) - hankel1(-2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + (-x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - 12*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 12*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) - x0**2*(-4*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 4*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**2 + 12*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 12*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) + 4*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 4*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - (-x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + (-x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - 2*hankel1(3, sqrt(x0**2 + x1**2)) + hankel1(5, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(2, sqrt(x0**2 + x1**2)) - hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(2, sqrt(x0**2 + x1**2)) - hankel1(4, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - 12*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 12*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) + 6*x0**2*(-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(3/2) - 6*x0**2*(-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(3/2) + 144*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 144*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2) - 6*(-x0**2*(hankel1(-4, sqrt(x0**2 + x1**2)) - 2*hankel1(-2, sqrt(x0**2 + x1**2)) + hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-3, sqrt(x0**2 + x1**2)) - hankel1(-1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) + 6*(-x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - 2*hankel1(0, sqrt(x0**2 + x1**2)) + hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - 2*hankel1(2, sqrt(x0**2 + x1**2)) + hankel1(4, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) + 2*x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*x0**2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) - 2*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) + 2*(hankel1(1, sqrt(x0**2 + x1**2)) - hankel1(3, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/sqrt(x0**2 + x1**2) - 24*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 24*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2))/sqrt(x0**2 + x1**2) + 72*(x0**2*(hankel1(-3, sqrt(x0**2 + x1**2)) - 2*hankel1(-1, sqrt(x0**2 + x1**2)) + hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - x0**2*(hankel1(-1, sqrt(x0**2 + x1**2)) - 2*hankel1(1, sqrt(x0**2 + x1**2)) + hankel1(3, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2) - 2*x0**2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*x0**2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(3/2) + 2*(hankel1(-2, sqrt(x0**2 + x1**2)) - hankel1(0, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2) - 2*(hankel1(0, sqrt(x0**2 + x1**2)) - hankel1(2, sqrt(x0**2 + x1**2)))/sqrt(x0**2 + x1**2))/(x0**2 + x1**2)**(3/2) - 720*(hankel1(-1, sqrt(x0**2 + x1**2)) - hankel1(1, sqrt(x0**2 + x1**2)))/(x0**2 + x1**2)**(5/2))]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "derivs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From e4627fd6a1242e9509072e78cb9d93dd51f8be74 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 12:56:31 -0600 Subject: [PATCH 69/75] Updated helmholtz2d to deal with inefficiecy --- sumpy/recurrence.py | 1 + test/test_recurrence.py | 46 ++++++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index e77ad901a..e2974e8b2 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -323,6 +323,7 @@ def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, :arg r: recurrence to extract terms from """ + # We're assuming here that s(...) are the only function calls. terms = list(r.atoms(sp.Function)) terms = np.array(terms) diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 5331604e2..9845655a0 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -10,8 +10,8 @@ import numpy as np import sympy as sp +from sympy import hankel1 -# from sympy import hankel1 from sumpy.expansion.diff_op import ( laplacian, make_identity_diff_op, @@ -109,6 +109,7 @@ def test_helmholtz3d(): def test_helmholtz2d(): r""" Tests recurrence code for orders up to 6 helmholtz2d. + """ w = make_identity_diff_op(2) helmholtz2d = laplacian(w) + w _, _, r = get_processed_and_shifted_recurrence(helmholtz2d) @@ -118,17 +119,39 @@ def test_helmholtz2d(): var = _make_sympy_vec("x", 2) var_t = _make_sympy_vec("t", 2) + abs_dist = sp.sqrt((var[0]-var_t[0])**2 + + (var[1]-var_t[1])**2) k = 1 - abs_dist = sp.sqrt((var[0]-var_t[0])**2 + (var[1]-var_t[1])**2) g_x_y = (1j/4) * hankel1(0, k * abs_dist) - x_coord = np.random.rand() - y_coord = np.random.rand() - derivs = [sp.diff(g_x_y, var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) - for i in range(6)] - derivs = [derivs[i].subs(var[0], x_coord).subs(var[1], y_coord).evalf() - for i in range(6)] - """ - print("HELLO!") + derivs = [sp.diff(g_x_y, + var_t[0], i).subs(var_t[0], 0).subs(var_t[1], 0) + for i in range(6)] + x_coord = np.random.rand() # noqa: NPY002 + y_coord = np.random.rand() # noqa: NPY002 + coord_dict = {var[0]: x_coord, var[1]: y_coord} + derivs = [derivs[i].subs(coord_dict) for i in range(6)] + + # pylint: disable-next=not-callable + subs_dict = {s(0): derivs[0], s(1): derivs[1]} + check_2_s = r.subs(n, 2).subs(subs_dict) - derivs[2] + # pylint: disable-next=not-callable + subs_dict[s(2)] = derivs[2] + check_3_s = r.subs(n, 3).subs(subs_dict) - derivs[3] + # pylint: disable-next=not-callable + subs_dict[s(3)] = derivs[3] + check_4_s = r.subs(n, 4).subs(subs_dict) - derivs[4] + # pylint: disable-next=not-callable + subs_dict[s(4)] = derivs[4] + check_5_s = r.subs(n, 5).subs(subs_dict) - derivs[5] + + f2 = sp.lambdify([var[0], var[1]], check_2_s) + assert abs(f2(x_coord, y_coord)) <= 1e-13 + f3 = sp.lambdify([var[0], var[1]], check_3_s) + assert abs(f3(x_coord, y_coord)) <= 1e-13 + f4 = sp.lambdify([var[0], var[1]], check_4_s) + assert abs(f4(x_coord, y_coord)) <= 1e-13 + f5 = sp.lambdify([var[0], var[1]], check_5_s) + assert abs(f5(x_coord, y_coord)) <= 1e-12 def test_laplace2d(): @@ -173,3 +196,6 @@ def test_laplace2d(): test_laplace2d() +test_helmholtz2d() +test_helmholtz3d() +test_laplace3d() From 81e1fb62696fb70c8f9359b05e00669d3fb6481b Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 13:56:09 -0600 Subject: [PATCH 70/75] Update recurrence.py --- sumpy/recurrence.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index e2974e8b2..c553f019a 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -319,7 +319,7 @@ def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, np.ndarray]: r""" Given a recurrence extracts the variables in the recurrence - as well as the indexes in sorted order. + as well as the indexes, both in sorted order. :arg r: recurrence to extract terms from """ @@ -342,7 +342,7 @@ def _extract_idx_terms_from_recurrence(r: sp.Expr) -> tuple[np.ndarray, return idx_l, terms -def __check_neg_ind(r_n): +def _check_neg_ind(r_n): r""" Simply checks if a negative index exists in a recurrence relation. """ @@ -352,7 +352,7 @@ def __check_neg_ind(r_n): return np.any(idx_l < 0) -def __get_initial_c(recurrence): +def _get_initial_c(recurrence): r""" For a given recurrence checks how many initial conditions by checking for non-negative indexed terms. @@ -361,7 +361,7 @@ def __get_initial_c(recurrence): i = 0 r_c = recurrence.subs(n, i) - while __check_neg_ind(r_c): + while _check_neg_ind(r_c): i += 1 r_c = recurrence.subs(n, i) return i @@ -391,10 +391,12 @@ def get_processed_and_shifted_recurrence(pde) -> tuple[int, int, A function that "shifts" the recurrence so the expansion center is placed at the origin and source is the input for the recurrence generated. + Also processes the recurrence so s(n) is in terms of s(n-1), etc. + :arg recurrence: a recurrence relation in :math:`s(n)` """ r = recurrence_from_pde(pde) order, r_p = process_recurrence_relation(r) - n_initial = __get_initial_c(r_p) + n_initial = _get_initial_c(r_p) r_s = shift_recurrence(r_p) return n_initial, order, r_s From b3d17eb676c9fa06b1fb1e9fbf2abad48425dcde Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 14:01:29 -0600 Subject: [PATCH 71/75] Update test_recurrenceqbx.py --- test/test_recurrenceqbx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index 6ca09f2e2..525d773c3 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -1,6 +1,6 @@ r""" With the functionality in this module, we aim to test recurrence -code. ++ qbx code. """ from __future__ import annotations @@ -95,7 +95,7 @@ def _create_ellipse(n_p): def test_recurrence_laplace_2d_ellipse(): r""" - Tests recurrence code for orders up to 6 laplace3d. + Tests recurrence + qbx code for orders up to 6 laplace3d. """ # ------------- 1. Define PDE, Green's Function From 044bedee7ba6ac3daf5dffd1dcb50c8ad64adb79 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 15:09:01 -0600 Subject: [PATCH 72/75] Update test_recurrenceqbx.py --- test/test_recurrenceqbx.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index 525d773c3..603bd6dae 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -7,7 +7,7 @@ import numpy as np import sympy as sp -# from sympy import hankel1 +from sympy import hankel1 from sumpy.array_context import _acf from sumpy.expansion.diff_op import ( laplacian, @@ -45,7 +45,7 @@ def _qbx_lp_helmholtz_general(sources, targets, centers, radius, strengths, orde actx.queue, targets, sources, centers, strengths, expansion_radii=expansion_radii, - kwargs=extra_kernel_kwargs) + k=1) result_qbx = actx.to_numpy(result_qbx) return result_qbx @@ -125,6 +125,7 @@ def test_recurrence_laplace_2d_ellipse(): def test_recurrence_helmholtz_2d_ellipse(): r""" Tests recurrence code for orders up to 6 laplace3d. + """ # ------------- 1. Define PDE, Green's Function w = make_identity_diff_op(2) helmholtz2d = laplacian(w) + w @@ -136,16 +137,15 @@ def test_recurrence_helmholtz_2d_ellipse(): g_x_y = (1j/4) * hankel1(0, k * abs_dist) p = 4 - # err = [] + err = [] for n_p in range(200, 1001, 200): sources, centers, normals, density, h, radius = _create_ellipse(n_p) strengths = h * density exp_res = recurrence_qbx_lp(sources, centers, normals, strengths, radius, helmholtz2d, g_x_y, 2, p) - #qbx_res = _qbx_lp_helmholtz_general(sources, sources, centers, - # radius, strengths, p) + qbx_res = _qbx_lp_helmholtz_general(sources, sources, centers, radius, strengths, p) #qbx_res,_ = lpot_eval_circle(sources.shape[1], p) - #err.append(np.max(np.abs(exp_res - qbx_res))) - #assert np.max(err) <= 1e-13 - """ - print("Hello") + err.append(np.max(np.abs(exp_res - qbx_res))) + assert np.max(err) <= 1e-13 + +test_recurrence_helmholtz_2d_ellipse() \ No newline at end of file From ebb24222274ae77c6bc472178309f58af85add35 Mon Sep 17 00:00:00 2001 From: Hirish Chandrasekaran Date: Mon, 4 Nov 2024 15:10:55 -0600 Subject: [PATCH 73/75] Update test_recurrenceqbx.py --- test/test_recurrenceqbx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index 603bd6dae..b36d8e83f 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -95,7 +95,7 @@ def _create_ellipse(n_p): def test_recurrence_laplace_2d_ellipse(): r""" - Tests recurrence + qbx code for orders up to 6 laplace3d. + Tests recurrence + qbx code. """ # ------------- 1. Define PDE, Green's Function @@ -124,7 +124,7 @@ def test_recurrence_laplace_2d_ellipse(): def test_recurrence_helmholtz_2d_ellipse(): r""" - Tests recurrence code for orders up to 6 laplace3d. + Tests recurrence + qbx code. """ # ------------- 1. Define PDE, Green's Function w = make_identity_diff_op(2) From 60fc51dc6952e276e1ddce039e1f15a540adee42 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 4 Nov 2024 15:44:26 -0600 Subject: [PATCH 74/75] Minor style fixes --- sumpy/recurrence.py | 3 +-- sumpy/recurrenceqbx.py | 10 +++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sumpy/recurrence.py b/sumpy/recurrence.py index c553f019a..916b9d756 100644 --- a/sumpy/recurrence.py +++ b/sumpy/recurrence.py @@ -32,8 +32,6 @@ from __future__ import annotations -from typing import TypeVar - __copyright__ = """ Copyright (C) 2024 Hirish Chandrasekaran @@ -60,6 +58,7 @@ THE SOFTWARE. """ import math +from typing import TypeVar import numpy as np import sympy as sp diff --git a/sumpy/recurrenceqbx.py b/sumpy/recurrenceqbx.py index 083a28fbf..0407ba02b 100644 --- a/sumpy/recurrenceqbx.py +++ b/sumpy/recurrenceqbx.py @@ -8,6 +8,7 @@ from __future__ import annotations # noqa: I001 import math +from typing import Sequence import numpy as np import sympy as sp @@ -19,7 +20,7 @@ # ================ Transform/Rotate ================= -def _produce_orthogonal_basis(normals): +def _produce_orthogonal_basis(normals: np.ndarray) -> Sequence[np.ndarray]: ndim, ncenters = normals.shape orth_coordsys = [normals] for i in range(1, ndim): @@ -34,8 +35,11 @@ def _produce_orthogonal_basis(normals): return orth_coordsys -def _compute_rotated_shifted_coordinates(sources, centers, normals): - +def _compute_rotated_shifted_coordinates( + sources: np.ndarray, + centers: np.ndarray, + normals: np.ndarray + ) -> np.ndarray: cts = sources[:, None] - centers[:, :, None] orth_coordsys = _produce_orthogonal_basis(normals) cts_rotated_shifted = np.einsum("idc,dcs->ics", orth_coordsys, cts) From a7e685e2d4341fe1c27562c6543c790a3c91eafc Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 4 Nov 2024 16:03:43 -0600 Subject: [PATCH 75/75] Remove function invocations from test --- test/test_recurrence.py | 6 ------ test/test_recurrenceqbx.py | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/test/test_recurrence.py b/test/test_recurrence.py index 9845655a0..8ad80f3db 100644 --- a/test/test_recurrence.py +++ b/test/test_recurrence.py @@ -193,9 +193,3 @@ def test_laplace2d(): assert abs(abs(check_3_s.subs(coord_dict))) <= 1e-14 assert abs(abs(check_4_s.subs(coord_dict))) <= 1e-12 assert abs(abs(check_5_s.subs(coord_dict))) <= 1e-12 - - -test_laplace2d() -test_helmholtz2d() -test_helmholtz3d() -test_laplace3d() diff --git a/test/test_recurrenceqbx.py b/test/test_recurrenceqbx.py index b36d8e83f..788f41494 100644 --- a/test/test_recurrenceqbx.py +++ b/test/test_recurrenceqbx.py @@ -148,4 +148,4 @@ def test_recurrence_helmholtz_2d_ellipse(): err.append(np.max(np.abs(exp_res - qbx_res))) assert np.max(err) <= 1e-13 -test_recurrence_helmholtz_2d_ellipse() \ No newline at end of file +# test_recurrence_helmholtz_2d_ellipse()