Skip to content

add DAETS integrator #2609

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions lib/OrdinaryDiffEqTaylorSeries/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
The OrdinaryDiffEq.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2016-2020: ChrisRackauckas, Yingbo Ma, Julia Computing Inc, and
> other contributors:
>
> https://github.com/SciML/OrdinaryDiffEq.jl/graphs/contributors
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> SOFTWARE.
65 changes: 65 additions & 0 deletions lib/OrdinaryDiffEqTaylorSeries/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name = "OrdinaryDiffEqTaylorSeries"
uuid = "9c7f1690-dd92-42a3-8318-297ee24d8d39"
authors = ["ParamThakkar123 <[email protected]>"]
version = "1.1.0"

[deps]
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
FastBroadcast = "7034ab61-46d4-4ed7-9d0f-46aef9175898"
GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6"
Hungarian = "e91730f6-4275-51fb-a7a0-7064cfbd3b39"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MuladdMacro = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221"
NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
TaylorDiff = "b36ab563-344f-407b-a36a-4f200bebf99c"
TruncatedStacktraces = "781d530d-4396-4725-bb49-402e4bee1e77"

[compat]
DiffEqBase = "6.152.2"
DiffEqDevTools = "2.44.4"
FastBroadcast = "0.3.5"
GLPK = "1.2.1"
Hungarian = "0.7.0"
JuMP = "1.24.0"
LinearAlgebra = "<0.0.1, 1"
MuladdMacro = "0.2.4"
NLsolve = "4.5.1"
OrdinaryDiffEq = "6.91.0"
OrdinaryDiffEqCore = "1.1"
Plots = "1.40.9"
PrecompileTools = "1.2.1"
Preferences = "1.4.3"
Random = "<0.0.1, 1"
RecursiveArrayTools = "3.27.0"
Reexport = "1.2.2"
SafeTestsets = "0.1.0"
SciMLBase = "2.72.2"
Static = "1.1.1"
SymbolicUtils = "3.15.0"
Symbolics = "6.28.0"
TaylorDiff = "0.3.1"
Test = "<0.0.1, 1"
TruncatedStacktraces = "1.4.0"
julia = "1.10"

[extras]
DiffEqDevTools = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
ODEProblemLibrary = "fdc4e326-1af4-4b90-96e7-779fcce2daa5"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["DiffEqDevTools", "Random", "SafeTestsets", "Test", "ODEProblemLibrary"]
223 changes: 223 additions & 0 deletions lib/OrdinaryDiffEqTaylorSeries/src/DAETs_symbolics.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
module DAETS_utils
using Symbolics
using SymbolicUtils
using Hungarian
using JuMP
using GLPK
using OrdinaryDiffEq: ODEFunction, DAEFunction
using SciMLBase
using LinearAlgebra: UniformScaling

export signature_matrix, highest_value_transversal, find_offsets, system_jacobian
"""
signature_matrix(eqs, vars, t_ind) -> Matrix{Float64}

Construct the signature matrix Σ for the system of equations `eqs` with respect to
the variables `vars`. Each entry Σ[i,j] is the (highest) derivative order of `vars[j]`
appearing in eqs[i], or -Inf if it does not appear.

`eqs` can be a vector of symbolic expressions, an ODEFunction/DAEFunction object, or a function.
`vars` can be a vector of variables or a NullParameters object.
"""
function signature_matrix(eqs, vars, t_ind)
# For non-symbolic inputs, just return a default signature matrix
if !(vars isa Vector) || !(eqs isa Vector)
# Try to determine the system size
n = 1

# If eqs is an ODEFunction, try to get size from u_prototype
if eqs isa ODEFunction && hasproperty(eqs, :u_prototype) && !isnothing(eqs.u_prototype)
n = length(eqs.u_prototype)
end
#########################################################
# TODO: This is a placeholder ###########################
#########################################################
Σ = zeros(n, n)
for i in 1:n
for j in 1:n
Σ[i,j] = i == j ? 1.0 : 0.0
end
end
return Σ
end

# Original implementation for symbolic expressions (probably do not need this but keeping it here for now)
n_eqs = length(eqs)
n_vars = length(vars)
Σ = fill(-Inf, n_eqs, n_vars) # Initialize with -Inf

