@@ -89,3 +89,73 @@ using Test
8989end
9090nothing # hide
9191```
92+
93+
94+ ## Symbolic computation and automatic differentiation
95+
96+ The implementation is fully generic using plain Julia code. In particular,
97+ this enables automatic differentiation (AD) and symbolic computations.
98+
99+ ### Symbolic computations
100+
101+ For example, you can determine the order conditions symbolically as follows.
102+ ``` @example
103+ using RootedTrees, SymPy
104+
105+ s = 3 # number of stages
106+ A = [symbols("a_$(i)$(j)", real=true) for i in 1:s, j in 1:s]
107+ b = [symbols("b_$(i)", real=true) for i in 1:s]
108+ rk = RungeKuttaMethod(A, b)
109+
110+ for o in 1:3
111+ println("Order ", o)
112+ for t in RootedTreeIterator(o)
113+ println("t = ", t)
114+ println(residual_order_condition(t, rk))
115+ end
116+ println()
117+ end
118+
119+ nothing # hide
120+ ```
121+
122+ ### Automatic differentiation
123+
124+ The order conditions can be differentiated with respect to the Runge-Kutta
125+ coefficients. For example, we can use
126+ [ ForwardDiff.jl] ( https://github.com/JuliaDiff/ForwardDiff.jl ) and
127+ [ Zygote.jl] ( https://github.com/FluxML/Zygote.jl )
128+ as follows.
129+
130+ ``` @example AD-Jacobian
131+ using RootedTrees, ForwardDiff, Zygote
132+
133+ # collect all rooted trees up to order 4
134+ trees = [copy(t) for o in 1:4 for t in RootedTreeIterator(o)]
135+
136+ # classical RK4 method
137+ A = [0 0 0 0; 1//2 0 0 0; 0 1//2 0 0; 0 0 1 0]
138+ b = [1//6, 1//3, 1//3, 1//6]
139+ coeffs = vcat(vec(A), vec(b)) # one vector of all parameters
140+
141+ function all_order_conditions(trees, coeffs)
142+ # extract Butcher coefficients from the vector of all coefficients
143+ # for an RK method with four stages
144+ A = reshape(view(coeffs, 1:16), 4, 4)
145+ b = view(coeffs, 17:20)
146+ rk = RungeKuttaMethod(A, b)
147+ map(t -> residual_order_condition(t, rk), trees)
148+ end
149+
150+ @assert iszero(all_order_conditions(trees, coeffs)) # fourth-order accurate
151+
152+ ForwardDiff.jacobian(coeffs -> all_order_conditions(trees, coeffs), coeffs)
153+ ```
154+
155+ ``` @example AD-Jacobian
156+ Zygote.jacobian(coeffs -> all_order_conditions(trees, coeffs), coeffs)
157+ ```
158+
159+ ``` @example AD-Jacobian
160+ ForwardDiff.jacobian(coeffs -> all_order_conditions(trees, coeffs), coeffs) == first(Zygote.jacobian(coeffs -> all_order_conditions(trees, coeffs), coeffs))
161+ ```
0 commit comments