numo-gsl-integration 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +31 -0
- data/LICENSE +674 -0
- data/README.md +80 -0
- data/docs/development.md +49 -0
- data/docs/error-handling.md +19 -0
- data/docs/routines.md +288 -0
- data/ext/numo/gsl/integration/callback.c +61 -0
- data/ext/numo/gsl/integration/cquad.c +84 -0
- data/ext/numo/gsl/integration/extconf.rb +13 -0
- data/ext/numo/gsl/integration/glfixed.c +74 -0
- data/ext/numo/gsl/integration/integration.c +35 -0
- data/ext/numo/gsl/integration/integration_internal.h +52 -0
- data/ext/numo/gsl/integration/options.c +26 -0
- data/ext/numo/gsl/integration/qag.c +90 -0
- data/ext/numo/gsl/integration/qagi.c +73 -0
- data/ext/numo/gsl/integration/qagil.c +70 -0
- data/ext/numo/gsl/integration/qagiu.c +70 -0
- data/ext/numo/gsl/integration/qagp.c +130 -0
- data/ext/numo/gsl/integration/qags.c +79 -0
- data/ext/numo/gsl/integration/qawc.c +75 -0
- data/ext/numo/gsl/integration/qawf.c +107 -0
- data/ext/numo/gsl/integration/qawo.c +99 -0
- data/ext/numo/gsl/integration/qaws.c +102 -0
- data/ext/numo/gsl/integration/qng.c +75 -0
- data/ext/numo/gsl/integration/resources.c +35 -0
- data/ext/numo/gsl/integration/romberg.c +79 -0
- data/lib/numo/gsl/integration/version.rb +9 -0
- data/lib/numo/gsl/integration.rb +15 -0
- data/sig/numo/gsl/integration.rbs +131 -0
- data/sig/numo/gsl.rbs +6 -0
- metadata +96 -0
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Numo::GSL::Integration
|
|
2
|
+
|
|
3
|
+
`numo-gsl-integration` provides numerical integration routines from the [GNU Scientific Library (GSL)](https://www.gnu.org/software/gsl/) under the `Numo::GSL::Integration` namespace. It depends on [`numo-gsl-alt`](https://github.com/mvz/numo-gsl-alt) for the `Numo::GSL` namespace and GSL error handling.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- [Ruby](https://www.ruby-lang.org/) 3.2 or later
|
|
8
|
+
- GNU Scientific Library (GSL)
|
|
9
|
+
- `gsl-config` available in `PATH`
|
|
10
|
+
|
|
11
|
+
The `numo-gsl-alt` Ruby gem is installed as a runtime dependency. GSL itself must be installed separately.
|
|
12
|
+
|
|
13
|
+
## Why numo-gsl-alt?
|
|
14
|
+
|
|
15
|
+
This gem intentionally depends on [`numo-gsl-alt`](https://github.com/mvz/numo-gsl-alt) rather than [`numo-gsl`](https://github.com/ruby-numo/numo-gsl). The main reason is Ruby compatibility: `numo-gsl` 0.1.2 does not build with Ruby 4.0 because its generated native extension registers a three-argument C function as a one-argument Ruby method. Ruby 4.0 rejects that incompatible function-pointer type during compilation. `numo-gsl-integration` targets Ruby 3.2 or later and is developed and tested with `numo-gsl-alt` 0.1.x, including Ruby 4.0.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add the gem to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "numo-gsl-integration"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The native extension uses `gsl-config` to locate the GSL headers and libraries.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Load the library:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require "numo/gsl/integration"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The entry point loads `numo/gsl` before the integration native extension. This ensures that `numo-gsl-alt` initializes the `Numo::GSL` namespace and installs its GSL error handler before integration routines are loaded.
|
|
42
|
+
|
|
43
|
+
For example, use `qag` to integrate a function over a finite interval:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
result, error = Numo::GSL::Integration.qag(0.0, 1.0) do |x|
|
|
47
|
+
x * x
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Supported routines
|
|
52
|
+
|
|
53
|
+
| Method | Interval | Intended use |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `qag` | Finite | General-purpose adaptive integration |
|
|
56
|
+
| `qng` | Finite | Non-adaptive integration of smooth functions |
|
|
57
|
+
| `qags` | Finite | Functions with integrable singularities or discontinuities |
|
|
58
|
+
| `qagp` | Finite | Known singular or discontinuity points |
|
|
59
|
+
| `qagi` | Infinite | Integration over the entire real line |
|
|
60
|
+
| `qagiu` | Semi-infinite | Integration from a finite lower bound to positive infinity |
|
|
61
|
+
| `qagil` | Semi-infinite | Integration from negative infinity to a finite upper bound |
|
|
62
|
+
| `qawc` | Finite | Cauchy principal value with a known singular point |
|
|
63
|
+
| `qaws` | Finite | Algebraic and logarithmic endpoint singularities |
|
|
64
|
+
| `qawo` | Finite | Sine- or cosine-weighted oscillatory integrals |
|
|
65
|
+
| `qawf` | Semi-infinite | Sine- or cosine-weighted Fourier integrals |
|
|
66
|
+
| `cquad` | Finite | Difficult integrands and endpoint singularities |
|
|
67
|
+
| `romberg` | Finite | Smooth functions with extrapolated trapezoidal integration |
|
|
68
|
+
| `glfixed` | Finite | Fixed-order Gauss–Legendre integration of smooth functions |
|
|
69
|
+
|
|
70
|
+
See [Integration routines](docs/routines.md) for parameters and examples.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
- [Integration routines](docs/routines.md)
|
|
75
|
+
- [GSL error handling](docs/error-handling.md)
|
|
76
|
+
- [Development](docs/development.md)
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
This project is licensed under the [GNU General Public License version 3 or later](https://www.gnu.org/licenses/gpl-3.0.html). See the [LICENSE](LICENSE) file for the full license text.
|
data/docs/development.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
After checking out the repository, install the dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bundle install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Build the native extension and run the complete verification suite:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle exec rake
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The default Rake task performs the following checks:
|
|
16
|
+
|
|
17
|
+
- compiles the native extension;
|
|
18
|
+
- runs the [RSpec](https://rspec.info/) suite;
|
|
19
|
+
- runs [RuboCop](https://rubocop.org/);
|
|
20
|
+
- validates RBS signatures;
|
|
21
|
+
- checks Ruby files with [rubyfmt](https://github.com/fables-tales/rubyfmt);
|
|
22
|
+
- checks C source files with `clang-format` using the LLVM style; and
|
|
23
|
+
- checks `README.md` and `CHANGELOG.md` with `markdownlint-cli2`.
|
|
24
|
+
|
|
25
|
+
Check source formatting without applying changes:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle exec rake format:check
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Format the Ruby and C source files:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bundle exec rake format:apply
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
RuboCop is configured with `rubocop-rake`, `rubocop-rspec`, and `rubocop-rubyfmt`.
|
|
38
|
+
|
|
39
|
+
## clangd
|
|
40
|
+
|
|
41
|
+
To enable accurate code completion, navigation, and diagnostics when editing the native extension, generate a compilation database with [Bear](https://rizsotto.github.io/Bear/):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bear -- bundle exec rake clean compile
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The compilation database provides `clangd` with the compiler options and Ruby and GSL header paths used to build each C source file. See the [clangd documentation](https://clangd.llvm.org/design/compile-commands) for details.
|
|
48
|
+
|
|
49
|
+
The generated `compile_commands.json` contains paths specific to the local build environment and should not be committed. Regenerate it after changing the Ruby or GSL installation or other build settings.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# GSL error handling
|
|
2
|
+
|
|
3
|
+
`numo-gsl-alt` installs the process-wide GSL error handler that converts GSL errors to `Numo::GSL::GslError`. `numo-gsl-integration` neither replaces nor temporarily disables that handler.
|
|
4
|
+
|
|
5
|
+
Errors reported by GSL are raised directly as `Numo::GSL::GslError` by the handler installed by `numo-gsl-alt`. `numo-gsl-integration` does not convert integration return statuses into separate exceptions.
|
|
6
|
+
|
|
7
|
+
The workspaces used by QAG, QAGS, QAGP, QAGI, QAGIU, QAGIL, QAWC, QAWS, QAWO, QAWF, CQUAD, and Romberg are released when a call completes normally or raises an exception. QAWS, QAWO, and QAWF also release their weight tables, GLFIXED releases its Gauss–Legendre table, and QAGP releases its temporary copy of the point array.
|
|
8
|
+
|
|
9
|
+
`Numo::GSL::Integration::Error` is reserved for validation or state errors detected by `numo-gsl-integration` itself. It inherits from `Numo::GSL::GslError`:
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart BT
|
|
13
|
+
IntegrationError["Numo::GSL::Integration::Error"] -->|inherits from| GslError["Numo::GSL::GslError"]
|
|
14
|
+
GslError -->|inherits from| StandardError
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Code can rescue all GSL-related errors with `Numo::GSL::GslError`, or rescue `Numo::GSL::Integration::Error` when it needs to handle only errors detected by the integration wrapper.
|
|
18
|
+
|
|
19
|
+
The GSL error handler is shared by the entire process. This gem leaves its ownership with `numo-gsl-alt` and does not switch the handler around individual integration calls.
|
data/docs/routines.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Integration routines
|
|
2
|
+
Load the library before calling an integration routine:
|
|
3
|
+
```ruby
|
|
4
|
+
require "numo/gsl/integration"
|
|
5
|
+
```
|
|
6
|
+
Exceptions raised by an integrand are propagated to the caller. A non-numeric return value from an integrand raises `TypeError`.
|
|
7
|
+
## QAG integration
|
|
8
|
+
GSL [QAG](https://www.gnu.org/software/gsl/doc/html/integration.html#qag-adaptive-integration) provides adaptive integration over a finite interval. `Numo::GSL::Integration.qag` exposes this routine to Ruby and returns the integral and its estimated absolute error.
|
|
9
|
+
```ruby
|
|
10
|
+
result, error = Numo::GSL::Integration.qag(0.0, 1.0) do |x|
|
|
11
|
+
x * x
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
Integration parameters can be specified with the `epsabs`, `epsrel`, `limit`, and `key` keyword arguments:
|
|
15
|
+
```ruby
|
|
16
|
+
result, error = Numo::GSL::Integration.qag(
|
|
17
|
+
0.0,
|
|
18
|
+
1.0,
|
|
19
|
+
epsabs: 1e-12,
|
|
20
|
+
epsrel: 1e-12,
|
|
21
|
+
limit: 100,
|
|
22
|
+
key: Numo::GSL::Integration::GAUSS21,
|
|
23
|
+
) do |x|
|
|
24
|
+
x * x
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
The `key` keyword selects the Gauss-Kronrod integration rule and defaults to `GAUSS61`. The available values are `GAUSS15`, `GAUSS21`, `GAUSS31`, `GAUSS41`, `GAUSS51`, and `GAUSS61`.
|
|
28
|
+
## QNG integration
|
|
29
|
+
GSL [QNG](https://www.gnu.org/software/gsl/doc/html/integration.html#qng-non-adaptive-gauss-kronrod-integration) provides non-adaptive integration for smooth functions over a finite interval. It applies increasingly accurate Gauss-Kronrod-Patterson rules and evaluates the integrand at up to 87 points.
|
|
30
|
+
```ruby
|
|
31
|
+
result, error, evaluations = Numo::GSL::Integration.qng(0.0, 1.0) do |x|
|
|
32
|
+
x * x
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
The return value contains the integral, its estimated absolute error, and the number of integrand evaluations. The `epsabs` and `epsrel` keyword arguments control the requested absolute and relative error limits:
|
|
36
|
+
```ruby
|
|
37
|
+
result, error, evaluations = Numo::GSL::Integration.qng(
|
|
38
|
+
0.0,
|
|
39
|
+
1.0,
|
|
40
|
+
epsabs: 1e-12,
|
|
41
|
+
epsrel: 1e-12
|
|
42
|
+
) do |x|
|
|
43
|
+
x * x
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
## QAGS integration
|
|
47
|
+
GSL [QAGS](https://www.gnu.org/software/gsl/doc/html/integration.html#qags-adaptive-integration-with-singularities) combines adaptive subdivision with extrapolation to integrate functions with integrable singularities or discontinuities over a finite interval.
|
|
48
|
+
```ruby
|
|
49
|
+
result, error = Numo::GSL::Integration.qags(0.0, 1.0) do |x|
|
|
50
|
+
Math.log(x) / Math.sqrt(x)
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
The example integral has an endpoint singularity and an exact value of `-4.0`. The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals:
|
|
54
|
+
```ruby
|
|
55
|
+
result, error = Numo::GSL::Integration.qags(
|
|
56
|
+
0.0,
|
|
57
|
+
1.0,
|
|
58
|
+
epsabs: 1e-12,
|
|
59
|
+
epsrel: 1e-12,
|
|
60
|
+
limit: 100
|
|
61
|
+
) do |x|
|
|
62
|
+
x * x
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
## QAGP integration
|
|
66
|
+
GSL [QAGP](https://www.gnu.org/software/gsl/doc/html/integration.html#qagp-adaptive-integration-with-known-singular-points) applies adaptive integration when the locations of interior singularities or discontinuities are known. Pass an array containing the left endpoint, zero or more known interior singularity or discontinuity points, and the right endpoint:
|
|
67
|
+
```ruby
|
|
68
|
+
result, error = Numo::GSL::Integration.qagp(
|
|
69
|
+
[0.0, 0.5, 1.0]
|
|
70
|
+
) do |x|
|
|
71
|
+
1.0 / Math.sqrt((x - 0.5).abs)
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
The points must be finite numeric values in strictly increasing order. The array must contain at least two values. Invalid point arrays raise `Numo::GSL::Integration::Error` before GSL is called.
|
|
75
|
+
The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals:
|
|
76
|
+
```ruby
|
|
77
|
+
result, error = Numo::GSL::Integration.qagp(
|
|
78
|
+
[0.0, 0.25, 0.75, 1.0],
|
|
79
|
+
epsabs: 1e-12,
|
|
80
|
+
epsrel: 1e-12,
|
|
81
|
+
limit: 100
|
|
82
|
+
) do |x|
|
|
83
|
+
x * x
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
## QAGI integration
|
|
87
|
+
GSL [QAGI](https://www.gnu.org/software/gsl/doc/html/integration.html#qagi-adaptive-integration-on-infinite-intervals) provides adaptive integration over the infinite interval from negative infinity to positive infinity. `Numo::GSL::Integration.qagi` takes no interval endpoints and returns the integral and its estimated absolute error.
|
|
88
|
+
```ruby
|
|
89
|
+
result, error = Numo::GSL::Integration.qagi do |x|
|
|
90
|
+
Math.exp(-(x * x))
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
The example integrates the Gaussian function, whose exact value is `Math.sqrt(Math::PI)`. The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals:
|
|
94
|
+
```ruby
|
|
95
|
+
result, error = Numo::GSL::Integration.qagi(
|
|
96
|
+
epsabs: 1e-12,
|
|
97
|
+
epsrel: 1e-12,
|
|
98
|
+
limit: 100
|
|
99
|
+
) do |x|
|
|
100
|
+
1.0 / (1.0 + (x * x))
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
## QAGIU integration
|
|
104
|
+
GSL [QAGIU](https://www.gnu.org/software/gsl/doc/html/integration.html#qagi-adaptive-integration-on-infinite-intervals) provides adaptive integration from a finite lower bound to positive infinity. `Numo::GSL::Integration.qagiu` takes the lower bound and returns the integral and its estimated absolute error.
|
|
105
|
+
```ruby
|
|
106
|
+
result, error = Numo::GSL::Integration.qagiu(0.0) do |x|
|
|
107
|
+
Math.exp(-x)
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
The example integral has an exact value of `1.0`. The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals:
|
|
111
|
+
```ruby
|
|
112
|
+
result, error = Numo::GSL::Integration.qagiu(
|
|
113
|
+
1.0,
|
|
114
|
+
epsabs: 1e-12,
|
|
115
|
+
epsrel: 1e-12,
|
|
116
|
+
limit: 100
|
|
117
|
+
) do |x|
|
|
118
|
+
Math.exp(-x)
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
## QAGIL integration
|
|
122
|
+
GSL [QAGIL](https://www.gnu.org/software/gsl/doc/html/integration.html#qagi-adaptive-integration-on-infinite-intervals) provides adaptive integration from negative infinity to a finite upper bound. `Numo::GSL::Integration.qagil` takes the upper bound and returns the integral and its estimated absolute error.
|
|
123
|
+
```ruby
|
|
124
|
+
result, error = Numo::GSL::Integration.qagil(0.0) do |x|
|
|
125
|
+
Math.exp(x)
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
The example integral has an exact value of `1.0`. The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals:
|
|
129
|
+
```ruby
|
|
130
|
+
result, error = Numo::GSL::Integration.qagil(
|
|
131
|
+
-1.0,
|
|
132
|
+
epsabs: 1e-12,
|
|
133
|
+
epsrel: 1e-12,
|
|
134
|
+
limit: 100
|
|
135
|
+
) do |x|
|
|
136
|
+
Math.exp(x)
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
## QAWC integration
|
|
140
|
+
GSL [QAWC](https://www.gnu.org/software/gsl/doc/html/integration.html#qawc-adaptive-integration-for-cauchy-principal-values) computes the Cauchy principal value (PV) of an integral over a finite interval with a singularity at $c$:
|
|
141
|
+
```math
|
|
142
|
+
PV \int_a^b \frac{f(x)}{x-c}\,dx
|
|
143
|
+
=
|
|
144
|
+
\lim_{\varepsilon\to 0^+}
|
|
145
|
+
\left(
|
|
146
|
+
\int_a^{c-\varepsilon}\frac{f(x)}{x-c}\,dx
|
|
147
|
+
+
|
|
148
|
+
\int_{c+\varepsilon}^b\frac{f(x)}{x-c}\,dx
|
|
149
|
+
\right)
|
|
150
|
+
```
|
|
151
|
+
The block passed to `Numo::GSL::Integration.qawc` returns $f(x)$. Do not divide the block result by $x - c$; QAWC applies that weight internally.
|
|
152
|
+
The following example uses the default integration options and computes the principal value of `x / (x - 1.0)` over the interval from `0.0` to `2.0`, whose exact value is `2.0`:
|
|
153
|
+
```ruby
|
|
154
|
+
result, error = Numo::GSL::Integration.qawc(0.0, 2.0, 1.0) do |x|
|
|
155
|
+
x
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals. The following example computes the principal value of `1.0 / x` over the symmetric interval from `-1.0` to `1.0`, whose exact value is `0.0`. It specifies a positive `epsabs` because a relative error bound alone is not suitable when the expected result is zero:
|
|
159
|
+
```ruby
|
|
160
|
+
result, error = Numo::GSL::Integration.qawc(
|
|
161
|
+
-1.0,
|
|
162
|
+
1.0,
|
|
163
|
+
0.0,
|
|
164
|
+
epsabs: 1e-12,
|
|
165
|
+
epsrel: 1e-12,
|
|
166
|
+
limit: 100
|
|
167
|
+
) do |_x|
|
|
168
|
+
1.0
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
## QAWS integration
|
|
172
|
+
GSL [QAWS](https://www.gnu.org/software/gsl/doc/html/integration.html#qaws-adaptive-integration-for-singular-functions) provides adaptive integration for functions with algebraic or logarithmic singularities at the endpoints of a finite interval. It computes an integral with the weight
|
|
173
|
+
```math
|
|
174
|
+
(x-a)^\alpha (b-x)^\beta
|
|
175
|
+
\log^\mu(x-a) \log^\nu(b-x).
|
|
176
|
+
```
|
|
177
|
+
Call `Numo::GSL::Integration.qaws` with the endpoints `a` and `b`, followed by `alpha`, `beta`, `mu`, and `nu`. The exponents `alpha` and `beta` must be greater than `-1.0`. The logarithmic powers `mu` and `nu` must each be `0` or `1`.
|
|
178
|
+
The block returns the unweighted function $f(x)$. QAWS applies the endpoint weight internally. The following example integrates
|
|
179
|
+
```math
|
|
180
|
+
\frac{1}{\sqrt{x(1-x)}}
|
|
181
|
+
```
|
|
182
|
+
over the interval from `0.0` to `1.0`. Its exact value is `Math::PI`:
|
|
183
|
+
```ruby
|
|
184
|
+
result, error = Numo::GSL::Integration.qaws(
|
|
185
|
+
0.0,
|
|
186
|
+
1.0,
|
|
187
|
+
-0.5,
|
|
188
|
+
-0.5,
|
|
189
|
+
0,
|
|
190
|
+
0
|
|
191
|
+
) do |_x|
|
|
192
|
+
1.0
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
Set `mu` to `1` to include `log(x - a)`, and set `nu` to `1` to include `log(b - x)`. The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and maximum number of subintervals:
|
|
196
|
+
```ruby
|
|
197
|
+
result, error = Numo::GSL::Integration.qaws(
|
|
198
|
+
0.0,
|
|
199
|
+
1.0,
|
|
200
|
+
0.0,
|
|
201
|
+
0.0,
|
|
202
|
+
1,
|
|
203
|
+
0,
|
|
204
|
+
epsabs: 1e-12,
|
|
205
|
+
epsrel: 1e-12,
|
|
206
|
+
limit: 100
|
|
207
|
+
) do |_x|
|
|
208
|
+
1.0
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
This example computes the integral of `log(x)` from `0.0` to `1.0`, whose exact value is `-1.0`.
|
|
212
|
+
|
|
213
|
+
## QAWO integration
|
|
214
|
+
|
|
215
|
+
GSL [QAWO](https://www.gnu.org/software/gsl/doc/html/integration.html#qawo-adaptive-integration-for-oscillatory-functions) provides adaptive integration for finite-interval integrals with a sine or cosine weight. The block returns the unweighted function; QAWO applies the oscillatory weight internally.
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
result, error = Numo::GSL::Integration.qawo(
|
|
219
|
+
0.0,
|
|
220
|
+
Math::PI,
|
|
221
|
+
1.0,
|
|
222
|
+
Numo::GSL::Integration::SINE
|
|
223
|
+
) do |_x|
|
|
224
|
+
1.0
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The `epsabs`, `epsrel`, and `limit` keyword arguments control the requested error limits and workspace capacity.
|
|
229
|
+
|
|
230
|
+
## QAWF integration
|
|
231
|
+
|
|
232
|
+
GSL [QAWF](https://www.gnu.org/software/gsl/doc/html/integration.html#qawf-adaptive-integration-for-fourier-integrals) provides adaptive integration for sine- or cosine-weighted Fourier integrals over a semi-infinite interval.
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
result, error = Numo::GSL::Integration.qawf(
|
|
236
|
+
0.0,
|
|
237
|
+
1.0,
|
|
238
|
+
Numo::GSL::Integration::COSINE
|
|
239
|
+
) do |x|
|
|
240
|
+
Math.exp(-x)
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The `epsabs` keyword controls the requested absolute error. The `limit` keyword controls the maximum number of Fourier cycles and the allocated workspace capacity.
|
|
245
|
+
|
|
246
|
+
## CQUAD integration
|
|
247
|
+
|
|
248
|
+
GSL [CQUAD](https://www.gnu.org/software/gsl/doc/html/integration.html#cquad-doubly-adaptive-integration) provides doubly-adaptive integration over a finite interval. It is intended for difficult integrands, including functions with integrable singularities. The return value contains the integral, its estimated absolute error, and the number of integrand evaluations.
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
result, error, evaluations = Numo::GSL::Integration.cquad(0.0, 1.0) do |x|
|
|
252
|
+
Math.log(x) / Math.sqrt(x)
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The example integral has an endpoint singularity and an exact value of `-4.0`. The `epsabs` and `epsrel` keyword arguments control the requested error limits. The `limit` keyword controls the number of intervals retained in the CQUAD workspace and defaults to `100`; GSL requires at least `3`.
|
|
257
|
+
|
|
258
|
+
## Romberg integration
|
|
259
|
+
|
|
260
|
+
GSL [Romberg integration](https://www.gnu.org/software/gsl/doc/html/integration.html#romberg-integration) applies Richardson extrapolation to successive trapezoidal approximations. It is intended for smooth functions over a finite interval. The return value contains the integral and the number of integrand evaluations.
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
result, evaluations = Numo::GSL::Integration.romberg(0.0, Math::PI) do |x|
|
|
264
|
+
Math.sin(x)
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The example integral has an exact value of `2.0`. The `epsabs` and `epsrel` keyword arguments control the requested error limits. The `limit` keyword controls the maximum number of Romberg levels and defaults to `20`. GSL requires at least one level and limits the effective workspace to 30 levels.
|
|
269
|
+
|
|
270
|
+
Romberg integration does not return an estimated absolute error. Use CQUAD, QAG, QAGS, or another routine that returns an error estimate when the caller needs one.
|
|
271
|
+
|
|
272
|
+
## GLFIXED integration
|
|
273
|
+
|
|
274
|
+
GSL [fixed-order Gauss–Legendre integration](https://www.gnu.org/software/gsl/doc/html/integration.html#gauss-legendre-integration) provides fast integration of smooth functions when an appropriate polynomial order is known. An `n`-point rule integrates polynomials of degree `2 * n - 1` or less exactly, subject to floating-point precision.
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
result = Numo::GSL::Integration.glfixed(
|
|
278
|
+
0.0,
|
|
279
|
+
1.0,
|
|
280
|
+
order: 3
|
|
281
|
+
) do |x|
|
|
282
|
+
x**4
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The example integral has an exact value of `1.0 / 5.0`. The `order` keyword controls the number of Gauss–Legendre points and defaults to `10`. It must be a positive integer.
|
|
287
|
+
|
|
288
|
+
GLFIXED returns only the integral. It does not accept absolute or relative error bounds and does not return an error estimate. Use an adaptive routine when the required fixed order is not known in advance.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#include "integration_internal.h"
|
|
2
|
+
|
|
3
|
+
static VALUE integration_call_block(VALUE data) {
|
|
4
|
+
integration_callback_params *params = (integration_callback_params *)data;
|
|
5
|
+
VALUE value;
|
|
6
|
+
|
|
7
|
+
value = rb_funcall(params->block, rb_intern("call"), 1, params->x);
|
|
8
|
+
|
|
9
|
+
return rb_Float(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
void integration_callback_params_init(integration_callback_params *params) {
|
|
13
|
+
params->block = rb_block_proc();
|
|
14
|
+
params->x = Qnil;
|
|
15
|
+
params->exception = Qnil;
|
|
16
|
+
params->state = 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void integration_function_init(gsl_function *function,
|
|
20
|
+
integration_callback_params *params) {
|
|
21
|
+
integration_callback_params_init(params);
|
|
22
|
+
function->function = integration_callback;
|
|
23
|
+
function->params = params;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
double integration_callback(double x, void *data) {
|
|
27
|
+
integration_callback_params *params = data;
|
|
28
|
+
VALUE value;
|
|
29
|
+
|
|
30
|
+
if (params->state != 0) {
|
|
31
|
+
return 0.0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
params->x = DBL2NUM(x);
|
|
35
|
+
value = rb_protect(integration_call_block, (VALUE)params, ¶ms->state);
|
|
36
|
+
|
|
37
|
+
if (params->state != 0) {
|
|
38
|
+
params->exception = rb_errinfo();
|
|
39
|
+
return 0.0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return NUM2DBL(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void integration_raise_callback_exception(integration_callback_params *params) {
|
|
46
|
+
if (params->state != 0) {
|
|
47
|
+
rb_exc_raise(params->exception);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void integration_finish_callback(integration_callback_params *params,
|
|
52
|
+
int state) {
|
|
53
|
+
integration_raise_callback_exception(params);
|
|
54
|
+
if (state != 0) {
|
|
55
|
+
rb_jump_tag(state);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
VALUE integration_error_class(void) {
|
|
60
|
+
return rb_path2class("Numo::GSL::Integration::Error");
|
|
61
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#include "integration_internal.h"
|
|
2
|
+
|
|
3
|
+
typedef struct {
|
|
4
|
+
gsl_function *function;
|
|
5
|
+
double a;
|
|
6
|
+
double b;
|
|
7
|
+
double epsabs;
|
|
8
|
+
double epsrel;
|
|
9
|
+
size_t limit;
|
|
10
|
+
gsl_integration_cquad_workspace *workspace;
|
|
11
|
+
double *result;
|
|
12
|
+
double *error;
|
|
13
|
+
size_t *evaluations;
|
|
14
|
+
} integration_cquad_params;
|
|
15
|
+
|
|
16
|
+
static VALUE integration_run_cquad(VALUE data) {
|
|
17
|
+
integration_cquad_params *params = (integration_cquad_params *)data;
|
|
18
|
+
|
|
19
|
+
(void)gsl_integration_cquad(
|
|
20
|
+
params->function, params->a, params->b, params->epsabs, params->epsrel,
|
|
21
|
+
params->workspace, params->result, params->error, params->evaluations);
|
|
22
|
+
|
|
23
|
+
return Qnil;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static VALUE integration_run_cquad_with_workspace(VALUE data) {
|
|
27
|
+
integration_cquad_params *params = (integration_cquad_params *)data;
|
|
28
|
+
|
|
29
|
+
return rb_ensure(integration_run_cquad, data,
|
|
30
|
+
integration_free_cquad_workspace, (VALUE)params->workspace);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
VALUE integration_cquad(int argc, VALUE *argv, VALUE self) {
|
|
34
|
+
VALUE a;
|
|
35
|
+
VALUE b;
|
|
36
|
+
VALUE options;
|
|
37
|
+
integration_options parsed_options;
|
|
38
|
+
double epsabs;
|
|
39
|
+
double epsrel;
|
|
40
|
+
size_t limit;
|
|
41
|
+
gsl_integration_cquad_workspace *workspace;
|
|
42
|
+
gsl_function function;
|
|
43
|
+
integration_callback_params params;
|
|
44
|
+
integration_cquad_params cquad_params;
|
|
45
|
+
double result;
|
|
46
|
+
double error;
|
|
47
|
+
size_t evaluations;
|
|
48
|
+
int state = 0;
|
|
49
|
+
|
|
50
|
+
(void)self;
|
|
51
|
+
|
|
52
|
+
rb_scan_args(argc, argv, "2:", &a, &b, &options);
|
|
53
|
+
|
|
54
|
+
integration_options_parse(options, &parsed_options, 0.0, 1e-8, 100);
|
|
55
|
+
epsabs = parsed_options.epsabs;
|
|
56
|
+
epsrel = parsed_options.epsrel;
|
|
57
|
+
limit = parsed_options.limit;
|
|
58
|
+
|
|
59
|
+
integration_function_init(&function, ¶ms);
|
|
60
|
+
|
|
61
|
+
workspace = gsl_integration_cquad_workspace_alloc(limit);
|
|
62
|
+
|
|
63
|
+
cquad_params.function = &function;
|
|
64
|
+
cquad_params.a = NUM2DBL(a);
|
|
65
|
+
cquad_params.b = NUM2DBL(b);
|
|
66
|
+
cquad_params.epsabs = epsabs;
|
|
67
|
+
cquad_params.epsrel = epsrel;
|
|
68
|
+
cquad_params.limit = limit;
|
|
69
|
+
cquad_params.workspace = workspace;
|
|
70
|
+
cquad_params.result = &result;
|
|
71
|
+
cquad_params.error = &error;
|
|
72
|
+
cquad_params.evaluations = &evaluations;
|
|
73
|
+
|
|
74
|
+
rb_protect(integration_run_cquad_with_workspace, (VALUE)&cquad_params,
|
|
75
|
+
&state);
|
|
76
|
+
|
|
77
|
+
RB_GC_GUARD(params.block);
|
|
78
|
+
RB_GC_GUARD(params.exception);
|
|
79
|
+
|
|
80
|
+
integration_finish_callback(¶ms, state);
|
|
81
|
+
|
|
82
|
+
return rb_ary_new_from_args(3, DBL2NUM(result), DBL2NUM(error),
|
|
83
|
+
SIZET2NUM(evaluations));
|
|
84
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
gsl_config = find_executable("gsl-config")
|
|
6
|
+
abort("gsl-config is required") unless gsl_config
|
|
7
|
+
|
|
8
|
+
$CFLAGS << " #{`#{gsl_config} --cflags`.strip}"
|
|
9
|
+
$libs = "#{`#{gsl_config} --libs`.strip} #{$libs}"
|
|
10
|
+
|
|
11
|
+
abort("gsl/gsl_integration.h is required") unless have_header("gsl/gsl_integration.h")
|
|
12
|
+
|
|
13
|
+
create_makefile("numo/gsl/integration/integration")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#include "integration_internal.h"
|
|
2
|
+
|
|
3
|
+
typedef struct {
|
|
4
|
+
gsl_function *function;
|
|
5
|
+
double a;
|
|
6
|
+
double b;
|
|
7
|
+
size_t order;
|
|
8
|
+
gsl_integration_glfixed_table *table;
|
|
9
|
+
double *result;
|
|
10
|
+
} integration_glfixed_params;
|
|
11
|
+
|
|
12
|
+
static VALUE integration_run_glfixed(VALUE data) {
|
|
13
|
+
integration_glfixed_params *params = (integration_glfixed_params *)data;
|
|
14
|
+
|
|
15
|
+
*(params->result) = gsl_integration_glfixed(params->function, params->a,
|
|
16
|
+
params->b, params->table);
|
|
17
|
+
|
|
18
|
+
return Qnil;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static VALUE integration_run_glfixed_with_table(VALUE data) {
|
|
22
|
+
integration_glfixed_params *params = (integration_glfixed_params *)data;
|
|
23
|
+
|
|
24
|
+
return rb_ensure(integration_run_glfixed, data,
|
|
25
|
+
integration_free_glfixed_table, (VALUE)params->table);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
VALUE integration_glfixed(int argc, VALUE *argv, VALUE self) {
|
|
29
|
+
VALUE a;
|
|
30
|
+
VALUE b;
|
|
31
|
+
VALUE options;
|
|
32
|
+
VALUE order_value = Qundef;
|
|
33
|
+
ID keywords[] = {rb_intern("order")};
|
|
34
|
+
VALUE keyword_values[1];
|
|
35
|
+
size_t order;
|
|
36
|
+
gsl_function function;
|
|
37
|
+
integration_callback_params params;
|
|
38
|
+
integration_glfixed_params glfixed_params;
|
|
39
|
+
double result;
|
|
40
|
+
int state = 0;
|
|
41
|
+
|
|
42
|
+
(void)self;
|
|
43
|
+
|
|
44
|
+
rb_scan_args(argc, argv, "2:", &a, &b, &options);
|
|
45
|
+
|
|
46
|
+
if (!NIL_P(options)) {
|
|
47
|
+
rb_get_kwargs(options, keywords, 0, 1, keyword_values);
|
|
48
|
+
order_value = keyword_values[0];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
order = order_value == Qundef ? 10 : NUM2SIZET(order_value);
|
|
52
|
+
if (order == 0) {
|
|
53
|
+
rb_raise(integration_error_class(), "order must be positive");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
integration_function_init(&function, ¶ms);
|
|
57
|
+
|
|
58
|
+
glfixed_params.function = &function;
|
|
59
|
+
glfixed_params.a = NUM2DBL(a);
|
|
60
|
+
glfixed_params.b = NUM2DBL(b);
|
|
61
|
+
glfixed_params.order = order;
|
|
62
|
+
glfixed_params.table = gsl_integration_glfixed_table_alloc(order);
|
|
63
|
+
glfixed_params.result = &result;
|
|
64
|
+
|
|
65
|
+
rb_protect(integration_run_glfixed_with_table, (VALUE)&glfixed_params,
|
|
66
|
+
&state);
|
|
67
|
+
|
|
68
|
+
RB_GC_GUARD(params.block);
|
|
69
|
+
RB_GC_GUARD(params.exception);
|
|
70
|
+
|
|
71
|
+
integration_finish_callback(¶ms, state);
|
|
72
|
+
|
|
73
|
+
return DBL2NUM(result);
|
|
74
|
+
}
|