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

Implementing CSRK code in BSeries.jl #144

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7853dcb
comentario
May 17, 2023
aaf5940
Merge branch 'main' into Energy_Preserving
Sondar74 May 21, 2023
afe7373
Merge branch 'main' of github.com:Sondar74/BSeries.jl into main
Sondar74 Jun 26, 2023
e4d79cc
updated 'main' branch and created new branch 'csrkupdated'. This one …
Sondar74 Jun 26, 2023
823684d
deleted extra file
Sondar74 Jun 26, 2023
0df736b
fixing 'Spell Check'
Sondar74 Jun 26, 2023
09d4629
added docstrings and removed elementary_differentials_csrk from export
Sondar74 Jun 27, 2023
335265f
modified the comments in the functions 'PolynomialA' and 'elementary_…
Sondar74 Jun 27, 2023
8c4e0b1
Modified the function 'bseries' for CSRK
Sondar74 Jun 27, 2023
7ad5325
exported 'ContinuousStageRungeKuttaMethod'
Sondar74 Jun 27, 2023
5d82c87
added one test, and modified the function 'bseries' for CSRK
Sondar74 Jun 27, 2023
c0a724d
Spell Check
Sondar74 Jun 27, 2023
c45de1c
Update src/BSeries.jl
Sondar74 Jun 28, 2023
6616b9c
Update src/BSeries.jl
Sondar74 Jun 28, 2023
1227e1a
Update src/BSeries.jl
Sondar74 Jun 28, 2023
3691f69
Update src/BSeries.jl
Sondar74 Jun 28, 2023
68cecb0
Update test/runtests.jl
Sondar74 Jun 28, 2023
5d30865
updating docstring
Sondar74 Jun 28, 2023
7387ffc
Merge branch 'csrkupdated' of github.com:Sondar74/BSeries.jl into csr…
Sondar74 Jun 28, 2023
53fd493
fixed the values of bseries()
Sondar74 Jun 28, 2023
d6a23d6
added test for floating-point and symbolic coefficientes using SymPy …
Sondar74 Jun 28, 2023
c0ba86f
added test for Symbolics.jl: same as SymEngine
Sondar74 Jun 28, 2023
b5813d2
Update test/runtests.jl
Sondar74 Jun 28, 2023
691507f
Update test/runtests.jl
Sondar74 Jun 28, 2023
02acff4
Update test/runtests.jl
Sondar74 Jun 28, 2023
fd1a23e
added Energy_Preserving test
Sondar74 Jun 28, 2023
0c29499
Merge branch 'csrkupdated' of github.com:Sondar74/BSeries.jl into csr…
Sondar74 Jun 28, 2023
c8113e6
fixed the funtion bseries(csrk, order)
Sondar74 Jul 10, 2023
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
176 changes: 175 additions & 1 deletion src/BSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ end

using Latexify: Latexify, LaTeXString
using Combinatorics: Combinatorics, permutations
using LinearAlgebra: LinearAlgebra, rank
using LinearAlgebra: LinearAlgebra, rank, dot
using SparseArrays: SparseArrays, sparse
using SymPy

@reexport using Polynomials: Polynomials, Polynomial

Expand All @@ -40,6 +41,8 @@ export renormalize!

export is_energy_preserving, energy_preserving_order

export ContinuousStageRungeKuttaMethod

# Types used for traits
# These traits may decide between different algorithms based on the
# corresponding complexity etc.
Expand Down Expand Up @@ -74,6 +77,7 @@ end

TruncatedBSeries{T, V}() where {T, V} = TruncatedBSeries{T, V}(OrderedDict{T, V}())


# general interface methods of `AbstractDict` for `TruncatedBSeries`
@inline Base.iterate(series::TruncatedBSeries) = iterate(series.coef)
@inline Base.iterate(series::TruncatedBSeries, state) = iterate(series.coef, state)
Expand Down Expand Up @@ -612,6 +616,94 @@ function bseries(ros::RosenbrockMethod, order)
return series
end

