2022-08-26 14:45:24 -04:00

11 lines
41 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"hash": "a14790e03a1c5ab753ca9cdf016f7980",
"result": {
"markdown": "# Symbolics.jl\n\n\n\n\nThere are a few options in `Julia` for symbolic math, for example, the `SymPy` package which wraps a Python library. This section describes a collection of native `Julia` packages providing many features of symbolic math.\n\n\n## About\n\n\nThe `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.\n\n\nWe begin by loading the `Symbolics` package which when loaded re-exports the `SymbolicUtils` package.\n\n``` {.julia .cell-code}\nusing Symbolics\n```\n\n\n## Symbolic variables\n\n\nSymbolic 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.\n\n\n`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\n\n::: {.cell execution_count=2}\n``` {.julia .cell-code}\n@syms x y::Int f(x::Real)::Real\n```\n\n::: {.cell-output .cell-output-display execution_count=3}\n```\n(x, y, f)\n```\n:::\n:::\n\n\nThis 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.\n\n\nThe non-exported `symtype` function reveals the underlying type:\n\n::: {.cell execution_count=3}\n``` {.julia .cell-code}\nimport Symbolics.SymbolicUtils: symtype\n\nsymtype(x), symtype(y)\n```\n\n::: {.cell-output .cell-output-display execution_count=4}\n```\n(Number, Int64)\n```\n:::\n:::\n\n\nFor `y`, the symbolic type being real does not imply the type of `y` is a subtype of `Real`:\n\n::: {.cell execution_count=4}\n``` {.julia .cell-code}\nisa(y, Real)\n```\n\n::: {.cell-output .cell-output-display execution_count=5}\n```\nfalse\n```\n:::\n:::\n\n\nWe see that the function `f` when called with `y` would return a value of (symbolic) type `Real`:\n\n::: {.cell execution_count=5}\n``` {.julia .cell-code}\nf(y) |> symtype\n```\n\n::: {.cell-output .cell-output-display execution_count=6}\n```\nReal\n```\n:::\n:::\n\n\nAs the symbolic type of `x` is `Number` which is not a subtype of `Real` the following will error:\n\n::: {.cell execution_count=6}\n``` {.julia .cell-code}\nf(x)\n```\n\n::: {.cell-output .cell-output-error}\n```\nLoadError: Tuple{Number} is not a subtype of Tuple{Real}.\n```\n:::\n:::\n\n\n:::{.callout-note}\n## Note\nThe `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.\n\n:::\n\nFor `Symbolics`, symbolic variables are created using a wrapper around an underlying `SymbolicUtils` object. This wrapper, `Num`, is a subtype of `Real` (the underlying `SymbolicUtils` object may have symbolic type `Real`, but it won't be a subtype of `Real`.)\n\n\nSymbolic values are created with the `@variables` macro. For example:\n\n::: {.cell execution_count=7}\n``` {.julia .cell-code}\n@variables x y::Int z[1:3]::Int f(..)::Int\n```\n\n::: {.cell-output .cell-output-display execution_count=8}\n```\n4-element Vector{Any}:\n x\n y\n z[1:3]\n f⋆\n```\n:::\n:::\n\n\nThis creates\n\n\n * a symbolic value `x` of `symtype` `Real`\n * a symbolic value `y` of `symtype` `Int`\n * a vector of symbolic values each of `symtype` `Int`\n * a symbolic function `f` returning an object of `symtype` `Int`\n\n\nThe symbolic type reflects that of the underlying object behind the `Num` wrapper:\n\n::: {.cell execution_count=8}\n``` {.julia .cell-code}\ntypeof(x), symtype(x), typeof(Symbolics.value(x))\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n```\n(Num, Real, SymbolicUtils.Sym{Real, Base.ImmutableDict{DataType, Any}})\n```\n:::\n:::\n\n\n(The `value` method unwraps the `Num` wrapper.)\n\n\n## Symbolic expressions\n\n\nSymbolic expressions are built up from symbolic variables through natural `Julia` idioms. `SymbolicUtils` privileges a few key operations: `Add`, `Mul`, `Pow`, and `Div`. For examples:\n\n::: {.cell execution_count=9}\n``` {.julia .cell-code}\n@syms x y\ntypeof(x + y) # `Add`\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n```\nSymbolicUtils.Add{Number, Int64, Dict{Any, Number}, Nothing}\n```\n:::\n:::\n\n\n::: {.cell execution_count=10}\n``` {.julia .cell-code}\ntypeof(x * y) # `Mul`\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n```\nSymbolicUtils.Mul{Number, Int64, Dict{Any, Number}, Nothing}\n```\n:::\n:::\n\n\nWhereas, applying a function leaves a different type:\n\n::: {.cell execution_count=11}\n``` {.julia .cell-code}\ntypeof(sin(x))\n```\n\n::: {.cell-output .cell-output-display execution_count=12}\n```\nSymbolicUtils.Term{Number, Nothing}\n```\n:::\n:::\n\n\nThe `Term` wrapper just represents the effect of calling a function (in this case `sin`) on its arguments (in this case `x`).\n\n\nThis happens in the background with symbolic variables in `Symbolics`:\n\n::: {.cell execution_count=12}\n``` {.julia .cell-code}\n@variables x\ntypeof(sin(x)), typeof(Symbolics.value(sin(x)))\n```\n\n::: {.cell-output .cell-output-display execution_count=13}\n```\n(Num, SymbolicUtils.Term{Real, Nothing})\n```\n:::\n:::\n\n\n### Tree structure to expressions\n\n\nThe `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)):\n\n\n * `istree(ex)`: `true` if `ex` is not a *leaf* node (like a symbol or numeric literal)\n * `operation(ex)`: the function being called (if `istree` returns `true`)\n * `arguments(ex)`: the arguments to the function begin called\n * `symtype(ex)`: the inferred type of the expression\n\n\nIn addition, the `issym` function, to determine if `x` is of type `Sym`, is useful to distinguish *leaf* nodes, as will be illustrated below.\n\n\nThese methods can be used to \"walk\" the tree:\n\n::: {.cell execution_count=13}\n``` {.julia .cell-code}\n@syms x y\nex = 1 + x^2 + y\noperation(ex) # the outer function is `+`\n```\n\n::: {.cell-output .cell-output-display execution_count=14}\n```\n+ (generic function with 550 methods)\n```\n:::\n:::\n\n\n::: {.cell execution_count=14}\n``` {.julia .cell-code}\narguments(ex) # `+` is n-ary, in this case with 3 arguments\n```\n\n::: {.cell-output .cell-output-display execution_count=15}\n```\n3-element Vector{Any}:\n 1\n y\n x^2\n```\n:::\n:::\n\n\n::: {.cell execution_count=15}\n``` {.julia .cell-code}\nex1 = arguments(ex)[3] # terms have been reordered\noperation(ex1) # operation for `x^2` is `^`\n```\n\n::: {.cell-output .cell-output-display execution_count=16}\n```\n^ (generic function with 216 methods)\n```\n:::\n:::\n\n\n::: {.cell execution_count=16}\n``` {.julia .cell-code}\na, b = arguments(ex1)\n```\n\n::: {.cell-output .cell-output-display execution_count=17}\n```\n2-element Vector{Union{Int64, SymbolicUtils.Sym{Number, Nothing}}}:\n x\n 2\n```\n:::\n:::\n\n\n::: {.cell execution_count=17}\n``` {.julia .cell-code}\nistree(ex1), istree(a)\n```\n\n::: {.cell-output .cell-output-display execution_count=18}\n```\n(true, false)\n```\n:::\n:::\n\n\nHere `a` is not a \"tree\", as it has no operation or arguments, it is just a variable (the `x` variable).\n\n\nThe value of `symtype` is the *inferred* type of an expression, which may not match the actual type. For example,\n\n::: {.cell execution_count=18}\n``` {.julia .cell-code}\n@variables x::Int\nsymtype(x), symtype(sin(x)), symtype(x/x), symtype(x / x^2)\n```\n\n::: {.cell-output .cell-output-display execution_count=19}\n```\n(Int64, Real, Int64, Int64)\n```\n:::\n:::\n\n\nThe last one, is not likely to be an integer, but that is the inferred type in this case.\n\n\n##### Example\n\n\nAs an example, we write a function to find the free symbols in a symbolic expression comprised of `SymbolicUtils` variables. (The `Symbolics.get_variables` also does this task.) To find the symbols involves walking the expression tree until a leaf node is found and then adding that to our collection if it matches `issym`.\n\n::: {.cell execution_count=19}\n``` {.julia .cell-code}\nimport Symbolics.SymbolicUtils: issym\nfree_symbols(ex) = (s=Set(); free_symbols!(s, ex); s)\nfunction free_symbols!(s, ex)\n if istree(ex)\n for a ∈ arguments(ex)\n free_symbols!(s, a)\n end\n else\n issym(ex) && push!(s, ex) # push new symbol onto set\n end\nend\n```\n\n::: {.cell-output .cell-output-display execution_count=20}\n```\nfree_symbols! (generic function with 1 method)\n```\n:::\n:::\n\n\n::: {.cell execution_count=20}\n``` {.julia .cell-code}\n@syms x y z\nex = sin(x + 1)*cos(z)\nfree_symbols(ex)\n```\n\n::: {.cell-output .cell-output-display execution_count=21}\n```\nSet{Any} with 2 elements:\n x\n z\n```\n:::\n:::\n\n\n## Expression manipulation\n\n\n### Substitute\n\n\nThe `substitute` command is used to replace values with other values. For example:\n\n::: {.cell execution_count=21}\n``` {.julia .cell-code}\n@variables x y z\nex = 1 + x + x^2/2 + x^3/6\nsubstitute(ex, x=>1)\n```\n\n::: {.cell-output .cell-output-display execution_count=22}\n```\n8//3\n```\n:::\n:::\n\n\nThis defines a symbolic expression, then substitutes the value `1` in for `x`. The `Pair` notation is useful for a *single* substitution. When there is more than one substitution, a dictionary is used:\n\n::: {.cell execution_count=22}\n``` {.julia .cell-code}\nw = x^3 + y^3 - 2z^3\nsubstitute(w, Dict(x=>2, y=>3))\n```\n\n::: {.cell-output .cell-output-display execution_count=23}\n```\n35 - 2(z^3)\n```\n:::\n:::\n\n\nThe `fold` argument can be passed `false` to inhibit evaluation of values. Compare:\n\n::: {.cell execution_count=23}\n``` {.julia .cell-code}\nex = 1 + sqrt(x)\nsubstitute(ex, x=>2), substitute(ex, x=>2, fold=false)\n```\n\n::: {.cell-output .cell-output-display execution_count=24}\n```\n(2.414213562373095, 1 + sqrt(2))\n```\n:::\n:::\n\n\nOr\n\n::: {.cell execution_count=24}\n``` {.julia .cell-code}\nex = sin(x)\nsubstitute(ex, x=>π), substitute(ex, x=>π, fold=false)\n```\n\n::: {.cell-output .cell-output-display execution_count=25}\n```\n(1.2246467991473532e-16, sin(π))\n```\n:::\n:::\n\n\n### Simplify\n\n\nAlgebraic 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.\n\n\nFor example, `0+x` should simplify to `x`, as well `1*x`, `x^0`, or `x^1` should each simplify, to some natural answer.\n\n\n`SymbolicUtils` also [simplifies](https://symbolicutils.juliasymbolics.org/#simplification) several other expressions, including:\n\n\n * `-x` becomes `(-1)*x`\n * `x * x` becomes `x^2` (and `x^n` if more terms). Meaning this expression is represented as a power, not a product\n * `x + x` becomes `2*x` (and `n*x` if more terms). Similarly, this represented as a product, not a sum.\n * `p/q * x` becomes `(p*x)/q)`, similarly `p/q * x/y` becomes `(p*x)/(q*y)`. (Division wraps multiplication.)\n\n\nIn `SymbolicUtils`, this *rewriting* is accomplished by means of *rewrite rules*. The package makes it easy to apply user-written rewrite rules.\n\n\n### Rewriting\n\n\nMany algebraic simplifications are done by the `simplify` command. For example, the basic trigonometric identities are applied:\n\n::: {.cell execution_count=25}\n``` {.julia .cell-code}\n@variables x\nex = sin(x)^2 + cos(x)^2\nex, simplify(ex)\n```\n\n::: {.cell-output .cell-output-display execution_count=26}\n```\n(cos(x)^2 + sin(x)^2, 1)\n```\n:::\n:::\n\n\nThe `simplify` function applies a series of rewriting rule until the expression stabilizes. The rewrite rules can be user generated, if desired. For example, the Pythagorean identity of trigonometry, just used, can be implement with this rule:\n\n::: {.cell execution_count=26}\n``` {.julia .cell-code}\nr = @acrule(sin(~x)^2 + cos(~x)^2 => one(~x))\nex |> Symbolics.value |> r |> Num\n```\n\n::: {.cell-output .cell-output-display execution_count=27}\n```\n1\n```\n:::\n:::\n\n\nThe rewrite rule, `r`, is defined by the `@acrule` macro. The `a` is for associative, the `c` for commutative, assumptions made by the macro. (The `c` means `cos(x)^2 + sin(x)^2` will also simplify.) Rewrite rules are called on the underlying `SymbolicUtils` expression, so we first unwrap, then after re-wrap.\n\n\nThe above expression for `r` is fairly easy to appreciate. The value `~x` matches the same variable or expression. So the above rule will also simplify more complicated expressions:\n\n::: {.cell execution_count=27}\n``` {.julia .cell-code}\n@variables y z\nex1 = substitute(ex, x => sin(x + y + z))\nex1 |> Symbolics.value |> r |> Num\n```\n\n::: {.cell-output .cell-output-display execution_count=28}\n```\n1\n```\n:::\n:::\n\n\nRewrite rules when applied return the rewritten expression, if there is a match, or `nothing`.\n\n\nRules involving two values are also easily created. This one, again, comes from the set of simplifications defined for trigonometry and exponential simplifications:\n\n::: {.cell execution_count=28}\n``` {.julia .cell-code}\nr = @rule(exp(~x)^(~y) => exp(~x * ~y)) # (e^x)^y -> e^(x*y)\nex = exp(-x+z)^y\nex, ex |> Symbolics.value |> r |> Num\n```\n\n::: {.cell-output .cell-output-display execution_count=29}\n```\n(exp(z - x)^y, exp(y*(z - x)))\n```\n:::\n:::\n\n\nThis rule is not commutative or associative, as `x^y` is not the same as `y^x` and `(x^y)^z` is not `x^(y^z)` in general.\n\n\nThe application of rules can be filtered through qualifying predicates. This artificial example uses `iseven` which returns `true` for even numbers. Here we subtract `1` when a number is not even, and otherwise leave the number alone. We do this with two rules:\n\n::: {.cell execution_count=29}\n``` {.julia .cell-code}\nreven = @rule ~x::iseven => ~x\nrodd = @rule ~x::(!iseven) => ~x - 1\nr = SymbolicUtils.Chain([rodd, reven])\nr(2), r(3)\n```\n\n::: {.cell-output .cell-output-display execution_count=30}\n```\n(2, 2)\n```\n:::\n:::\n\n\nThe `Chain` function conveniently allows the sequential application of rewrite rules.\n\n\nThe notation `~x` is called a \"slot variable\" in the [documentation](https://symbolicutils.juliasymbolics.org/rewrite/) for `SymbolicUtils`. It matches a single expression. To match more than one expression, a \"segment variable\", denoted with two `~`s is used.\n\n\n### Creating functions\n\n\nBy utilizing the tree-like nature of a symbolic expression, a `Julia` expression can be built from an symbolic expression easily enough. The `Symbolics.toexpr` function does this:\n\n::: {.cell execution_count=30}\n``` {.julia .cell-code}\nex = exp(-x + z)^y\nSymbolics.toexpr(ex)\n```\n\n::: {.cell-output .cell-output-display execution_count=31}\n```\n:((^)((exp)((+)(z, (*)(-1, x))), y))\n```\n:::\n:::\n\n\nThis 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`.)\n\n\nSuch `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:\n\n::: {.cell execution_count=31}\n``` {.julia .cell-code}\nh = build_function(ex, x, y, z; expression=Val(false))\nh(1, 2, 3)\n```\n\n::: {.cell-output .cell-output-display execution_count=32}\n```\n54.59815003314424\n```\n:::\n:::\n\n\nThe above is *similar* to substitution:\n\n::: {.cell execution_count=32}\n``` {.julia .cell-code}\nsubstitute(ex, Dict(x=>1, y=>2, z=>3))\n```\n\n::: {.cell-output .cell-output-display execution_count=33}\n```\n54.59815003314424\n```\n:::\n:::\n\n\nHowever, `build_function` will be **significantly** more performant, which when many function calls are used such as with plotting is a big advantage.\n\n\n:::{.callout-note}\n## Note\nThe documentation colorfully says \"`build_function` is kind of like if `lambdify` (from `SymPy`) ate its spinach.\"\n\n:::\n\nThe 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:\n\n::: {.cell execution_count=33}\n``` {.julia .cell-code}\nh1 = build_function(ex, [x, y, z]; expression=Val(false))\nh1([1, 2, 3]) # not h1(1,2,3)\n```\n\n::: {.cell-output .cell-output-display execution_count=34}\n```\n54.59815003314424\n```\n:::\n:::\n\n\n##### Example\n\n\nAs an example, here we use the `Roots` package to find a zero of a function defined symbolically:\n\n::: {.cell execution_count=34}\n``` {.julia .cell-code}\nimport Roots\n@variables x\nex = x^5 - x - 1\nλ = build_function(ex, x; expression=Val(false))\nRoots.find_zero(λ, (1, 2))\n```\n\n::: {.cell-output .cell-output-display execution_count=35}\n```\n1.1673039782614187\n```\n:::\n:::\n\n\n### Plotting\n\n\nUsing `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`.\n\n\nFor example,\n\n::: {.cell execution_count=35}\n``` {.julia .cell-code}\nusing Plots\n@variables x\nplot(x^x^x, 0, 2)\n```\n\n::: {.cell-output .cell-output-display execution_count=36}\n![](symbolics_files/figure-html/cell-36-output-1.svg){}\n:::\n:::\n\n\nA parametric plot is easily defined:\n\n::: {.cell execution_count=36}\n``` {.julia .cell-code}\nplot(sin(x), cos(x), 0, pi/4)\n```\n\n::: {.cell-output .cell-output-display execution_count=37}\n![](symbolics_files/figure-html/cell-37-output-1.svg){}\n:::\n:::\n\n\nExpressions to be plotted can represent multivariate functions.\n\n::: {.cell execution_count=37}\n``` {.julia .cell-code}\n@variables x y\nex = 3*(1-x)^2*exp(-x^2 - (y+1)^2) - 10(x/5-x^3-y^5)*exp(-x^2-y^2) - 1/3*exp(-(x+1)^2-y^2)\nxs = ys = range(-5, 5, length=100)\nsurface(xs, ys, ex)\n```\n\n::: {.cell-output .cell-output-display execution_count=38}\n![](symbolics_files/figure-html/cell-38-output-1.svg){}\n:::\n:::\n\n\nThe ordering of the variables is determined by `Symbolics.get_variables`:\n\n::: {.cell execution_count=38}\n``` {.julia .cell-code}\nSymbolics.get_variables(ex)\n```\n\n::: {.cell-output .cell-output-display execution_count=39}\n```\n2-element Vector{Any}:\n y\n x\n```\n:::\n:::\n\n\n### Polynomial manipulations\n\n\nThere are some facilities for manipulating polynomial expressions in `Symbolics`. A polynomial, mathematically, is an expression involving one or more symbols with coefficients from a collection that has, at a minimum, addition and multiplication defined. The basic building blocks of polynomials are *monomials*, which are comprised of products of powers of the symbols. Mathematically, monomials are often allowed to have a multiplying coefficient and may be just a coefficient (if each symbol is taken to the power $0$), but here we consider just expressions of the type $x_1^{a_1} \\cdot x_2^{a_2} \\cdots x_k^{a_k}$ with the $a_i > 0$ as monomials.\n\n\nWith this understanding, then an expression can be broken up into monomials with a possible leading coefficient (possibly $1$) *and* terms which are not monomials (such as a constant or a more complicated function of the symbols). This is what is returned by the `polynomial_coeffs` function.\n\n\nFor example\n\n::: {.cell execution_count=39}\n``` {.julia .cell-code}\n@variables a b c x\nd, r = polynomial_coeffs(a*x^2 + b*x + c, (x,))\n```\n\n::: {.cell-output .cell-output-display execution_count=40}\n```\n(Dict{Any, Any}(x => b, x^2 => a), c)\n```\n:::\n:::\n\n\nThe 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`.\n\n\nThe expression can then be reconstructed through\n\n::: {.cell execution_count=40}\n``` {.julia .cell-code}\nr + sum(v*k for (k,v) ∈ d)\n```\n\n::: {.cell-output .cell-output-display execution_count=41}\n```\nc + b*x + a*(x^2)\n```\n:::\n:::\n\n\nThe 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.)\n\n\nMore complicated polynomials can be similarly decomposed:\n\n::: {.cell execution_count=41}\n``` {.julia .cell-code}\n@variables a b c x y z\nex = a*x^2*y*z + b*x*y^2*z + c*x*y*z^2\nd, r = polynomial_coeffs(ex, (x, y, z))\n```\n\n::: {.cell-output .cell-output-display execution_count=42}\n```\n(Dict{Any, Any}(x*z*(y^2) => b, y*z*(x^2) => a, x*y*(z^2) => c), 0)\n```\n:::\n:::\n\n\nThe (sparse) decomposition of the polynomial is returned through `d`. The same pattern as above can be used to reconstruct the expression. To extract the coefficient for a monomial term, indexing can be used. Of note, is an expression like `x^2*y*z` could *possibly* not equal the algebraically equal `x*y*z*x`, as they are only equal after some simplification, but the keys are in a canonical form, so this is not a concern:\n\n::: {.cell execution_count=42}\n``` {.julia .cell-code}\nd[x*y*z*x], d[z*y*x^2]\n```\n\n::: {.cell-output .cell-output-display execution_count=43}\n```\n(a, a)\n```\n:::\n:::\n\n\nThe residual term will capture any non-polynomial terms:\n\n::: {.cell execution_count=43}\n``` {.julia .cell-code}\nex = sin(x) - x + x^3/6\nd, r = polynomial_coeffs(ex, (x,))\nr\n```\n\n::: {.cell-output .cell-output-display execution_count=44}\n```\nsin(x)\n```\n:::\n:::\n\n\nTo find the degree of a monomial expression, the `degree` function is available. Here it is applied to each monomial in `d`:\n\n::: {.cell execution_count=44}\n``` {.julia .cell-code}\n[degree(k) for (k,v) ∈ d]\n```\n\n::: {.cell-output .cell-output-display execution_count=45}\n```\n2-element Vector{Int64}:\n 1\n 3\n```\n:::\n:::\n\n\nThe `degree` function will also identify the degree of more complicated terms:\n\n::: {.cell execution_count=45}\n``` {.julia .cell-code}\ndegree(1 + x + x^2)\n```\n\n::: {.cell-output .cell-output-display execution_count=46}\n```\n2\n```\n:::\n:::\n\n\nMathematically the degree of the $0$ polynomial may be $-1$ or undefined, but here it is $0$:\n\n::: {.cell execution_count=46}\n``` {.julia .cell-code}\ndegree(0), degree(1), degree(x), degree(x^a)\n```\n\n::: {.cell-output .cell-output-display execution_count=47}\n```\n(0, 0, 1, a)\n```\n:::\n:::\n\n\nThe coefficients are returned as *values* of a dictionary, and dictionaries are unsorted. To have a natural map between polynomials of a single symbol in the standard basis and a vector, we could use a pattern like this:\n\n::: {.cell execution_count=47}\n``` {.julia .cell-code}\n@variables x a0 as[1:10]\np = a0 + sum(as[i]*x^i for i ∈ eachindex(collect(as)))\nd, r = polynomial_coeffs(p, (x,))\nd\n```\n\n::: {.cell-output .cell-output-display execution_count=48}\n```\nDict{Any, Any} with 10 entries:\n x^8 => as[8]\n x => as[1]\n x^3 => as[3]\n x^5 => as[5]\n x^4 => as[4]\n x^6 => as[6]\n x^7 => as[7]\n x^2 => as[2]\n x^10 => as[10]\n x^9 => as[9]\n```\n:::\n:::\n\n\nTo sort the values we can use a pattern like the following:\n\n::: {.cell execution_count=48}\n``` {.julia .cell-code}\nvcat(r, [d[k] for k ∈ sort(collect(keys(d)), by=degree)])\n```\n\n::: {.cell-output .cell-output-display execution_count=49}\n```\n11-element Vector{SymbolicUtils.Symbolic{Real}}:\n a0\n as[1]\n as[2]\n as[3]\n as[4]\n as[5]\n as[6]\n as[7]\n as[8]\n as[9]\n as[10]\n```\n:::\n:::\n\n\n---\n\n\nRational expressions can be decomposed into a numerator and denominator using the following idiom, which ensures the outer operation is division (a binary operation):\n\n::: {.cell execution_count=49}\n``` {.julia .cell-code}\n@variables x\nex = (1 + x + x^2) / (1 + x + x^2 + x^3)\nfunction nd(ex)\n ex1 = Symbolics.value(ex)\n (operation(ex1) == /) || return (ex, one(ex))\n Num.(arguments(ex1))\nend\nnd(ex)\n```\n\n::: {.cell-output .cell-output-display execution_count=50}\n```\n2-element Vector{Num}:\n 1 + x + x^2\n 1 + x + x^2 + x^3\n```\n:::\n:::\n\n\nWith this, the study of asymptotic behaviour of a univariate rational expression would involve an investigation like the following:\n\n::: {.cell execution_count=50}\n``` {.julia .cell-code}\nm,n = degree.(nd(ex))\nm > n ? \"limit is infinite\" : m < n ? \"limit is 0\" : \"limit is a constant\"\n```\n\n::: {.cell-output .cell-output-display execution_count=51}\n```\n\"limit is 0\"\n```\n:::\n:::\n\n\n### Vectors and matrices\n\n\nSymbolic vectors and matrices can be created with a specified size:\n\n::: {.cell execution_count=51}\n``` {.julia .cell-code}\n@variables v[1:3] M[1:2, 1:3] N[1:3, 1:3]\n```\n\n::: {.cell-output .cell-output-display execution_count=52}\n```\n3-element Vector{Symbolics.Arr{Num}}:\n v[1:3]\n M[1:2,1:3]\n N[1:3,1:3]\n```\n:::\n:::\n\n\nComputations, like finding the determinant below, are lazy unless the values are `collect`ed:\n\n::: {.cell execution_count=52}\n``` {.julia .cell-code}\nusing LinearAlgebra\ndet(N)\n```\n\n::: {.cell-output .cell-output-display execution_count=53}\n```\nSymbolics._det(N, true)\n```\n:::\n:::\n\n\n::: {.cell execution_count=53}\n``` {.julia .cell-code}\ndet(collect(N))\n```\n\n::: {.cell-output .cell-output-display execution_count=54}\n```\n(N[2, 1]*N[3, 2] - N[2, 2]*N[3, 1])*N[1, 3] + (N[2, 2]*N[3, 3] - N[2, 3]*N[3, 2])*N[1, 1] - (N[2, 1]*N[3, 3] - N[2, 3]*N[3, 1])*N[1, 2]\n```\n:::\n:::\n\n\nSimilarly, with `norm`:\n\n::: {.cell execution_count=54}\n``` {.julia .cell-code}\nnorm(v)\n```\n\n::: {.cell-output .cell-output-display execution_count=55}\n```\nsqrt(Symbolics._mapreduce(#327, +, v, Colon(), (:init => false,)))\n```\n:::\n:::\n\n\nand\n\n::: {.cell execution_count=55}\n``` {.julia .cell-code}\nnorm(collect(v))\n```\n\n::: {.cell-output .cell-output-display execution_count=56}\n```\nsqrt(abs2(v[1]) + abs2(v[2]) + abs2(v[3]))\n```\n:::\n:::\n\n\nMatrix multiplication is also deferred, but the size compatability of the matrices and vectors is considered early:\n\n::: {.cell execution_count=56}\n``` {.julia .cell-code}\nM*N, N*N, M*v\n```\n\n::: {.cell-output .cell-output-display execution_count=57}\n```\n((M*N)[1:2,1:3], (N*N)[1:3,1:3], (M*v)[1:2])\n```\n:::\n:::\n\n\nThis errors, as the matrix dimensions are not compatible for multiplication:\n\n::: {.cell execution_count=57}\n``` {.julia .cell-code}\nN*M\n```\n\n::: {.cell-output .cell-output-error}\n```\nLoadError: DimensionMismatch(\"expected axes(M, 1) = 1:3\")\n```\n:::\n:::\n\n\nSimilarly, linear solutions can be symbolically specified:\n\n::: {.cell execution_count=58}\n``` {.julia .cell-code}\n@variables R[1:2, 1:2] b[1:2]\nR \\ b\n```\n\n::: {.cell-output .cell-output-display execution_count=59}\n```\n(R \\ b)[1:2]\n```\n:::\n:::\n\n\n::: {.cell execution_count=59}\n``` {.julia .cell-code}\ncollect(R \\ b)\n```\n\n::: {.cell-output .cell-output-stderr}\n```\n┌ Warning: could not attach metadata of subexpression R \\ b to the scalarized form at idx\n└ @ Symbolics /Users/verzani/.julia/packages/Symbolics/4VdEG/src/arrays.jl:636\n```\n:::\n\n::: {.cell-output .cell-output-display execution_count=60}\n```\n2-element Vector{Num}:\n ((-((-b[1]*R[2, 1]) / R[1, 1] + b[2])*R[1, 2]) / ((-R[1, 2]*R[2, 1]) / R[1, 1] + R[2, 2]) + b[1]) / R[1, 1]\n ((-b[1]*R[2, 1]) / R[1, 1] + b[2]) / ((-R[1, 2]*R[2, 1]) / R[1, 1] + R[2, 2])\n```\n:::\n:::\n\n\n### Algebraically solving equations\n\n\nThe `~` operator creates a symbolic equation. For example\n\n::: {.cell execution_count=60}\n``` {.julia .cell-code}\n@variables x y\nx^5 - x ~ 1\n```\n\n::: {.cell-output .cell-output-display execution_count=61}\n```\nx^5 - x ~ 1\n```\n:::\n:::\n\n\nor\n\n::: {.cell execution_count=61}\n``` {.julia .cell-code}\neqs = [5x + 2y, 6x + 3y] .~ [1, 2]\n```\n\n::: {.cell-output .cell-output-display execution_count=62}\n```\n2-element Vector{Equation}:\n 5x + 2y ~ 1\n 6x + 3y ~ 2\n```\n:::\n:::\n\n\nThe `Symbolics.solve_for` function can solve *linear* equations. For example,\n\n::: {.cell execution_count=62}\n``` {.julia .cell-code}\nSymbolics.solve_for(eqs, [x, y])\n```\n\n::: {.cell-output .cell-output-display execution_count=63}\n```\n2-element Vector{Float64}:\n -0.3333333333333333\n 1.3333333333333333\n```\n:::\n:::\n\n\nThe coefficients can be symbolic. Two examples could be:\n\n::: {.cell execution_count=63}\n``` {.julia .cell-code}\n@variables m b x y\neq = y ~ m*x + b\nSymbolics.solve_for(eq, x)\n```\n\n::: {.cell-output .cell-output-display execution_count=64}\n```\n(y - b) / m\n```\n:::\n:::\n\n\n::: {.cell execution_count=64}\n``` {.julia .cell-code}\n@variables a11 a12 a22 x y b1 b2\nR,X,b = [a11 a12; 0 a22], [x; y], [b1, b2]\neqs = R*X .~ b\n```\n\n::: {.cell-output .cell-output-display execution_count=65}\n```\n2-element Vector{Equation}:\n a11*x + a12*y ~ b1\n a22*y ~ b2\n```\n:::\n:::\n\n\n::: {.cell execution_count=65}\n``` {.julia .cell-code}\nSymbolics.solve_for(eqs, [x,y])\n```\n\n::: {.cell-output .cell-output-display execution_count=66}\n```\n2-element Vector{SymbolicUtils.Div{Real, N, D, Nothing} where {N, D}}:\n ((a12*b2) / a22 - b1) / (-a11)\n b2 / a22\n```\n:::\n:::\n\n\n### Limits\n\n\nAs of writing, there is no extra functionality provided by `Symbolics` for computing limits.\n\n\n### Derivatives\n\n\n`Symbolics` provides the `derivative` function to compute the derivative of a function with respect to a variable:\n\n::: {.cell execution_count=66}\n``` {.julia .cell-code}\n@variables a b c x\ny = a*x^2 + b*x + c\nyp = Symbolics.derivative(y, x)\n```\n\n::: {.cell-output .cell-output-display execution_count=67}\n```\nb + 2a*x\n```\n:::\n:::\n\n\nOr to find a critical point:\n\n::: {.cell execution_count=67}\n``` {.julia .cell-code}\nSymbolics.solve_for(yp ~ 0, x) # linear equation to solve\n```\n\n::: {.cell-output .cell-output-display execution_count=68}\n```\nb / (-2a)\n```\n:::\n:::\n\n\nThe derivative computation can also be broken up into an expression indicating the derivative and then a function to apply the derivative rules:\n\n::: {.cell execution_count=68}\n``` {.julia .cell-code}\nD = Differential(x)\nD(y)\n```\n\n::: {.cell-output .cell-output-display execution_count=69}\n```\nDifferential(x)(c + b*x + a*(x^2))\n```\n:::\n:::\n\n\nand then\n\n::: {.cell execution_count=69}\n``` {.julia .cell-code}\nexpand_derivatives(D(y))\n```\n\n::: {.cell-output .cell-output-display execution_count=70}\n```\nb + 2a*x\n```\n:::\n:::\n\n\nUsing `Differential`, differential equations can be specified. An example was given in [ODEs](../ODEs/differential_equations.html), using `ModelingToolkit`.\n\n\nHigher order derivatives can be done through composition:\n\n::: {.cell execution_count=70}\n``` {.julia .cell-code}\nD(D(y)) |> expand_derivatives\n```\n\n::: {.cell-output .cell-output-display execution_count=71}\n```\n2a\n```\n:::\n:::\n\n\nDifferentials can also be multiplied to create operators for taking higher-order derivatives:\n\n::: {.cell execution_count=71}\n``` {.julia .cell-code}\n@variables x y\nex = (x - y^2)/(x^2 + y^2)\nDx, Dy = Differential(x), Differential(y)\nDxx, Dxy, Dyy = Dx*Dx, Dx*Dy, Dy*Dy\n[Dxx(ex) Dxy(ex); Dxy(ex) Dyy(ex)] .|> expand_derivatives\n```\n\n::: {.cell-output .cell-output-display execution_count=72}\n```\n2×2 Matrix{Num}:\n (2(y^2) - 2x) / ((x^2 + y^2)^2) - 2x*(1 / ((x^2 + y^2)^2)) - 2x*(1 / ((x^2 + y^2)^2) - 2x*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2))) … -2x*((-2y) / ((x^2 + y^2)^2)) - 2y*(1 / ((x^2 + y^2)^2) - 2x*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2)))\n -2x*((-2y) / ((x^2 + y^2)^2)) - 2y*(1 / ((x^2 + y^2)^2) - 2x*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2))) -2 / (x^2 + y^2) + (2(y^2) - 2x) / ((x^2 + y^2)^2) - 2y*((-2y) / ((x^2 + y^2)^2)) - 2y*((-2y) / ((x^2 + y^2)^2) - 2y*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2)))\n```\n:::\n:::\n\n\nIn addition to `Symbolics.derivative` there are also the helper functions, such as `hessian` which performs the above\n\n::: {.cell execution_count=72}\n``` {.julia .cell-code}\nSymbolics.hessian(ex, [x,y])\n```\n\n::: {.cell-output .cell-output-display execution_count=73}\n```\n2×2 Matrix{Num}:\n (2(y^2) - 2x) / ((x^2 + y^2)^2) - 2x*(1 / ((x^2 + y^2)^2)) - 2x*(1 / ((x^2 + y^2)^2) - 2x*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2))) … -2y*(1 / ((x^2 + y^2)^2)) - 2x*((-2y) / ((x^2 + y^2)^2) - 2y*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2)))\n -2y*(1 / ((x^2 + y^2)^2)) - 2x*((-2y) / ((x^2 + y^2)^2) - 2y*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2))) -2 / (x^2 + y^2) + (2(y^2) - 2x) / ((x^2 + y^2)^2) - 2y*((-2y) / ((x^2 + y^2)^2)) - 2y*((-2y) / ((x^2 + y^2)^2) - 2y*((x - (y^2)) / ((x^2 + y^2)^4))*(2(x^2) + 2(y^2)))\n```\n:::\n:::\n\n\nThe `gradient` function is also defined\n\n::: {.cell execution_count=73}\n``` {.julia .cell-code}\n@variables x y z\nex = x^2 - 2x*y + z*y\nSymbolics.gradient(ex, [x, y, z])\n```\n\n::: {.cell-output .cell-output-display execution_count=74}\n```\n3-element Vector{Num}:\n 2x - 2y\n z - 2x\n y\n```\n:::\n:::\n\n\nThe `jacobian` function takes an array of expressions:\n\n::: {.cell execution_count=74}\n``` {.julia .cell-code}\n@variables x y\neqs = [ x^2 - y^2, 2x*y]\nSymbolics.jacobian(eqs, [x,y])\n```\n\n::: {.cell-output .cell-output-display execution_count=75}\n```\n2×2 Matrix{Num}:\n 2x -2y\n 2y 2x\n```\n:::\n:::\n\n\n### Integration\n\n\nThe `SymbolicNumericIntegration` package provides a means to integrate *univariate* expressions through its `integrate` function.\n\n\nSymbolic 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.\n\n\nFor a trivial example, here is a rule that could be used to integrate a single integral\n\n::: {.cell execution_count=75}\n``` {.julia .cell-code}\n@syms x ∫(x)\n\nis_var(x) = (xs = Symbolics.get_variables(x); length(xs) == 1 && xs[1] === x)\nr = @rule ∫(~x::is_var) => x^2/2\n\nr(∫(x))\n```\n\n::: {.cell-output .cell-output-display execution_count=76}\n```\n(1//2)*(x^2)\n```\n:::\n:::\n\n\nThe `SymbolicNumericIntegration` package includes many more predicates for doing rules-based integration, but it primarily approaches the task in a different manner.\n\n\nIf $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.\n\n\nThe 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:\n\n\n\n$$\n\\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)\n$$\n\n\nWe differentiate the right hand side:\n\n::: {.cell execution_count=76}\n``` {.julia .cell-code}\n@variables q[1:5] x\nΣqᵢΘᵢ = dot(collect(q), (x, sin(x), cos(x), x*sin(x), x*cos(x)))\nsimplify(Symbolics.derivative(ΣqᵢΘᵢ, x))\n```\n\n::: {.cell-output .cell-output-display execution_count=77}\n```\n(q[2] + q[5])*cos(x) + sin(x)*q[4] + x*cos(x)*q[4] + q[1] - sin(x)*q[3] - x*sin(x)*q[5]\n```\n:::\n:::\n\n\nThis must match $x\\sin(x)$ so we have by equating coefficients of the respective terms:\n\n\n\n$$\nq_2 + q_5 = 0, \\quad q_4 = 0, \\quad q_1 = 0, \\quad q_3 = 0, \\quad q_5 = -1\n$$\n\n\nThat is $q_2=1$, $q_5=-1$, and the other coefficients are $0$, giving an answer computed with:\n\n::: {.cell execution_count=77}\n``` {.julia .cell-code}\nd = Dict(q[i] => v for (i,v) ∈ enumerate((0,1,0,0,-1)))\nsubstitute(ΣqᵢΘᵢ, d)\n```\n\n::: {.cell-output .cell-output-display execution_count=78}\n```\nsin(x) - x*cos(x)\n```\n:::\n:::\n\n\nThe 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.\n\n\nTo see, we have:\n\n::: {.cell execution_count=78}\n``` {.julia .cell-code}\nusing SymbolicNumericIntegration\n@variables x\n\nintegrate(x * sin(x))\n```\n\n::: {.cell-output .cell-output-display execution_count=79}\n```\n(sin(x) - x*cos(x), 0, 0)\n```\n:::\n:::\n\n\nThe second term is `0`, as this integrand has an identified antiderivative.\n\n::: {.cell execution_count=79}\n``` {.julia .cell-code}\nintegrate(exp(x^2) + sin(x))\n```\n\n::: {.cell-output .cell-output-display execution_count=80}\n```\n(-cos(x), exp(x^2), Inf)\n```\n:::\n:::\n\n\nThis returns `exp(x^2)` for the unsolved part, as this function has no simple antiderivative.\n\n\nPowers of trig functions have antiderivatives, as can be deduced using integration by parts. When the fifth power is used, there is a numeric aspect to the algorithm that is seen:\n\n::: {.cell execution_count=80}\n``` {.julia .cell-code}\nu,v,w = integrate(sin(x)^5)\n```\n\n::: {.cell-output .cell-output-display execution_count=81}\n```\n(-0.5333333334686141(cos(x)^5) - (sin(x)^4)*cos(x) - 1.3333333333333333(cos(x)^3)*(sin(x)^2), 0, 2.10912146938623e-10)\n```\n:::\n:::\n\n\nThe derivative of `u` matches up to some numeric tolerance:\n\n::: {.cell execution_count=81}\n``` {.julia .cell-code}\nSymbolics.derivative(u, x) - sin(x)^5\n```\n\n::: {.cell-output .cell-output-display execution_count=82}\n```\n6.764042659312963e-10(cos(x)^4)*sin(x)\n```\n:::\n:::\n\n\n---\n\nThe 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.\n\n::: {.cell execution_count=82}\n``` {.julia .cell-code}\nimport SymbolicNumericIntegration: factor_rational\n@variables x\nu = (1 + x + x^2)/ (x^2 -2x + 1)\nv = factor_rational(u)\n```\n\n::: {.cell-output .cell-output-display execution_count=83}\n```\nx*((x - 1)^-2) + (x - 1)^-2 + (x^2)*((x - 1)^-2)\n```\n:::\n:::\n\n\nThe summands in `v` are each integrable. We can see that `v` is a reexpression through\n\n::: {.cell execution_count=83}\n``` {.julia .cell-code}\nsimplify(u - v)\n```\n\n::: {.cell-output .cell-output-display execution_count=84}\n```\n0\n```\n:::\n:::\n\n\nThe algorithm is numeric, not symbolic. This can be seen in these two factorizations:\n\n::: {.cell execution_count=84}\n``` {.julia .cell-code}\nu = 1 / expand((x^2-1)*(x-2)^2)\nv = factor_rational(u)\n```\n\n::: {.cell-output .cell-output-display execution_count=85}\n```\n0.5((x - 1)^-1) + 0.3333333333333333((x - 2)^-2) - 0.05555555555555705((1 + x)^-1) - 0.4444444444444444((x - 2)^-1)\n```\n:::\n:::\n\n\nor\n\n::: {.cell execution_count=85}\n``` {.julia .cell-code}\nu = 1 / expand((x^2+1)*(x-2)^2)\nv = factor_rational(u)\n```\n\n::: {.cell-output .cell-output-display execution_count=86}\n```\n0.12000000000000012((1 + x^2)^-1) + 0.2((x - 2)^-2) + 0.15999999999999995x*((1 + x^2)^-1) - 0.1599999999999994((x - 2)^-1)\n```\n:::\n:::\n\n\nAs such, the integrals have numeric differences from their mathematical counterparts:\n\n::: {.cell execution_count=86}\n``` {.julia .cell-code}\na,b,c = integrate(u)\n```\n\n::: {.cell-output .cell-output-display execution_count=87}\n```\n(0.07999999995936365log(1 + x^2) + 0.1200000001879806atan(x) - 0.15999999338167692log(x - 2) - 0.1x*((x - 2)^-1), 0, 2.327469790901071e-9)\n```\n:::\n:::\n\n\nWe 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:\n\n::: {.cell execution_count=87}\n``` {.julia .cell-code}\ncs = [first(arguments(term)) for term ∈ arguments(a)] # pick off coefficients\n```\n\n::: {.cell-output .cell-output-display execution_count=88}\n```\n4-element Vector{Float64}:\n 0.07999999995936365\n 0.1200000001879806\n -0.15999999338167692\n -0.1\n```\n:::\n:::\n\n\n::: {.cell execution_count=88}\n``` {.julia .cell-code}\nrationalize.(cs; tol=1e-8)\n```\n\n::: {.cell-output .cell-output-display execution_count=89}\n```\n4-element Vector{Rational{Int64}}:\n 2//25\n 3//25\n -4//25\n -1//10\n```\n:::\n:::\n\n\n",
"supporting": [
"symbolics_files/figure-html"
],
"filters": [],
"includes": {}
}
}