for i in 1:n_eqs
for j in 1:n_vars
# Check for each variable in the equation what the highest derivative order is
order = max_derivative_order(eqs[i], vars[j], t_ind)
Σ[i,j] = order
end
end
return Σ
end
"""
max_derivative_order(ex, var, t_ind)

Returns the highest derivative order of `var` that appears in the symbolic expression `ex`,
using `t_ind` as the independent variable (e.g., time). If `var` does not appear, returns -Inf.
"""
function max_derivative_order(ex, var, t_ind)
# This is also for symbolic expressions. (probably do not need this but keeping it here for now)
# Base case: if ex is exactly var(t_ind), order is 0.
if isequal(ex, var(t_ind))
return 0
end

# If it's a number or unrelated symbol ignore
if ex isa Number || (ex isa Symbol && ex != var)
return -Inf
end

# Check if ex is a derivative expression.
if iscall(ex) && operation(ex) isa Symbolics.Differential
inner = arguments(ex)[1] # Function being differentiated.
# Recurse
sub_order = max_derivative_order(inner, var, t_ind)
return sub_order == -Inf ? -Inf : 1 + sub_order
end

# For composite expressions (e.g., sums, products), traverse arguments
if iscall(ex)
best = -Inf
for arg in arguments(ex)
# Recursively check the order of each component
best = max(best, max_derivative_order(arg, var, t_ind))
end
return best
end
return -Inf
end

"""
highest_value_transversal(Σ) -> (Vector{Tuple{Int, Int}}, Float64)

Finds the highest value transversal (HVT) of the signature matrix `Σ` using the Hungarian algorithm.
Returns the transversal as a vector of (row, column) indices and its value.
"""
function highest_value_transversal(Σ::Matrix{Float64})
n = size(Σ, 1)

# The Hungarian algorithm minimizes so multiply by -1 to max
cost_matrix = -Σ
assignment = hungarian(cost_matrix)[1]
# Extract transversal and its value.
transversal = [(i, assignment[i]) for i in 1:n]
value = sum(Σ[i, assignment[i]] for i in 1:n)

return transversal, value
end


"""
find_offsets(Σ::Matrix{Float64}, T::Vector{Tuple{Int, Int}}) -> (Vector{Int}, Vector{Int})

Finds the canonical offsets `c` and `d` for the signature matrix `Σ` and the highest value transversal `T`.
Returns the vectors `c` and `d`.
"""
function find_offsets(Σ::Matrix{Float64}, T::Vector{Tuple{Int, Int}})
n = size(Σ, 1)
# Create JuMP model with GLPK solver
model = Model(GLPK.Optimizer)
# Define variables for c and d (offsets)
@variable(model, c[1:n] >= 0, Int)
@variable(model, d[1:n] >= 0, Int)

# Add constraints for all i, j: d[j] - c[i] >= Σ[i, j]
for i in 1:n
for j in 1:n
if Σ[i, j] != -Inf
@constraint(model, d[j] - c[i] >= Σ[i, j])
end
end
end

# Add constraints for equality over transversal
for (i, j) in T
@constraint(model, d[j] - c[i] == Σ[i, j])
end

# min sum c and d finds the "canonical" offsets
@objective(model, Min, sum(c) + sum(d))
optimize!(model)
c_values = value.(c)
d_values = value.(d)
return c_values, d_values
end


"""
system_jacobian(eqs, vars, t_ind, c, d, Σ) -> Matrix

Constructs the System Jacobian matrix J for the system of equations `eqs` with respect to the variables `vars`.
The offsets `c` and `d` and the signature matrix `Σ` are used to determine the structure of the Jacobian.

Handles both symbolic expressions and ODE/DAE function objects.
"""
function system_jacobian(
eqs,
vars,
t_ind,
c::Vector{Int},
d::Vector{Int},
Σ::Matrix{Float64}
)
# Try to create numerical Jacobian
if !(vars isa Vector{<:SymbolicUtils.BasicSymbolic}) || !(eqs isa Vector{<:SymbolicUtils.BasicSymbolic})
# Get the size from the signature matrix
n = size(Σ, 1)