"""
ContinuousStageRungeKuttaMethod

A struct that describes a CSRK method. This kind of `struct` should be constructed
via [ContinuousStageRungeKuttaMethod]
`csrk = ContinuousStageRungeKuttaMethod(M)`
in order to later call the 'bseries' function.

# References

- Yuto Miyatake and John C. Butcher.
"A characterization of energy-preserving methods and the construction of
parallel integrators for Hamiltonian systems."
SIAM Journal on Numerical Analysis 54, no. 3 (2016):
[DOI: 10.1137/15M1020861](https://doi.org/10.1137/15M1020861)
"""
struct ContinuousStageRungeKuttaMethod{MatT <: AbstractMatrix}
matrix::MatT
end


"""
bseries(csrk::ContinuousStageRungeKuttaMethod, order)

Compute the B-series of the [`ContinuousStageRungeKuttaMethod`](@ref) `csrk`
up to the prescribed integer `order` as described by Miyatake & Butcher (2015).

!!! note "Normalization by elementary differentials"
The coefficients of the B-series returned by this method need to be
multiplied by a power of the time step divided by the `symmetry` of the
rooted tree and multiplied by the corresponding elementary differential
of the input vector field ``f``.
See also [`evaluate`](@ref).

# Example:
The energy-preserving 4x4 matrix given by Miyatake & Butcher (2015) is
```
M = [-6//5 72//5 -36//1 24//1;
72//5 -144//5 -48//1 72//1;
-36//1 -48//1 720//1 -720//1;
24//1 72//1 -720//1 720//1]
```

Then, we calculate the B-series with the following code:

```
csrk = CSRK(M)
series = bseries(csrk, 4)
TruncatedBSeries{RootedTree{Int64, Vector{Int64}}, Rational{Int64}} with 9 entries:
RootedTree{Int64}: Int64[] => 1//1
RootedTree{Int64}: [1] => 1//1
RootedTree{Int64}: [1, 2] => 1//2
RootedTree{Int64}: [1, 2, 3] => 1//6
RootedTree{Int64}: [1, 2, 2] => 1//3
RootedTree{Int64}: [1, 2, 3, 4] => 1//24
RootedTree{Int64}: [1, 2, 3, 3] => 1//12
RootedTree{Int64}: [1, 2, 3, 2] => 1//8
RootedTree{Int64}: [1, 2, 2, 2] => 1//4

```
# References
- Yuto Miyatake and John C. Butcher.
"A characterization of energy-preserving methods and the construction of
parallel integrators for Hamiltonian systems."
SIAM Journal on Numerical Analysis 54, no. 3 (2016):
[DOI: 10.1137/15M1020861](https://doi.org/10.1137/15M1020861)
"""
function bseries(csrk::ContinuousStageRungeKuttaMethod, order)
csrk = csrk.matrix
V_tmp = eltype(csrk)
if V_tmp <: Integer
# If people use integer coefficients, they will likely want to have results
# as exact as possible. However, general terms are not integers. Thus, we
# use rationals instead.
V = Rational{V_tmp}
else
V = V_tmp
end
series = TruncatedBSeries{RootedTree{Int, Vector{Int}}, V}()
series[rootedtree(Int[])] = one(V)
for o in 1:order
for t in RootedTreeIterator(o)
series[copy(t)] = N(elementary_differentials_csrk(csrk, t))
end
end
return series
end

# TODO: bseries(ros::RosenbrockMethod)
# should create a lazy version, optionally a memoized one

Expand Down Expand Up @@ -2016,4 +2108,86 @@ function equivalent_trees(tree)
return equivalent_trees_set
end


