Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding computation of complete elliptic integrals into amrex::Math so… #4151

Merged
merged 7 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Docs/sphinx_documentation/source/Basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ the Heaviside step function, ``heaviside(x1,x2)`` that gives ``0``, ``x2``,
``1``, for ``x1 < 0``, ``x1 = 0`` and ``x1 > 0``, respectively.
It supports the Bessel function of the first kind of order ``n``
``jn(n,x)``. Complete elliptic integrals of the first and second kind, ``comp_ellint_1`` and ``comp_ellint_2``,
are supported only for gcc and CPUs.
are supported.
There is ``if(a,b,c)`` that gives ``b`` or ``c`` depending on the value of
``a``. A number of comparison operators are supported, including ``<``,
``>``, ``==``, ``!=``, ``<=``, and ``>=``. The Boolean results from
Expand Down
65 changes: 65 additions & 0 deletions Src/Base/AMReX_Math.H
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <AMReX_GpuQualifiers.H>
#include <AMReX_Extension.H>
#include <AMReX_INT.H>
#include <AMReX_REAL.H>
#include <cmath>
#include <cstdlib>
#include <type_traits>
Expand Down Expand Up @@ -225,6 +226,70 @@ std::uint64_t umulhi (std::uint64_t a, std::uint64_t b)
}
#endif

template <typename T>
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
T comp_ellint_1 (T m)
{
// Computing K based on DLMF
// https://dlmf.nist.gov/19.8
T tol = 1e-12;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if T is float?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add a check on it to adjust the tolerance for a float to 1e-6.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fixed now.

T a0 = 1.0;
T g0 = std::sqrt(1.0 - m);
T a, g;

// Find Arithmetic Geometric mean
while(std::abs(a0 - g0) > tol) {
a = 0.5*(a0 + g0);
g = std::sqrt(a0 * g0);

a0 = a;
g0 = g;
}

return 0.5*pi<T>()/a;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If m is 0, a is uninitialized.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comp_ellint_1(0.5), I get 1.685750355 with std::comp_ellint_1, but 1.854074677 with amrex::Math::comp_ellint_1. The error seems to be too big.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a definition issue. In most cases the integrals are defined with m as the argument. In this case with gcc it is k = sqrt(m). I will update it to be consistent and I will additionally add some test figures doing comparisons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WeiqunZhang in general though do you think this is the right spot for this calculation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we could use T tol = std::numeric_limits<T>::epsilon();. If we want to be conservative, we could make it 10x bigger than epsilon. In my testing, it only increases the number of iteration slightly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the latest push should resolve these concerns. I have also added a comparison of the AGM algorithm to what is done in Python's ellipm and ellipk routines.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above figure is the difference between the two cases for the Z-components of the analytical solution to a current loop.

}

template <typename T>
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
T comp_ellint_2 (T m)
{
// Computing E based on DLMF
// https://dlmf.nist.gov/19.8
T Kcomp = amrex::Math::comp_ellint_1<T>(m);
T tol = 1e-12;

// Step Zero
T a0 = 1.0;
T g0 = std::sqrt(1.0 - m);
T cn = std::sqrt(a0*a0 - g0*g0);

// Step 1
int n = 1;
T a = 0.5 * (a0 + g0);
T g = std::sqrt(a0*g0);
cn = 0.25*cn*cn/a;

T sum_val = a*a;
a0 = a;
g0 = g;

while(std::abs(cn*cn) > tol) {
// Compute coefficients for this iteration
a = 0.5 * (a0 + g0);
g = std::sqrt(a0*g0);
cn = 0.25*cn*cn/a;

n++;
sum_val -= std::pow(2,n-1)*cn*cn;

// Save a and g for next iteration
a0 = a;
g0 = g;
}

return Kcomp*sum_val;
}

/***************************************************************************************************
* Copyright (c) 2017 - 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
Expand Down
20 changes: 8 additions & 12 deletions Src/Base/Parser/AMReX_Parser_Y.H
Original file line number Diff line number Diff line change
Expand Up @@ -349,28 +349,24 @@ AMREX_GPU_HOST_DEVICE AMREX_NO_INLINE
T parser_math_atanh (T a) { return std::atanh(a); }

template <typename T>
AMREX_GPU_HOST_DEVICE AMREX_NO_INLINE
T parser_math_comp_ellint_1 (T a)
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
T parser_math_comp_ellint_1 (T k)
{
#if defined(__GNUC__) && !defined(__clang__) && !defined(__CUDA_ARCH__) && !defined(__NVCOMPILER)
return std::comp_ellint_1(a);
return std::comp_ellint_1(k);
#else
amrex::ignore_unused(a);
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(false, "parser: comp_ellint_1 only supported with gcc and cpu");
return 0.0;
return amrex::Math::comp_ellint_1<T>(k);
#endif
}

template <typename T>
AMREX_GPU_HOST_DEVICE AMREX_NO_INLINE
T parser_math_comp_ellint_2 (T a)
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE
T parser_math_comp_ellint_2 (T k)
{
#if defined(__GNUC__) && !defined(__clang__) && !defined(__CUDA_ARCH__) && !defined(__NVCOMPILER)
return std::comp_ellint_2(a);
return std::comp_ellint_2(k);
#else
amrex::ignore_unused(a);
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(false, "parser: comp_ellint_2 only supported with gcc and cpu");
return 0.0;
return amrex::Math::comp_ellint_2<T>(k);
#endif
}

Expand Down
Loading