-
Notifications
You must be signed in to change notification settings - Fork 24
Add Gaussian quadrature integration for line shape models #378
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
Merged
jacklovell
merged 11 commits into
cherab:development
from
vsnever:feature/quadrature_integration
Dec 22, 2022
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
b202dbb
Add Gaussian quadrature integrator and tests.
vsnever a32a6a7
Fix an error in GaussianQuadrature. Increase default relative toleran…
vsnever c4a604d
Add NumericallyIntegrableLineShapeModel class. Update StarkBroadenedL…
vsnever 6308e0f
Update changelog.
vsnever 526cc17
Make Integrator1D a Function2D instance.
vsnever 86d908b
Remove NumericallyIntegratedLineShapeModel. Add integrator to LineSha…
vsnever 803d529
Merge branch 'development' into feature/quadrature_integration
vsnever 6a915f9
Remove Function2D inheritance for Integrator1D. Forbid setting the in…
vsnever e62f0ad
Add initialisation checks to StarkFunction and make it return a norma…
vsnever 308208c
Update CHANGELOG.md
vsnever f0bc1aa
Merge branch 'development' into feature/quadrature_integration
jacklovell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Copyright 2016-2022 Euratom | ||
| # Copyright 2016-2022 United Kingdom Atomic Energy Authority | ||
| # Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas | ||
| # | ||
| # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the | ||
| # European Commission - subsequent versions of the EUPL (the "Licence"); | ||
| # You may not use this work except in compliance with the Licence. | ||
| # You may obtain a copy of the Licence at: | ||
| # | ||
| # https://joinup.ec.europa.eu/software/page/eupl5 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software distributed | ||
| # under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR | ||
| # CONDITIONS OF ANY KIND, either express or implied. | ||
| # | ||
| # See the Licence for the specific language governing permissions and limitations | ||
| # under the Licence. | ||
|
|
||
| from cherab.core.math.integrators.integrators1d cimport Integrator1D, GaussianQuadrature | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Copyright 2016-2022 Euratom | ||
| # Copyright 2016-2022 United Kingdom Atomic Energy Authority | ||
| # Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas | ||
| # | ||
| # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the | ||
| # European Commission - subsequent versions of the EUPL (the "Licence"); | ||
| # You may not use this work except in compliance with the Licence. | ||
| # You may obtain a copy of the Licence at: | ||
| # | ||
| # https://joinup.ec.europa.eu/software/page/eupl5 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software distributed | ||
| # under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR | ||
| # CONDITIONS OF ANY KIND, either express or implied. | ||
| # | ||
| # See the Licence for the specific language governing permissions and limitations | ||
| # under the Licence. | ||
|
|
||
| from .integrators1d import Integrator1D, GaussianQuadrature |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Copyright 2016-2022 Euratom | ||
| # Copyright 2016-2022 United Kingdom Atomic Energy Authority | ||
| # Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas | ||
| # | ||
| # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the | ||
| # European Commission - subsequent versions of the EUPL (the "Licence"); | ||
| # You may not use this work except in compliance with the Licence. | ||
| # You may obtain a copy of the Licence at: | ||
| # | ||
| # https://joinup.ec.europa.eu/software/page/eupl5 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software distributed | ||
| # under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR | ||
| # CONDITIONS OF ANY KIND, either express or implied. | ||
| # | ||
| # See the Licence for the specific language governing permissions and limitations | ||
| # under the Licence. | ||
|
|
||
| from numpy cimport ndarray | ||
| from raysect.core.math.function.float cimport Function1D, Function2D | ||
|
|
||
|
|
||
| cdef class Integrator1D: | ||
|
|
||
| cdef: | ||
| Function1D function | ||
|
|
||
| cdef double evaluate(self, double a, double b) except? -1e999 | ||
|
|
||
|
|
||
| cdef class GaussianQuadrature(Integrator1D): | ||
|
|
||
| cdef: | ||
| int _min_order, _max_order | ||
| double _rtol | ||
| ndarray _roots, _weights | ||
| double[:] _roots_mv, _weights_mv | ||
|
|
||
| cdef _build_cache(self) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| # cython: language_level=3 | ||
|
|
||
| # Copyright 2016-2022 Euratom | ||
| # Copyright 2016-2022 United Kingdom Atomic Energy Authority | ||
| # Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas | ||
| # | ||
| # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the | ||
| # European Commission - subsequent versions of the EUPL (the "Licence"); | ||
| # You may not use this work except in compliance with the Licence. | ||
| # You may obtain a copy of the Licence at: | ||
| # | ||
| # https://joinup.ec.europa.eu/software/page/eupl5 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software distributed | ||
| # under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR | ||
| # CONDITIONS OF ANY KIND, either express or implied. | ||
| # | ||
| # See the Licence for the specific language governing permissions and limitations | ||
| # under the Licence. | ||
|
|
||
| import numpy as np | ||
| from scipy.special import roots_legendre | ||
|
|
||
| from raysect.core.math.function.float cimport autowrap_function1d, Constant1D | ||
|
|
||
| from libc.math cimport INFINITY | ||
| cimport cython | ||
|
|
||
|
|
||
| cdef class Integrator1D: | ||
| """ | ||
| Compute a definite integral of a one-dimensional function. | ||
|
|
||
| :ivar Function1D integrand: A 1D function to integrate. | ||
| """ | ||
|
|
||
| @property | ||
| def integrand(self): | ||
| """ | ||
| A 1D function to integrate. | ||
|
|
||
| :rtype: int | ||
| """ | ||
| return self.function | ||
|
|
||
| @integrand.setter | ||
| def integrand(self, object func not None): | ||
|
|
||
| self.function = autowrap_function1d(func) | ||
|
|
||
| cdef double evaluate(self, double a, double b) except? -1e999: | ||
|
|
||
| raise NotImplementedError("The evaluate() method has not been implemented.") | ||
|
|
||
| def __call__(self, double a, double b): | ||
| """ | ||
| Integrates a one-dimensional function over a finite interval. | ||
|
|
||
| :param double a: Lower limit of integration. | ||
| :param double b: Upper limit of integration. | ||
|
|
||
| :returns: Definite integral of a one-dimensional function. | ||
| """ | ||
|
|
||
| return self.evaluate(a, b) | ||
|
|
||
|
|
||
| cdef class GaussianQuadrature(Integrator1D): | ||
| """ | ||
| Compute an integral of a one-dimensional function over a finite interval | ||
| using fixed-tolerance Gaussian quadrature. | ||
| (see Scipy `quadrature <https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.quadrature.html>`). | ||
|
|
||
| :param object integrand: A 1D function to integrate. Default is Constant1D(0). | ||
| :param double relative_tolerance: Iteration stops when relative error between | ||
| last two iterates is less than this value. Default is 1.e-5. | ||
| :param int max_order: Maximum order on Gaussian quadrature. Default is 50. | ||
| :param int min_order: Minimum order on Gaussian quadrature. Default is 1. | ||
|
|
||
| :ivar Function1D integrand: A 1D function to integrate. | ||
| :ivar double relative_tolerance: Iteration stops when relative error between | ||
| last two iterates is less than this value. | ||
| :ivar int max_order: Maximum order on Gaussian quadrature. | ||
| :ivar int min_order: Minimum order on Gaussian quadrature. | ||
| """ | ||
|
|
||
| def __init__(self, object integrand=Constant1D(0), double relative_tolerance=1.e-5, int max_order=50, int min_order=1): | ||
|
|
||
| if min_order < 1 or max_order < 1: | ||
| raise ValueError("Order of Gaussian quadrature must be >= 1.") | ||
|
|
||
| if min_order > max_order: | ||
| raise ValueError("Minimum order of Gaussian quadrature must be less than or equal to the maximum order.") | ||
|
|
||
| self._min_order = min_order | ||
| self._max_order = max_order | ||
| self._build_cache() | ||
|
|
||
| self.integrand = integrand | ||
|
|
||
| self.relative_tolerance = relative_tolerance | ||
|
|
||
| @property | ||
| def min_order(self): | ||
| """ | ||
| Minimum order on Gaussian quadrature. | ||
|
|
||
| :rtype: int | ||
| """ | ||
| return self._min_order | ||
|
|
||
| @min_order.setter | ||
| def min_order(self, int value): | ||
|
|
||
| if value < 1: | ||
| raise ValueError("Order of Gaussian quadrature must be >= 1.") | ||
|
|
||
| if value > self._max_order: | ||
| raise ValueError("Minimum order of Gaussian quadrature must be less than or equal to the maximum order.") | ||
|
|
||
| self._min_order = value | ||
|
|
||
| self._build_cache() | ||
|
|
||
| @property | ||
| def max_order(self): | ||
| """ | ||
| Maximum order on Gaussian quadrature. | ||
|
|
||
| :rtype: float | ||
| """ | ||
| return self._max_order | ||
|
|
||
| @max_order.setter | ||
| def max_order(self, int value): | ||
|
|
||
| if value < 1: | ||
| raise ValueError("Order of Gaussian quadrature must be >= 1.") | ||
|
|
||
| if value < self._min_order: | ||
| raise ValueError("Maximum order of Gaussian quadrature must be greater than or equal to the minimum order.") | ||
|
|
||
| self._max_order = value | ||
|
|
||
| self._build_cache() | ||
|
|
||
| @property | ||
| def relative_tolerance(self): | ||
| """ | ||
| Iteration stops when relative error between last two iterates is less than this value. | ||
|
|
||
| :rtype: double | ||
| """ | ||
| return self._rtol | ||
|
|
||
| @relative_tolerance.setter | ||
| def relative_tolerance(self, double value): | ||
|
|
||
| if value <= 0: | ||
| raise ValueError("Relative tolerance must be positive.") | ||
|
|
||
| self._rtol = value | ||
|
|
||
| cdef _build_cache(self): | ||
| """ | ||
| Caches the roots and weights of the Gauss-Legendre quadrature. | ||
| """ | ||
|
|
||
| cdef: | ||
| int order, n, i | ||
|
|
||
| n = (self._max_order + self._min_order) * (self._max_order - self._min_order + 1) // 2 | ||
|
|
||
| self._roots = np.zeros(n, dtype=np.float64) | ||
| self._weights = np.zeros(n, dtype=np.float64) | ||
|
|
||
| i = 0 | ||
| for order in range(self._min_order, self._max_order + 1): | ||
| self._roots[i:i + order], self._weights[i:i + order] = roots_legendre(order) | ||
| i += order | ||
|
|
||
| self._roots_mv = self._roots | ||
| self._weights_mv = self._weights | ||
|
|
||
| @cython.boundscheck(False) | ||
| @cython.wraparound(False) | ||
| @cython.cdivision(True) | ||
| @cython.initializedcheck(False) | ||
| cdef double evaluate(self, double a, double b) except? -1e999: | ||
| """ | ||
| Integrates a one-dimensional function over a finite interval. | ||
|
|
||
| :param double a: Lower limit of integration. | ||
| :param double b: Upper limit of integration. | ||
|
|
||
| :returns: Gaussian quadrature approximation to integral. | ||
| """ | ||
|
|
||
| cdef: | ||
| int order, i, ibegin | ||
| double newval, oldval, error, x, c, d | ||
|
|
||
| oldval = INFINITY | ||
| ibegin = 0 | ||
| c = 0.5 * (a + b) | ||
| d = 0.5 * (b - a) | ||
|
|
||
| for order in range(self._min_order, self._max_order + 1): | ||
| newval = 0 | ||
| for i in range(ibegin, ibegin + order): | ||
| x = c + d * self._roots_mv[i] | ||
| newval += self._weights_mv[i] * self.function.evaluate(x) | ||
| newval *= d | ||
|
|
||
| error = abs(newval - oldval) | ||
| oldval = newval | ||
|
|
||
| ibegin += order | ||
|
|
||
| if error < self._rtol * abs(newval): | ||
| break | ||
|
|
||
| return newval | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Copyright 2016-2022 Euratom | ||
| # Copyright 2016-2022 United Kingdom Atomic Energy Authority | ||
| # Copyright 2016-2022 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas | ||
| # | ||
| # Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the | ||
| # European Commission - subsequent versions of the EUPL (the "Licence"); | ||
| # You may not use this work except in compliance with the Licence. | ||
| # You may obtain a copy of the Licence at: | ||
| # | ||
| # https://joinup.ec.europa.eu/software/page/eupl5 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software distributed | ||
| # under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR | ||
| # CONDITIONS OF ANY KIND, either express or implied. | ||
| # | ||
| # See the Licence for the specific language governing permissions and limitations | ||
| # under the Licence. | ||
|
|
||
| from raysect.core.math.function.float import Exp1D, Arg1D | ||
| from cherab.core.math.integrators import GaussianQuadrature | ||
| from math import sqrt, pi | ||
| from scipy.special import erf | ||
| import unittest | ||
|
|
||
|
|
||
| class TestGaussianQuadrature(unittest.TestCase): | ||
| """Gaussian quadrature integrator tests.""" | ||
|
|
||
| def test_properties(self): | ||
| """Test property assignment.""" | ||
| min_order = 3 | ||
| max_order = 30 | ||
| reltol = 1.e-6 | ||
| quadrature = GaussianQuadrature(integrand=Arg1D, relative_tolerance=reltol, max_order=max_order, min_order=min_order) | ||
|
|
||
| self.assertEqual(quadrature.relative_tolerance, reltol) | ||
| self.assertEqual(quadrature.max_order, max_order) | ||
| self.assertEqual(quadrature.min_order, min_order) | ||
| self.assertEqual(quadrature.integrand, Arg1D) | ||
|
|
||
| min_order = 0 | ||
| max_order = 2 # < min_order | ||
| reltol = -1 | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| quadrature.max_order = max_order | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| quadrature.min_order = min_order | ||
|
|
||
| with self.assertRaises(ValueError): | ||
| quadrature.relative_tolerance = reltol | ||
|
|
||
| min_order = 1 | ||
| max_order = 20 | ||
| reltol = 1.e-5 | ||
|
|
||
| quadrature.relative_tolerance = reltol | ||
| quadrature.min_order = min_order | ||
| quadrature.max_order = max_order | ||
| quadrature.integrand = Exp1D | ||
|
|
||
| self.assertEqual(quadrature.relative_tolerance, reltol) | ||
| self.assertEqual(quadrature.min_order, min_order) | ||
| self.assertEqual(quadrature.max_order, max_order) | ||
| self.assertEqual(quadrature.integrand, Exp1D) | ||
|
|
||
| def test_integrate(self): | ||
| """Test integration.""" | ||
| quadrature = GaussianQuadrature(relative_tolerance=1.e-8) | ||
| a = -0.5 | ||
| b = 3. | ||
| quadrature.integrand = (2 / sqrt(pi)) * Exp1D(- Arg1D() * Arg1D()) | ||
| exact_integral = erf(b) - erf(a) | ||
|
|
||
| self.assertAlmostEqual(quadrature(a, b), exact_integral, places=8) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the integrate method you do operation
(self._roots_mv[i] + 1.), storingroot + 1value in the array could bring some speedups and it seems that you don't use the value of root elsewhereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rewrote the integration, so
xnow taken asx = c + d * self._roots_mv[i], wherec = 0.5 * (a + b)andd = 0.5 * (b - a). Adding 1 is no longer required.