WIP
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
# seems like with
|
||||
* quadrature (renamed)
|
||||
* nonlinsolve
|
||||
* GalacticOptim (renamed)
|
||||
* symbolic-numeric integration
|
||||
* symbolics.jl
|
||||
|
||||
...
|
||||
|
||||
This should be mentioned
|
||||
@@ -1,392 +0,0 @@
|
||||
# Symbolics.jl
|
||||
|
||||
Incorporate:
|
||||
|
||||
Basics
|
||||
|
||||
|
||||
https://github.com/SciML/ModelingToolkit.jl
|
||||
https://github.com/JuliaSymbolics/Symbolics.jl
|
||||
https://github.com/JuliaSymbolics/SymbolicUtils.jl
|
||||
|
||||
* Rewriting
|
||||
|
||||
https://github.com/JuliaSymbolics/SymbolicUtils.jl
|
||||
|
||||
* Plotting
|
||||
|
||||
Polynomials
|
||||
|
||||
|
||||
Limits
|
||||
|
||||
XXX ... room here!
|
||||
|
||||
Derivatives
|
||||
|
||||
https://github.com/JuliaSymbolics/Symbolics.jl
|
||||
|
||||
|
||||
Integration
|
||||
|
||||
https://github.com/SciML/SymbolicNumericIntegration.jl
|
||||
|
||||
|
||||
|
||||
|
||||
The `Symbolics.jl` package is a Computer Algebra System (CAS) built entirely in `Julia`.
|
||||
This package is under heavy development.
|
||||
|
||||
## Algebraic manipulations
|
||||
|
||||
### construction
|
||||
|
||||
|
||||
|
||||
|
||||
@variables
|
||||
|
||||
SymbolicUtils.@syms assumptions
|
||||
|
||||
|
||||
|
||||
x is a `Num`, `Symbolics.value(x)` is of type `SymbolicUtils{Real, Nothing}
|
||||
|
||||
relation to SymbolicUtils
|
||||
Num wraps things; Term
|
||||
|
||||
|
||||
|
||||
### Substitute
|
||||
|
||||
### Simplify
|
||||
|
||||
simplify
|
||||
expand
|
||||
|
||||
rewrite rules
|
||||
|
||||
### Solving equations
|
||||
|
||||
solve_for
|
||||
|
||||
|
||||
|
||||
## Expressions to functions
|
||||
|
||||
build_function
|
||||
|
||||
## Derivatives
|
||||
|
||||
1->1: Symbolics.derivative(x^2 + cos(x), x)
|
||||
|
||||
1->3: Symbolics.derivative.([x^2, x, cos(x)], x)
|
||||
|
||||
3 -> 1: Symbolics.gradient(x*y^z, [x,y,z])
|
||||
|
||||
2 -> 2: Symbolics.jacobian([x,y^z], [x,y])
|
||||
|
||||
# higher order
|
||||
|
||||
1 -> 1: D(ex, x, n=1) = foldl((ex,_) -> Symbolics.derivative(ex, x), 1:n, init=ex)
|
||||
|
||||
2 -> 1: (2nd) Hessian
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Differential equations
|
||||
|
||||
|
||||
## Integrals
|
||||
|
||||
WIP
|
||||
|
||||
## ----
|
||||
# follow sympy tutorial
|
||||
|
||||
using Symbolics
|
||||
import SymbolicUtils
|
||||
|
||||
@variables x y z
|
||||
|
||||
# substitution
|
||||
|
||||
ex = cos(x) + 1
|
||||
substitute(ex, Dict(x=>y))
|
||||
|
||||
substitute(ex, Dict(x=>0)) # does eval
|
||||
|
||||
ex = x^y
|
||||
substitute(ex, Dict(y=> x^y))
|
||||
|
||||
|
||||
|
||||
# expand trig
|
||||
r1 = @rule sin(2 * ~x) => 2sin(~x)*cos(~x)
|
||||
r2 = @rule cos(2 * ~x) => cos(~x)^2 - sin(~x)^2
|
||||
expand_trig(ex) = simplify(ex, RuleSet([r1, r2]))
|
||||
|
||||
ex = sin(2x) + cos(2x)
|
||||
expand_trig(ex)
|
||||
|
||||
## Multiple
|
||||
@variables x y z
|
||||
ex = x^3 + 4x*y -z
|
||||
substitute(ex, Dict(x=>2, y=>4, z=>0))
|
||||
|
||||
|
||||
# Converting Strings to Expressions
|
||||
# what is sympify?
|
||||
|
||||
# evalf
|
||||
|
||||
|
||||
# lambdify: symbolic expression -> function
|
||||
ex = x^3 + 4x*y -z
|
||||
λ = build_function(ex, x,y,z, expression=Val(false))
|
||||
λ(2,4,0)
|
||||
|
||||
# pretty printing
|
||||
using Latexify
|
||||
latexify(ex)
|
||||
|
||||
|
||||
# Simplify
|
||||
@variables x y z t
|
||||
|
||||
simplify(sin(x)^2 + cos(x)^2)
|
||||
|
||||
|
||||
simplify((x^3 + x^2 - x - 1) / (x^2 + 2x + 1)) # fails, no factor
|
||||
simplify(((x+1)*(x^2-1))/((x+1)^2)) # works
|
||||
|
||||
import SpecialFunctions: gamma
|
||||
|
||||
simplify(gamma(x) / gamma(x-2)) # fails
|
||||
|
||||
# Polynomial
|
||||
|
||||
## expand
|
||||
expand((x+1)^2)
|
||||
expand((x+2)*(x-3))
|
||||
expand((x+1)*(x-2) - (x-1)*x)
|
||||
|
||||
## factor
|
||||
### not defined
|
||||
|
||||
|
||||
## collect
|
||||
COLLECT_RULES = [
|
||||
@rule(~x*x^(~n::SymbolicUtils.isnonnegint) => (~x, ~n))
|
||||
@rule(~x * x => (~x, 1))
|
||||
]
|
||||
function _collect(ex, x)
|
||||
d = Dict()
|
||||
|
||||
exs = expand(ex)
|
||||
if SymbolicUtils.operation(Symbolics.value(ex)) != +
|
||||
d[0] => ex
|
||||
else
|
||||
for aᵢ ∈ SymbolicUtils.arguments(Symbolics.value(expand(ex)))
|
||||
u = simplify(aᵢ, RuleSet(COLLECT_RULES))
|
||||
if isa(u, Tuple)
|
||||
a,n = u
|
||||
else
|
||||
a,n = u,0
|
||||
end
|
||||
d[n] = get(d, n, 0) + a
|
||||
end
|
||||
end
|
||||
d
|
||||
end
|
||||
|
||||
|
||||
## cancel -- no factor
|
||||
|
||||
## apart -- no factor
|
||||
|
||||
## Trignometric simplification
|
||||
|
||||
INVERSE_TRIG_RUELS = [@rule(cos(acos(~x)) => ~x)
|
||||
@rule(acos(cos(~x)) => abs(rem2pi(~x, RoundNearest)))
|
||||
@rule(sin(asin(~x)) => ~x)
|
||||
@rule(asin(sin(~x)) => abs(rem2pi(x + pi/2, RoundNearest)) - pi/2)
|
||||
]
|
||||
|
||||
@variables θ
|
||||
simplify(cos(acos(θ)), RuleSet(INVERSE_TRIG_RUELS))
|
||||
|
||||
# Copy from https://github.com/JuliaSymbolics/SymbolicUtils.jl/blob/master/src/simplify_rules.jl
|
||||
# the TRIG_RULES are applied by simplify by default
|
||||
HTRIG_RULES = [
|
||||
@acrule(-sinh(~x)^2 + cosh(~x)^2 => one(~x))
|
||||
@acrule(sinh(~x)^2 + 1 => cosh(~x)^2)
|
||||
@acrule(cosh(~x)^2 + -1 => -sinh(~x)^2)
|
||||
|
||||
@acrule(tanh(~x)^2 + 1*sech(~x)^2 => one(~x))
|
||||
@acrule(-tanh(~x)^2 + 1 => sech(~x)^2)
|
||||
@acrule(sech(~x)^2 + -1 => -tanh(~x)^2)
|
||||
|
||||
@acrule(coth(~x)^2 + -1*csch(~x)^2 => one(~x))
|
||||
@acrule(coth(~x)^2 + -1 => csch(~x)^2)
|
||||
@acrule(csch(~x)^2 + 1 => coth(~x)^2)
|
||||
|
||||
@acrule(tanh(~x) => sinh(~x)/cosh(~x))
|
||||
|
||||
@acrule(sinh(-~x) => -sinh(~x))
|
||||
@acrule(cosh(-~x) => -cosh(~x))
|
||||
]
|
||||
|
||||
trigsimp(ex) = simplify(simplify(ex, RuleSet(HTRIG_RULES)))
|
||||
|
||||
trigsimp(sin(x)^2 + cos(x)^2)
|
||||
trigsimp(sin(x)^4 -2cos(x)^2*sin(x)^2 + cos(x)^4) # no factor
|
||||
trigsimp(cosh(x)^2 + sinh(x)^2)
|
||||
trigsimp(sinh(x)/tanh(x))
|
||||
|
||||
EXPAND_TRIG_RULES = [
|
||||
|
||||
@acrule(sin(~x+~y) => sin(~x)*cos(~y) + cos(~x)*sin(~y))
|
||||
@acrule(sinh(~x+~y) => sinh(~x)*cosh(~y) + cosh(~x)*sinh(~y))
|
||||
|
||||
@acrule(sin(2*~x) => 2sin(~x)*cos(~x))
|
||||
@acrule(sinh(2*~x) => 2sinh(~x)*cosh(~x))
|
||||
|
||||
|
||||
|
||||
@acrule(cos(~x+~y) => cos(~x)*cos(~y) - sin(~x)*sin(~y))
|
||||
@acrule(cosh(~x+~y) => cosh(~x)*cosh(~y) + sinh(~x)*sinh(~y))
|
||||
|
||||
@acrule(cos(2*~x) => cos(~x)^2 - sin(~x)^2)
|
||||
@acrule(cosh(2*~x) => cosh(~x)^2 + sinh(~x)^2)
|
||||
|
||||
|
||||
@acrule(tan(~x+~y) => (tan(~x) - tan(~y)) / (1 + tan(~x)*tan(~y)))
|
||||
@acrule(tanh(~x+~y) => (tanh(~x) + tanh(~y)) / (1 + tanh(~x)*tanh(~y)))
|
||||
|
||||
@acrule(tan(2*~x) => 2*tan(~x)/(1 - tan(~x)^2))
|
||||
@acrule(tanh(2*~x) => 2*tanh(~x)/(1 + tanh(~x)^2))
|
||||
|
||||
]
|
||||
|
||||
expandtrig(ex) = simplify(simplify(ex, RuleSet(EXPAND_TRIG_RULES)))
|
||||
|
||||
expandtrig(sin(x+y))
|
||||
expandtrig(tan(2x))
|
||||
|
||||
|
||||
# powers
|
||||
|
||||
# in genearl x^a*x^b = x^(a+b)
|
||||
@variables x y a b
|
||||
simplify(x^a*x^b - x^(a+b)) # 0
|
||||
|
||||
# x^a*y^a = (xy)^a When x,y >=0, a ∈ R
|
||||
simplify(x^a*y^a - (x*y)^a)
|
||||
|
||||
## ??? How to specify such assumptions?
|
||||
|
||||
# (x^a)^b = x^(ab) only if b ∈ Int
|
||||
@syms x a b
|
||||
simplify((x^a)^b - x^(a*b))
|
||||
|
||||
@syms x a b::Int
|
||||
simplify((x^a)^b - x^(a*b)) # nope
|
||||
|
||||
|
||||
ispositive(x) = isa(x, Real) && x > 0
|
||||
_isinteger(x) = isa(x, Integer)
|
||||
_isinteger(x::SymbolicUtils.Sym{T,S}) where {T <: Integer, S} = true
|
||||
POWSIMP_RULES = [
|
||||
@acrule((~x::ispositive)^(~a::isreal) * (~y::ispositive)^(~a::isreal) => (~x*~y)^~a)
|
||||
@rule(((~x)^(~a))^(~b::_isinteger) => ~x^(~a * ~b))
|
||||
]
|
||||
powsimp(ex) = simplify(simplify(ex, RuleSet(POWSIMP_RULES)))
|
||||
|
||||
@syms x a b::Int
|
||||
simplify((x^a)^b - x^(a*b)) # nope
|
||||
|
||||
|
||||
EXPAND_POWER_RULES = [
|
||||
@rule((~x)^(~a + ~b) => (_~)^(~a) * (~x)^(~b))
|
||||
@rule((~x*~y)^(~a) => (~x)^(~a) * (~y)^(~a))
|
||||
|
||||
## ... more on simplification...
|
||||
|
||||
## Calculus
|
||||
@variables x y z
|
||||
import Symbolics: derivative
|
||||
derivative(cos(x), x)
|
||||
derivative(exp(x^2), x)
|
||||
|
||||
# multiple derivative
|
||||
Symbolics.derivative(ex, x, n::Int) = reduce((ex,_) -> derivative(ex, x), 1:n, init=ex) # helper
|
||||
derivative(x^4, x, 3)
|
||||
|
||||
ex = exp(x*y*z)
|
||||
|
||||
using Chain
|
||||
@chain ex begin
|
||||
derivative(x, 3)
|
||||
derivative(y, 3)
|
||||
derivative(z, 3)
|
||||
end
|
||||
|
||||
# using Differential operator
|
||||
expr = exp(x*y*z)
|
||||
expr |> Differential(x)^2 |> Differential(y)^3 |> expand_derivatives
|
||||
|
||||
# no integrate
|
||||
|
||||
# no limit
|
||||
|
||||
# Series
|
||||
function series(ex, x, x0=0, n=5)
|
||||
Σ = zero(ex)
|
||||
for i ∈ 0:n
|
||||
ex = expand_derivatives((Differential(x))(ex))
|
||||
Σ += substitute(ex, Dict(x=>0)) * x^i / factorial(i)
|
||||
end
|
||||
Σ
|
||||
end
|
||||
|
||||
# finite differences
|
||||
|
||||
|
||||
# Solvers
|
||||
|
||||
@variables x y z a
|
||||
eq = x ~ a
|
||||
Symbolics.solve_for(eq, x)
|
||||
|
||||
eqs = [x + y + z ~ 1
|
||||
x + y + 2z ~ 3
|
||||
x + 2y + 3z ~ 3
|
||||
]
|
||||
vars = [x,y,z]
|
||||
xs = Symbolics.solve_for(eqs, vars)
|
||||
|
||||
[reduce((ex, r)->substitute(ex, r), Pair.(vars, xs), init=ex.lhs) for ex ∈ eqs] == [eq.rhs for eq ∈ eqs]
|
||||
|
||||
|
||||
A = [1 1; 1 2]
|
||||
b = [1, 3]
|
||||
xs = Symbolics.solve_for(A*[x,y] .~ b, [x,y])
|
||||
A*xs - b
|
||||
|
||||
|
||||
A = [1 1 1; 1 1 2]
|
||||
b = [1,3]
|
||||
A*[x,y,z] - b
|
||||
Symbolics.solve_for(A*[x,y,z] .~ b, [x,y,z]) # fails, singular
|
||||
|
||||
# nonlinear solve
|
||||
# use `λ = mk_function(ex, args, expression=Val(false))`
|
||||
|
||||
|
||||
# polynomial roots
|
||||
|
||||
|
||||
# differential equations
|
||||
@@ -9,7 +9,7 @@ There are a few options in `Julia` for symbolic math, for example, the `SymPy` p
|
||||
## About
|
||||
|
||||
|
||||
The `Symbolics` package bill itself as a "fast and modern Computer Algebra System (CAS) for a fast and modern programming language." This package relies on the `SymbolicUtils` package and is built upon by the `ModelingToolkit` package, which we don't describe here.
|
||||
The `Symbolics` package bills itself as a "fast and modern Computer Algebra System (CAS) for a fast and modern programming language." This package relies on the `SymbolicUtils` package and is built upon by the `ModelingToolkit` package, which we don't describe here.
|
||||
|
||||
|
||||
We begin by loading the `Symbolics` package which when loaded re-exports the `SymbolicUtils` package.
|
||||
@@ -22,7 +22,7 @@ using Symbolics
|
||||
## Symbolic variables
|
||||
|
||||
|
||||
Symbolic math at its core involves symbolic variables, which essentially defer evaluation until requested. The creation of symbolic variables differs between the two package discussed here.
|
||||
Symbolic math at its core involves symbolic variables, which essentially defer evaluation until requested. The creation of symbolic variables differs between the two packages discussed here.
|
||||
|
||||
|
||||
`SymbolicUtils` creates variables which carry `Julia` type information (e.g. `Int`, `Float64`, ...). This type information carries through operations involving these variables. Symbolic variables can be created with the `@syms` macro. For example
|
||||
@@ -32,10 +32,10 @@ Symbolic math at its core involves symbolic variables, which essentially defer e
|
||||
@syms x y::Int f(x::Real)::Real
|
||||
```
|
||||
|
||||
This creates `x` a symbolic value with type `Number`, `y` a symbolic variable holding integer values, and `f` a symbolic function of a single real variable outputing a real variable.
|
||||
This creates `x` a symbolic value with symbolic type `Number`, `y` a symbolic variable holding integer values, and `f` a symbolic function of a single real variable outputting a real variable.
|
||||
|
||||
|
||||
The `symtype` function reveals the underlying type:
|
||||
The non-exported `symtype` function reveals the underlying type:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -44,14 +44,14 @@ import Symbolics.SymbolicUtils: symtype
|
||||
symtype(x), symtype(y)
|
||||
```
|
||||
|
||||
For `y`, the symbolic type being real does not imply the `y` has a subtype of `Real`:
|
||||
For `y`, the symbolic type being real does not imply the type of `y` is a subtype of `Real`:
|
||||
|
||||
|
||||
```{julia}
|
||||
isa(y, Real)
|
||||
```
|
||||
|
||||
We see that the function `f`, when called with `y` would return a value of (symbolic) type `Real`:
|
||||
We see that the function `f` when called with `y` would return a value of (symbolic) type `Real`:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -68,7 +68,7 @@ f(x)
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
The `SymPy` package also has an `@syms` macro to create variables. Though they names agree, they do different things. Using both packages together would require qualifying many shared method names. For `SymbolicUtils`, the `@syms` macro uses `Julia` types to parameterize the variables. In `SymPy` it is possible to specify *assumptions* on the variables, but that is different and not useful for dispatch without some extra effort.
|
||||
The `SymPy` package also has an `@syms` macro to create variables. Though their names agree, they do different things. Using both packages together would require qualifying many shared method names. For `SymbolicUtils`, the `@syms` macro uses `Julia` types to parameterize the variables. In `SymPy` it is possible to specify *assumptions* on the variables, but that is different and not useful for dispatch without some extra effort.
|
||||
|
||||
:::
|
||||
|
||||
@@ -137,7 +137,7 @@ typeof(sin(x)), typeof(Symbolics.value(sin(x)))
|
||||
### Tree structure to expressions
|
||||
|
||||
|
||||
The `TermInterface` package is used by `SymbolicUtils` to explore the tree structdure of an expression. The main methods are (cf. [symbolicutils.jl](https://symbolicutils.juliasymbolics.org/#expression_interface)):
|
||||
The `TermInterface` package is used by `SymbolicUtils` to explore the tree structure of an expression. The main methods are (cf. [SymbolicUtils.jl](https://symbolicutils.juliasymbolics.org/#expression_interface)):
|
||||
|
||||
|
||||
* `istree(ex)`: `true` if `ex` is not a *leaf* node (like a symbol or numeric literal)
|
||||
@@ -221,7 +221,7 @@ free_symbols(ex)
|
||||
### Substitute
|
||||
|
||||
|
||||
The `substitute` command is used to replace values with other values. For examples:
|
||||
The `substitute` command is used to replace values with other values. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -260,7 +260,7 @@ substitute(ex, x=>π), substitute(ex, x=>π, fold=false)
|
||||
Algebraic operations with symbolic values can involve an exponentially increasing number of terms. As such, some simplification rules are applied after an operation to reduce the complexity of the computed value.
|
||||
|
||||
|
||||
For example, `0+x` should simplify to `x`, `0+x`, `x^0`, or `x^1` should simplify, to some natural answer.
|
||||
For example, `0+x` should simplify to `x`, as well `1*x`, `x^0`, or `x^1` should each simplify, to some natural answer.
|
||||
|
||||
|
||||
`SymbolicUtils` also [simplifies](https://symbolicutils.juliasymbolics.org/#simplification) several other expressions, including:
|
||||
@@ -269,7 +269,7 @@ For example, `0+x` should simplify to `x`, `0+x`, `x^0`, or `x^1` should simplif
|
||||
* `-x` becomes `(-1)*x`
|
||||
* `x * x` becomes `x^2` (and `x^n` if more terms). Meaning this expression is represented as a power, not a product
|
||||
* `x + x` becomes `2*x` (and `n*x` if more terms). Similarly, this represented as a product, not a sum.
|
||||
* `p/q * x` becomes `(p*x)/q)`, similarly `p/q * x/y` becomes `(p*x)/(q*y)`
|
||||
* `p/q * x` becomes `(p*x)/q)`, similarly `p/q * x/y` becomes `(p*x)/(q*y)`. (Division wraps multiplication.)
|
||||
|
||||
|
||||
In `SymbolicUtils`, this *rewriting* is accomplished by means of *rewrite rules*. The package makes it easy to apply user-written rewrite rules.
|
||||
@@ -278,7 +278,7 @@ In `SymbolicUtils`, this *rewriting* is accomplished by means of *rewrite rules*
|
||||
### Rewriting
|
||||
|
||||
|
||||
Many algebraic simplifications are done by the `simplify` command. For example, the basic trignometric identities are applied:
|
||||
Many algebraic simplifications are done by the `simplify` command. For example, the basic trigonometric identities are applied:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -307,11 +307,14 @@ ex1 = substitute(ex, x => sin(x + y + z))
|
||||
ex1 |> Symbolics.value |> r |> Num
|
||||
```
|
||||
|
||||
Rules involving two values are also easily created. This one, again, comes from the set of simplifications defined for trignometry and exponential simplifications:
|
||||
Rewrite rules when applied return the rewritten expression, if there is a match, or `nothing`.
|
||||
|
||||
|
||||
Rules involving two values are also easily created. This one, again, comes from the set of simplifications defined for trigonometry and exponential simplifications:
|
||||
|
||||
|
||||
```{julia}
|
||||
r = @rule(exp(~x)^(~y) => exp(~x * ~y))
|
||||
r = @rule(exp(~x)^(~y) => exp(~x * ~y)) # (e^x)^y -> e^(x*y)
|
||||
ex = exp(-x+z)^y
|
||||
ex, ex |> Symbolics.value |> r |> Num
|
||||
```
|
||||
@@ -346,7 +349,7 @@ ex = exp(-x + z)^y
|
||||
Symbolics.toexpr(ex)
|
||||
```
|
||||
|
||||
This output shows an internal representation of the steps for computing the value `ex` given different inputs.
|
||||
This output shows an internal representation of the steps for computing the value `ex` given different inputs. (The number `(-1)` multiplies `x`, this is added to `z` and the result passed to `exp`. That values is then used as the base for `^` with exponent `y`.)
|
||||
|
||||
|
||||
Such `Julia` expressions are one step away from building `Julia` functions for evaluating symbolic expressions fast (though with some technical details about "world age" to be reckoned with). The `build_function` function with the argument `expression=Val(false)` will compile a `Julia` function:
|
||||
@@ -376,7 +379,7 @@ The documentation colorfully says "`build_function` is kind of like if `lambdify
|
||||
The above, through passing $3$ variables after the expression, creates a function of $3$ variables. Functions of a vector of inputs can also be created, just by expressing the variables in that manner:
|
||||
|
||||
|
||||
```{juila}
|
||||
```{julia}
|
||||
h1 = build_function(ex, [x, y, z]; expression=Val(false))
|
||||
h1([1, 2, 3]) # not h1(1,2,3)
|
||||
```
|
||||
@@ -398,7 +401,7 @@ Roots.find_zero(λ, (1, 2))
|
||||
### Plotting
|
||||
|
||||
|
||||
Using `Plots`, the plotting symbolic expressions is similar to the plotting of a function, as there is a plot recipe that converts the expression into a function via `build_function`.
|
||||
Using `Plots`, the plotting of symbolic expressions is similar to the plotting of a function, as there is a plot recipe that converts the expression into a function via `build_function`.
|
||||
|
||||
|
||||
For example,
|
||||
@@ -451,7 +454,7 @@ For example
|
||||
d, r = polynomial_coeffs(a*x^2 + b*x + c, (x,))
|
||||
```
|
||||
|
||||
The first term output is dictionary who's keys are the monomials and who's values are the coefficients. The second term, the residual, is all the remaining parts of the expression, in this case just the constant `c`.
|
||||
The first term output is dictionary with keys which are the monomials and with values which are the coefficients. The second term, the residual, is all the remaining parts of the expression, in this case just the constant `c`.
|
||||
|
||||
|
||||
The expression can then be reconstructed through
|
||||
@@ -461,7 +464,7 @@ The expression can then be reconstructed through
|
||||
r + sum(v*k for (k,v) ∈ d)
|
||||
```
|
||||
|
||||
The above has `a,b,c` as parameters and `x` as the symbol. This separation is designated by passing the desired polynomials symbols to `polynomial_coeff` as an iterable. (Above as a $1$-element tuple.)
|
||||
The above has `a,b,c` as parameters and `x` as the symbol. This separation is designated by passing the desired polynomial symbols to `polynomial_coeff` as an iterable. (Above as a $1$-element tuple.)
|
||||
|
||||
|
||||
More complicated polynomials can be similarly decomposed:
|
||||
@@ -552,6 +555,69 @@ m,n = degree.(nd(ex))
|
||||
m > n ? "limit is infinite" : m < n ? "limit is 0" : "limit is a constant"
|
||||
```
|
||||
|
||||
### Vectors and matrices
|
||||
|
||||
|
||||
Symbolic vectors and matrices can be created with a specified size:
|
||||
|
||||
|
||||
```{julia}
|
||||
@variables v[1:3] M[1:2, 1:3] N[1:3, 1:3]
|
||||
```
|
||||
|
||||
Computations, like finding the determinant below, are lazy unless the values are `collect`ed:
|
||||
|
||||
|
||||
```{julia}
|
||||
using LinearAlgebra
|
||||
det(N)
|
||||
```
|
||||
|
||||
```{julia}
|
||||
det(collect(N))
|
||||
```
|
||||
|
||||
Similarly, with `norm`:
|
||||
|
||||
|
||||
```{julia}
|
||||
norm(v)
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
|
||||
```{julia}
|
||||
norm(collect(v))
|
||||
```
|
||||
|
||||
Matrix multiplication is also deferred, but the size compatability of the matrices and vectors is considered early:
|
||||
|
||||
|
||||
```{julia}
|
||||
M*N, N*N, M*v
|
||||
```
|
||||
|
||||
This errors, as the matrix dimensions are not compatible for multiplication:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| error: true
|
||||
N*M
|
||||
```
|
||||
|
||||
Similarly, linear solutions can be symbolically specified:
|
||||
|
||||
|
||||
```{julia}
|
||||
@variables R[1:2, 1:2] b[1:2]
|
||||
R \ b
|
||||
```
|
||||
|
||||
```{julia}
|
||||
collect(R \ b)
|
||||
```
|
||||
|
||||
### Algebraically solving equations
|
||||
|
||||
|
||||
@@ -567,14 +633,33 @@ or
|
||||
|
||||
|
||||
```{julia}
|
||||
ex = [5x + 2y, 6x + 3y] .~ [1, 2]
|
||||
eqs = [5x + 2y, 6x + 3y] .~ [1, 2]
|
||||
```
|
||||
|
||||
The `Symbolics.solve_for` function can solve *linear* equations. For example,
|
||||
|
||||
|
||||
```{julia}
|
||||
Symbolics.solve_for(ex, [x, y])
|
||||
Symbolics.solve_for(eqs, [x, y])
|
||||
```
|
||||
|
||||
The coefficients can be symbolic. Two examples could be:
|
||||
|
||||
|
||||
```{julia}
|
||||
@variables m b x y
|
||||
eq = y ~ m*x + b
|
||||
Symbolics.solve_for(eq, x)
|
||||
```
|
||||
|
||||
```{julia}
|
||||
@variables a11 a12 a22 x y b1 b2
|
||||
R,X,b = [a11 a12; 0 a22], [x; y], [b1, b2]
|
||||
eqs = R*X .~ b
|
||||
```
|
||||
|
||||
```{julia}
|
||||
Symbolics.solve_for(eqs, [x,y])
|
||||
```
|
||||
|
||||
### Limits
|
||||
@@ -591,26 +676,43 @@ As of writing, there is no extra functionality provided by `Symbolics` for compu
|
||||
|
||||
```{julia}
|
||||
@variables a b c x
|
||||
ex = a*x^2 + b*x + c
|
||||
Symbolics.derivative(ex, x)
|
||||
y = a*x^2 + b*x + c
|
||||
yp = Symbolics.derivative(y, x)
|
||||
```
|
||||
|
||||
The computation can also be broken up into an expression indicating the derivative and then a function to apply the derivative rules:
|
||||
Or to find a critical point:
|
||||
|
||||
|
||||
```{julia}
|
||||
Symbolics.solve_for(yp ~ 0, x) # linear equation to solve
|
||||
```
|
||||
|
||||
The derivative computation can also be broken up into an expression indicating the derivative and then a function to apply the derivative rules:
|
||||
|
||||
|
||||
```{julia}
|
||||
D = Differential(x)
|
||||
D(ex)
|
||||
D(y)
|
||||
```
|
||||
|
||||
and then
|
||||
|
||||
|
||||
```{julia}
|
||||
expand_derivatives(D(ex))
|
||||
expand_derivatives(D(y))
|
||||
```
|
||||
|
||||
The differentials can be multiplied to create operators for taking higher-order derivatives:
|
||||
Using `Differential`, differential equations can be specified. An example was given in [ODEs](../ODEs/differential_equations.html), using `ModelingToolkit`.
|
||||
|
||||
|
||||
Higher order derivatives can be done through composition:
|
||||
|
||||
|
||||
```{julia}
|
||||
D(D(y)) |> expand_derivatives
|
||||
```
|
||||
|
||||
Differentials can also be multiplied to create operators for taking higher-order derivatives:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -628,7 +730,7 @@ In addition to `Symbolics.derivative` there are also the helper functions, such
|
||||
Symbolics.hessian(ex, [x,y])
|
||||
```
|
||||
|
||||
The `gradient` function is also available
|
||||
The `gradient` function is also defined
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -637,13 +739,13 @@ ex = x^2 - 2x*y + z*y
|
||||
Symbolics.gradient(ex, [x, y, z])
|
||||
```
|
||||
|
||||
The `jacobian` takes an array of expressions:
|
||||
The `jacobian` function takes an array of expressions:
|
||||
|
||||
|
||||
```{julia}
|
||||
@variables x y
|
||||
exs = [ x^2 - y^2, 2x*y]
|
||||
Symbolics.jacobian(exs, [x,y])
|
||||
eqs = [ x^2 - y^2, 2x*y]
|
||||
Symbolics.jacobian(eqs, [x,y])
|
||||
```
|
||||
|
||||
### Integration
|
||||
@@ -655,13 +757,15 @@ The `SymbolicNumericIntegration` package provides a means to integrate *univaria
|
||||
Symbolic integration can be approached in different ways. SymPy implements part of the Risch algorithm in addition to other algorithms. Rules-based algorithms could also be implemented.
|
||||
|
||||
|
||||
For example, here is a simple rule that could be used to integrate a single integral
|
||||
For a trivial example, here is a rule that could be used to integrate a single integral
|
||||
|
||||
|
||||
```{julia}
|
||||
is_var(x) = (xs = Symbolics.get_variables(x); length(xs) == 1 && xs[1] === x)
|
||||
@syms x ∫(x)
|
||||
|
||||
is_var(x) = (xs = Symbolics.get_variables(x); length(xs) == 1 && xs[1] === x)
|
||||
r = @rule ∫(~x::is_var) => x^2/2
|
||||
|
||||
r(∫(x))
|
||||
```
|
||||
|
||||
@@ -671,23 +775,37 @@ The `SymbolicNumericIntegration` package includes many more predicates for doing
|
||||
If $f(x)$ is to be integrated, a set of *candidate* answers is generated. The following is **proposed** as an answer: $\sum q_i \Theta_i(x)$. Differentiating the proposed answer leads to a *linear system of equations* that can be solved.
|
||||
|
||||
|
||||
The example in the [paper](https://arxiv.org/pdf/2201.12468v2.pdf) describing the method is with $f(x) = x \sin(x)$ and the candidate thetas are ${x, \sin(x), \cos(x), x\sin(x), x\cos(x)}$ so that we propose:
|
||||
The example in the [paper](https://arxiv.org/pdf/2201.12468v2.pdf) describing the method is with $f(x) = x \sin(x)$ and the candidate thetas are ${x, \sin(x), \cos(x), x\sin(x), x\cos(x)}$ so that the propose answer is:
|
||||
|
||||
|
||||
$$
|
||||
\int f(x) dx = q_1 x + q_2 \sin(x) + q_3 \cos(x) + q_4 x \sin(x) + q_4 x \cos(x)
|
||||
$$
|
||||
|
||||
Differentiating both sides, yields a term $x\sin(x)$ on the left, and equating coefficients gives:
|
||||
We differentiate the right hand side:
|
||||
|
||||
|
||||
```{julia}
|
||||
@variables q[1:5] x
|
||||
ΣqᵢΘᵢ = dot(collect(q), (x, sin(x), cos(x), x*sin(x), x*cos(x)))
|
||||
simplify(Symbolics.derivative(ΣqᵢΘᵢ, x))
|
||||
```
|
||||
|
||||
This must match $x\sin(x)$ so we have by equating coefficients of the respective terms:
|
||||
|
||||
|
||||
$$
|
||||
q_1 = q_4 = 0,\quad q_5 = -1, \quad q_4 - q_3 = q_2 - q_5 = 0
|
||||
q_2 + q_5 = 0, \quad q_4 = 0, \quad q_1 = 0, \quad q_3 = 0, \quad q_5 = -1
|
||||
$$
|
||||
|
||||
which can be solved with $q_5=-1$, $q_2=1$, and the other coefficients being $0$. That is $\int f(x) dx = 1 \sin(x) + (-1) x\cos(x)$.
|
||||
That is $q_2=1$, $q_5=-1$, and the other coefficients are $0$, giving an answer computed with:
|
||||
|
||||
|
||||
```{julia}
|
||||
d = Dict(q[i] => v for (i,v) ∈ enumerate((0,1,0,0,-1)))
|
||||
substitute(ΣqᵢΘᵢ, d)
|
||||
```
|
||||
|
||||
The package provides an algorithm for the creation of candidates and the means to solve when possible. The `integrate` function is the main entry point. It returns three values: `solved`, `unsolved`, and `err`. The `unsolved` is the part of the integrand which can not be solved through this package. It is `0` for a given problem when `integrate` is successful in identifying an antiderivative, in which case `solved` is the answer. The value of `err` is a bound on the numerical error introduced by the algorithm.
|
||||
|
||||
|
||||
@@ -701,7 +819,7 @@ using SymbolicNumericIntegration
|
||||
integrate(x * sin(x))
|
||||
```
|
||||
|
||||
The second term is `0`, as this has an identified antiderivative.
|
||||
The second term is `0`, as this integrand has an identified antiderivative.
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -725,6 +843,9 @@ The derivative of `u` matches up to some numeric tolerance:
|
||||
Symbolics.derivative(u, x) - sin(x)^5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
The integration of rational functions (ratios of polynomials) can be done algorithmically, provided the underlying factorizations can be identified. The `SymbolicNumericIntegration` package has a function `factor_rational` that can identify factorizations.
|
||||
|
||||
|
||||
@@ -758,14 +879,14 @@ u = 1 / expand((x^2+1)*(x-2)^2)
|
||||
v = factor_rational(u)
|
||||
```
|
||||
|
||||
As such, the integrals have numeric differences:
|
||||
As such, the integrals have numeric differences from their mathematical counterparts:
|
||||
|
||||
|
||||
```{julia}
|
||||
a,b,c = integrate(u)
|
||||
```
|
||||
|
||||
We can see a bit of why through the following which needs a tolerance set to identify the rational numbers correctly:
|
||||
We can see a bit of how much through the following, which needs a tolerance set to identify the rational numbers of the mathematical factorization correctly:
|
||||
|
||||
|
||||
```{julia}
|
||||
|
||||
Reference in New Issue
Block a user