# This function generates a polynomial
# A_{t,z} = [t,t^2/2,..., t^s/s]*M*[1, z, ..., z^(s-1)]^T
# for a given square matrix M of dimension s and chars 't' and 'z'.
function PolynomialA(M,t,z)
# get the dimension of the matrix
s = size(M,1)
# we need symbolic variables to work with
variable1 = Sym(t)
variable2 = Sym(z)
# conjugate the variable 1, since this will be the variable of the left polynomial
# and the function 'dot' assumes it to be conjugated
variable1 = conjugate(variable1)
# generate the components of the polynomial with powers of t
poli_z = Array{SymPy.Sym}(undef, s)
for i in 1:s
poli_z[i] = variable2^(i-1)
end
# generate the components of the polynomial with powers of z
poli_t = Array{SymPy.Sym}(undef, s)
for i in 1:s
poli_t[i] = (1 // i)*(variable1^i)
end
# multiply matrix times vector
result = M * poli_z
# use dot product for the two vectors
return dot(poli_t,result)
end


"""
elementary_differentials_csrk(M,tree)

This function calculates the CSRK elementary differential for a given
square matrix 'M' and a given RootedTree according to [@ref].

# References
Butcher, John & Miyatake, Yuto. (2015). A Characterization of Energy-Preserving
Methods and the Construction of Parallel Integrators for Hamiltonian Systems.
SIAM Journal on Numerical Analysis. 54. 10.1137/15M1020861.
(https://www.researchgate.net/publication/276211444_A_Characterization_of_Energy-Preserving_Methods_and_the_Construction_of_Parallel_Integrators_for_Hamiltonian_Systems)

"""
function elementary_differentials_csrk(M,rootedtree)
# we extract the level_sequence of 'rootedtree'
tree = rootedtree.level_sequence
m = maximum(tree)
l = length(tree)
# Since we will integrate with symbolic variables for
# every node in the tree, we create the variables called
# 'xi' for 1 <= i <= m, because m is the most distant node from the root.
variables = []
for i in 1:m
var_name = "x$i"
var = Sym(var_name)
push!(variables, var)
end
# we will calculate an integral for every node in the level_sequence from right to left
inverse_counter = l-1
# stablish initial integrand, which is the rightmost leaf (last node of the level sequence)
if l > 1
integrand = integrate(PolynomialA(M,variables[tree[end]-1],variables[tree[end]]),(variables[tree[end]],0,1))
else
# if the RootedTree is [1] or [], the elementary differential will be 1.
return 1
end
# Start a cycle for integrating
while inverse_counter > 1
# we define the pseudo_integrand as the product between the last integral and the new polynomial (since the polynomials are
# multiplying each others inside the biggest integral). For a node 'i', this new Polynomial is computed for the variables
# 'xi' and 'x(i-1)'.
pseudo_integrand = PolynomialA(M,variables[tree[inverse_counter]-1],variables[tree[inverse_counter]])*integrand
# integrate this new pseudo_integrand with respect to the variable 'xi'
integrand = integrate(pseudo_integrand,(variables[tree[inverse_counter]],0,1))
inverse_counter -= 1
end
# Once we have covered every node except for the base, multiply for the Basis_Polynomial, i.e. the Polynomial B
# defined by B_{x1} = A_{1, x1}.
# return the integral with respect to x1.
return integrate(PolynomialA(M,1,variables[1])*integrand,(variables[1],0,1))
end

end # module
88 changes: 88 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2058,4 +2058,92 @@ using Aqua: Aqua
Aqua.test_project_toml_formatting(BSeries)
end
end

@testset "Continuous Stage Runge-Kutta Method" begin
@testset "(Inverse) 4x4-Hilbert Matrix" begin
# References
# - Yuto Miyatake and John C. Butcher.
# "A characterization of energy-preserving methods and the construction of
# parallel integrators for Hamiltonian systems."
# SIAM Journal on Numerical Analysis 54, no. 3 (2016):
# [DOI: 10.1137/15M1020861](https://doi.org/10.1137/15M1020861)

# This is the matrix obtained by inverting the 4x4-Hilbert matrix
M = [-6//5 72//5 -36//1 24//1;
72//5 -144//5 -48//1 72//1;
-36//1 -48//1 720//1 -720//1;
24//1 72//1 -720//1 720//1]
csrk = ContinuousStageRungeKuttaMethod(M)
# Generate the bseries up to order 4
order = 4
series = bseries(csrk, order)
expected_coefficients = [1//1 ,1//1, 1//2, 1//6, 1//3, 1//24, 1//12, 1//8, 1//4]
@test collect(values(series)) == expected_coefficients
end
Sondar74 marked this conversation as resolved.
Show resolved Hide resolved

@testset "Floating-points coefficients" begin
# Define variables
a = -0.37069987
b = 0.74139974
c = 2.51720052

# Create symbolic matrix
Minv = [a 1//2 b 1//6;
1//2 1//4 1//6 1//8;
b 1//6 c 1//10;
1//6 1//8 1//10 1//12]
Copy link
Owner

Choose a reason for hiding this comment

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

Please check your comments everywhere. This is not a symbolic matrix since you set the coefficients above to fixed floating point values. Where did you get these special choices from? Please add some references

Copy link
Contributor Author

@Sondar74 Sondar74 Jun 28, 2023

Choose a reason for hiding this comment

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

Okey, I'll fix the comments. As for the values, I took them from the project I am working on with @ketch, I calculated them.
Since it is the same matrix from Miyatake & Butcher, I will add this reference too.


# Calculate inverse of the matrix
M = inv(Minv)
csrk = ContinuousStageRungeKuttaMethod(M)
# Generate the bseries up to order 4
order = 4
series = bseries(csrk, order)
expected_coefficients = [1.0 ,1.0, 0.5000000000000002, 0.16666666666666666, 0.3333333333333336, 0.04166666666666661, 0.0833333333333332, 0.12500000000000003, 0.2500000000000003]
@test collect(values(series)) == expected_coefficients
Comment on lines +2107 to +2111
Copy link
Owner

Choose a reason for hiding this comment

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

Same comment about the order, energy-preserving property etc. as above

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 is_energy_preserving function will not necesary work accurately for Floating coefficients: for example, if we wanted to express 1/3 in floating we may need to cut it in some point at some decimal place, so there would be loss of information and is_energy_preserving would return false

Copy link
Owner

Choose a reason for hiding this comment

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

Did you test it?

end
@testset "Symbolic coefficients using SymPy.jl" begin
# Define variables
import SymPy: symbols
x = symbols("x")
y = symbols("y")
Sondar74 marked this conversation as resolved.
Show resolved Hide resolved
# Create symbolic matrix
M = [x y;
y x]
csrk = ContinuousStageRungeKuttaMethod(M)
# Generate the bseries up to order 4
order = 3
Sondar74 marked this conversation as resolved.
Show resolved Hide resolved
series = bseries(csrk, order)
expected_coefficients = [1,1, 25*x^2/32 + 5*x*y/4 + y^2/2, 1105*x^3/2304 + 671*x^2*y/576 + 545*x*y^2/576 + 37*y^3/144, 125*x^3/192 + 25*x^2*y/16 + 5*x*y^2/4 + y^3/3]
Copy link
Owner

Choose a reason for hiding this comment

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

Did you compute this by hand? How did you arrive at these coefficients?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I just copied from the output of the working code

Copy link
Owner

Choose a reason for hiding this comment

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

Hmm... So it is just a regression test - assuming that the current implementation is correct. Could you please also add real tests using examples with known behavior? IIRC, Miyatake & Butcher (2015) have some examples with free coefficients in their paper. You could express these free coefficients as symbolic variables and check whether the resulting B-series of the CSRK method

  • has the correct order_of_accuracy
  • is_energy_preserving

@test collect(values(series)) == expected_coefficients
end
@testset "Symbolic coefficients using SymEngine.jl" begin
# Define variables
import SymEngine: symbols
Sondar74 marked this conversation as resolved.
Show resolved Hide resolved
x,y = SymEngine.symbols("x y")
# Create symbolic matrix
M = [x y;
y x]
csrk = ContinuousStageRungeKuttaMethod(M)
# Generate the bseries up to order 4
order = 3
series = bseries(csrk, order)
expected_coefficients = [1,1, 25*x^2/32 + 5*x*y/4 + y^2/2, 1105*x^3/2304 + 671*x^2*y/576 + 545*x*y^2/576 + 37*y^3/144, 125*x^3/192 + 25*x^2*y/16 + 5*x*y^2/4 + y^3/3]
@test collect(values(series)) == expected_coefficients
end
@testset "Symbolic coefficients using Symbolics.jl" begin
# Define variables
import Symbolics: @variables
@variables x y
Sondar74 marked this conversation as resolved.
Show resolved Hide resolved
# Create symbolic matrix
M = [x y;
y x]
csrk = ContinuousStageRungeKuttaMethod(M)
# Generate the bseries up to order 4
order = 3
Sondar74 marked this conversation as resolved.
Show resolved Hide resolved
series = bseries(csrk, order)
expected_coefficients = [1,1, 25*x^2/32 + 5*x*y/4 + y^2/2, 1105*x^3/2304 + 671*x^2*y/576 + 545*x*y^2/576 + 37*y^3/144, 125*x^3/192 + 25*x^2*y/16 + 5*x*y^2/4 + y^3/3]
@test collect(values(series)) == expected_coefficients
end
end
end # @testset "BSeries"