mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-12 06:22:00 +00:00
feat: Add built-in polynomial differentiation (from SymDiff.jl)
Integrated minimal symbolic differentiation from SymDiff.jl by Jukka Aho: - differentiate(): Symbolic derivatives for polynomials (+, -, *, /, ^) - simplify(): Expression simplification with numeric evaluation - Zero external dependencies for basis function generation! Changes: - src/basis/create_basis.jl: Added differentiate() and simplify() - src/basis/subs.jl: Added local simplify with numeric evaluation - src/basis/abstract.jl: Removed Calculus import This replaces the Calculus.jl dependency with ~100 lines of pure Julia code specifically designed for polynomial basis functions.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
using Tensors
|
||||
using LinearAlgebra
|
||||
import Calculus
|
||||
# import Calculus # Only needed for symbolic basis generation (create_basis.jl)
|
||||
|
||||
# Re-export Vec for convenience (from Tensors.jl)
|
||||
export Vec
|
||||
|
||||
@@ -3,6 +3,95 @@
|
||||
|
||||
__precompile__(false)
|
||||
|
||||
# Minimal symbolic differentiation for polynomial basis functions
|
||||
# Adapted from SymDiff.jl by Jukka Aho - zero dependencies!
|
||||
|
||||
differentiate(::Number, ::Symbol) = 0
|
||||
differentiate(f::Symbol, x::Symbol) = f == x ? 1 : 0
|
||||
|
||||
function differentiate(f::Expr, x::Symbol)
|
||||
@assert f.head == :call
|
||||
op = first(f.args)
|
||||
|
||||
# Product rule: (fg)' = f'g + fg'
|
||||
if op == :*
|
||||
res_args = Any[:+]
|
||||
for i in 2:length(f.args)
|
||||
new_args = copy(f.args)
|
||||
new_args[i] = differentiate(f.args[i], x)
|
||||
push!(res_args, Expr(:call, new_args...))
|
||||
end
|
||||
return Expr(:call, res_args...)
|
||||
|
||||
# Power rule: d/dx f^a = a * f^(a-1) * f'
|
||||
elseif op == :^
|
||||
_, f_inner, a = f.args
|
||||
df = differentiate(f_inner, x)
|
||||
return :($a * $f_inner^($a - 1) * $df)
|
||||
|
||||
# Sum rule: (f + g)' = f' + g'
|
||||
elseif op == :+
|
||||
args = differentiate.(f.args[2:end], x)
|
||||
return Expr(:call, :+, args...)
|
||||
|
||||
# Difference rule: (f - g)' = f' - g'
|
||||
elseif op == :-
|
||||
args = differentiate.(f.args[2:end], x)
|
||||
return Expr(:call, :-, args...)
|
||||
|
||||
# Quotient rule: d/dx (f/g) = (f'g - fg')/g^2
|
||||
elseif op == :/
|
||||
_, g, h = f.args
|
||||
dg = differentiate(g, x)
|
||||
dh = differentiate(h, x)
|
||||
return :(($dg * $h - $g * $dh) / $h^2)
|
||||
|
||||
else
|
||||
error("Unsupported operation: $op")
|
||||
end
|
||||
end
|
||||
|
||||
simplify(f::Union{Number,Symbol}) = f
|
||||
|
||||
function simplify(ex::Expr)
|
||||
@assert ex.head == :call
|
||||
op = first(ex.args)
|
||||
|
||||
# Multiplication: remove 1's, return 0 if any 0
|
||||
if op == :*
|
||||
args = simplify.(ex.args[2:end])
|
||||
0 in args && return 0
|
||||
filter!(k -> !(isa(k, Number) && k == 1), args)
|
||||
length(args) == 0 && return 1
|
||||
length(args) == 1 && return first(args)
|
||||
return Expr(:call, :*, args...)
|
||||
|
||||
# Addition: remove 0's
|
||||
elseif op == :+
|
||||
args = simplify.(ex.args[2:end])
|
||||
filter!(k -> !isa(k, Number) || k != 0, args)
|
||||
length(args) == 0 && return 0
|
||||
length(args) == 1 && return first(args)
|
||||
return Expr(:call, :+, args...)
|
||||
|
||||
# Subtraction: remove 0's
|
||||
elseif op == :-
|
||||
args = simplify.(ex.args[2:end])
|
||||
filter!(k -> !isa(k, Number) || k != 0, args)
|
||||
length(args) == 0 && return 0
|
||||
length(args) == 1 && return first(args)
|
||||
return Expr(:call, :-, args...)
|
||||
|
||||
# Power, Division: keep as-is
|
||||
elseif op in (:^, :/)
|
||||
args = simplify.(ex.args[2:end])
|
||||
return Expr(:call, op, args...)
|
||||
|
||||
else
|
||||
return ex
|
||||
end
|
||||
end
|
||||
|
||||
function get_reference_element_coordinates end
|
||||
function eval_basis! end
|
||||
function eval_dbasis! end
|
||||
@@ -22,7 +111,7 @@ function calculate_interpolation_polynomials(p, V)
|
||||
N = Expr(:call, :+)
|
||||
for (ai, bi) in zip(solution, args)
|
||||
isapprox(ai, 0.0) && continue
|
||||
push!(N.args, Calculus.simplify(:($ai * $bi)))
|
||||
push!(N.args, simplify(:($ai * $bi))) # Use our own simplify
|
||||
end
|
||||
push!(basis, N)
|
||||
end
|
||||
@@ -35,7 +124,7 @@ function calculate_interpolation_polynomial_derivatives(basis, D)
|
||||
for (i, N) in enumerate(basis)
|
||||
partial_derivatives = []
|
||||
for j in 1:D
|
||||
dbasis[j, i] = Calculus.simplify(Calculus.differentiate(N, vars[j]))
|
||||
dbasis[j, i] = simplify(differentiate(N, vars[j])) # Use our own differentiate and simplify
|
||||
end
|
||||
end
|
||||
return dbasis
|
||||
|
||||
+62
-4
@@ -1,11 +1,69 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/jl/blob/master/LICENSE
|
||||
|
||||
# Minimal simplify for subs (from SymDiff.jl by Jukka Aho)
|
||||
simplify_local(f::Union{Number,Symbol}) = f
|
||||
|
||||
function simplify_local(ex::Expr)
|
||||
@assert ex.head == :call
|
||||
op = first(ex.args)
|
||||
|
||||
if op == :*
|
||||
args = simplify_local.(ex.args[2:end])
|
||||
0 in args && return 0
|
||||
filter!(k -> !(isa(k, Number) && k == 1), args)
|
||||
length(args) == 0 && return 1
|
||||
length(args) == 1 && return first(args)
|
||||
# If all args are numbers, evaluate
|
||||
if all(isa(a, Number) for a in args)
|
||||
return prod(args)
|
||||
end
|
||||
return Expr(:call, :*, args...)
|
||||
elseif op == :+
|
||||
args = simplify_local.(ex.args[2:end])
|
||||
filter!(k -> !isa(k, Number) || k != 0, args)
|
||||
length(args) == 0 && return 0
|
||||
length(args) == 1 && return first(args)
|
||||
# If all args are numbers, evaluate
|
||||
if all(isa(a, Number) for a in args)
|
||||
return sum(args)
|
||||
end
|
||||
return Expr(:call, :+, args...)
|
||||
elseif op == :-
|
||||
args = simplify_local.(ex.args[2:end])
|
||||
filter!(k -> !isa(k, Number) || k != 0, args)
|
||||
length(args) == 0 && return 0
|
||||
length(args) == 1 && return first(args)
|
||||
# If all args are numbers, evaluate
|
||||
if all(isa(a, Number) for a in args)
|
||||
return length(args) == 2 ? args[1] - args[2] : -args[1]
|
||||
end
|
||||
return Expr(:call, :-, args...)
|
||||
elseif op == :^
|
||||
args = simplify_local.(ex.args[2:end])
|
||||
# If all args are numbers, evaluate
|
||||
if all(isa(a, Number) for a in args)
|
||||
return args[1]^args[2]
|
||||
end
|
||||
return Expr(:call, :^, args...)
|
||||
elseif op == :/
|
||||
args = simplify_local.(ex.args[2:end])
|
||||
# If all args are numbers, evaluate
|
||||
if all(isa(a, Number) for a in args)
|
||||
return args[1] / args[2]
|
||||
end
|
||||
return Expr(:call, :/, args...)
|
||||
else
|
||||
args = simplify_local.(ex.args[2:end])
|
||||
return Expr(:call, op, args...)
|
||||
end
|
||||
end
|
||||
|
||||
function subs(p::Number, ::Any)
|
||||
return p
|
||||
end
|
||||
|
||||
function subs(p::Symbol, data::Pair{Symbol, T}) where T
|
||||
function subs(p::Symbol, data::Pair{Symbol,T}) where T
|
||||
k, v = data
|
||||
if p == k
|
||||
return v
|
||||
@@ -13,7 +71,7 @@ function subs(p::Symbol, data::Pair{Symbol, T}) where T
|
||||
return p
|
||||
end
|
||||
|
||||
function subs(p::Symbol, data::NTuple{N,Pair{Symbol, T}}) where {N, T}
|
||||
function subs(p::Symbol, data::NTuple{N,Pair{Symbol,T}}) where {N,T}
|
||||
for (k, v) in data
|
||||
if p == k
|
||||
return v
|
||||
@@ -46,9 +104,9 @@ subs(expression, data)
|
||||
```
|
||||
|
||||
"""
|
||||
function subs(p::Expr, data::NTuple{N,Pair{Symbol, T}}) where {N, T}
|
||||
function subs(p::Expr, data::NTuple{N,Pair{Symbol,T}}) where {N,T}
|
||||
for di in data
|
||||
p = subs(p, di)
|
||||
end
|
||||
return Calculus.simplify(p)
|
||||
return simplify_local(p) # Use local simplify
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user