-
-
Notifications
You must be signed in to change notification settings - Fork 30
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
add Trapezoidal rule #173
add Trapezoidal rule #173
Changes from all commits
32e438e
881804b
afc0f12
ade9f6b
7265f24
c4a9208
efaf2e1
913f7f7
aa677c4
e7aefc9
608af76
2eb0090
346c0ac
9ab8ab2
6ba2e3c
813a460
7c3d710
65cf88d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Integrating pre-sampled data | ||
|
||
In some cases, instead of a function that acts as integrand, | ||
one only possesses a list of data points `y` at a set of sampling | ||
locations `x`, that must be integrated. This package contains functionality | ||
for doing that. | ||
|
||
## Example | ||
|
||
Say, by some means we have generated a dataset `x` and `y`: | ||
```example 1 | ||
using Integrals # hide | ||
f = x -> x^2 | ||
x = range(0, 1, length=20) | ||
y = f.(x) | ||
``` | ||
|
||
Now, we can integrate this data set as follows: | ||
|
||
```example 1 | ||
problem = SampledIntegralProblem(y, x) | ||
method = TrapezoidalRule() | ||
solve(problem, method) | ||
``` | ||
|
||
The exact aswer is of course \$ 1/3 \$. | ||
|
||
## Details | ||
|
||
### Non-equidistant grids | ||
|
||
If the sampling points `x` are provided as an `AbstractRange` | ||
(constructed with the `range` function for example), faster methods are used that take advantage of | ||
the fact that the points are equidistantly spaced. Otherwise, general methods are used for | ||
non-uniform grids. | ||
|
||
Example: | ||
|
||
```example 2 | ||
using Integrals # hide | ||
f = x -> x^7 | ||
x = [0.0; sort(rand(1000)); 1.0] | ||
y = f.(x) | ||
problem = SampledIntegralProblem(y, x) | ||
method = TrapezoidalRule() | ||
solve(problem, method) | ||
``` | ||
|
||
### Evaluating multiple integrals at once | ||
|
||
If the provided data set `y` is a multidimensional array, the integrals are evaluated across only one | ||
of its axes. For performance reasons, the last axis of the array `y` is chosen by default, but this can be modified with the `dim` | ||
keyword argument to the problem definition. | ||
|
||
```example 3 | ||
using Integrals # hide | ||
f1 = x -> x^2 | ||
f2 = x -> x^3 | ||
f3 = x -> x^4 | ||
x = range(0, 1, length=20) | ||
y = [f1.(x) f2.(x) f3.(x)] | ||
problem = SampledIntegralProblem(y, x; dim=1) | ||
method = TrapezoidalRule() | ||
solve(problem, method) | ||
``` | ||
|
||
### Supported methods | ||
|
||
Right now, only the `TrapezoidalRule` is supported, [see wikipedia](https://en.wikipedia.org/wiki/Trapezoidal_rule). | ||
|
||
```@docs | ||
TrapezoidalRule | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,3 +94,65 @@ end | |
function __solvebp_call(cache::IntegralCache, args...; kwargs...) | ||
__solvebp_call(build_problem(cache), args...; kwargs...) | ||
end | ||
|
||
|
||
mutable struct SampledIntegralCache{Y, X, D, PK, A, K, Tc} | ||
y::Y | ||
x::X | ||
dim::D | ||
prob_kwargs::PK | ||
alg::A | ||
kwargs::K | ||
isfresh::Bool # state of whether weights have been calculated | ||
cacheval::Tc # store alg weights here | ||
end | ||
|
||
function Base.setproperty!(cache::SampledIntegralCache, name::Symbol, x) | ||
if name === :x | ||
setfield!(cache, :isfresh, true) | ||
end | ||
setfield!(cache, name, x) | ||
end | ||
|
||
function SciMLBase.init(prob::SampledIntegralProblem, | ||
alg::SciMLBase.AbstractIntegralAlgorithm; | ||
kwargs...) | ||
NamedTuple(kwargs) == NamedTuple() || throw(ArgumentError("There are no keyword arguments allowed to `solve`")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's odd, why not? There are many keyword arguments that would go here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lxvm is there a specific reason why not, or did you just not expect it to ever be necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of the solver doesn't use any keyword arguments, so I didn't want an api with keywords. If future algorithms for sampled integral problems need them I would expect this to change, but there are no convergence criteria for this kind of problem, so I wasn't expecting any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh just the sampled data methods. Okay, yeah for now might as well throw. We always try to throw if keyword arguments that are incorrect so this is good. |
||
|
||
cacheval = init_cacheval(alg, prob) | ||
isfresh = true | ||
|
||
SampledIntegralCache( | ||
prob.y, | ||
prob.x, | ||
prob.dim, | ||
prob.kwargs, | ||
alg, | ||
kwargs, | ||
isfresh, | ||
cacheval) | ||
end | ||
|
||
|
||
""" | ||
```julia | ||
solve(prob::SampledIntegralProblem, alg::SciMLBase.AbstractIntegralAlgorithm; kwargs...) | ||
``` | ||
|
||
## Keyword Arguments | ||
|
||
There are no keyword arguments used to solve `SampledIntegralProblem`s | ||
""" | ||
function SciMLBase.solve(prob::SampledIntegralProblem, | ||
alg::SciMLBase.AbstractIntegralAlgorithm; | ||
kwargs...) | ||
solve!(init(prob, alg; kwargs...)) | ||
end | ||
|
||
function SciMLBase.solve!(cache::SampledIntegralCache) | ||
__solvebp(cache, cache.alg; cache.kwargs...) | ||
end | ||
|
||
function build_problem(cache::SampledIntegralCache) | ||
SampledIntegralProblem(cache.y, cache.x; dim = dimension(cache.dim), cache.prob_kwargs...) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
abstract type AbstractWeights end | ||
|
||
# must have field `n` for length, and a field `h` for stepsize | ||
abstract type UniformWeights <: AbstractWeights end | ||
@inline Base.iterate(w::UniformWeights) = (0 == w.n) ? nothing : (w[1], 1) | ||
@inline Base.iterate(w::UniformWeights, i) = (i == w.n) ? nothing : (w[i+1], i+1) | ||
Base.length(w::UniformWeights) = w.n | ||
Base.eltype(w::UniformWeights) = typeof(w.h) | ||
Base.size(w::UniformWeights) = (length(w), ) | ||
|
||
# must contain field `x` which are the sampling points | ||
abstract type NonuniformWeights <: AbstractWeights end | ||
@inline Base.iterate(w::NonuniformWeights) = (0 == length(w.x)) ? nothing : (w[firstindex(w.x)], firstindex(w.x)) | ||
@inline Base.iterate(w::NonuniformWeights, i) = (i == lastindex(w.x)) ? nothing : (w[i+1], i+1) | ||
Base.length(w::NonuniformWeights) = length(w.x) | ||
Base.eltype(w::NonuniformWeights) = eltype(w.x) | ||
Base.size(w::NonuniformWeights) = (length(w), ) | ||
|
||
_eachslice(data::AbstractArray; dims=ndims(data)) = eachslice(data; dims=dims) | ||
_eachslice(data::AbstractArray{T, 1}; dims=ndims(data)) where T = data | ||
|
||
|
||
# these can be removed when the Val(dim) is removed from SciMLBase | ||
dimension(::Val{D}) where {D} = D | ||
dimension(D::Int) = D | ||
|
||
|
||
function evalrule(data::AbstractArray, weights, dim) | ||
fw = zip(_eachslice(data, dims=dim), weights) | ||
next = iterate(fw) | ||
next === nothing && throw(ArgumentError("No points to integrate")) | ||
(f1, w1), state = next | ||
out = w1 * f1 | ||
next = iterate(fw, state) | ||
if isbits(out) | ||
while next !== nothing | ||
(fi, wi), state = next | ||
out += wi * fi | ||
next = iterate(fw, state) | ||
end | ||
else | ||
while next !== nothing | ||
(fi, wi), state = next | ||
out .+= wi .* fi | ||
next = iterate(fw, state) | ||
end | ||
end | ||
return out | ||
end | ||
|
||
|
||
# can be reused for other sampled rules, which should implement find_weights(x, alg) | ||
|
||
function init_cacheval(alg::SciMLBase.AbstractIntegralAlgorithm, prob::SampledIntegralProblem) | ||
find_weights(prob.x, alg) | ||
end | ||
|
||
function __solvebp_call(cache::SampledIntegralCache, alg::SciMLBase.AbstractIntegralAlgorithm; kwargs...) | ||
dim = dimension(cache.dim) | ||
err = nothing | ||
data = cache.y | ||
grid = cache.x | ||
if cache.isfresh | ||
cache.cacheval = find_weights(grid, alg) | ||
cache.isfresh = false | ||
end | ||
weights = cache.cacheval | ||
I = evalrule(data, weights, dim) | ||
prob = build_problem(cache) | ||
return SciMLBase.build_solution(prob, alg, I, err, retcode = ReturnCode.Success) | ||
end |
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.
oh that's pretty cool.