# For ODEFunction, we can try to use its jacobian if available
if eqs isa ODEFunction && hasproperty(eqs, :jac) && !isnothing(eqs.jac)
return eqs.jac
else
# Create a default jacobian based on the signature matrix
#########################################################
# TODO: This is a placeholder. Fix Jacobian Calculation #
#########################################################
J = zeros(n, n)
for i in 1:n
for j in 1:n
if d[j] - c[i] == Σ[i, j]
J[i, j] = 1.0 # Non-zero entry where the signature matrix indicates
else
J[i, j] = 0.0
end
end
end
return J
end
end

# The original implementation for symbolics.
n = length(eqs)
J = zeros(Symbolics.Num, n, n)

for i in 1:n
for j in 1:n
if d[j] - c[i] == Σ[i, j]
f_i = eqs[i]
x_j = vars[j]
σ_ij = Int(Σ[i, j])
# Compute the σ_ij-th derivative of x_j
x_j_deriv = x_j(t_ind)
for _ in 1:σ_ij
x_j_deriv = Differential(t_ind)(x_j_deriv)
end
# Compute the partial derivative ∂f_i / ∂x_j^(σ_ij)
J[i, j] = expand_derivatives(Differential(x_j_deriv)(f_i))
else
# Set J[i, j] = 0 if d[j] - c[i] != Σ[i, j]
J[i, j] = 0
end
end
end

return J
end

end
73 changes: 73 additions & 0 deletions lib/OrdinaryDiffEqTaylorSeries/src/OrdinaryDiffEqTaylorSeries.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
module OrdinaryDiffEqTaylorSeries

# Had to add this to make the tests work. There is probably a better way to do this.
include("DAETS_utils.jl")
using .DAETS_utils: signature_matrix, max_derivative_order, highest_value_transversal,
find_offsets, system_jacobian

export signature_matrix, max_derivative_order, highest_value_transversal,
find_offsets, system_jacobian

import OrdinaryDiffEqCore: alg_order, alg_stability_size, explicit_rk_docstring,
OrdinaryDiffEqAdaptiveAlgorithm, OrdinaryDiffEqMutableCache,
alg_cache,
OrdinaryDiffEqConstantCache, @fold, trivial_limiter!,
constvalue, @unpack, perform_step!, calculate_residuals, @cache,
calculate_residuals!, _ode_interpolant, _ode_interpolant!,
CompiledFloats, @OnDemandTableauExtract, initialize!,
perform_step!, OrdinaryDiffEqAlgorithm,
DAEAlgorithm,
CompositeAlgorithm, _ode_addsteps!, copyat_or_push!,
AutoAlgSwitch, get_fsalfirstlast,
full_cache, DerivativeOrderNotPossibleError
import Static: False
import MuladdMacro: @muladd
import FastBroadcast: @..
import RecursiveArrayTools: recursivefill!, recursive_unitless_bottom_eltype
import LinearAlgebra: norm, ldiv!, lu, cond
using TruncatedStacktraces
using TaylorDiff
import DiffEqBase: @def
import OrdinaryDiffEqCore

using Reexport
@reexport using DiffEqBase

include("DAETS_symbolics.jl")

include("algorithms.jl")
include("alg_utils.jl")
include("TaylorSeries_caches.jl")
include("interp_func.jl")
# include("interpolants.jl")
include("TaylorSeries_perform_step.jl")
include("initialize_dae.jl")

import PrecompileTools
import Preferences

PrecompileTools.@compile_workload begin
lorenz = OrdinaryDiffEqCore.lorenz
lorenz_oop = OrdinaryDiffEqCore.lorenz_oop
solver_list = [ExplicitTaylor2()]
prob_list = []

if Preferences.@load_preference("PrecompileNoSpecialize", false)
push!(prob_list,
ODEProblem{true, SciMLBase.NoSpecialize}(lorenz, [1.0; 0.0; 0.0], (0.0, 1.0)))
push!(prob_list,
ODEProblem{true, SciMLBase.NoSpecialize}(lorenz, [1.0; 0.0; 0.0], (0.0, 1.0),
Float64[]))
end

for prob in prob_list, solver in solver_list
solve(prob, solver)(5.0)
end

prob_list = nothing
solver_list = nothing
end

export ExplicitTaylor2, ExplicitTaylor, DAETS

end
Loading