From 1a9131cc548bf46278d7e71a33eb4bc828ba2d25 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 21 Oct 2023 18:52:55 -0400 Subject: [PATCH] move SymPyPyCall -> SymPy; breaking change --- .github/ISSUE_TEMPLATE/bug_report.md | 8 + .github/workflows/CompatHelper.yml | 4 +- .github/workflows/TagBot.yml | 18 +- .github/workflows/ci.yml | 40 +- .github/workflows/documenter.yml | 28 - Project.toml | 26 +- README.md | 161 +- REQUIRE | 4 - docs/Project.toml | 5 - docs/make.jl | 18 - docs/src/Tutorial/LICENSE-orig.txt | 97 - docs/src/Tutorial/basic_operations.md | 589 ----- docs/src/Tutorial/calculus.md | 912 -------- docs/src/Tutorial/gotchas.md | 736 ------ docs/src/Tutorial/index.md | 21 - docs/src/Tutorial/intro.md | 538 ----- docs/src/Tutorial/manipulation.md | 987 -------- docs/src/Tutorial/matrices.md | 1335 ----------- docs/src/Tutorial/simplification.md | 1787 --------------- docs/src/Tutorial/solvers.md | 747 ------ docs/src/index.md | 92 +- docs/src/introduction.md | 2993 ------------------------- docs/src/reference.md | 18 - ext/SymPySymbolicUtilsExt.jl | 52 - src/SymPy.jl | 271 +-- src/assumptions.jl | 367 --- src/constructors.jl | 169 -- src/decl.jl | 145 -- src/deprecated.jl | 136 -- src/generic.jl | 117 - src/importexport.jl | 152 -- src/lambdify.jl | 300 --- src/latexify_recipe.jl | 12 - src/mathfuns.jl | 355 --- src/mathops.jl | 36 - src/matrix.jl | 197 -- src/numbers.jl | 346 --- src/patternmatch.jl | 211 -- src/permutations.jl | 255 --- src/physics.jl | 10 - src/plot_recipes.jl | 262 --- src/python_connection.jl | 83 + src/sets.jl | 26 - src/symfunction.jl | 197 -- src/types.jl | 175 -- src/utils.jl | 534 ----- test/runtests.jl | 15 +- test/test-external-module.jl | 48 - test/test-latexify.jl | 9 - test/test-logical.jl | 8 - test/test-math.jl | 57 - test/test-matrix.jl | 196 -- test/test-ode.jl | 86 - test/test-physics.jl | 48 - test/test-plots.jl | 48 - test/test-specialfuncs.jl | 180 -- test/tests.jl | 862 ------- 57 files changed, 163 insertions(+), 16966 deletions(-) delete mode 100644 .github/workflows/documenter.yml delete mode 100644 REQUIRE delete mode 100644 docs/src/Tutorial/LICENSE-orig.txt delete mode 100644 docs/src/Tutorial/basic_operations.md delete mode 100644 docs/src/Tutorial/calculus.md delete mode 100644 docs/src/Tutorial/gotchas.md delete mode 100644 docs/src/Tutorial/index.md delete mode 100644 docs/src/Tutorial/intro.md delete mode 100644 docs/src/Tutorial/manipulation.md delete mode 100644 docs/src/Tutorial/matrices.md delete mode 100644 docs/src/Tutorial/simplification.md delete mode 100644 docs/src/Tutorial/solvers.md delete mode 100644 docs/src/introduction.md delete mode 100644 docs/src/reference.md delete mode 100644 ext/SymPySymbolicUtilsExt.jl delete mode 100644 src/assumptions.jl delete mode 100644 src/constructors.jl delete mode 100644 src/decl.jl delete mode 100644 src/deprecated.jl delete mode 100644 src/generic.jl delete mode 100644 src/importexport.jl delete mode 100644 src/lambdify.jl delete mode 100644 src/latexify_recipe.jl delete mode 100644 src/mathfuns.jl delete mode 100644 src/mathops.jl delete mode 100644 src/matrix.jl delete mode 100644 src/numbers.jl delete mode 100644 src/patternmatch.jl delete mode 100644 src/permutations.jl delete mode 100644 src/physics.jl delete mode 100644 src/plot_recipes.jl create mode 100644 src/python_connection.jl delete mode 100644 src/sets.jl delete mode 100644 src/symfunction.jl delete mode 100644 src/types.jl delete mode 100644 src/utils.jl delete mode 100644 test/test-external-module.jl delete mode 100644 test/test-latexify.jl delete mode 100644 test/test-logical.jl delete mode 100644 test/test-math.jl delete mode 100644 test/test-matrix.jl delete mode 100644 test/test-ode.jl delete mode 100644 test/test-physics.jl delete mode 100644 test/test-plots.jl delete mode 100644 test/test-specialfuncs.jl delete mode 100644 test/tests.jl diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea78..83897259 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,14 @@ assignees: '' --- +**Wait** + +Most all issues are actually issues with [SymPyCore](https://github.com/jverzani/SymPyCore.jl/issues). If so please report them there. + +`SymPy` contains very little code. Basically only that necessary to support the "glue" package `PyCall` to interop `Julia` with `Python`. + + + **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index a67914aa..cba9134c 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -1,7 +1,7 @@ name: CompatHelper on: schedule: - - cron: '00 00 * * *' + - cron: 0 0 * * * workflow_dispatch: jobs: CompatHelper: @@ -12,5 +12,5 @@ jobs: - name: CompatHelper.main() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} # optional + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 623860f7..2bacdb87 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,22 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: 3 +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' @@ -12,4 +28,4 @@ jobs: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} \ No newline at end of file + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99d7aa1b..25881043 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,16 @@ name: CI on: - pull_request: - branches: - - master push: branches: - - master - tags: '*' + - main + tags: ['*'] + pull_request: + workflow_dispatch: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} @@ -15,33 +19,19 @@ jobs: fail-fast: false matrix: version: - - '1.6' # lowest supported version - - '1' # last released version - os: + - '1.6' + - '1.9' + +os: - ubuntu-latest - - macos-latest - - windows-latest arch: - x64 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 - with: - file: lcov.info diff --git a/.github/workflows/documenter.yml b/.github/workflows/documenter.yml deleted file mode 100644 index f5545bf5..00000000 --- a/.github/workflows/documenter.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Documentation - -on: - push: - branches: - - master # update to match your development branch (master, main, dev, trunk, ...) - tags: '*' - pull_request: - -jobs: - build: - permissions: - contents: write - statuses: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 - with: - version: '1.9' - - run: pip3 install sympy - - name: Install dependencies - run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - - name: Build and deploy - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key - run: julia --project=docs/ docs/make.jl \ No newline at end of file diff --git a/Project.toml b/Project.toml index 2da4f23c..55a9b940 100644 --- a/Project.toml +++ b/Project.toml @@ -1,36 +1,30 @@ name = "SymPy" uuid = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6" -version = "1.2.0" +version = "2.0.0." [deps] CommonEq = "3709ef60-1bee-4518-9f2f-acd86f176c50" CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" -Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" -RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" - -[weakdeps] -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" - -[extensions] -SymPySymbolicUtilsExt = "SymbolicUtils" +SymPyCore = "458b697b-88f0-4a86-b56b-78b75cfb3531" [compat] CommonEq = "0.2" CommonSolve = "0.2" -Latexify = "0.15, 0.16" -PyCall = "1.91" -RecipesBase = "0.7, 0.8, 1.0, 1.1" -SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" +LinearAlgebra = "1.6" +PyCall = "1.96" +SpecialFunctions = "0.7, 0.8, 0.8, 0.10, 1, 2" SymbolicUtils = "1" -julia = "1.0" +SymPyCore = "0.1, 1" +julia = "1.6" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" [targets] -test = ["SymbolicUtils", "Test"] +test = ["Test", "LinearAlgebra", "SpecialFunctions", "SymbolicUtils"] diff --git a/README.md b/README.md index 21be0f0d..322decd5 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,16 @@ -[![Build status](https://github.com/JuliaPy/SymPy.jl/workflows/CI/badge.svg)](https://github.com/JuliaPy/SymPy.jl/actions) -  -[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliapy.github.io/SymPy.jl/dev/) +# SymPy +[![Docs](https://img.shields.io/badge/docs-dev-blue.svg)](https://jverzani.github.io/SymPyCore.jl/dev) +[![Build Status](https://github.com/jverzani/SymPy.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/jverzani/SymPy.jl/actions/workflows/CI.yml?query=branch%3Amain) +[![Coverage](https://codecov.io/gh/jverzani/SymPy.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/jverzani/SymPy.jl) -> **Note** -> `SymPy` is on the move... Well, actually, the `SymPy.jl` package is planned to utilize -> the new [SymPyCore](https://github.com/jverzani/SymPyCore.jl) backend, which is used by [SymPyPythonCall](https://github.com/jverzani/SymPyPythonCall.jl) and (temporarily) by the unregistered [SymPyPyCall](https://github.com/jverzani/SymPyPyCall.jl). -> -> The plan is: -> -> * `v"1.2.0"` will include most all deprecations. See `src/deprecated.jl` for details -> -> * `v"2.0.0"` will be what is currently `SymPyPyCall`. It is expected that most all code will continue to work as is, though if a few of the dustier corners are used, there may need to be some modest adjustments. +[SymPyCore](https://github.com/jverzani/SymPyCore.jl) provides a `Julia`n interface to the [SymPy](https://www.sympy.org/) library of Python. -# SymPy Package to bring Python's `SymPy` functionality into `Julia` via `PyCall` -SymPy (`http://sympy.org/`) is a Python library for symbolic mathematics. +`SymPy` utilizes `SymPyCore` and the `PyCall` package (to provide the interop between `Julia` and `Python`) to enable access to Python's SymPy library using the practices and idioms of `Julia`. -With the excellent `PyCall` package of `julia`, one has access to the -many features of the SymPy library from within a `Julia` session. - -This `SymPy` package provides a light interface for the -features of the SymPy library that makes working with SymPy objects a bit -easier. - -(The [SymPyPythonCall](https://github.com/jverzani/SymPyPythonCall.jl/) package provides an -alternative relying on the [PythonCall](https://github.com/cjdoris/PythonCall.jl) -package for interfacing with `Python`.) - -The documentation inludes an introduction document and a version of -the SymPy tutorial translated from the Python syntax into Julia. +The package [SymPyPythonCall](https://github.com/jverzani/SymPyPythonCall.jl) does a similar thing with the `PythonCall` package providing the interop. ### Installation @@ -39,7 +19,7 @@ installed on your system. If `PyCall` is installed using `Conda` (which is the default if no system `python` is found), then the underlying SymPy library will be installed via `Conda` when the package is first loaded. Otherwise, installing both Python and the -SymPy library (which also requires mpmath) can be done by other means. +SymPy library can be done by other means. In this case, the `Anaconda` distribution is suggested, as it provides a single installation of Python that includes SymPy and many other scientific libraries that can be profitably accessed within `Julia` @@ -56,128 +36,3 @@ Pkg.add("Conda") # if needed using Conda Conda.update() ``` - -## The `PyCall` interface to `SymPy` - -The only point to this package is that using `PyCall` to access -SymPy is somewhat cumbersome. The following is how one would define -a symbolic value `x`, take its sine, then evaluate the symboic -expression for `x` equal `pi`, say: - -``` -using PyCall -sympy = pyimport("sympy") # -x = sympy.Symbol("x") # PyObject x -y = sympy.sin(x) # PyObject sin(x) -z = y.subs(x, sympy.pi) # PyObject 0 -convert(Float64, z) # 0.0 -``` - - -The `sympy` object imported on the second line provides the access to -much of SymPy's functionality, allowing access to functions -(`sympy.sin`), properties, modules (`sympy`), and classes -(`sympy.Symbol`, `sympy.Pi`). The `Symbol` and `sin` operations are found -within the imported `sympy` module and, as seen, are referenced with -`Python`'s dot call syntax, as implemented in `PyCall` through a -specialized `getproperty` method. - -SymPy's functionality is also found through methods bound to -an object of a certain class. The `subs` method of the `y` object is an -example. Such methods are also accessed with Python's dot-call -syntax. The call above substitutes a value of `sympy.pi` for the -symbolic variable `x`. This leaves the object as a `PyObject` storing -a number which can be brought back into `julia` through conversion, in -this case through an explicit `convert` call. - - -Alternatively, `PyCall` now has a `*` method, so the above could also be done with: - -``` -x = sympy.Symbol("x") -y = sympy.pi * x -z = sympy.sin(y) -convert(Float64, z.subs(x, 1)) -``` - -With the `SymPy` package this gets replaced by a more `julia`n syntax: - -``` -using SymPy -x = symbols("x") # or @syms x -y = sin(pi*x) -y(1) # Does y.subs(x, 1). Use y(x=>1) to be specific as to which symbol to substitute -``` - -The object `x` we create is of type `Sym`, a simple proxy for the -underlying `PyObject`. The package overloads the familiar math functions so -that working with symbolic expressions can use natural `julia` -idioms. The final result here is a symbolic value of `0`, which -prints as `0` and not `PyObject 0`. To convert it into a numeric value -within `Julia`, the `N` function may be used, which acts like the -float conversion, only there is an attempt to preserve the variable type. - -(There is a subtlety, the value of `pi` here (an `Irrational` in -`Julia`) is converted to the symbolic `PI`, but in general won't be if -the math constant is coerced to a floating point value before it -encounters a symbolic object. It is better to just use the symbolic -value `PI`, an alias for `sympy.pi` used above.) - - ----- - - -SymPy has a mix of function calls (as in `sin(x)`) and method calls -(as in `y.subs(x,1)`). The function calls are from objects in the base -`sympy` module. When the `SymPy` package is loaded, in addition to -specialized methods for many generic `Julia` functions, such as `sin`, -a priviledged set of the function calls in `sympy` are imported as -generic functions narrowed on their first argument being a symbolic -object, as constructed by `@syms`, `Sym`, or `symbols`. (Calling -`import_from(sympy)` will import all the function calls.) - -The basic usage follows these points: - -* generic methods from `Julia` and imported functions in the `sympy` - namespace are called through `fn(object)` - -* SymPy methods are called through Python's dot-call syntax: - `object.fn(...)` - -* Contructors, like `sympy.Symbol`, and other non-function calls from `sympy` are qualified - with `sympy.Constructor(...)`. Such qualified calls are also useful - when the first argument is not symbolic. - - -So, these three calls are different, - -``` -sin(1), sin(Sym(1)), sympy.sin(1) -``` - -The first involves no symbolic values. The second and third are -related and return a symbolic value for `sin(1)`. The second -dispatches on the symbolic argument `Sym(1)`, the third has no -dispatch, but refers to a SymPy function from the `sympy` object. Its -argument, `1`, is converted by `PyCall` into a Python object for the -function to process. - -In the initial example, slightly rewritten, we could have issued: - -``` -x = symbols("x") -y = sin(pi*x) -y.subs(x, 1) -``` - -The first line calls a provided alias for `sympy.symbols` which is -defined to allow a string (or a symbol) as an argument. The second, -dispatches to `sympy.sin`, as `pi*x` is symbolic-- `x` is, and -multiplication promotes to a symbolic value. The third line uses the -dot-call syntax of `PyCall` to call the `subs` method of the symbolic -`y` object. - - -Not illustrated above, but classes and other objects from SymPy are -not brought in by default, and can be accessed using qualification, as -in `sympy.Function` (used, as is `@syms`, to define symbolic functions). diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index 40b4a086..00000000 --- a/REQUIRE +++ /dev/null @@ -1,4 +0,0 @@ -julia 1.0.0 -PyCall 1.90.0 -RecipesBase 0.5.0 -SpecialFunctions 0.3.4 diff --git a/docs/Project.toml b/docs/Project.toml index f1489335..dfa65cd1 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,7 +1,2 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" - -[compat] -Documenter = "0.24, 1" diff --git a/docs/make.jl b/docs/make.jl index f300baa2..ec42daa7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,7 +1,3 @@ -ENV["PLOTS_TEST"] = "true" -ENV["GKSwstype"] = "100" - - using Documenter using SymPy @@ -11,20 +7,6 @@ format = Documenter.HTML(), modules = [SymPy], pages = [ "Home" => "index.md", - "Examples" => "introduction.md", - "SymPy tutorial"=> [ - "About" => "Tutorial/index.md", - "Introduction" => "Tutorial/intro.md", - "Gotchas" => "Tutorial/gotchas.md", - "Basic operations" => "Tutorial/basic_operations.md", - "Simplification" => "Tutorial/simplification.md", - "Calculus" => "Tutorial/calculus.md", - "Solvers" => "Tutorial/solvers.md", - "Matrices" => "Tutorial/matrices.md", - "Advanced expression manipulation" => "Tutorial/manipulation.md" - ], - "Reference/API" => "reference.md" - ], ) # Documenter can also automatically deploy documentation to gh-pages. diff --git a/docs/src/Tutorial/LICENSE-orig.txt b/docs/src/Tutorial/LICENSE-orig.txt deleted file mode 100644 index eb4cad68..00000000 --- a/docs/src/Tutorial/LICENSE-orig.txt +++ /dev/null @@ -1,97 +0,0 @@ -Copyright (c) 2006-2018 SymPy Development Team - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - a. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - b. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - c. Neither the name of SymPy nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - --------------------------------------------------------------------------------- - -Patches taken from the Diofant project (https://github.com/diofant/diofant) -are licensed as: - -Copyright (c) 2006-2017 SymPy Development Team, - 2013-2017 Sergey B Kirpichev - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - a. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - b. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - c. Neither the name of Diofant, or SymPy nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - --------------------------------------------------------------------------------- - -Submodule taken from the multipledispatch project (https://github.com/mrocklin/multipledispatch) -are licensed as: - -Copyright (c) 2014 Matthew Rocklin - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - a. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - b. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - c. Neither the name of multipledispatch nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. diff --git a/docs/src/Tutorial/basic_operations.md b/docs/src/Tutorial/basic_operations.md deleted file mode 100644 index d06fcaca..00000000 --- a/docs/src/Tutorial/basic_operations.md +++ /dev/null @@ -1,589 +0,0 @@ -# Basic Operations - -[From](https://docs.sympy.org/latest/tutorial/basic_operations.html) - - -Here we discuss some of the most basic operations needed for expression -manipulation in SymPy. Some more advanced operations will be discussed later -in the :ref:`advanced expression manipulation ` section. - -```python - >>> from sympy import * - >>> x, y, z = symbols("x y z") -``` - -##### In `Julia`: - -```@setup basicoperations -using SymPy -sympy.init_printing(use_unicode=True) -``` - -```jldoctest basicoperations -julia> using SymPy - -julia> x, y, z = symbols("x y z") -(x, y, z) -``` - -* We didn't replicate `from sympy import *`, though this is mostly - done through the command `import_from(sympy)`. By default, `SymPy` - only makes available a priviledged collection of the functions - available through the `sympy` object. The `import_from` imports most - all of the rest. - -* If a function is not imported, it may be referenced through qualification, asin `sympy.expand_trig`, as will be seen in the following. - -* the use of `symbols` to construct symbolic values is more easily facilitated with the macro `@syms`, used as follows - -```jldoctest basicoperations -julia> @syms x, y, z -(x, y, z) -``` - ----- - -## Substitution - - -One of the most common things you might want to do with a mathematical -expression is substitution. Substitution replaces all instances of something -in an expression with something else. It is done using the `subs` method. -For example - -```python - >>> expr = cos(x) + 1 - >>> expr.subs(x, y) - cos(y) + 1 -``` - -##### In `Julia`: - -```jldoctest basicoperations -julia> expr = cos(x) + 1 -cos(x) + 1 - -julia> expr.subs(x, y) -cos(y) + 1 -``` - -Julia also allows "call" notation using a pairs to indicate the substitution: - -```jldoctest basicoperations -julia> expr(x => y) -cos(y) + 1 -``` - ----- - -Substitution is usually done for one of two reasons: - -1. Evaluating an expression at a point. For example, if our expression is - `cos(x) + 1` and we want to evaluate it at the point `x = 0`, so that - we get `cos(0) + 1`, which is 2. - -```python - >>> expr.subs(x, 0) - 2 -``` - -##### In `Julia`: - -```jldoctest basicoperations -julia> expr(x => 0) -2 -``` - ----- - -2. Replacing a subexpression with another subexpression. There are two - reasons we might want to do this. The first is if we are trying to build - an expression that has some symmetry, such as `x^{x^{x^x}}`. To build - this, we might start with `x**y`, and replace `y` with `x**y`. We - would then get `x**(x**y)`. If we replaced `y` in this new expression - with `x**x`, we would get `x**(x**(x**x))`, the desired expression. - -```python - >>> expr = x**y - >>> expr - x**y - >>> expr = expr.subs(y, x**y) - >>> expr - x**(x**y) - >>> expr = expr.subs(y, x**x) - >>> expr - x**(x**(x**x)) -``` - -##### In `Julia`: - -```jldoctest basicoperations -julia> expr = x^y - y -x - -julia> expr = expr(y => x^y) - ⎛ y⎞ - ⎝x ⎠ -x - -julia> expr = expr(y => x^x) - ⎛ ⎛ x⎞⎞ - ⎜ ⎝x ⎠⎟ - ⎝x ⎠ -x - -``` - ----- - -The second is if we want to perform a very controlled simplification, or - perhaps a simplification that SymPy is otherwise unable to do. For - example, say we have `\sin(2x) + \cos(2x)`, and we want to replace - `\sin(2x)` with `2\sin(x)\cos(x)`. As we will learn later, the function - `expand_trig` does this. However, this function will also expand - `\cos(2x)`, which we may not want. While there are ways to perform such - precise simplification, and we will learn some of them in the - :ref:`advanced expression manipulation ` section, an - easy way is to just replace `\sin(2x)` with `2\sin(x)\cos(x)`. - -```python - >>> expr = sin(2*x) + cos(2*x) - >>> expand_trig(expr) - 2*sin(x)*cos(x) + 2*cos(x)**2 - 1 - >>> expr.subs(sin(2*x), 2*sin(x)*cos(x)) - 2*sin(x)*cos(x) + cos(2*x) -``` - -##### In `Julia`: - -* `expand_trig` is not exported, so we qualify it: - -```jldoctest basicoperations -julia> expr = sin(2*x) + cos(2*x) -sin(2⋅x) + cos(2⋅x) - -julia> sympy.expand_trig(expr) |> string -"2*sin(x)*cos(x) + 2*cos(x)^2 - 1" - -julia> expr(sin(2*x) => 2*sin(x)*cos(x)) -2⋅sin(x)⋅cos(x) + cos(2⋅x) -``` - ----- - -There are two important things to note about `subs`. First, it returns a new expression. SymPy objects are immutable. That means that `subs` does not modify it in-place. For example - -```python - >>> expr = cos(x) - >>> expr.subs(x, 0) - 1 - >>> expr - cos(x) - >>> x - x -``` - -##### In `Julia`: - -```jldoctest basicoperations -julia> expr = cos(x) -cos(x) - -julia> expr(x => 0) -1 - -julia> expr -cos(x) - -julia> x -x -``` - ----- - -!!! note "Quick Tip" - SymPy expressions are immutable. No function will change them in-place. - - -Here, we see that performing `expr.subs(x, 0)` leaves `expr` unchanged. -In fact, since SymPy expressions are immutable, no function will change them -in-place. All functions will return new expressions. - -To perform multiple substitutions at once, pass a list of `(old, new)` pairs -to `subs`. - -```python - >>> expr = x**3 + 4*x*y - z - >>> expr.subs([(x, 2), (y, 4), (z, 0)]) - 40 -``` - -##### In `Julia`: - -```jldoctest basicoperations -julia> expr = x^3 + 4*x*y - z; string(expr) -"x^3 + 4*x*y - z" - -julia> expr.subs([(x, 2), (y, 4), (z, 0)]) -40 -``` - -Or, using pairs: - -```jldoctest basicoperations -julia> expr(x=>2, y=>4, z=>0) -40 -``` - ----- - -It is often useful to combine this with a list comprehension to do a large set -of similar replacements all at once. For example, say we had `x^4 - 4x^3 + 4x^2 - -2x + 3` and we wanted to replace all instances of `x` that have an even power -with `y`, to get `y^4 - 4x^3 + 4y^2 - 2x + 3`. - -```python - >>> expr = x**4 - 4*x**3 + 4*x**2 - 2*x + 3 - >>> replacements = [(x**i, y**i) for i in range(5) if i % 2 == 0] - >>> expr.subs(replacements) - -4*x**3 - 2*x + y**4 + 4*y**2 + 3 -``` - -##### In `Julia`: - -```jldoctest basicoperations -julia> expr = x^4 - 4*x^3 + 4*x^2 - 2*x + 3 - 4 3 2 -x - 4⋅x + 4⋅x - 2⋅x + 3 - -julia> replacements = [(x^i, y^i) for i in 1:5 if iseven(i)] -2-element Vector{Tuple{Sym, Sym}}: - (x^2, y^2) - (x^4, y^4) - -julia> expr.subs(replacements) - 3 4 2 -- 4⋅x - 2⋅x + y + 4⋅y + 3 -``` - ----- - -## Converting Strings to SymPy Expressions - - -The `sympify` function (that's `sympify`, not to be confused with -`simplify`) can be used to convert strings into SymPy expressions. - -For example - -```python - >>> str_expr = "x**2 + 3*x - 1/2" - >>> expr = sympify(str_expr) - >>> expr - x**2 + 3*x - 1/2 - >>> expr.subs(x, 2) - 19/2 -``` - -##### In `Julia`: - -As `sympify` is not passed a symbolic value, it is qualified: - -```jldoctest basicoperations -julia> str_expr = "x^2 + 3*x - 1/2" -"x^2 + 3*x - 1/2" - -julia> expr = sympy.sympify(str_expr) - 2 1 -x + 3⋅x - ─ - 2 -julia> expr.subs(x, 2) -19/2 -``` - ----- - -!!! note "Alert:" - `sympify` uses `eval`. Don't use it on unsanitized input. - -## `evalf` - - -To evaluate a numerical expression into a floating point number, use -`evalf`. - -```python - >>> expr = sqrt(8) - >>> expr.evalf() - 2.82842712474619 -``` - -##### In `Julia`: - -* We must use a symbolic value for `8`: - -```jldoctest basicoperations -julia> expr = sqrt(Sym(8)) -2⋅√2 - -julia> expr.evalf() -2.82842712474619 -``` - -!!! note "N is different in SymPy.jl" - - More importantly, `SymPy.jl` treats `N` differently from - `evalf`. `N` is used to convert a SymPy numeric (or Boolean) value - to a `Julia`n counterpart. The main difference between `N(x)` and - `convert(T, x)`, is that rather than specify the `Julia` type as - `T`, `N` works to guess the appropriate type for the `SymPy` - object. - -```jldoctest basicoperations -julia> N(sqrt(8)) # brings back as BigFloat -2.8284271247461903 -``` - -```jldoctest basicoperations -julia> N(sqrt(9)) # an Int -3.0 -``` - - ----- - -SymPy can evaluate floating point expressions to arbitrary precision. By -default, 15 digits of precision are used, but you can pass any number as the -argument to `evalf`. Let's compute the first 100 digits of `\pi`. - -```python - >>> pi.evalf(100) -3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 -``` - -##### In `Julia`: - -```julia -julia> PI.evalf(100) -3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 -``` - ----- - -To numerically evaluate an expression with a Symbol at a point, we might use -`subs` followed by `evalf`, but it is more efficient and numerically -stable to pass the substitution to `evalf` using the `subs` flag, which -takes a dictionary of `Symbol: point` pairs. - -```python - >>> expr = cos(2*x) - >>> expr.evalf(subs={x: 2.4}) - 0.0874989834394464 -``` - -##### In `Julia`: - -A Dict can be used: - -```jldoctest basicoperations -julia> expr = cos(2*x) -cos(2⋅x) - -julia> expr.evalf(subs=Dict(x => 2.4)) -0.0874989834394464 -``` - ----- - -Sometimes there are roundoff errors smaller than the desired precision that -remain after an expression is evaluated. Such numbers can be removed at the -user's discretion by setting the `chop` flag to True. - -```python - >>> one = cos(1)**2 + sin(1)**2 - >>> (one - 1).evalf() - -0.e-124 - >>> (one - 1).evalf(chop=True) - 0 -``` - -##### In `Julia`: - -* we need to use symbolic values for `1` in defining `_one`: - -```jldoctest basicoperations -julia> _one = cos(Sym(1))^2 + sin(Sym(1))^2 - 2 2 -cos (1) + sin (1) - -julia> (_one - 1).evalf() --0.e-124 -``` - -```jldoctest basicoperations -julia> (_one - 1).evalf(chop=true) -0 -``` - ----- - -## `N` with `Julia` - -The `N` function is used to convert a symbolic number or boolean into a `Julia` counterpart. - -```jldoctest basicoperations -julia> two = Sym(2) -2 - -julia> a,b,c,d = two, sqrt(two), two^20, two^100 -(2, sqrt(2), 1048576, 1267650600228229401496703205376) - -julia> N.((a,b,c,d)) -(2, 1.414213562373095048801688724209698078569671875376948073176679737990732478462102, 1048576, 1267650600228229401496703205376) -``` - - -## `lambdify` - - -`subs` and `evalf` are good if you want to do simple evaluation, but if -you intend to evaluate an expression at many points, there are more efficient -ways. For example, if you wanted to evaluate an expression at a thousand -points, using SymPy would be far slower than it needs to be, especially if you -only care about machine precision. Instead, you should use libraries like -`NumPy `_ and `SciPy `_. - -The easiest way to convert a SymPy expression to an expression that can be -numerically evaluated is to use the `lambdify` function. `lambdify` acts -like a `lambda` function, except it converts the SymPy names to the names of -the given numerical library, usually NumPy. For example - -```python - >>> import numpy # doctest:+SKIP - >>> a = numpy.arange(10) # doctest:+SKIP - >>> expr = sin(x) - >>> f = lambdify(x, expr, "numpy") # doctest:+SKIP - >>> f(a) # doctest:+SKIP - [ 0. 0.84147098 0.90929743 0.14112001 -0.7568025 -0.95892427 - -0.2794155 0.6569866 0.98935825 0.41211849] -``` - - -!!! note "Alert" - `lambdify` uses `eval`. Don't use it on unsanitized input. - -##### In `Julia`: - -* `lambdify` is defined seperately and with a different argument order: `lambdify(ex, vars=free_symbols(ex))`. - -```jldoctest basicoperations -julia> a = 0:10 -0:10 - -julia> @syms x -(x,) - -julia> expr = sin(x) -sin(x) - -julia> fn = lambdify(expr); - -julia> fn.(a) -11-element Vector{Float64}: - 0.0 - 0.8414709848078965 - 0.9092974268256817 - 0.1411200080598672 - -0.7568024953079282 - -0.9589242746631385 - -0.27941549819892586 - 0.6569865987187891 - 0.9893582466233818 - 0.4121184852417566 - -0.5440211108893698 -``` - -!!! note "Technical note" - The `lambdify` function converts a symbolic expression into a `Julia` expression, and then creates a function using `invokelatest` to avoid world age issues. - -More performant functions can be produced using the following pattern: - -```jldoctest basicoperations -julia> ex = sin(x)^2 + x^2 - 2 2 -x + sin (x) - -julia> body = convert(Expr, ex) -:(x ^ 2 + sin(x) ^ 2) - -julia> syms = Symbol.(free_symbols(ex)) -1-element Vector{Symbol}: - :x - -julia> fn = eval(Expr(:function, Expr(:call, gensym(), syms...), body)); - -julia> fn(pi) -9.869604401089358 -``` - ----- - -You can use other libraries than NumPy. For example, to use the standard -library math module, use `"math"`. - -```python - >>> f = lambdify(x, expr, "math") - >>> f(0.1) - 0.0998334166468 -``` - -##### In `Julia`: - -* this doesn't apply, so is not implemented. - - ----- - -To use lambdify with numerical libraries that it does not know about, pass a -dictionary of `sympy_name:numerical_function` pairs. For example - -```python - >>> def mysin(x): - ... """ - ... My sine. Note that this is only accurate for small x. - ... """ - ... return x - >>> f = lambdify(x, expr, {"sin":mysin}) - >>> f(0.1) - 0.1 -``` - -##### In `Julia`: - -* The `fns` dictionary coud be used to do this, though due to the call of `eval`, we must do this in the proper module: - -```jldoctest basicoperations -julia> mysin(x) = cos(x) -mysin (generic function with 1 method) - -julia> ex = sin(x) -sin(x) - -julia> body = SymPy.walk_expression(ex, fns=Dict("sin" => :mysin)) -:(mysin(x)) - -julia> syms = (:x,) -(:x,) - -julia> fn = eval(Expr(:function, Expr(:call, gensym(), syms...), body)); - -julia> fn(0) -1.0 -``` - ----- - -!!! note "TODO" - - Write an advanced numerics section diff --git a/docs/src/Tutorial/calculus.md b/docs/src/Tutorial/calculus.md deleted file mode 100644 index cfcf0bd0..00000000 --- a/docs/src/Tutorial/calculus.md +++ /dev/null @@ -1,912 +0,0 @@ -# Calculus - -[From](https://docs.sympy.org/latest/tutorial/calculus.html) - -This section covers how to do basic calculus tasks such as derivatives, -integrals, limits, and series expansions in SymPy. If you are not familiar -with the math of any part of this section, you may safely skip it. - -```python - >>> from sympy import * - >>> x, y, z = symbols('x y z') - >>> init_printing(use_unicode=True) -``` - - -```@setup calculus -using SymPy -sympy.init_printing(use_unicode=True) -``` - -##### In `Julia` - -we have -* all functions from `sympy` are imported by default -* unicode printing is enabled by default -* double quotes are for strings - -so the above can be: - -```jldoctest calculus -julia> using SymPy - -julia> x, y, z = symbols("x y z") -(x, y, z) -``` - -We primarily will use the convenient `@syms` macro, as with - -```jldoctest calculus -julia> @syms x y z -(x, y, z) -``` - ------ - -## Derivatives - - -To take derivatives, use the `diff` function. - -```python - >>> diff(cos(x), x) - -sin(x) - >>> diff(exp(x**2), x) - ⎛ 2⎞ - ⎝x ⎠ - 2⋅x⋅ℯ -``` - -##### In `Julia` - -save for `**` becoming `^` this is the same - -```jldoctest calculus -julia> diff(cos(x), x) --sin(x) - -julia> diff(exp(x^2), x) - ⎛ 2⎞ - ⎝x ⎠ -2⋅x⋅ℯ -``` - ----- - -`diff` can take multiple derivatives at once. To take multiple derivatives, -pass the variable as many times as you wish to differentiate, or pass a number -after the variable. For example, both of the following find the third -derivative of `x^4`. - -```python - >>> diff(x**4, x, x, x) - 24⋅x - >>> diff(x**4, x, 3) - 24⋅x -``` - -##### In `Julia` - -```jldoctest calculus -julia> diff(x^4, x, x, x) -24⋅x - -julia> diff(x^4, x, 3) -24⋅x -``` - ----- - -You can also take derivatives with respect to many variables at once. Just -pass each derivative in order, using the same syntax as for single variable -derivatives. For example, each of the following will compute -$\frac{\partial^7}{\partial x\partial y^2\partial z^4} e^{x y z}$. - -```python - >>> expr = exp(x*y*z) - >>> diff(expr, x, y, y, z, z, z, z) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z - x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ - >>> diff(expr, x, y, 2, z, 4) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z - x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ - >>> diff(expr, x, y, y, z, 4) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z - x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ -``` - -##### In `Julia`: - -```jldoctest calculus -julia> expr = exp(x*y*z) - x⋅y⋅z -ℯ - -julia> diff(expr, x, y, y, z, z, z, z) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z -x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ - -julia> diff(expr, x, y, 2, z, 4) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z -x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ - -julia> diff(expr, x, y, y, z, 4) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z -x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ -``` - ----- - -`diff` can also be called as a method. The two ways of calling `diff` are exactly the same, and are provided only for convenience. - -```python - >>> expr.diff(x, y, y, z, 4) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z - x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ -``` - -##### In `Julia`: - -```jldoctest calculus -julia> expr.diff(x, y, y, z, 4) - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z -x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ -``` - ----- - -To create an unevaluated derivative, use the `Derivative` class. It has the same syntax as `diff`. - -```python - >>> deriv = Derivative(expr, x, y, y, z, 4) - >>> deriv - 7 - ∂ ⎛ x⋅y⋅z⎞ - ──────────⎝ℯ ⎠ - 4 2 - ∂z ∂y ∂x -``` - -##### In `Julia`, - -classes are not exported, so we use `sympy.Derivative`: - -```jldoctest calculus -julia> deriv = sympy.Derivative(expr, x, y, y, z, 4) - 7 - ∂ ⎛ x⋅y⋅z⎞ -──────────⎝ℯ ⎠ - 4 2 -∂z ∂y ∂x -``` - ----- - -To evaluate an unevaluated derivative, use the `doit` method. - -```python - >>> deriv.doit() - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z - x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ -``` - -##### In `Julia`: - -```jldoctest calculus -julia> deriv.doit() - 3 2 ⎛ 3 3 3 2 2 2 ⎞ x⋅y⋅z -x ⋅y ⋅⎝x ⋅y ⋅z + 14⋅x ⋅y ⋅z + 52⋅x⋅y⋅z + 48⎠⋅ℯ -``` - ----- - -These unevaluated objects are useful for delaying the evaluation of the -derivative, or for printing purposes. They are also used when SymPy does not -know how to compute the derivative of an expression (for example, if it -contains an undefined function, which are described in the :ref:`Solving -Differential Equations ` section). - -Derivatives of unspecified order can be created using tuple `(x, n)` where -`n` is the order of the derivative with respect to `x`. - -```python - >>> m, n, a, b = symbols('m n a b') - >>> expr = (a*x + b)**m - >>> expr.diff((x, n)) - n - ∂ ⎛ m⎞ - ───⎝(a⋅x + b) ⎠ - n - ∂x -``` - -##### In `Julia`: - -```jldoctest calculus -julia> @syms m n a b -(m, n, a, b) - -julia> expr = (a*x + b)^m; string(expr) - m -m - -julia> expr.diff((x, n)) |> string -"Derivative((a*x + b)^m, (x, n))" -``` - ----- - -## Integrals - - -To compute an integral, use the `integrate` function. There are two kinds -of integrals, definite and indefinite. To compute an indefinite integral, -that is, an antiderivative, or primitive, just pass the variable after the -expression. - -```python - >>> integrate(cos(x), x) - sin(x) -``` - -##### In `Julia`: - -```jldoctest calculus -julia> integrate(cos(x), x) -sin(x) -``` - ----- - -Note that SymPy does not include the constant of integration. If you want it, -you can add one yourself, or rephrase your problem as a differential equation -and use `dsolve` to solve it, which does add the constant (see :ref:`tutorial-dsolve`). - -!!! note "Quick tip" - $\infty$ in SymPy is `oo` (that's the lowercase letter "oh" twice). This - is because `oo` looks like $\infty$, and is easy to type. - ----- - -To compute a definite integral, pass the argument `(integration_variable, -lower_limit, upper_limit)`. For example, to compute - -$$~ - - \int_0^\infty e^{-x}\,dx, -~$$ -we would do - -```python - >>> integrate(exp(-x), (x, 0, oo)) - 1 -``` - -##### In Julia: - -```jldoctest calculus -julia> integrate(exp(-x), (x, 0, oo)) -1 -``` - ----- - -As with indefinite integrals, you can pass multiple limit tuples to perform a -multiple integral. For example, to compute - - -$$~ - \int_{-\infty}^{\infty}\int_{-\infty}^{\infty} e^{- x^{2} - y^{2}}\, dx\, dy, -~$$ -do - -```python - >>> integrate(exp(-x**2 - y**2), (x, -oo, oo), (y, -oo, oo)) - π -``` - -##### In `Julia`: - -```jldoctest calculus -julia> integrate(exp(-x^2 - y^2), (x, -oo, oo), (y, -oo, oo)) -π -``` - ----- - -If `integrate` is unable to compute an integral, it returns an unevaluated -`Integral` object. - -```python - >>> expr = integrate(x**x, x) - >>> print(expr) - Integral(x**x, x) - >>> expr - ⌠ - ⎮ x - ⎮ x dx - ⌡ -``` - - -##### In Julia: - -```jldoctest calculus -julia> expr = integrate(x^x, x) -⌠ -⎮ x -⎮ x dx -⌡ -``` - ----- - -As with `Derivative`, you can create an unevaluated integral using -`Integral`. To later evaluate this integral, call `doit`. - -```python - >>> expr = Integral(log(x)**2, x) - >>> expr - ⌠ - ⎮ 2 - ⎮ log (x) dx - ⌡ - >>> expr.doit() - 2 - x⋅log (x) - 2⋅x⋅log(x) + 2⋅x -``` - -##### In `Julia`: - -the `Integral` class is not exported, so it must be qualified: - -```jldoctest calculus -julia> expr = sympy.Integral(log(x)^2, x) -⌠ -⎮ 2 -⎮ log (x) dx -⌡ -``` - -```jldoctest calculus -julia> expr.doit() - 2 -x⋅log (x) - 2⋅x⋅log(x) + 2⋅x -``` - ----- - -`integrate` uses powerful algorithms that are always improving to compute -both definite and indefinite integrals, including heuristic pattern matching -type algorithms, a partial implementation of the `Risch algorithm -`, and an algorithm using -`Meijer G-functions ` that is -useful for computing integrals in terms of special functions, especially -definite integrals. Here is a sampling of some of the power of `integrate`. - -```python - >>> integ = Integral((x**4 + x**2*exp(x) - x**2 - 2*x*exp(x) - 2*x - - ... exp(x))*exp(x)/((x - 1)**2*(x + 1)**2*(exp(x) + 1)), x) - >>> integ - ⌠ - ⎮ ⎛ 4 2 x 2 x x⎞ x - ⎮ ⎝x + x ⋅ℯ - x - 2⋅x⋅ℯ - 2⋅x - ℯ ⎠⋅ℯ - ⎮ ──────────────────────────────────────── dx - ⎮ 2 2 ⎛ x ⎞ - ⎮ (x - 1) ⋅(x + 1) ⋅⎝ℯ + 1⎠ - ⌡ - >>> integ.doit() - x - ⎛ x ⎞ ℯ - log⎝ℯ + 1⎠ + ────── - 2 - x - 1 - - >>> integ = Integral(sin(x**2), x) - >>> integ - ⌠ - ⎮ ⎛ 2⎞ - ⎮ sin⎝x ⎠ dx - ⌡ - >>> integ.doit() - ⎛√2⋅x⎞ - 3⋅√2⋅√π⋅fresnels⎜────⎟⋅Γ(3/4) - ⎝ √π ⎠ - ───────────────────────────── - 8⋅Γ(7/4) - - >>> integ = Integral(x**y*exp(-x), (x, 0, oo)) - >>> integ - ∞ - ⌠ - ⎮ y -x - ⎮ x ⋅ℯ dx - ⌡ - 0 - >>> integ.doit() - ⎧ Γ(y + 1) for -re(y) < 1 - ⎪ - ⎪∞ - ⎪⌠ - ⎨⎮ y -x - ⎪⎮ x ⋅ℯ dx otherwise - ⎪⌡ - ⎪0 - ⎩ -``` - -##### In `Julia`: - -```jldoctest calculus -julia> integ = sympy.Integral((x^4 + x^2*exp(x) - x^2 - 2*x*exp(x) - 2*x - exp(x))*exp(x)/((x - 1)^2*(x + 1)^2*(exp(x) + 1)), x) -⌠ -⎮ ⎛ 4 2 x 2 x x⎞ x -⎮ ⎝x + x ⋅ℯ - x - 2⋅x⋅ℯ - 2⋅x - ℯ ⎠⋅ℯ -⎮ ──────────────────────────────────────── dx -⎮ 2 2 ⎛ x ⎞ -⎮ (x - 1) ⋅(x + 1) ⋅⎝ℯ + 1⎠ -⌡ - -julia> integ.doit() |> string -"log(exp(x) + 1) + exp(x)/(x^2 - 1)" -``` - -```jldoctest calculus -julia> integ = sympy.Integral(sin(x^2), x) -⌠ -⎮ ⎛ 2⎞ -⎮ sin⎝x ⎠ dx -⌡ - -julia> integ.doit() |> string -"3*sqrt(2)*sqrt(pi)*fresnels(sqrt(2)*x/sqrt(pi))*gamma(3/4)/(8*gamma(7/4))" - -``` - -```jldoctest calculus -julia> integ = sympy.Integral(x^y*exp(-x), (x, 0, oo)) -∞ -⌠ -⎮ y -x -⎮ x ⋅ℯ dx -⌡ -0 - -julia> integ.doit() -⎧ Γ(y + 1) for re(y) > -1 -⎪ -⎪∞ -⎪⌠ -⎨⎮ y -x -⎪⎮ x ⋅ℯ dx otherwise -⎪⌡ -⎪0 -⎩ -``` - ----- - -This last example returned a `Piecewise` expression because the integral -does not converge unless $\Re(y) > 1.$ - -## Limits - - -SymPy can compute symbolic limits with the `limit` function. The syntax to compute - -$$~ - \lim_{x\to x_0} f(x) -~$$ - -is `limit(f(x), x, x0)`. - -```python - >>> limit(sin(x)/x, x, 0) - 1 -``` - -##### In `Julia`: - -```jldoctest calculus -julia> limit(sin(x)/x, x, 0) -1 -``` - -In `Julia`, a pair can be used to indicate the limit: - -```jldoctest calculus -julia> limit(sin(x)/x, x=>0) -1 -``` - -Sometimes, a symbolic value is needed to have a proper limit: - -```jldoctest calculus -julia> limit((pi/2-x-acos(x))/x^3, x=>0) -∞ - -julia> limit((PI/2-x-acos(x))/x^3, x=>0) -1/6 - -``` - -(In the first case, the numerator is not `0` when `x=0` due to roundoff error in computing `pi/2`.) - - ----- - -`limit` should be used instead of `subs` whenever the point of evaluation -is a singularity. Even though SymPy has objects to represent $\infty$, using -them for evaluation is not reliable because they do not keep track of things -like rate of growth. Also, things like $\infty - \infty$ and -$\frac{\infty}{\infty}$ return $\mathrm{nan}$ (not-a-number). For example - -```python - >>> expr = x**2/exp(x) - >>> expr.subs(x, oo) - nan - >>> limit(expr, x, oo) - 0 -``` - -##### In `Julia`: - -```jldoctest calculus -julia> expr = x^2/exp(x) - 2 -x -x ⋅ℯ - -julia> expr.subs(x, oo) -nan - -julia> limit(expr, x, oo) -0 -``` - ----- - -Like `Derivative` and `Integral`, `limit` has an unevaluated -counterpart, `Limit`. To evaluate it, use `doit`. - -```python - >>> expr = Limit((cos(x) - 1)/x, x, 0) - >>> expr - ⎛cos(x) - 1⎞ - lim ⎜──────────⎟ - x─→0⁺⎝ x ⎠ - >>> expr.doit() - 0 -``` - -##### In `Julia`: - -```jldoctest calculus -julia> expr = sympy.Limit((cos(x) - 1)/x, x, 0) - ⎛cos(x) - 1⎞ - lim ⎜──────────⎟ -x─→0⁺⎝ x ⎠ - -julia> expr.doit() -0 -``` - -To evaluate a limit at one side only, pass `'+'` or `'-'` as a third -argument to `limit`. For example, to compute - -$$~ - \lim_{x\to 0^+}\frac{1}{x}, -~$$ - -do - -```python - >>> limit(1/x, x, 0, '+') - ∞ -``` - -##### In `Julia`: - -```jldoctest calculus -julia> limit(1/x, x => 0, "+") -∞ -``` - ----- - -As opposed to - -```python - >>> limit(1/x, x, 0, '-') - -∞ -``` - - -##### In `Julia`: - -```jldoctest calculus -julia> limit(1/x, x, 0, "-") --∞ -``` - ----- - - -## Series Expansion - - -SymPy can compute asymptotic series expansions of functions around a point. To -compute the expansion of `f(x)` around the point `x = x_0` terms of order -`x^n`, use `f(x).series(x, x0, n)`. `x0` and `n` can be omitted, in -which case the defaults `x0=0` and `n=6` will be used. - -```python - >>> expr = exp(sin(x)) - >>> expr.series(x, 0, 4) - 2 - x ⎛ 4⎞ - 1 + x + ── + O⎝x ⎠ - 2 -``` - -##### In `Julia`: - -```jldoctest calculus -julia> expr = exp(sin(x)) - sin(x) -ℯ - -julia> expr.series(x, 0, 4) |> string -"1 + x + x^2/2 + O(x^4)" -``` - ----- - -The `O\left (x^4\right )` term at the end represents the Landau order term at -`x=0` (not to be confused with big O notation used in computer science, which -generally represents the Landau order term at $x=\infty$). It means that all -x terms with power greater than or equal to `x^4` are omitted. Order terms -can be created and manipulated outside of `series`. They automatically -absorb higher order terms. - -```python - >>> x + x**3 + x**6 + O(x**4) - 3 ⎛ 4⎞ - x + x + O⎝x ⎠ - >>> x*O(1) - O(x) -``` - -##### In `Julia`: - -`O` is not exported, so we must qualify it: - - -```jldoctest calculus -julia> x + x^3 + x^6 + sympy.O(x^4) - 3 ⎛ 4⎞ -x + x + O⎝x ⎠ - -``` - -```jldoctest calculus -julia> x*sympy.O(1) -O(x) - -``` - ----- - -If you do not want the order term, use the `removeO` method. - -```python - >>> expr.series(x, 0, 4).removeO() - 2 - x - ── + x + 1 - 2 -``` - -##### In `Julia`: - -```jldoctest calculus -julia> expr.series(x, 0, 4).removeO() - 2 -x -── + x + 1 -2 - -``` - ----- - -The `O` notation supports arbitrary limit points (other than 0): - -```python - >>> exp(x - 6).series(x, x0=6) - 2 3 4 5 - (x - 6) (x - 6) (x - 6) (x - 6) ⎛ 6 ⎞ - -5 + ──────── + ──────── + ──────── + ──────── + x + O⎝(x - 6) ; x → 6⎠ - 2 6 24 120 -``` - -##### In `Julia`: - -```jldoctest calculus -julia> exp(x - 6).series(x, x0=6) |> string -"-5 + (x - 6)^2/2 + (x - 6)^3/6 + (x - 6)^4/24 + (x - 6)^5/120 + x + O((x - 6)^6, (x, 6))" - -``` - -## Finite differences - - -So far we have looked at expressions with analytic derivatives -and primitive functions respectively. But what if we want to have an -expression to estimate a derivative of a curve for which we lack a -closed form representation, or for which we don't know the functional -values for yet. One approach would be to use a finite difference -approach. - -The simplest way the differentiate using finite differences is to use -the `differentiate_finite` function: - -```python - >>> f, g = symbols('f g', cls=Function) - >>> differentiate_finite(f(x)*g(x)) - -f(x - 1/2)⋅g(x - 1/2) + f(x + 1/2)⋅g(x + 1/2) -``` - -##### In `Julia`: - -* `differentiate_finite` is not exported - -```jldoctest calculus -julia> @syms f(), g() -(f, g) - -julia> sympy.differentiate_finite(f(x)*g(x)) --f(x - 1/2)⋅g(x - 1/2) + f(x + 1/2)⋅g(x + 1/2) - -``` - -(The functions `f` and `g` can also be created with `SymFunction`.) - - ----- - -This form however does not respect the product rule. - -If you already have a `Derivative` instance, you can use the -`as_finite_difference` method to generate approximations of the -derivative to arbitrary order: - -```python - >>> f = Function('f') - >>> dfdx = f(x).diff(x) - >>> dfdx.as_finite_difference() - -f(x - 1/2) + f(x + 1/2) -``` - -##### In `Julia`: - -```jldoctest calculus -julia> f = sympy.Function("f") -PyObject f - -julia> dfdx = f(x).diff(x) -d -──(f(x)) -dx - -julia> dfdx.as_finite_difference() --f(x - 1/2) + f(x + 1/2) - -``` - ----- - -here the first order derivative was approximated around x using a -minimum number of points (2 for 1st order derivative) evaluated -equidistantly using a step-size of 1. We can use arbitrary steps -(possibly containing symbolic expressions): - -```python - >>> f = Function('f') - >>> d2fdx2 = f(x).diff(x, 2) - >>> h = Symbol('h') - >>> d2fdx2.as_finite_difference([-3*h,-h,2*h]) - f(-3⋅h) f(-h) 2⋅f(2⋅h) - ─────── - ───── + ──────── - 2 2 2 - 5⋅h 3⋅h 15⋅h -``` - -##### In `Julia`: - -```jldoctest calculus -julia> f = sympy.Function("f") -PyObject f - -julia> d2fdx2 = f(x).diff(x, 2) - 2 - d -───(f(x)) - 2 -dx - -julia> h = sympy.Symbol("h") -h -``` - -```julia -julia> d2fdx2.as_finite_difference([-3*h,-h,2*h]) -f(-3⋅h) f(-h) 2⋅f(2⋅h) -─────── - ───── + ──────── - 2 2 2 - 5⋅h 3⋅h 15⋅h - -``` - ----- - -If you are just interested in evaluating the weights, you can do so -manually: - -```python - >>> finite_diff_weights(2, [-3, -1, 2], 0)[-1][-1] - [1/5, -1/3, 2/15] -``` - -##### In `Julia`: - -the `finite_diff_weights` function that is not exported: - - -```julia -julia> sympy.finite_diff_weights(2, [-3, -1, 2], 0)[end][end] --2/15 -``` - ----- - -note that we only need the last element in the last sublist -returned from `finite_diff_weights`. The reason for this is that -the function also generates weights for lower derivatives and -using fewer points (see the documentation of `finite_diff_weights` -for more details). - -If using `finite_diff_weights` directly looks complicated, and the -`as_finite_difference` method of `Derivative` instances -is not flexible enough, you can use `apply_finite_diff` which -takes `order`, `x_list`, `y_list` and `x0` as parameters: - -```python - >>> x_list = [-3, 1, 2] - >>> y_list = symbols('a b c') - >>> apply_finite_diff(1, x_list, y_list, 0) - 3⋅a b 2⋅c - - ─── - ─ + ─── - 20 4 5 -``` - -##### In `Julia`, - -* `apply_finite_diff` is not exported: - -```jldoctest calculus -julia> xs = [-3, 1, 2] -3-element Vector{Int64}: - -3 - 1 - 2 - -julia> @syms ys[1:3] -(Sym[ys₁, ys₂, ys₃],) -``` - -```julia -julia> sympy.apply_finite_diff(1, xs, ys, 0) - 3⋅ys₁ ys₂ 2⋅ys₃ -- ───── - ─── + ───── - 20 4 5 -``` diff --git a/docs/src/Tutorial/gotchas.md b/docs/src/Tutorial/gotchas.md deleted file mode 100644 index 0cdc9a65..00000000 --- a/docs/src/Tutorial/gotchas.md +++ /dev/null @@ -1,736 +0,0 @@ -# Gotchas - -```@setup gotchas -using SymPy -sympy.init_printing(pretty_print=False, use_unicode=False) -``` - - -[From](https://docs.sympy.org/latest/tutorial/gotchas.html) - -To begin, we should make something about SymPy clear. SymPy is nothing more -than a Python library, like `NumPy`, `Django`, or even modules in the -Python standard library `sys` or `re`. What this means is that SymPy does -not add anything to the Python language. Limitations that are inherent in the -Python language are also inherent in SymPy. It also means that SymPy tries to -use Python idioms whenever possible, making programming with SymPy easy for -those already familiar with programming with Python. As a simple example, -SymPy uses Python syntax to build expressions. Implicit multiplication (like -`3x` or `3 x`) is not allowed in Python, and thus not allowed in SymPy. -To multiply `3` and `x`, you must type `3*x` with the `*`. - - -##### In `Julia`: - -* implicit multiplication by a *literal* is supported, unlike Python - ------ - -## Symbols - -One consequence of this fact is that SymPy can be used in any environment -where Python is available. We just import it, like we would any other -library: - -```python - >>> from sympy import * -``` - -##### In `Julia`: - -* the functions from the `sympy` module are loaded with the package: - -```jldoctest gotchas -julia> using SymPy -``` - ----- - -This imports all the functions and classes from SymPy into our interactive -Python session. Now, suppose we start to do a computation. - -```python - >>> x + 1 - Traceback (most recent call last): - ... - NameError: name 'x' is not defined -``` - -##### In `Julia`: - -* the error output may differ, but an `UndefVarError` is thrown - -```julia -julia> x + 1 -ERROR: UndefVarError: x not defined -Stacktrace: - [1] top-level scope at REPL[86]:1 -``` - ----- - -Oops! What happened here? We tried to use the variable `x`, but it tells us -that `x` is not defined. In Python, variables have no meaning until they -are defined. SymPy is no different. Unlike many symbolic manipulation -systems you may have used, in SymPy, variables are not defined automatically. -To define variables, we must use `symbols`. - -```python - >>> x = symbols('x') - >>> x + 1 - x + 1 -``` - -##### In `Julia`: - -We can use `symbols`, as here: - -```jldoctest gotchas -julia> x = symbols("x") -x - -julia> x + 1 -x + 1 -``` - -but the recommended way is to use `@syms`: - -```jldoctest gotchas -julia> @syms x -(x,) -``` - ----- - -`symbols` takes a string of variable names separated by spaces or commas, -and creates Symbols out of them. We can then assign these to variable names. -Later, we will investigate some convenient ways we can work around this issue. -For now, let us just define the most common variable names, `x`, `y`, and -`z`, for use through the rest of this section - -```python - >>> x, y, z = symbols('x y z') -``` - -##### In `Julia`: - -Again, we use the `@syms` macro: - -```jldoctest gotchas -julia> x + 1 -x + 1 - -julia> @syms x, y, z -(x, y, z) -``` - ----- - -As a final note, we note that the name of a Symbol and the name of the -variable it is assigned to need not have anything to do with one another. - -```python - >>> a, b = symbols('b a') - >>> a - b - >>> b - a -``` - -##### In `Julia`: - -The same holds: - -```jldoctest gotchas -julia> a, b = symbols("b a") -(b, a) - -julia> a -b -``` - -```jldoctest gotchas -julia> b -a -``` - -This can also be done with the `@syms` macro: - -```jldoctest gotchas -julia> @syms a=>"b" b=>"c" -(b, c) - -julia> a + b -b + c -``` - - ----- - -Here we have done the very confusing thing of assigning a Symbol with the name -`a` to the variable `b`, and a Symbol of the name `b` to the variable -`a`. Now the Python variable named `a` points to the SymPy Symbol named -`b`, and visa versa. How confusing. We could have also done something like - -```python - >>> crazy = symbols('unrelated') - >>> crazy + 1 - unrelated + 1 -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> crazy = symbols("unrelated") -unrelated - -julia> crazy + 1 -unrelated + 1 -``` - ----- - -This also shows that Symbols can have names longer than one character if we -want. - -Usually, the best practice is to assign Symbols to Python variables of the -same name, although there are exceptions: Symbol names can contain characters -that are not allowed in Python variable names, or may just want to avoid -typing long names by assigning Symbols with long names to single letter Python -variables. - -To avoid confusion, throughout this tutorial, Symbol names and Python variable -names will always coincide. Furthermore, the word "Symbol" will refer to a -SymPy Symbol and the word "variable" will refer to a Python variable. - -Finally, let us be sure we understand the difference between SymPy Symbols and -Python variables. Consider the following:: - -```python - x = symbols('x') - expr = x + 1 - x = 2 - print(expr) -``` - - -What do you think the output of this code will be? If you thought `3`, -you're wrong. Let's see what really happens - -```python - >>> x = symbols('x') - >>> expr = x + 1 - >>> x = 2 - >>> print(expr) - x + 1 -``` - -##### In `Julia`: - -* we must change to double quotes (or, as recommended, use `@syms x`) - -```jldoctest gotchas -julia> x = symbols("x") -x - -julia> expr = x + 1 -x + 1 - -julia> x = 2 -2 - -julia> expr -x + 1 -``` - ----- - -Changing `x` to `2` had no effect on `expr`. This is because `x = 2` -changes the Python variable `x` to `2`, but has no effect on the SymPy -Symbol `x`, which was what we used in creating `expr`. When we created -`expr`, the Python variable `x` was a Symbol. After we created, it, we -changed the Python variable `x` to 2. But `expr` remains the same. This -behavior is not unique to SymPy. All Python programs work this way: if a -variable is changed, expressions that were already created with that variable -do not change automatically. For example - -```python - >>> x = 'abc' - >>> expr = x + 'def' - >>> expr - 'abcdef' - >>> x = 'ABC' - >>> expr - 'abcdef' -``` - -##### In `Julia`: - -* The `*` infix operator is used for string concatenation - -```jldoctest gotchas -julia> x = "abc" -"abc" - -julia> expr = x * "def" -"abcdef" - -julia> expr -"abcdef" -``` - -```jldoctest gotchas -julia> x = "ABC" -"ABC" - -julia> expr -"abcdef" -``` - ----- - - -!!! note "Quick Tip" - To change the value of a Symbol in an expression, use `subs` - -```python - >>> x = symbols('x') - >>> expr = x + 1 - >>> expr.subs(x, 2) - 3 -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> @syms x -(x,) - -julia> expr = x + 1 -x + 1 - -julia> expr.subs(x, 2) -3 -``` - - -Or use the "call" form of `subs`: `expr(x => 2)` - ----- - -In this example, if we want to know what `expr` is with the new value of -`x`, we need to reevaluate the code that created `expr`, namely, `expr = -x + 1`. This can be complicated if several lines created `expr`. One -advantage of using a symbolic computation system like SymPy is that we can -build a symbolic representation for `expr`, and then substitute `x` with -values. The correct way to do this in SymPy is to use `subs`, which will be -discussed in more detail later. - -```python - >>> x = symbols('x') - >>> expr = x + 1 - >>> expr.subs(x, 2) - 3 -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> @syms x -(x,) - -julia> expr = x + 1 -x + 1 - -julia> expr.subs(x, 2) -3 -``` - ----- - -!!! note "TODO" - Add link to basic operations section - - -## Equals signs - -Another very important consequence of the fact that SymPy does not extend -Python syntax is that `=` does not represent equality in SymPy. Rather it -is Python variable assignment. This is hard-coded into the Python language, -and SymPy makes no attempts to change that. - -You may think, however, that `==`, which is used for equality testing in -Python, is used for SymPy as equality. This is not quite correct either. Let -us see what happens when we use `==`. - -```python - >>> x + 1 == 4 - False -``` - -##### In `Julia`: - -* `==` is similar as in Python: -```jldoctest gotchas -julia> x + 1 == 4 -false -``` - -Recall `==` promotes values, so we have a Julia object may be "equal" to a `SymPy` one: - -```jldoctest gotchas -julia> 0 == zero(Sym) ## or Sym(0) -true -``` - - ----- - -Instead of treating `x + 1 == 4` symbolically, we just got `False`. In -SymPy, `==` represents exact structural equality testing. This means that -`a == b` means that we are *asking* if `a = b`. We always get a `bool` as -the result of `==`. There is a separate object, called `Eq`, which can be -used to create symbolic equalities - -```python - >>> Eq(x + 1, 4) - Eq(x + 1, 4) -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> Eq(x + 1, 4) -x + 1 = 4 -``` - ----- - -There is one additional caveat about `==` as well. Suppose we want to know -if $(x + 1)^2 = x^2 + 2x + 1$. We might try something like this: - -```python - >>> (x + 1)**2 == x**2 + 2*x + 1 - False -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> (x + 1)^2 == x^2 + 2*x + 1 -false -``` - ----- - -We got `False` again. However, $(x + 1)^2$ *does* equal $x^2 + 2x + 1$. What -is going on here? Did we find a bug in SymPy, or is it just not powerful -enough to recognize this basic algebraic fact? - -Recall from above that `==` represents *exact* structural equality testing. -"Exact" here means that two expressions will compare equal with `==` only if -they are exactly equal structurally. Here, $(x + 1)^2$ and $x^2 + 2x + 1$ are -not the same symbolically. One is the power of an addition of two terms, and -the other is the addition of three terms. - -It turns out that when using SymPy as a library, having `==` test for exact -structural equality is far more useful than having it represent symbolic -equality, or having it test for mathematical equality. However, as a new -user, you will probably care more about the latter two. We have already seen -an alternative to representing equalities symbolically, `Eq`. To test if -two things are equal, it is best to recall the basic fact that if `a = b`, -then `a - b = 0`. Thus, the best way to check if `a = b` is to take `a - b` -and simplify it, and see if it goes to 0. We will learn :ref:`later -` that the function to do this is called `simplify`. This -method is not infallible---in fact, it can be `theoretically proven -`_ that it is impossible -to determine if two symbolic expressions are identically equal in -general---but for most common expressions, it works quite well. - -```python - >>> a = (x + 1)**2 - >>> b = x**2 + 2*x + 1 - >>> simplify(a - b) - 0 - >>> c = x**2 - 2*x + 1 - >>> simplify(a - c) - 4*x -``` - -##### In `Julia`: - - -```jldoctest gotchas -julia> @syms x -(x,) - -julia> a = (x + 1)^2; string(a) -"(x + 1)^2" - -julia> b = x^2 + 2*x + 1; string(b) -"x^2 + 2*x + 1" - -julia> simplify(a - b) -0 - -julia> c = x^2 - 2*x + 1; string(c) -"x^2 - 2*x + 1" - -julia> simplify(a - c) -4⋅x -``` - ----- - -There is also a method called `equals` that tests if two expressions are -equal by evaluating them numerically at random points. - -```python - >>> a = cos(x)**2 - sin(x)**2 - >>> b = cos(2*x) - >>> a.equals(b) - True -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> a = cos(x)^2 - sin(x)^2 - 2 2 -- sin (x) + cos (x) - -julia> b = cos(2*x) -cos(2⋅x) - -julia> a.equals(b) -true -``` - ----- - - - -## Two Final Notes: `^` and `/` - -You may have noticed that we have been using `**` for exponentiation instead -of the standard `^`. That's because SymPy follows Python's conventions. In -Python, `^` represents logical exclusive or. SymPy follows this convention: - -```python - >>> True ^ False - True - >>> True ^ True - False - >>> x^y - Xor(x, y) -``` - -##### In `Julia`: - -* we export `True` and `False` for symbolic Boolean values - -* This does **not** apply, as we use `^` for exponentiation. - -* Use the prefix `Or` for logical -```jldoctest gotchas -julia> Or(True, False) -True -``` - -```jldoctest gotchas -julia> Or(True, True) -True -``` - -```jldoctest gotchas -julia> Or(x, y) -x ∨ y -``` - ----- - -Finally, a small technical discussion on how SymPy works is in order. When -you type something like `x + 1`, the SymPy Symbol `x` is added to the -Python int `1`. Python's operator rules then allow SymPy to tell Python -that SymPy objects know how to be added to Python ints, and so `1` is -automatically converted to the SymPy Integer object. - -This sort of operator magic happens automatically behind the scenes, and you -rarely need to even know that it is happening. However, there is one -exception. Whenever you combine a SymPy object and a SymPy object, or a SymPy -object and a Python object, you get a SymPy object, but whenever you combine -two Python objects, SymPy never comes into play, and so you get a Python -object. - -```python - >>> type(Integer(1) + 1) - - >>> type(1 + 1) - <... 'int'> -``` - -##### In `Julia`: - -* In Julia, most operations between `SymPy` objects and `Julia` objects will promote to a `SymPy` objects, but of course `Julia` objects combined will produce `Julia` Objects: - -```jldoctest gotchas -julia> typeof(sympy.Integer(1) + 1) -Sym -``` - -```jldoctest gotchas -julia> typeof(1 + 1) -Int64 -``` - -To convert a `Julia` object to a `SymPy` object, the `Sym` constructor may be useful: - -```jldoctest gotchas -julia> Sym(1) -1 -``` - -To convert a `SymPy` object to a `Julia` object, the `N` function is useful for numbers and booleans: - -```jldoctest gotchas -julia> N(Sym(1)), N(True) -(1, true) -``` - -And the `lambdify` function can produce a function from an expression: - -```jldoctest gotchas -julia> ex = x^2 - 2x + 2 - 2 -x - 2⋅x + 2 - -julia> fn = lambdify(ex); - -julia> fn(1) - ex(1) -0 -``` - ----- - -!!! note - On running the example above in SymPy Live, (1+1) is wrapped by Integer, so it does not show the correct output. - -This is usually not a big deal. Python ints work much the same as SymPy -Integers, but there is one important exception: division. In SymPy, the -division of two Integers gives a Rational: - -```python - >>> Integer(1)/Integer(3) - 1/3 - >>> type(Integer(1)/Integer(3)) - -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> sympy.Integer(1)/sympy.Integer(3) -1/3 -``` - -```jldoctest gotchas -julia> typeof(sympy.Integer(1)/sympy.Integer(3)) -Sym -``` - -And to get the Python, type, we can use `__class__`: - -```jldoctest gotchas -julia> (sympy.Integer(1)/sympy.Integer(3)).__class__ -PyObject -``` - ----- - -But in Python `/` represents either integer division or floating point -division, depending on whether you are in Python 2 or Python 3, and depending -on whether or not you have run `from __future__ import division`: - -```python - >>> from __future__ import division - >>> 1/2 #doctest: +SKIP - 0.5 -``` - -##### In `Julia`: - -* This does not apply, as `/` is not integer division. - ----- - - -To avoid this, we can construct the rational object explicitly - -```python - >>> Rational(1, 2) - 1/2 -``` - -##### In `Julia`: - -* `Rational` from `sympy` is *not* exported, it would conflict with `Julia`'s `Rational` costructor. We must qualify it: - -```jldoctest gotchas -julia> Rational(1, 2) -1//2 -``` - -```jldoctest gotchas -julia> sympy.Rational(1, 2) -1/2 -``` - ----- - -This problem also comes up whenever we have a larger symbolic expression with -`int/int` in it. For example: - -```python - >>> x + 1/2 #doctest: +SKIP - x + 0.5 -``` - -##### In `Julia`: - -* `Int/Int` will produce a floating point value, whereas `Int//Int` will produce a rational, which can then be promoted without loss to a symbolic object: - -```jldoctest gotchas -julia> x + 1/2 -x + 0.5 -``` - ----- - -!!! note - On running the example above in SymPy Live, (1/2) is wrapped - by Integer, so it does not show the correct output. - -This happens because Python first evaluates `1/2` into `0.5`, and then -that is cast into a SymPy type when it is added to `x`. Again, we can get -around this by explicitly creating a Rational: - -```python - >>> x + Rational(1, 2) - x + 1/2 -``` - -##### In `Julia`: - -```jldoctest gotchas -julia> x + 1//2 -x + 1/2 -``` - ----- - -There are several tips on avoiding this situation in the :ref:`gotchas` -document. - -## Further Reading - -For more discussion on the topics covered in this section, see :ref:`gotchas`. diff --git a/docs/src/Tutorial/index.md b/docs/src/Tutorial/index.md deleted file mode 100644 index c05e39a4..00000000 --- a/docs/src/Tutorial/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# The SymPy tutorial (1.3) in `Julia` - -Here the tutorial for SymPy 1.3 is re-expressed using Julia commands and `SymPy.jl`. It attempts to show the similarities and differences in using SymPy under Python and `Julia`. - ----- - -The `SymPy` package for `Julia` allows `Julia` users to interact with -python's SymPy module in a mostly seamless manner, thanks to the power -of the `PyCall` package for `Julia`. The following pages reexpress the -SymPy tutorial also illustrating the associated `Julia` -commands. There are some changes, but mostly modest ones. To create -these pages, the `.rst` files were downloaded and modfied. There is -only hand sychronization available with new versions of the SymPy -tutorial for Python. - ----- - -```@contents -Pages = ["intro.md","gotchas.md", "basic_operations.md", "simplification.md", "calculus.md", "solvers.md", -"matrices.md", "manipulation.md"] -``` diff --git a/docs/src/Tutorial/intro.md b/docs/src/Tutorial/intro.md deleted file mode 100644 index eb24fcae..00000000 --- a/docs/src/Tutorial/intro.md +++ /dev/null @@ -1,538 +0,0 @@ -# Introduction - -Taken [from](https://docs.sympy.org/latest/tutorial/intro.html) the SymPy tutorial (version 1.3). - -```@setup intro -using SymPy -sympy.init_printing(use_unicode=True) -``` - - - -## What is Symbolic Computation? - -Symbolic computation deals with the computation of mathematical objects -symbolically. This means that the mathematical objects are represented -exactly, not approximately, and mathematical expressions with unevaluated -variables are left in symbolic form. - -Let's take an example. Say we wanted to use the built-in Python functions to -compute square roots. We might do something like this - -```python - >>> import math - >>> math.sqrt(9) - 3.0 -``` - -##### In `Julia`: - -* Of course, `sqrt` is already there: - -```jldoctest intro -julia> sqrt(9) -3.0 -``` - ----- - -9 is a perfect square, so we got the exact answer, 3. But suppose we computed -the square root of a number that isn't a perfect square - -```python - >>> math.sqrt(8) - 2.82842712475 -``` - -##### In `Julia`: - -```jldoctest intro -julia> sqrt(8) -2.8284271247461903 -``` - ----- - -Here we got an approximate result. 2.82842712475 is not the exact square root -of 8 (indeed, the actual square root of 8 cannot be represented by a finite -decimal, since it is an irrational number). If all we cared about was the -decimal form of the square root of 8, we would be done. - -But suppose we want to go further. Recall that $\sqrt{8} = \sqrt{4\cdot 2} = -2\sqrt{2}$. We would have a hard time deducing this from the above result. -This is where symbolic computation comes in. With a symbolic computation -system like SymPy, square roots of numbers that are not perfect squares are -left unevaluated by default - -```python - >>> import sympy - >>> sympy.sqrt(3) - sqrt(3) -``` - -##### In `Julia`: - - -```jldoctest intro -julia> using SymPy - -julia> sympy.sqrt(3) -√3 -``` - -* When `SymPy` is loaded, the `sqrt` function is overloaded for symbolic objects, so this could also be done through: - -```jldoctest intro -julia> sqrt(Sym(3)) -√3 -``` - ----- - -Furthermore---and this is where we start to see the real power of symbolic -computation---symbolic results can be symbolically simplified. - -```python - >>> sympy.sqrt(8) - 2*sqrt(2) -``` - -##### In `Julia`: - -```jldoctest intro -julia> sympy.sqrt(8) -2⋅√2 -``` - ----- - -## A More Interesting Example - -The above example starts to show how we can manipulate irrational numbers -exactly using SymPy. But it is much more powerful than that. Symbolic -computation systems (which by the way, are also often called computer algebra -systems, or just CASs) such as SymPy are capable of computing symbolic -expressions with variables. - -As we will see later, in SymPy, variables are defined using `symbols`. -Unlike many symbolic manipulation systems, variables in SymPy must be defined -before they are used (the reason for this will be discussed in the :ref:`next -section `). - -Let us define a symbolic expression, representing the mathematical expression -`x + 2y`. - -```python - >>> from sympy import symbols - >>> x, y = symbols('x y') - >>> expr = x + 2*y - >>> expr - x + 2*y -``` - -##### In `Julia`: - -* the command `from sympy import *` is *essentially* run (only functions are "imported", not all objects), so this becomes the same after adjusting the quotes: - -```jldoctest intro -julia> @syms x, y -(x, y) - -julia> expr = x + 2*y -x + 2⋅y -``` - ----- - -Note that we wrote `x + 2*y` just as we would if `x` and `y` were -ordinary Python variables. But in this case, instead of evaluating to -something, the expression remains as just `x + 2*y`. Now let us play around -with it: - -```python - >>> expr + 1 - x + 2*y + 1 - >>> expr - x - 2*y -``` - -##### In `Julia`: - -```jldoctest intro -julia> expr + 1 -x + 2⋅y + 1 -``` - -```jldoctest intro -julia> expr - x -2⋅y -``` - ----- - -Notice something in the above example. When we typed `expr - x`, we did not -get `x + 2*y - x`, but rather just `2*y`. The `x` and the `-x` -automatically canceled one another. This is similar to how `sqrt(8)` -automatically turned into `2*sqrt(2)` above. This isn't always the case in -SymPy, however: - -```python - >>> x*expr - x*(x + 2*y) -``` - -##### In `Julia`: - -```jldoctest intro -julia> x*expr -x⋅(x + 2⋅y) -``` - ----- - -Here, we might have expected `x(x + 2y)` to transform into `x^2 + 2xy`, but -instead we see that the expression was left alone. This is a common theme in -SymPy. Aside from obvious simplifications like `x - x = 0` and `\sqrt{8} = -2\sqrt{2}`, most simplifications are not performed automatically. This is -because we might prefer the factored form `x(x + 2y)`, or we might prefer the -expanded form `x^2 + 2xy`. Both forms are useful in different circumstances. -In SymPy, there are functions to go from one form to the other - -```python - >>> from sympy import expand, factor - >>> expanded_expr = expand(x*expr) - >>> expanded_expr - x**2 + 2*x*y - >>> factor(expanded_expr) - x*(x + 2*y) -``` - -##### In `Julia`: - -```jldoctest intro -julia> expanded_expr = expand(x*expr) - 2 -x + 2⋅x⋅y -``` - -```jldoctest intro -julia> factor(expanded_expr) -x⋅(x + 2⋅y) -``` - ----- - -## The Power of Symbolic Computation - -The real power of a symbolic computation system such as SymPy is the ability -to do all sorts of computations symbolically. SymPy can simplify expressions, -compute derivatives, integrals, and limits, solve equations, work with -matrices, and much, much more, and do it all symbolically. It includes -modules for plotting, printing (like 2D pretty printed output of math -formulas, or `\LaTeX`), code generation, physics, statistics, combinatorics, -number theory, geometry, logic, and more. Here is a small sampling of the sort -of symbolic power SymPy is capable of, to whet your appetite. - -```python - >>> from sympy import * - >>> x, t, z, nu = symbols('x t z nu') -``` - -##### In `Julia`: - -* again, the functions in the `sympy` module are already imported: - -```jldoctest intro -julia> @syms x, t, z, nu -(x, t, z, nu) -``` - ----- - -This will make all further examples pretty print with unicode characters. - -```python - >>> init_printing(use_unicode=True) -``` - -##### In `Julia`: - -* The printing in `Julia` is controlled by `show` and the appropriate MIME type. - ----- - -Take the derivative of $\sin{(x)}e^x$. - -```python - >>> diff(sin(x)*exp(x), x) - x x - ℯ ⋅sin(x) + ℯ ⋅cos(x) -``` - -##### In `Julia`: - -```jldoctest intro -julia> diff(sin(x)*exp(x), x) - x x -ℯ ⋅sin(x) + ℯ ⋅cos(x) -``` - ----- - -Compute $\int(e^x\sin{(x)} + e^x\cos{(x)})\,dx$. - -```python - >>> integrate(exp(x)*sin(x) + exp(x)*cos(x), x) - x - ℯ ⋅sin(x) -``` - -##### In `Julia`: - -```jldoctest intro -julia> integrate(exp(x)*sin(x) + exp(x)*cos(x), x) - x -ℯ ⋅sin(x) -``` - ----- - -Compute $\int_{-\infty}^\infty \sin{(x^2)}\,dx$. - -```python - >>> integrate(sin(x**2), (x, -oo, oo)) - √2⋅√π - ───── - 2 -``` - -##### In `Julia`: - -* In `Julia` `**` is `^`: - -```jldoctest intro -julia> integrate(sin(x^2), (x, -oo, oo)) -√2⋅√π -───── - 2 -``` - ----- - -Find $\lim_{x\to 0}\frac{\sin{(x)}}{x}$. - -```python - >>> limit(sin(x)/x, x, 0) - 1 -``` - -##### In `Julia`: - -```jldoctest intro -julia> limit(sin(x)/x, x, 0) -1 -``` - ----- - -Solve $x^2 - 2 = 0$. - -```python - >>> solve(x**2 - 2, x) - [-√2, √2] -``` - -##### In `Julia`: - -```jldoctest intro -julia> solve(x^2 - 2, x) -2-element Vector{Sym}: - -√2 - √2 -``` - ----- - -Solve the differential equation `y'' - y = e^t`. - -```python - >>> y = Function('y') - >>> dsolve(Eq(y(t).diff(t, t) - y(t), exp(t)), y(t)) - -t ⎛ t⎞ t - y(t) = C₂⋅ℯ + ⎜C₁ + ─⎟⋅ℯ - ⎝ 2⎠ -``` - -##### In `Julia`: - -* `Function` is not a function, so is not exported. We must qualify its use: - -```jldoctest intro -julia> y = sympy.Function("y") -PyObject y - -julia> dsolve(Eq(y(t).diff(t, t) - y(t), exp(t)), y(t)) |> string # work around formatting issue -"Eq(y(t), C2*exp(-t) + (C1 + t/2)*exp(t))" -``` - -!!! note "Why `string`?" - The uses of `|> string` above and elsewhere throughout this translation of the SymPy tutorial is only for technical reasons related to how `Documenter.jl` parses the output. It is not idiomatic, or suggested; it only allows the cell to be tested programatically for regressions. - -* This is made more familiar looking with the `SymFunction` class: - -```jldoctest intro -julia> y = SymFunction("y") -y - -julia> D = Differential(t); - -julia> dsolve(D(D(y))(t) - y(t) - exp(t), y(t)) |> string -"Eq(y(t), C2*exp(-t) + (C1 + t/2)*exp(t))" -``` - -Even more so, `@syms` allows the specification of symbolic functions, as follows: - -```jldoctest intro -julia> @syms y()::real t -(y, t) - -julia> dsolve(D(D(y))(t) - y(t) - exp(t), y(t)) |> string -"Eq(y(t), C2*exp(-t) + (C1 + t/2)*exp(t))" -``` ----- - -Find the eigenvalues of `\left[\begin{smallmatrix}1 & 2\\2 & -2\end{smallmatrix}\right]`. - -```python - >>> Matrix([[1, 2], [2, 2]]).eigenvals() - ⎧3 √17 √17 3 ⎫ - ⎨─ + ───: 1, - ─── + ─: 1⎬ - ⎩2 2 2 2 ⎭ -``` - -##### In `Julia`: - -* Like `Function`, `Matrix` is not imported and its use must by qualified (`Julia` matrix conventions are used): - -```jldoctest intro -julia> out = sympy.Matrix([1 2; 2 2]).eigenvals(); - -julia> sort(collect(keys(out))) -2-element Vector{Any}: - 3/2 - sqrt(17)/2 - 3/2 + sqrt(17)/2 -``` - -(The keys are returned as type `Any`, they may format more nicely if converted, say, through `convert(Dict{Sym,Sym},out)`.) - ----- - -Rewrite the Bessel function $J_{\nu}\left(z\right)$ in terms of the -spherical Bessel function $j_\nu(z)$. - -```python - >>> besselj(nu, z).rewrite(jn) - √2⋅√z⋅jn(ν - 1/2, z) - ──────────────────── - √π -``` - -##### In `Julia`: - -* we need to call in `SpecialFunctions` -* `jn` is imported as a function object and this is not what SymPy expects, instead we pass in the object `sympy.jn` - -```jldoctest intro -julia> using SpecialFunctions - - -julia> @syms ν z -(ν, z) - -julia> besselj(ν, z).rewrite(sympy.jn) -√2⋅√z⋅jn(ν - 1/2, z) -──────────────────── - √π -``` - ----- - -Print $\int_{0}^{\pi} \cos^{2}{\left (x \right )}\, dx$ using $\LaTeX$. - -```python - >>> latex(Integral(cos(x)**2, (x, 0, pi))) - \int_{0}^{\pi} \cos^{2}{\left (x \right )}\, dx -``` - -##### In `Julia`: - -* Latex printing occurs when the mime type is requested. However, the `latex` function can be called directly. However, this is not imported by default to avoid name collisions, and so must be qualified. Below, the latex is output as a string, though - -* `Integral`, like `Function` and `Matrix` is not a function and must be qualified - -* `**` must become `^` - -* and we use `PI`, an alias for `sympy.pi`, the symbolic value for $\pi$: - -```jldoctest intro -julia> sympy.latex(sympy.Integral(cos(x)^2, (x, 0, PI))) -"\\int\\limits_{0}^{\\pi} \\cos^{2}{\\left(x \\right)}\\, dx" -``` - ----- - -## Why SymPy? - -There are many computer algebra systems out there. `This -`_ Wikipedia -article lists many of them. What makes SymPy a better choice than the -alternatives? - -First off, SymPy is completely free. It is open source, and licensed under the -liberal BSD license, so you can modify the source code and even sell it if you -want to. This contrasts with popular commercial systems like Maple or -Mathematica that cost hundreds of dollars in licenses. - -Second, SymPy uses Python. Most computer algebra systems invent their own -language. Not SymPy. SymPy is written entirely in Python, and is executed -entirely in Python. This means that if you already know Python, it is much -easier to get started with SymPy, because you already know the syntax (and if -you don't know Python, it is really easy to learn). We already know that -Python is a well-designed, battle-tested language. The SymPy developers are -confident in their abilities in writing mathematical software, but programming -language design is a completely different thing. By reusing an existing -language, we are able to focus on those things that matter: the mathematics. - -Another computer algebra system, Sage also uses Python as its language. But -Sage is large, with a download of over a gigabyte. An advantage of SymPy is -that it is lightweight. In addition to being relatively small, it has no -dependencies other than Python, so it can be used almost anywhere easily. -Furthermore, the goals of Sage and the goals of SymPy are different. Sage -aims to be a full featured system for mathematics, and aims to do so by -compiling all the major open source mathematical systems together into -one. When you call some function in Sage, such as `integrate`, it calls out -to one of the open source packages that it includes. In fact, SymPy is -included in Sage. SymPy on the other hand aims to be an independent system, -with all the features implemented in SymPy itself. - -A final important feature of SymPy is that it can be used as a library. Many -computer algebra systems focus on being usable in interactive environments, but -if you wish to automate or extend them, it is difficult to do. With SymPy, -you can just as easily use it in an interactive Python environment or import -it in your own Python application. SymPy also provides APIs to make it easy -to extend it with your own custom functions. - - -##### In `Julia`: - -There are other symbolic packages for `Julia`: - -* [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl) -* [Reduce.jl](https://github.com/chakravala/Reduce.jl) -* [Symata.jl](https://github.com/jlapeyre/Symata.jl) -* [SymEngine.jl](https://github.com/symengine/SymEngine.jl) -* [Nemo.jl](https://github.com/Nemocas/Nemo.jl) -* [SymbolicUtils](https://github.com/JuliaSymbolics/SymbolicUtils.jl) - -SymPy is an attractive alternative as `PyCall` makes most all of its functinality directly available and SymPy is fairly feature rich. diff --git a/docs/src/Tutorial/manipulation.md b/docs/src/Tutorial/manipulation.md deleted file mode 100644 index 3ea1cd47..00000000 --- a/docs/src/Tutorial/manipulation.md +++ /dev/null @@ -1,987 +0,0 @@ -# Advanced Expression Manipulation - -[From](https://docs.sympy.org/latest/tutorial/manipulation.html) (version 1.3) - -```@setup manipulation -using SymPy -sympy.init_printing(use_unicode=True) -``` - - -In this section, we discuss some ways that we can perform advanced -manipulation of expressions. - -## Understanding Expression Trees - - -!!! note "Quick Tip" - To play with the `srepr` form of expressions in the SymPy Live shell, - change the output format to `Repr` in the settings. - -Before we can do this, we need to understand how expressions are represented -in SymPy. A mathematical expression is represented as a tree. Let us take -the expression `2^x + xy`, i.e., `2**x + x*y`. We can see what this -expression looks like internally by using `srepr` - -```python - >>> from sympy import * - >>> x, y, z = symbols('x y z') - - >>> expr = 2**x + x*y - >>> srepr(expr) - "Add(Pow(Integer(2), Symbol('x')), Mul(Symbol('x'), Symbol('y')))" -``` - -##### In `Julia`: - -* We replace the import command with a `using` command, as this will import functions (not Classes though) from `sympy` - - -```jldoctest manipulation -julia> using SymPy - -julia> @syms x, y, z -(x, y, z) - -julia> expr = 2^x + x*y - x -2 + x⋅y - -julia> srepr(expr) -"Add(Pow(Integer(2), Symbol('x')), Mul(Symbol('x'), Symbol('y')))" - -``` - ----- - -The easiest way to tear this apart is to look at a diagrm of the expression -tree. Here is a [diagram](https://docs.sympy.org/latest/tutorial/manipulation.html#understanding-expression-trees). - -!!! note - - This comes from `dotprint(2**x + x*y, labelfunc=srepr)`. But we don't render digraph objects here - - -First, let's look at the leaves of this tree. Symbols are instances of the -class Symbol. While we have been doing - -```python - >>> x = symbols('x') -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> x = symbols("x") -x - -``` - ----- - -we could have also done - -```python - >>> x = Symbol('x') -``` - -##### In `Julia`: - -* this can be done, but `@syms` would be suggested: - -```jldoctest manipulation -julia> x = sympy.Symbol("x") -x - -``` - -```jldoctest manipulation -julia> @syms x -(x,) - -``` - ----- -Either way, we get a Symbol with the name "x" [#symbols-fn]_. For the number in the -expression, 2, we got `Integer(2)`. `Integer` is the SymPy class for -integers. It is similar to the Python built-in type `int`, except that -`Integer` plays nicely with other SymPy types. - -When we write `2**x`, this creates a `Pow` object. `Pow` is short for -"power". - -```python - >>> srepr(2**x) - "Pow(Integer(2), Symbol('x'))" -``` - -##### In `Julia`: - -* we replace `**` by `^` - -```jldoctest manipulation -julia> srepr(2^x) -"Pow(Integer(2), Symbol('x'))" - -``` - ----- -We could have created the same object by calling `Pow(2, x)` - -```python - >>> Pow(2, x) - 2**x -``` - -##### In `Julia`: - -* `Pow` is *not* a function, rather a managed property, so we must qualify it, as it wasn't brought in when loading the package - -```jldoctest manipulation -julia> sympy.Pow(2, x) - x -2 - -``` - ----- - -Note that in the `srepr` output, we see `Integer(2)`, the SymPy version of -integers, even though technically, we input `2`, a Python int. In general, -whenever you combine a SymPy object with a non-SymPy object via some function -or operation, the non-SymPy object will be converted into a SymPy object. The -function that does this is `sympify` [#sympify-fn]_. - -```python - >>> type(2) - <... 'int'> - >>> type(sympify(2)) - -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> typeof(2) -Int64 - -julia> typeof(sympify(2)) -Sym - -``` - ----- - -We have seen that `2**x` is represented as `Pow(2, x)`. What about -`x*y`? As we might expect, this is the multiplication of `x` and `y`. -The SymPy class for multiplication is `Mul`. - -```python - >>> srepr(x*y) - "Mul(Symbol('x'), Symbol('y'))" -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> srepr(x*y) -"Mul(Symbol('x'), Symbol('y'))" - -``` - ----- -Thus, we could have created the same object by writing `Mul(x, y)`. - -```python - >>> Mul(x, y) - x*y -``` - -##### In `Julia`: - -* Again, `Mul` is not a function, so it must be qualified - -```jldoctest manipulation -julia> sympy.Mul(x, y) -x⋅y - -``` - ----- -Now we get to our final expression, `2**x + x*y`. This is the addition of -our last two objects, `Pow(2, x)`, and `Mul(x, y)`. The SymPy class for -addition is `Add`, so, as you might expect, to create this object, we use -`Add(Pow(2, x), Mul(x, y))`. - -```python - >>> Add(Pow(2, x), Mul(x, y)) - 2**x + x*y -``` - -##### In `Julia`: - -* We *can* import these operations to avoid qualifying them as done here: - -```jldoctest manipulation -julia> import_from(sympy, (:Add, :Mul, :Pow), typ=:Any) - -julia> Add(Pow(2, x), Mul(x, y)) - x -2 + x⋅y - -``` - ----- -SymPy expression trees can have many branches, and can be quite deep or quite -broad. Here is a more complicated example - -```python - >>> expr = sin(x*y)/2 - x**2 + 1/y - >>> srepr(expr) - "Add(Mul(Integer(-1), Pow(Symbol('x'), Integer(2))), Mul(Rational(1, 2), - sin(Mul(Symbol('x'), Symbol('y')))), Pow(Symbol('y'), Integer(-1)))" -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr = sin(x*y)/2 - x^2 + 1/y - 2 sin(x⋅y) 1 -- x + ──────── + ─ - 2 y - -julia> srepr(expr) -"Add(Mul(Integer(-1), Pow(Symbol('x'), Integer(2))), Mul(Rational(1, 2), sin(Mul(Symbol('x'), Symbol('y')))), Pow(Symbol('y'), Integer(-1)))" - -``` - ----- -Here is a [diagram](https://docs.sympy.org/latest/tutorial/manipulation.html#understanding-expression-trees) - -!!! note - - produced with `dotprint(sin(x*y)/2 - x**2 + 1/y, labelfunc=srepr)`, but not rendered here - - -This expression reveals some interesting things about SymPy expression -trees. Let's go through them one by one. - -Let's first look at the term `x**2`. As we expected, we see `Pow(x, 2)`. -One level up, we see we have `Mul(-1, Pow(x, 2))`. There is no subtraction -class in SymPy. `x - y` is represented as `x + -y`, or, more completely, -`x + -1*y`, i.e., `Add(x, Mul(-1, y))`. - -```python - >>> srepr(x - y) - "Add(Symbol('x'), Mul(Integer(-1), Symbol('y')))" -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> srepr(x - y) -"Add(Symbol('x'), Mul(Integer(-1), Symbol('y')))" - -``` - ----- - -Next, look at `1/y`. We might expect to see something like `Div(1, y)`, -but similar to subtraction, there is no class in SymPy for division. Rather, -division is represented by a power of -1. Hence, we have `Pow(y, -1)`. -What if we had divided something other than 1 by `y`, like `x/y`? Let's -see. - -```python - >>> expr = x/y - >>> srepr(expr) - "Mul(Symbol('x'), Pow(Symbol('y'), Integer(-1)))" -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr = x/y -x -─ -y - -julia> srepr(expr) -"Mul(Symbol('x'), Pow(Symbol('y'), Integer(-1)))" - -``` - ----- - -We see that `x/y` is represented as `x*y**-1`, i.e., `Mul(x, Pow(y, --1))`. - -Finally, let's look at the `sin(x*y)/2` term. Following the pattern of the -previous example, we might expect to see `Mul(sin(x*y), Pow(Integer(2), --1))`. But instead, we have `Mul(Rational(1, 2), sin(x*y))`. Rational -numbers are always combined into a single term in a multiplication, so that -when we divide by 2, it is represented as multiplying by 1/2. - -Finally, one last note. You may have noticed that the order we entered our -expression and the order that it came out from `srepr` or in the graph were -different. You may have also noticed this phenomenon earlier in the -tutorial. For example - -```python - >>> 1 + x - x + 1 -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> 1 + x -x + 1 - -``` - ----- -This because in SymPy, the arguments of the commutative operations `Add` and -`Mul` are stored in an arbitrary (but consistent!) order, which is -independent of the order inputted (if you're worried about noncommutative -multiplication, don't be. In SymPy, you can create noncommutative Symbols -using `Symbol('A', commutative=False)`, and the order of multiplication for -noncommutative Symbols is kept the same as the input). Furthermore, as we -shall see in the next section, the printing order and the order in which -things are stored internally need not be the same either. - -!!! note "Quick Tip" - The way an expression is represented internally and the way it is printed - are often not the same. - -In general, an important thing to keep in mind when working with SymPy expression -trees is this: the internal representation of an expression and the way it is -printed need not be the same. The same is true for the input form. If some -expression manipulation algorithm is not working in the way you expected it -to, chances are, the internal representation of the object is different from -what you thought it was. - -## Recursing through an Expression Tree - - -Now that you know how expression trees work in SymPy, let's look at how to dig -our way through an expression tree. Every object in SymPy has two very -important attributes, `func`, and `args`. - - -### `func` - -`func` is the head of the object. For example, `(x*y).func` is `Mul`. -Usually it is the same as the class of the object (though there are exceptions -to this rule). - -Two notes about `func`. First, the class of an object need not be the same -as the one used to create it. For example - -```python - >>> expr = Add(x, x) - >>> expr.func - -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr = Add(x, x) -2⋅x - -julia> expr.func -PyObject - -``` - -* The output isn't as desired, as `PyObject`s don't show nicely here. We can ask for the name, which does display as desired: - -```jldoctest manipulation -julia> expr.func.__name__ -"Mul" - -``` - -* In `SymPy` the `func` and `args` properties are exported as functions in the module `SymPy.Introspection`. - - ----- -We created `Add(x, x)`, so we might expect `expr.func` to be `Add`, but -instead we got `Mul`. Why is that? Let's take a closer look at `expr`. - -```python - >>> expr - 2*x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr -2⋅x - -``` - ----- - -`Add(x, x)`, i.e., `x + x`, was automatically converted into `Mul(2, -x)`, i.e., `2*x`, which is a `Mul`. SymPy classes make heavy use of the -`__new__` class constructor, which, unlike `__init__`, allows a different -class to be returned from the constructor. - -Second, some classes are special-cased, usually for efficiency reasons -[#singleton-fn]_. - -```python - >>> Integer(2).func - - >>> Integer(0).func - - >>> Integer(-1).func - -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> sympy.Integer(2).func.__name__ -"Integer" - -julia> sympy.Integer(0).func.__name__ -"Zero" - -julia> sympy.Integer(-1).func.__name__ -"NegativeOne" - -``` - ----- -For the most part, these issues will not bother us. The special classes -`Zero`, `One`, `NegativeOne`, and so on are subclasses of `Integer`, -so as long as you use `isinstance`, it will not be an issue. - -### args - - -`args` are the top-level arguments of the object. `(x*y).args` would be -`(x, y)`. Let's look at some examples - -```python - >>> expr = 3*y**2*x - >>> expr.func - - >>> expr.args - (3, x, y**2) -``` - -##### In `Julia`: - -* The `args` property can be accessed exactly as `func` - -```jldoctest manipulation -julia> expr = 3*y^2*x - 2 -3⋅x⋅y - -julia> expr.func.__name__ -"Mul" - -julia> expr.args -(3, x, y^2) - -``` - ----- -From this, we can see that `expr == Mul(3, y**2, x)`. In fact, we can see -that we can completely reconstruct `expr` from its `func` and its -`args`. - -```python - >>> expr.func(*expr.args) - 3*x*y**2 - >>> expr == expr.func(*expr.args) - True -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr.func(expr.args...) - 2 -3⋅x⋅y - -julia> expr == expr.func(expr.args...) -true - -``` - ----- -Note that although we entered `3*y**2*x`, the `args` are `(3, x, y**2)`. -In a `Mul`, the Rational coefficient will come first in the `args`, but -other than that, the order of everything else follows no special pattern. To -be sure, though, there is an order. - -```python - >>> expr = y**2*3*x - >>> expr.args - (3, x, y**2) -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr = y^2*3*x - 2 -3⋅x⋅y - -julia> expr.args -(3, x, y^2) -``` - ----- -Mul's `args` are sorted, so that the same `Mul` will have the same -`args`. But the sorting is based on some criteria designed to make the -sorting unique and efficient that has no mathematical significance. - -The `srepr` form of our `expr` is `Mul(3, x, Pow(y, 2))`. What if we -want to get at the `args` of `Pow(y, 2)`. Notice that the `y**2` is in -the third slot of `expr.args`, i.e., `expr.args[2]`. - -```python - >>> expr.args[2] - y**2 -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr.args[2] -x - -``` - ----- -So to get the `args` of this, we call `expr.args[2].args`. - -```python - >>> expr.args[2].args - (y, 2) -``` - -##### In `Julia`: - -* Python uses 0-based indexing, so we bump the index by 1 - -```jldoctest manipulation -julia> expr.args[3].args -(y, 2) - -``` - ----- -Now what if we try to go deeper. What are the args of `y`. Or `2`. -Let's see. - -```python - >>> y.args - () - >>> Integer(2).args - () -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> y.args -() - -julia> sympy.Integer(2).args -() - -``` - ----- -They both have empty `args`. In SymPy, empty `args` signal that we have -hit a leaf of the expression tree. - -So there are two possibilities for a SymPy expression. Either it has empty -`args`, in which case it is a leaf node in any expression tree, or it has -`args`, in which case, it is a branch node of any expression tree. When it -has `args`, it can be completely rebuilt from its `func` and its `args`. -This is expressed in the key invariant. - -!!! note "Key Invariant" - - Every well-formed SymPy expression must either have empty `args` or - satisfy `expr == expr.func(expr.args...)`. - -(Recall that in Python if `a` is a tuple, then `f(*a)` means to call `f` -with arguments from the elements of `a`, e.g., `f(*(1, 2, 3))` is the same -as `f(1, 2, 3)`.) - -This key invariant allows us to write simple algorithms that walk expression -trees, change them, and rebuild them into new expressions. - - -##### In `Julia`: - -* Splatting replaces the `*a` term above, or `f(a...)`. - ----- - -### Walking the Tree - - -With this knowledge, let's look at how we can recurse through an expression -tree. The nested nature of `args` is a perfect fit for recursive functions. -The base case will be empty `args`. Let's write a simple function that goes -through an expression and prints all the `args` at each level. - -```python - >>> def pre(expr): - ... print(expr) - ... for arg in expr.args: - ... pre(arg) -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> function pre(expr) - @show expr - for arg in expr.args - pre(arg) - end - end -pre (generic function with 1 method) -``` - ----- - -See how nice it is that `()` signals leaves in the expression tree. We -don't even have to write a base case for our recursion; it is handled -automatically by the for loop. - -Let's test our function. - -```python - >>> expr = x*y + 1 - >>> pre(expr) - x*y + 1 - 1 - x*y - x - y -``` - -##### In `Julia`: - -* Here we see the output: - -```jldoctest manipulation -julia> expr = x*y + 1 -x⋅y + 1 - -julia> pre(expr) -expr = x*y + 1 -expr = 1 -expr = x*y -expr = x -expr = y - -``` - ----- - -Can you guess why we called our function `pre`? We just wrote a pre-order -traversal function for our expression tree. See if you can write a -post-order traversal function. - -Such traversals are so common in SymPy that the generator functions -`preorder_traversal` and `postorder_traversal` are provided to make such -traversals easy. We could have also written our algorithm as - -```python - >>> for arg in preorder_traversal(expr): - ... print(arg) - x*y + 1 - 1 - x*y - x - y -``` - -##### In `Julia`: - -* The `preorder_traversal` function is not a function, so needs to be qualified: - -```jldoctest manipulation -julia> for arg in sympy.preorder_traversal(expr) - @show arg - end -arg = x*y + 1 -arg = 1 -arg = x*y -arg = x -arg = y - -``` - ----- - -## Prevent expression evaluation - - -There are generally two ways to prevent the evaluation, either pass an -`evaluate=False` parameter while constructing the expression, or create -an evaluation stopper by wrapping the expression with `UnevaluatedExpr`. - -For example: - -```python - >>> from sympy import Add - >>> from sympy.abc import x, y, z - >>> x + x - 2*x - >>> Add(x, x) - 2*x - >>> Add(x, x, evaluate=False) - x + x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> @syms x y z -(x, y, z) - -julia> x + x -2⋅x - -julia> Add(x, x) -2⋅x - -julia> Add(x, x, evaluate=False) -x + x - -``` - ----- -If you don't remember the class corresponding to the expression you -want to build (operator overloading usually assumes `evaluate=True`), -just use `sympify` and pass a string: - -```python - >>> from sympy import sympify - >>> sympify("x + x", evaluate=False) - x + x -``` - -##### In `Julia`: - - - -```jldoctest manipulation -julia> sympify("x + x", evaluate=false) -2⋅x - -``` - ----- -Note that `evaluate=False` won't prevent future evaluation in later -usages of the expression: - -```python - >>> expr = Add(x, x, evaluate=False) - >>> expr - x + x - >>> expr + x - 3*x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr = Add(x, x, evaluate=false) -x + x - -``` - -```jldoctest manipulation -julia> expr + x -3⋅x -``` - ----- -That's why the class `UnevaluatedExpr` comes handy. -`UnevaluatedExpr` is a method provided by SymPy which lets the user keep -an expression unevaluated. By *unevaluated* it is meant that the value -inside of it will not interact with the expressions outside of it to give -simplified outputs. For example: - -```python - >>> from sympy import UnevaluatedExpr - >>> expr = x + UnevaluatedExpr(x) - >>> expr - x + x - >>> x + expr - 2*x + x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> import_from(sympy, (:UnevaluatedExpr,)) - -julia> expr -x + x - -julia> x + expr -3⋅x - -``` - ----- -The `x` remaining alone is the `x` wrapped by `UnevaluatedExpr`. -To release it: - -```python - >>> (x + expr).doit() - 3*x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> (x + expr).doit() -3⋅x - -``` - ----- -Other examples: - -```python - >>> from sympy import * - >>> from sympy.abc import x, y, z - >>> uexpr = UnevaluatedExpr(S.One*5/7)*UnevaluatedExpr(S.One*3/4) - >>> uexpr - (5/7)*(3/4) - >>> x*UnevaluatedExpr(1/x) - x*1/x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> @syms x y z -(x, y, z) - -julia> const S = sympy.S -PyObject S - -julia> uexpr = UnevaluatedExpr(S.One * 5/7) * UnevaluatedExpr(S.One * 3/4) -5/7⋅3/4 - -julia> x * UnevaluatedExpr(1/x) - 1 -x⋅─ - x - -``` - ----- - -A point to be noted is that `UnevaluatedExpr` cannot prevent the -evaluation of an expression which is given as argument. For example: - -```python - >>> expr1 = UnevaluatedExpr(x + x) - >>> expr1 - 2*x - >>> expr2 = sympify('x + x', evaluate=False) - >>> expr2 - x + x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> expr1 = UnevaluatedExpr(x + x) -2⋅x - -julia> expr2 = sympify("x + x", evaluate=False) -2⋅x - -``` - ----- - - -Remember that `expr2` will be evaluated if included into another -expression. Combine both of the methods to prevent both inside and outside -evaluations: - -```python - >>> UnevaluatedExpr(sympify("x + x", evaluate=False)) + y - y + x + x -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> UnevaluatedExpr(sympify("x + x", evaluate=False)) + y -y + 2⋅x -``` - ----- - -`UnevalutedExpr` is supported by SymPy printers and can be used to print the -result in different output forms. For example - -```python - >>> from sympy import latex - >>> uexpr = UnevaluatedExpr(S.One*5/7)*UnevaluatedExpr(S.One*3/4) - >>> print(latex(uexpr)) - \frac{5}{7} \frac{3}{4} -``` - -##### In `Julia`: - -* The printing support is through `show`, but we can use SymPy's: - -``` -julia> uexpr = UnevaluatedExpr(S.One*5/7)*UnevaluatedExpr(S.One*3/4) -5/7⋅3/4 - -julia> sympy.latex(uexpr) -"\\frac{5}{7} \\frac{3}{4}" - - -``` - ----- - -In order to release the expression and get the evaluated LaTeX form, -just use `.doit()`: - -```python - >>> print(latex(uexpr.doit())) - \frac{15}{28} -``` - -##### In `Julia`: - -```jldoctest manipulation -julia> sympy.latex(uexpr.doit()) -"\\frac{15}{28}" - -``` - ----- - -!!! note "Footnotes" - * [#symbols-fn] We have been using `symbols` instead of `Symbol` because it automatically splits apart strings into multiple `Symbol`\ s. `symbols('x y z')` returns a tuple of three `Symbol`\ s. `Symbol('x y z')` returns a single `Symbol` called `x y z`. - * [#sympify-fn] Technically, it is an internal function called `_sympify`, which differs from `sympify` in that it does not convert strings. `x + '2'` is not allowed. - * [#singleton-fn] Classes like `One` and `Zero` are singletonized, meaning that only one object is ever created, no matter how many times the class is called. This is done for space efficiency, as these classes are very common. For example, `Zero` might occur very often in a sparse matrix represented densely. As we have seen, `NegativeOne` occurs any time we have `-x` or `1/x`. It is also done for speed efficiency because singletonized objects can be compared by `is`. The unique objects for each singletonized class can be accessed from the `S` object. diff --git a/docs/src/Tutorial/matrices.md b/docs/src/Tutorial/matrices.md deleted file mode 100644 index 99ba034f..00000000 --- a/docs/src/Tutorial/matrices.md +++ /dev/null @@ -1,1335 +0,0 @@ -# Matrices - -[From](https://docs.sympy.org/latest/tutorial/matrices.html) - -```python - >>> from sympy import * - >>> init_printing(use_unicode=True) -``` - -```@setup matrices -using SymPy -sympy.init_printing(use_unicode=True) -``` - -##### In `Julia`: - -* In `SymPy`, matrices can be store using `Julia`'s *generic* - `Matrix{T}` type where `T <: Sym` *or* using SymPy's matrix type, - wrapped in a `SymMatrix` type by `SymPy`. This tutorial shows - how to use the underlying `SymMatrix` values. To construct a matrix - of symbolic values is identical to construction a matrix of numeric - values within `Julia`, and will be illustrated at the end. - - -* methods for `SymMatrix` objects use the dot call syntax. As a - convenience, this will also work for `Array{Sym}` objects. The - returned value may be a `SymMatrix`, not an `Array{Sym}`. - -* The matrix constructor in SymPy using a vector of row vectors does *not* work in `SymPy`, as of newer versions (it does not work with version 1.5.1 of sympy and PyCall). This style is used in this document. The user of `SymPy` can eaesily avoid this specification, using Julia's matrix construction techniques. - - -```jldoctest matrices -julia> using SymPy - -julia> using LinearAlgebra - -``` - ----- - -To make a matrix in SymPy, use the `Matrix` object. A matrix is constructed -by providing a list of row vectors that make up the matrix. For example, -to construct the matrix - -$$~ - \left[\begin{array}{cc}1 & -1\\3 & 4\\0 & 2\end{array}\right] -~$$ - -use - -```python - >>> Matrix([[1, -1], [3, 4], [0, 2]]) - ⎡1 -1⎤ - ⎢ ⎥ - ⎢3 4 ⎥ - ⎢ ⎥ - ⎣0 2 ⎦ -``` - -##### In `Julia`: - -* In `Julia`, the `Matrix` constructor is *not* exported, so must be qualified. Here we *avoid* the vector of row vectors above: - -```jldoctest matrices -julia> sympy.Matrix([1 -1; 3 4; 0 2]) -3×2 Matrix{Sym}: - 1 -1 - 3 4 - 0 2 - -``` - -*However*, through the magic of `PyCall`, such matrices are converted into `Julia` matrices, of type `Array{Sym}`, so the familiar matrix operations for `Julia` users are available. - - -In fact, the above could be done in the more `Julia`n manner through - -```jldoctest matrices -julia> Sym[1 -1; 3 4; 0 2] -3×2 Matrix{Sym}: - 1 -1 - 3 4 - 0 2 - -``` - -using an annotation to ensure the type. Alternatively, through promotion, just a single symbolic object will result in the same: - -```jldoctest matrices -julia> [Sym(1) -1; 3 4; 0 2] -3×2 Matrix{Sym}: - 1 -1 - 3 4 - 0 2 - -``` - ----- - -To make it easy to make column vectors, a list of elements is considered to be -a column vector. - -```python - >>> Matrix([1, 2, 3]) - ⎡1⎤ - ⎢ ⎥ - ⎢2⎥ - ⎢ ⎥ - ⎣3⎦ -``` - -##### In `Julia`: - -For this use, `sympy.Matrix` does work, but again its usage is discouraged: - -```jldoctest matrices -julia> sympy.Matrix([1, 2, 3]) -3×1 Matrix{Sym}: - 1 - 2 - 3 - -``` - -* Again, this is converted into a `Vector{Sym}` object or entered directly: - -```jldoctest matrices -julia> Sym[1,2,3] -3-element Vector{Sym}: - 1 - 2 - 3 - -``` - -!!! note "Avoid `sympy.Matrix`" - As shown, it is better to avoid the `sympy.Matrix` constructor when possible, and when not, only pass in a symbolic array created through `Julia`'s array semantics. - - ----- - -Matrices are manipulated just like any other object in SymPy or Python. - - -```python - >>> M = Matrix([[1, 2, 3], [3, 2, 1]]) - >>> N = Matrix([0, 1, 1]) - >>> M*N - ⎡5⎤ - ⎢ ⎥ - ⎣3⎦ -``` - -##### In `Julia`: - -* In `Julia`, matrices are just matrices, and inherit all of the operations defined on them: - -```jldoctest matrices -julia> M = Sym[1 2 3; 3 2 1] -2×3 Matrix{Sym}: - 1 2 3 - 3 2 1 - -julia> N = Sym[0, 1, 1] -3-element Vector{Sym}: - 0 - 1 - 1 - -julia> M*N -2-element Vector{Sym}: - 5 - 3 - -``` - ----- - -One important thing to note about SymPy matrices is that, unlike every other -object in SymPy, they are mutable. This means that they can be modified in -place, as we will see below. The downside to this is that `Matrix` cannot -be used in places that require immutability, such as inside other SymPy -expressions or as keys to dictionaries. If you need an immutable version of -`Matrix`, use `ImmutableMatrix`. - -##### In `Julia`: - -A distinction is made between `ImmutableMatrix` and a mutable one. Mutable ones are mapped to `Julia` arrays, immutable ones are left as a symbolic object of type `SymMatrix`. The usual infix mathematical operations (but not dot broadcasting), 0-based indexing, and dot call syntax for methods maay be used with these objects. - - - -## Basic Operations - - -### Shape - - -Here are some basic operations on `Matrix`. To get the shape of a matrix -use `shape` - -```python - >>> M = Matrix([[1, 2, 3], [-2, 0, 4]]) - >>> M - ⎡1 2 3⎤ - ⎢ ⎥ - ⎣-2 0 4⎦ - >>> M.shape - (2, 3) -``` - -##### In `Julia`: - -```jldoctest matrices -julia> M = Sym[1 2 3; -2 0 4] -2×3 Matrix{Sym}: - 1 2 3 - -2 0 4 - -julia> M.shape -(2, 3) - -``` - -Or, the `Julia`n counterpart: - -```jldoctest matrices -julia> size(M) -(2, 3) - -``` - ----- - -### Accessing Rows and Columns - - -To get an individual row or column of a matrix, use `row` or `col`. For -example, `M.row(0)` will get the first row. `M.col(-1)` will get the last -column. - -```python - >>> M.row(0) - [1 2 3] - >>> M.col(-1) - ⎡3⎤ - ⎢ ⎥ - ⎣4⎦ -``` - -##### In `Julia`: - -* these 0-based operations are supported: - -```jldoctest matrices -julia> M.row(0) -1×3 Matrix{Sym}: - 1 2 3 - -julia> M.col(-1) -2×1 Matrix{Sym}: - 3 - 4 - -``` - -The more familiar counterparts would be: - -```jldoctest matrices -julia> M[1,:], M[:, end] -(Sym[1, 2, 3], Sym[3, 4]) - -``` - - ----- - -### Deleting and Inserting Rows and Columns - - -To delete a row or column, use `row_del` or `col_del`. These operations -will modify the Matrix **in place**. - -```python - >>> M.col_del(0) - >>> M - ⎡2 3⎤ - ⎢ ⎥ - ⎣0 4⎦ - >>> M.row_del(1) - >>> M - [2 3] -``` - -##### In `Julia`: - -These methods do **not** work on `Array{Sym}` objects, use `Julia's` indexing notation to remove a row or column. - -However, these methods **do** work on the `ImmutableMatrix` class: - -```jldoctest matrices -julia> M = sympy.ImmutableMatrix([1 2 3; -2 0 4]) # avoid vector of row vector construction -⎡1 2 3⎤ -⎢ ⎥ -⎣-2 0 4⎦ - -julia> M.col_del(0) -⎡2 3⎤ -⎢ ⎥ -⎣0 4⎦ - -``` - -```jldoctest matrices -julia> M.row_del(1) -[1 2 3] - -``` - - -!!! note "Alert" - For older versions of sympy, the following did not work (using symbolic values as matrix entries without reverting to their PyObjects had shape issues); this should work now: - - -```jldoctest matrices -julia> @syms x -(x,) - -julia> sympy.ImmutableMatrix([x 1; 1 x]) -⎡x 1⎤ -⎢ ⎥ -⎣1 x⎦ - -``` - ----- - -!!! note "TODO" - This is a mess. See issue 6992. - -To insert rows or columns, use `row_insert` or `col_insert`. These -operations **do not** operate in place. - -```python - >>> M - [2 3] - >>> M = M.row_insert(1, Matrix([[0, 4]])) - >>> M - ⎡2 3⎤ - ⎢ ⎥ - ⎣0 4⎦ - >>> M = M.col_insert(0, Matrix([1, -2])) - >>> M - ⎡1 2 3⎤ - ⎢ ⎥ - ⎣-2 0 4⎦ -``` - -##### In `Julia`: - -```jldoctest matrices -julia> M = sympy.ImmutableMatrix([2 3]) -[2 3] - -julia> M = M.row_insert(1, Sym[0 4]) -⎡2 3⎤ -⎢ ⎥ -⎣0 4⎦ - -``` - -```jldoctest matrices -julia> M = M.col_insert(0, Sym[1, -2]) -⎡1 2 3⎤ -⎢ ⎥ -⎣-2 0 4⎦ - -``` - ----- - -Unless explicitly stated, the methods mentioned below do not operate in -place. In general, a method that does not operate in place will return a new -`Matrix` and a method that does operate in place will return `None`. - -##### In `Julia` - -This would be the case for the immutable matrices. - ----- - -## Basic Methods - - -As noted above, simple operations like addition and multiplication are done -just by using `+`, `*`, and `**`. To find the inverse of a matrix, just -raise it to the `-1` power. - -```python - >>> M = Matrix([[1, 3], [-2, 3]]) - >>> N = Matrix([[0, 3], [0, 7]]) - >>> M + N - ⎡1 6 ⎤ - ⎢ ⎥ - ⎣-2 10⎦ - >>> M*N - ⎡0 24⎤ - ⎢ ⎥ - ⎣0 15⎦ - >>> 3*M - ⎡3 9⎤ - ⎢ ⎥ - ⎣-6 9⎦ - >>> M**2 - ⎡-5 12⎤ - ⎢ ⎥ - ⎣-8 3 ⎦ - >>> M**-1 - ⎡1/3 -1/3⎤ - ⎢ ⎥ - ⎣2/9 1/9 ⎦ - >>> N**-1 - Traceback (most recent call last): - ... - ValueError: Matrix det == 0; not invertible. -``` - -##### In `Julia`: - -In `Julia`, we use `M1` instead of `N`, an exported symbol of `SymPy`. Otherise, it all looks similar: - -```jldoctest matrices -julia> M = Sym[1 3; -2 3] -2×2 Matrix{Sym}: - 1 3 - -2 3 - -julia> M1 = Sym[0 3; 0 7] -2×2 Matrix{Sym}: - 0 3 - 0 7 - -julia> M + M1 -2×2 Matrix{Sym}: - 1 6 - -2 10 - -julia> M*M1 -2×2 Matrix{Sym}: - 0 24 - 0 15 - -julia> 3*M -2×2 Matrix{Sym}: - 3 9 - -6 9 - -julia> M^2 -2×2 Matrix{Sym}: - -5 12 - -8 3 - -julia> M^-1 -2×2 Matrix{Sym}: - 1/3 -1/3 - 2/9 1/9 -``` - -Attempting to find the inverse of `M1` will error (we suppress its lengthy output) - -```jldoctest matrices -julia> using Test - -julia> @test_throws Exception M1^-1 -Test Passed - Thrown: PyCall.PyError - -``` - -The above (except for the inverses) are using generic `Julia` definitions. For immutable matrices, we would have: - - - -```jldoctest matrices -julia> M = sympy.ImmutableMatrix([1 3; -2 3]) -⎡1 3⎤ -⎢ ⎥ -⎣-2 3⎦ - -julia> M1 = sympy.ImmutableMatrix([0 3; 0 7]) -⎡0 3⎤ -⎢ ⎥ -⎣0 7⎦ - -julia> M + M1 -⎡1 6 ⎤ -⎢ ⎥ -⎣-2 10⎦ - -``` - -```jldoctest matrices -julia> M*M1 -⎡0 24⎤ -⎢ ⎥ -⎣0 15⎦ - -julia> 3*M -⎡3 9⎤ -⎢ ⎥ -⎣-6 9⎦ - -julia> M^2 - 2 -2 - -julia> M^-1 -⎡1/3 -1/3⎤ -⎢ ⎥ -⎣2/9 1/9 ⎦ -``` - -Similarly, `M1^(-1)` would yield an error for the non-invertible matrix - -* There is no broadcasting defined for the `SymMatrix` type. - ------ - -To take the transpose of a Matrix, use `T`. - -```python - >>> M = Matrix([[1, 2, 3], [4, 5, 6]]) - >>> M - ⎡1 2 3⎤ - ⎢ ⎥ - ⎣4 5 6⎦ - >>> M.T - ⎡1 4⎤ - ⎢ ⎥ - ⎢2 5⎥ - ⎢ ⎥ - ⎣3 6⎦ -``` - -##### In `Julia`: - -```jldoctest matrices -julia> M = Sym[1 2 3; 4 5 6] -2×3 Matrix{Sym}: - 1 2 3 - 4 5 6 - -julia> M.T -3×2 Matrix{Sym}: - 1 4 - 2 5 - 3 6 - -``` - ----- - -## Matrix Constructors - - -Several constructors exist for creating common matrices. To create an -identity matrix, use `eye`. The command `eye(n)` will create an `n x n` identity matrix: - -```python - >>> eye(3) - ⎡1 0 0⎤ - ⎢ ⎥ - ⎢0 1 0⎥ - ⎢ ⎥ - ⎣0 0 1⎦ - >>> eye(4) - ⎡1 0 0 0⎤ - ⎢ ⎥ - ⎢0 1 0 0⎥ - ⎢ ⎥ - ⎢0 0 1 0⎥ - ⎢ ⎥ - ⎣0 0 0 1⎦ -``` - -##### In `Julia`: - -* `eye` is *not* exported so must qualified: - -```jldoctest matrices -julia> sympy.eye(3) -3×3 Matrix{Sym}: - 1 0 0 - 0 1 0 - 0 0 1 - -julia> sympy.eye(4) -4×4 Matrix{Sym}: - 1 0 0 0 - 0 1 0 0 - 0 0 1 0 - 0 0 0 1 - -``` - ----- - -To create a matrix of all zeros, use `zeros`. `zeros(n, m)` creates an -`n x m` matrix of `0`s. - -```python - >>> zeros(2, 3) - ⎡0 0 0⎤ - ⎢ ⎥ - ⎣0 0 0⎦ -``` - -##### In `Julia`: - -* zeros is extended but the method expects a symbolic first argument. Either qualify it: - -```jldoctest matrices -julia> sympy.zeros(2, 3) -2×3 Matrix{Sym}: - 0 0 0 - 0 0 0 - -``` - -*or* create a symbolic first value: - -```jldoctest matrices -julia> zeros(Sym(2), 3) -2×3 Matrix{Sym}: - 0 0 0 - 0 0 0 - -``` - -*or* use the `Julia` constructor: - -```jldoctest matrices -julia> zeros(Sym, 2, 3) -2×3 Matrix{Sym}: - 0 0 0 - 0 0 0 - -``` - ----- - -Similarly, `ones` creates a matrix of ones. - -```python - >>> ones(3, 2) - ⎡1 1⎤ - ⎢ ⎥ - ⎢1 1⎥ - ⎢ ⎥ - ⎣1 1⎦ -``` - -##### In `Julia`: - -* Similarly with `ones`: - -```jldoctest matrices -julia> sympy.ones(3, 2) -3×2 Matrix{Sym}: - 1 1 - 1 1 - 1 1 - -``` - ----- - -To create diagonal matrices, use `diag`. The arguments to `diag` can be -either numbers or matrices. A number is interpreted as a `1 x 1` -matrix. The matrices are stacked diagonally. The remaining elements are -filled with `0`\ s. - -```python - >>> diag(1, 2, 3) - ⎡1 0 0⎤ - ⎢ ⎥ - ⎢0 2 0⎥ - ⎢ ⎥ - ⎣0 0 3⎦ - >>> diag(-1, ones(2, 2), Matrix([5, 7, 5])) - ⎡-1 0 0 0⎤ - ⎢ ⎥ - ⎢0 1 1 0⎥ - ⎢ ⎥ - ⎢0 1 1 0⎥ - ⎢ ⎥ - ⎢0 0 0 5⎥ - ⎢ ⎥ - ⎢0 0 0 7⎥ - ⎢ ⎥ - ⎣0 0 0 5⎦ -``` - -##### In `Julia`: - -* similarly with `diag`: - -```jldoctest matrices -julia> sympy.diag(1, 2, 3) -3×3 Matrix{Sym}: - 1 0 0 - 0 2 0 - 0 0 3 - -julia> sympy.diag(-1, sympy.ones(2, 2), sympy.Matrix([5, 7, 5])) -6×4 Matrix{Sym}: - -1 0 0 0 - 0 1 1 0 - 0 1 1 0 - 0 0 0 5 - 0 0 0 7 - 0 0 0 5 - -``` - -* The first one, could also use `Julia`'s `diagm` function from the `LinearAlgebra` package: - -```jldoctest matrices -julia> diagm(0 => Sym[1,2,3]) -3×3 Matrix{Sym}: - 1 0 0 - 0 2 0 - 0 0 3 - -``` - - ----- - -## Advanced Methods - - -### Determinant - - -To compute the determinant of a matrix, use `det`. - -```python - >>> M = Matrix([[1, 0, 1], [2, -1, 3], [4, 3, 2]]) - >>> M - ⎡1 0 1⎤ - ⎢ ⎥ - ⎢2 -1 3⎥ - ⎢ ⎥ - ⎣4 3 2⎦ - >>> M.det() - -1 -``` - -##### In `Julia`: - -```jldoctest matrices -julia> M = Sym[1 0 1; 2 -1 3; 4 3 2] -3×3 Matrix{Sym}: - 1 0 1 - 2 -1 3 - 4 3 2 - -julia> M.det() --1 - -``` - - -Let - -```jldoctest matrices -julia> @syms x -(x,) - -julia> A = Sym[x 1; 1 x] -2×2 Matrix{Sym}: - x 1 - 1 x - -``` - -The method for `det` falls back the sympy method: - -```jldoctest matrices -julia> det(A) - 2 -x - 1 - -``` - -There is no reason to, but generic `Julia` methods could be used: - -```jldoctest matrices -julia> out = lu(A) -LU{Sym, Matrix{Sym}, Vector{Int64}} -L factor: -2×2 Matrix{Sym}: - 1 0 - 1/x 1 -U factor: -2×2 Matrix{Sym}: - x 1 - 0 x - 1/x - -julia> prod(diag(out.L)) * prod(diag(out.U)) - ⎛ 1⎞ -x⋅⎜x - ─⎟ - ⎝ x⎠ -``` - -### RREF - - -To put a matrix into reduced row echelon form, use `rref`. `rref` returns -a tuple of two elements. The first is the reduced row echelon form, and the -second is a tuple of indices of the pivot columns. - -```python - >>> M = Matrix([[1, 0, 1, 3], [2, 3, 4, 7], [-1, -3, -3, -4]]) - >>> M - ⎡1 0 1 3 ⎤ - ⎢ ⎥ - ⎢2 3 4 7 ⎥ - ⎢ ⎥ - ⎣-1 -3 -3 -4⎦ - >>> M.rref() - ⎛⎡1 0 1 3 ⎤ ⎞ - ⎜⎢ ⎥ ⎟ - ⎜⎢0 1 2/3 1/3⎥, (0, 1)⎟ - ⎜⎢ ⎥ ⎟ - ⎝⎣0 0 0 0 ⎦ ⎠ -``` - -##### In `Julia`: - -```jldoctest matrices -julia> M = Sym[1 0 1 3; 2 3 4 7; -1 -3 -3 -4] -3×4 Matrix{Sym}: - 1 0 1 3 - 2 3 4 7 - -1 -3 -3 -4 - -julia> M.rref() -(Sym[1 0 1 3; 0 1 2/3 1/3; 0 0 0 0], (0, 1)) -``` - - - -!!! note - The first element of the tuple returned by `rref` is of type - `Matrix`. The second is of type `tuple`. - -Nullspace ---------- - -To find the nullspace of a matrix, use `nullspace`. `nullspace` returns a -`list` of column vectors that span the nullspace of the matrix. - -```python - >>> M = Matrix([[1, 2, 3, 0, 0], [4, 10, 0, 0, 1]]) - >>> M - ⎡1 2 3 0 0⎤ - ⎢ ⎥ - ⎣4 10 0 0 1⎦ - >>> M.nullspace() - ⎡⎡-15⎤ ⎡0⎤ ⎡ 1 ⎤⎤ - ⎢⎢ ⎥ ⎢ ⎥ ⎢ ⎥⎥ - ⎢⎢ 6 ⎥ ⎢0⎥ ⎢-1/2⎥⎥ - ⎢⎢ ⎥ ⎢ ⎥ ⎢ ⎥⎥ - ⎢⎢ 1 ⎥, ⎢0⎥, ⎢ 0 ⎥⎥ - ⎢⎢ ⎥ ⎢ ⎥ ⎢ ⎥⎥ - ⎢⎢ 0 ⎥ ⎢1⎥ ⎢ 0 ⎥⎥ - ⎢⎢ ⎥ ⎢ ⎥ ⎢ ⎥⎥ - ⎣⎣ 0 ⎦ ⎣0⎦ ⎣ 1 ⎦⎦ -``` - -##### In `Julia`: - -* the list is mapped to an array of vectors, otherwise this is identical: - -```jldoctest matrices -julia> M = Sym[1 2 3 0 0; 4 10 0 0 1] -2×5 Matrix{Sym}: - 1 2 3 0 0 - 4 10 0 0 1 - -julia> M.nullspace() -3-element Vector{Matrix{Sym}}: - [-15; 6; … ; 0; 0;;] - [0; 0; … ; 1; 0;;] - [1; -1/2; … ; 0; 1;;] - -``` - - - -Columnspace ------------ - -To find the columnspace of a matrix, use `columnspace`. `columnspace` returns a -`list` of column vectors that span the columnspace of the matrix. - -```python - >>> M = Matrix([[1, 1, 2], [2 ,1 , 3], [3 , 1, 4]]) - >>> M - ⎡1 1 2⎤ - ⎢ ⎥ - ⎢2 1 3⎥ - ⎢ ⎥ - ⎣3 1 4⎦ - >>> M.columnspace() - ⎡⎡1⎤ ⎡1⎤⎤ - ⎢⎢ ⎥ ⎢ ⎥⎥ - ⎢⎢2⎥, ⎢1⎥⎥ - ⎢⎢ ⎥ ⎢ ⎥⎥ - ⎣⎣3⎦ ⎣1⎦⎦ -``` - -##### In `Julia`: - -* as with `nullspace`, the return value is a vector of vectors: - -```jldoctest matrices -julia> M = Sym[1 1 2; 2 1 3; 3 1 4] -3×3 Matrix{Sym}: - 1 1 2 - 2 1 3 - 3 1 4 - -julia> M.columnspace() -2-element Vector{Matrix{Sym}}: - [1; 2; 3;;] - [1; 1; 1;;] - -``` - ----- - -Eigenvalues, Eigenvectors, and Diagonalization ----------------------------------------------- - -To find the eigenvalues of a matrix, use `eigenvals`. `eigenvals` -returns a dictionary of `eigenvalue:algebraic multiplicity` pairs (similar to the -output of :ref:`roots `). - -```python - >>> M = Matrix([[3, -2, 4, -2], [5, 3, -3, -2], [5, -2, 2, -2], [5, -2, -3, 3]]) - >>> M - ⎡3 -2 4 -2⎤ - ⎢ ⎥ - ⎢5 3 -3 -2⎥ - ⎢ ⎥ - ⎢5 -2 2 -2⎥ - ⎢ ⎥ - ⎣5 -2 -3 3 ⎦ - >>> M.eigenvals() - {-2: 1, 3: 1, 5: 2} -``` - -##### In `Julia`: - -```jldoctest matrices -julia> M = Sym[3 -2 4 -2; 5 3 -3 -2; 5 -2 2 -2; 5 -2 -3 3] -4×4 Matrix{Sym}: - 3 -2 4 -2 - 5 3 -3 -2 - 5 -2 2 -2 - 5 -2 -3 3 - -julia> M.eigenvals() -Dict{Any, Any} with 3 entries: - 3 => 1 - 5 => 2 - -2 => 1 - -``` - ----- - -This means that `M` has eigenvalues -2, 3, and 5, and that the -eigenvalues -2 and 3 have algebraic multiplicity 1 and that the eigenvalue 5 -has algebraic multiplicity 2. - -To find the eigenvectors of a matrix, use `eigenvects`. `eigenvects` -returns a list of tuples of the form `(eigenvalue:algebraic multiplicity, -[eigenvectors])`. - -```python - >>> M.eigenvects() - ⎡⎛ ⎡⎡0⎤⎤⎞ ⎛ ⎡⎡1⎤⎤⎞ ⎛ ⎡⎡1⎤ ⎡0 ⎤⎤⎞⎤ - ⎢⎜ ⎢⎢ ⎥⎥⎟ ⎜ ⎢⎢ ⎥⎥⎟ ⎜ ⎢⎢ ⎥ ⎢ ⎥⎥⎟⎥ - ⎢⎜ ⎢⎢1⎥⎥⎟ ⎜ ⎢⎢1⎥⎥⎟ ⎜ ⎢⎢1⎥ ⎢-1⎥⎥⎟⎥ - ⎢⎜-2, 1, ⎢⎢ ⎥⎥⎟, ⎜3, 1, ⎢⎢ ⎥⎥⎟, ⎜5, 2, ⎢⎢ ⎥, ⎢ ⎥⎥⎟⎥ - ⎢⎜ ⎢⎢1⎥⎥⎟ ⎜ ⎢⎢1⎥⎥⎟ ⎜ ⎢⎢1⎥ ⎢0 ⎥⎥⎟⎥ - ⎢⎜ ⎢⎢ ⎥⎥⎟ ⎜ ⎢⎢ ⎥⎥⎟ ⎜ ⎢⎢ ⎥ ⎢ ⎥⎥⎟⎥ - ⎣⎝ ⎣⎣1⎦⎦⎠ ⎝ ⎣⎣1⎦⎦⎠ ⎝ ⎣⎣0⎦ ⎣1 ⎦⎦⎠⎦ -``` - -##### In `Julia`: - -* the output is less than desirable, as there is no special `show` method - -* the `eigvals` and `eigvecs` methods present the output in the manner that `Julia`'s generic functions do: - -```jldoctest matrices -julia> M.eigenvects() -3-element Vector{Tuple{Sym, Int64, Vector{Matrix{Sym}}}}: - (-2, 1, [[0; 1; 1; 1;;]]) - (3, 1, [[1; 1; 1; 1;;]]) - (5, 2, [[1; 1; 1; 0;;], [0; -1; 0; 1;;]]) - -``` - -compare with - -```jldoctest matrices -julia> eigvecs(M) -4×4 Matrix{Sym}: - 0 1 1 0 - 1 1 1 -1 - 1 1 1 0 - 1 1 0 1 - -``` - - - ----- - -This shows us that, for example, the eigenvalue 5 also has geometric -multiplicity 2, because it has two eigenvectors. Because the algebraic and -geometric multiplicities are the same for all the eigenvalues, `M` is -diagonalizable. - -To diagonalize a matrix, use `diagonalize`. `diagonalize` returns a tuple -`(P, D)`, where `D` is diagonal and `M = PDP^{-1}`. - -```python - >>> P, D = M.diagonalize() - >>> P - ⎡0 1 1 0 ⎤ - ⎢ ⎥ - ⎢1 1 1 -1⎥ - ⎢ ⎥ - ⎢1 1 1 0 ⎥ - ⎢ ⎥ - ⎣1 1 0 1 ⎦ - >>> D - ⎡-2 0 0 0⎤ - ⎢ ⎥ - ⎢0 3 0 0⎥ - ⎢ ⎥ - ⎢0 0 5 0⎥ - ⎢ ⎥ - ⎣0 0 0 5⎦ - >>> P*D*P**-1 - ⎡3 -2 4 -2⎤ - ⎢ ⎥ - ⎢5 3 -3 -2⎥ - ⎢ ⎥ - ⎢5 -2 2 -2⎥ - ⎢ ⎥ - ⎣5 -2 -3 3 ⎦ - >>> P*D*P**-1 == M - True -``` - -##### In `Julia`: - -```jldoctest matrices -julia> P, D = M.diagonalize() -(Sym[0 1 1 0; 1 1 1 -1; 1 1 1 0; 1 1 0 1], Sym[-2 0 0 0; 0 3 0 0; 0 0 5 0; 0 0 0 5]) - -julia> P -4×4 Matrix{Sym}: - 0 1 1 0 - 1 1 1 -1 - 1 1 1 0 - 1 1 0 1 - -julia> D -4×4 Matrix{Sym}: - -2 0 0 0 - 0 3 0 0 - 0 0 5 0 - 0 0 0 5 - -julia> P*D*P^-1 -4×4 Matrix{Sym}: - 3 -2 4 -2 - 5 3 -3 -2 - 5 -2 2 -2 - 5 -2 -3 3 - -julia> P*D*P^-1 == M -true - -``` - - - -!!! note "Quick Tip" - As `lambda` is a reserved keyword in Python, so to create a Symbol called λ, while using the same names for SymPy Symbols and Python variables, use `lamda` (without the `b`). It will still pretty print as λ. - -Note that since `eigenvects` also includes the eigenvalues, you should use -it instead of `eigenvals` if you also want the eigenvectors. However, as -computing the eigenvectors may often be costly, `eigenvals` should be -preferred if you only wish to find the eigenvalues. - -If all you want is the characteristic polynomial, use `charpoly`. This is -more efficient than `eigenvals`, because sometimes symbolic roots can be -expensive to calculate. - -```python - >>> lamda = symbols('lamda') - >>> p = M.charpoly(lamda) - >>> factor(p) - 2 - (λ - 5) ⋅(λ - 3)⋅(λ + 2) -``` - -##### In `Julia`: - -* note missing `b` is not needed with `Julia`: - -```julia -julia> @syms lambda -(lambda,) - -julia> p = M.charpoly(lambda) -PurePoly(lambda**4 - 11*lambda**3 + 29*lambda**2 + 35*lambda - 150, lambda, domain='ZZ') - -julia> factor(p) |> string -"PurePoly(lambda^4 - 11*lambda^3 + 29*lambda^2 + 35*lambda - 150, lambda, domain='ZZ')" -``` - - -As an aside, we can get prettier output by adjusting how `lambda` should print, as follows: - -```julia -julia> @syms lambda=>"λ" -(λ,) - -julia> p = M.charpoly(lambda) -PurePoly(λ**4 - 11*λ**3 + 29*λ**2 + 35*λ - 150, λ, domain='ZZ') -``` - ----- - -!!! note "TODO" - - Add an example for `jordan_form`, once it is fully implemented. - -## Possible Issues - - -Zero Testing ------------- - -If your matrix operations are failing or returning wrong answers, -the common reasons would likely be from zero testing. -If there is an expression not properly zero-tested, -it can possibly bring issues in finding pivots for gaussian elimination, -or deciding whether the matrix is inversible, -or any high level functions which relies on the prior procedures. - -Currently, the SymPy's default method of zero testing `_iszero` is only -guaranteed to be accurate in some limited domain of numerics and symbols, -and any complicated expressions beyond its decidability are treated as `None`, -which behaves similarly to logical `False`. - -The list of methods using zero testing procedures are as followings. - -`echelon_form` , `is_echelon` , `rank` , `rref` , `nullspace` , -`eigenvects` , `inverse_ADJ` , `inverse_GE` , `inverse_LU` , -`LUdecomposition` , `LUdecomposition_Simple` , `LUsolve` - -They have property `iszerofunc` opened up for user to specify zero testing -method, which can accept any function with single input and boolean output, -while being defaulted with `_iszero`. - -Here is an example of solving an issue caused by undertested zero. -[#zerotestexampleidea-fn]_ [#zerotestexamplediscovery-fn]_ - -```python - >>> from sympy import * - >>> q = Symbol("q", positive = True) - >>> m = Matrix([ - ... [-2*cosh(q/3), exp(-q), 1], - ... [ exp(q), -2*cosh(q/3), 1], - ... [ 1, 1, -2*cosh(q/3)]]) - >>> m.nullspace() - [] -``` - -##### In `Julia`: - -```jldoctest matrices -julia> q = sympy.Symbol("q", positive = true) -q - -julia> m = Sym[-2*cosh(q/3) exp(-q) 1; exp(q) -2*cosh(q/3) 1; 1 1 -2*cosh(q/3)] -3×3 Matrix{Sym}: - -2*cosh(q/3) exp(-q) 1 - exp(q) -2*cosh(q/3) 1 - 1 1 -2*cosh(q/3) - -julia> m.nullspace() -1-element Vector{Matrix{Sym}}: - [-(-2*exp(q)*cosh(q/3) - 4*cosh(q/3)^2 - 1 - 2*exp(-q)*cosh(q/3))/(4*exp(q)*cosh(q/3)^2 + 4*cosh(q/3) + exp(-q)); -(1 - 4*cosh(q/3)^2)/(2*cosh(q/3) + exp(-q)); 1;;] - -``` - ----- - -You can trace down which expression is being underevaluated, -by injecting a custom zero test with warnings enabled. - -```python - >>> import warnings - >>> - >>> def my_iszero(x): - ... try: - ... result = x.is_zero - ... except AttributeError: - ... result = None - ... - ... # Warnings if evaluated into None - ... if result == None: - ... warnings.warn("Zero testing of {} evaluated into {}".format(x, result)) - ... return result - ... - >>> m.nullspace(iszerofunc=my_iszero) # doctest: +SKIP - __main__:9: UserWarning: Zero testing of 4*cosh(q/3)**2 - 1 evaluated into None - __main__:9: UserWarning: Zero testing of (-exp(q) - 2*cosh(q/3))*(-2*cosh(q/3) - exp(-q)) - (4*cosh(q/3)**2 - 1)**2 evaluated into None - __main__:9: UserWarning: Zero testing of 2*exp(q)*cosh(q/3) - 16*cosh(q/3)**4 + 12*cosh(q/3)**2 + 2*exp(-q)*cosh(q/3) evaluated into None - __main__:9: UserWarning: Zero testing of -(4*cosh(q/3)**2 - 1)*exp(-q) - 2*cosh(q/3) - exp(-q) evaluated into None - [] -``` - -##### In `Julia`: - -Is this available?? - ----- - - -In this case, -`(-exp(q) - 2*cosh(q/3))*(-2*cosh(q/3) - exp(-q)) - (4*cosh(q/3)**2 - 1)**2` -should yield zero, but the zero testing had failed to catch. -possibly meaning that a stronger zero test should be introduced. -For this specific example, rewriting to exponentials and applying simplify would -make zero test stronger for hyperbolics, -while being harmless to other polynomials or transcendental functions. - -```python - >>> def my_iszero(x): - ... try: - ... result = x.rewrite(exp).simplify().is_zero - ... except AttributeError: - ... result = None - ... - ... # Warnings if evaluated into None - ... if result == None: - ... warnings.warn("Zero testing of {} evaluated into {}".format(x, result)) - ... return result - ... - >>> m.nullspace(iszerofunc=my_iszero) # doctest: +SKIP - __main__:9: UserWarning: Zero testing of -2*cosh(q/3) - exp(-q) evaluated into None - ⎡⎡ ⎛ q ⎛q⎞⎞ -q 2⎛q⎞ ⎤⎤ - ⎢⎢- ⎜- ℯ - 2⋅cosh⎜─⎟⎟⋅ℯ + 4⋅cosh ⎜─⎟ - 1⎥⎥ - ⎢⎢ ⎝ ⎝3⎠⎠ ⎝3⎠ ⎥⎥ - ⎢⎢─────────────────────────────────────────⎥⎥ - ⎢⎢ ⎛ 2⎛q⎞ ⎞ ⎛q⎞ ⎥⎥ - ⎢⎢ 2⋅⎜4⋅cosh ⎜─⎟ - 1⎟⋅cosh⎜─⎟ ⎥⎥ - ⎢⎢ ⎝ ⎝3⎠ ⎠ ⎝3⎠ ⎥⎥ - ⎢⎢ ⎥⎥ - ⎢⎢ ⎛ q ⎛q⎞⎞ ⎥⎥ - ⎢⎢ -⎜- ℯ - 2⋅cosh⎜─⎟⎟ ⎥⎥ - ⎢⎢ ⎝ ⎝3⎠⎠ ⎥⎥ - ⎢⎢ ──────────────────── ⎥⎥ - ⎢⎢ 2⎛q⎞ ⎥⎥ - ⎢⎢ 4⋅cosh ⎜─⎟ - 1 ⎥⎥ - ⎢⎢ ⎝3⎠ ⎥⎥ - ⎢⎢ ⎥⎥ - ⎣⎣ 1 ⎦⎦ -``` - -##### In `Julia`: - -Is this available? - - ----- - - -You can clearly see `nullspace` returning proper result, after injecting an -alternative zero test. - -Note that this approach is only valid for some limited cases of matrices -containing only numerics, hyperbolics, and exponentials. -For other matrices, you should use different method opted for their domains. - -Possible suggestions would be either taking advantage of rewriting and -simplifying, with tradeoff of speed [#zerotestsimplifysolution-fn]_ , -or using random numeric testing, with tradeoff of accuracy -[#zerotestnumerictestsolution-fn]_ . - -If you wonder why there is no generic algorithm for zero testing that can work -with any symbolic entities, -it's because of the constant problem stating that zero testing is undecidable -[#constantproblemwikilink-fn]_ , -and not only the SymPy, but also other computer algebra systems -[#mathematicazero-fn]_ [#matlabzero-fn]_ -would face the same fundamental issue. - -However, discovery of any zero test failings can provide some good examples to -improve SymPy, -so if you have encountered one, you can report the issue to -SymPy issue tracker [#sympyissues-fn]_ to get detailed help from the community. - -!!! note "Footnotes" - * [#zerotestexampleidea-fn] Inspired by https://gitter.im/sympy/sympy?at=5b7c3e8ee5b40332abdb206c - * [#zerotestexamplediscovery-fn] Discovered from https://github.com/sympy/sympy/issues/15141 - * [#zerotestsimplifysolution-fn] Suggested from https://github.com/sympy/sympy/issues/10120 - * [#zerotestnumerictestsolution-fn] Suggested from https://github.com/sympy/sympy/issues/10279 - * [#constantproblemwikilink-fn] https://en.wikipedia.org/wiki/Constant_problem - * [#mathematicazero-fn] How mathematica tests zero https://reference.wolfram.com/language/ref/PossibleZeroQ.html - * [#matlabzero-fn] How matlab tests zero https://www.mathworks.com/help/symbolic/mupad_ref/iszero.html - * [#sympyissues-fn] https://github.com/sympy/sympy/issues diff --git a/docs/src/Tutorial/simplification.md b/docs/src/Tutorial/simplification.md deleted file mode 100644 index e3d12b37..00000000 --- a/docs/src/Tutorial/simplification.md +++ /dev/null @@ -1,1787 +0,0 @@ -# Simplification - - - -[From](https://docs.sympy.org/latest/_sources/tutorial/simplification.rst.txt) - - - ----- - - -To make this document easier to read, we are going to enable pretty printing. - -```python - >>> from sympy import * - >>> x, y, z = symbols('x y z') - >>> init_printing(use_unicode=True) -``` - -##### In `Julia`: - -```@setup simplification -using SymPy -sympy.init_printing(use_unicode=True) -``` - - -* " not ' are used for strings: -* pretty printing si enable by default -* we will deliberatately input extra functions from the `sympy` module such as `powsimp`, ... - -```jldoctest simplification -julia> using SymPy - -julia> import_from(sympy) - -julia> @syms x, y, z -(x, y, z) -``` - ----- - -## `simplify` - -Now let's jump in and do some interesting mathematics. One of the most useful -features of a symbolic manipulation system is the ability to simplify -mathematical expressions. SymPy has dozens of functions to perform various -kinds of simplification. There is also one general function called -`simplify()` that attempts to apply all of these functions in an intelligent -way to arrive at the simplest form of an expression. Here are some examples - -```python - >>> simplify(sin(x)**2 + cos(x)**2) - 1 - >>> simplify((x**3 + x**2 - x - 1)/(x**2 + 2*x + 1)) - x - 1 - >>> simplify(gamma(x)/gamma(x - 2)) - (x - 2)⋅(x - 1) -``` - -##### In `Julia`: - -* we need to load in `SpecialFunctions` to have access to `gamma`: - -```jldoctest simplification -julia> simplify(sin(x)^2 + cos(x)^2) -1 - -julia> simplify((x^3 + x^2 - x - 1)/(x^2 + 2*x + 1)) -x - 1 - -julia> using SpecialFunctions - - -julia> simplify(gamma(x)/gamma(x - 2)) -(x - 2)⋅(x - 1) - -``` - ----- - -Here, `gamma(x)` is $\Gamma(x)$, the `gamma function -`_. We see that `simplify()` -is capable of handling a large class of expressions. - -But `simplify()` has a pitfall. It just applies all the major -simplification operations in SymPy, and uses heuristics to determine the -simplest result. But "simplest" is not a well-defined term. For example, say -we wanted to "simplify" `x^2 + 2x + 1` into `(x + 1)^2`: - -```python - >>> simplify(x**2 + 2*x + 1) - 2 - x + 2⋅x + 1 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> simplify(x^2 + 2*x + 1) |> string -"x^2 + 2*x + 1" -``` - ----- - -We did not get what we want. There is a function to perform this -simplification, called `factor()`, which will be discussed below. - -Another pitfall to `simplify()` is that it can be unnecessarily slow, since -it tries many kinds of simplifications before picking the best one. If you -already know exactly what kind of simplification you are after, it is better -to apply the specific simplification function(s) that apply those -simplifications. - -Applying specific simplification functions instead of `simplify()` also has -the advantage that specific functions have certain guarantees about the form -of their output. These will be discussed with each function below. For -example, `factor()`, when called on a polynomial with rational coefficients, -is guaranteed to factor the polynomial into irreducible factors. -`simplify()` has no guarantees. It is entirely heuristical, and, as we saw -above, it may even miss a possible type of simplification that SymPy is -capable of doing. - -`simplify()` is best when used interactively, when you just want to whittle -down an expression to a simpler form. You may then choose to apply specific -functions once you see what `simplify()` returns, to get a more precise -result. It is also useful when you have no idea what form an expression will -take, and you need a catchall function to simplify it. - -## Polynomial/Rational Function Simplification - -### expand - - -`expand()` is one of the most common simplification functions in SymPy. -Although it has a lot of scopes, for now, we will consider its function in -expanding polynomial expressions. For example: - -```python - >>> expand((x + 1)**2) - 2 - x + 2⋅x + 1 - >>> expand((x + 2)*(x - 3)) - 2 - x - x - 6 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand((x + 1)^2) |> string -"x^2 + 2*x + 1" - -julia> expand((x + 2)*(x - 3)) |> string -"x^2 - x - 6" -``` - ----- - -Given a polynomial, `expand()` will put it into a canonical form of a sum of -monomials. - -`expand()` may not sound like a simplification function. After all, by its -very name, it makes expressions bigger, not smaller. Usually this is the -case, but often an expression will become smaller upon calling `expand()` on -it due to cancellation. - -```python - >>> expand((x + 1)*(x - 2) - (x - 1)*x) - -2 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand((x + 1)*(x - 2) - (x - 1)*x) --2 -``` - ----- - -### factor - - -`factor()` takes a polynomial and factors it into irreducible factors over -the rational numbers. For example: - -```python - >>> factor(x**3 - x**2 + x - 1) - ⎛ 2 ⎞ - (x - 1)⋅⎝x + 1⎠ - >>> factor(x**2*z + 4*x*y*z + 4*y**2*z) - 2 - z⋅(x + 2⋅y) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> factor(x^3 - x^2 + x - 1) |> string -"(x - 1)*(x^2 + 1)" -julia> factor(x^2*z + 4*x*y*z + 4*y^2*z) |> string -"z*(x + 2*y)^2" -``` - ----- - -For polynomials, `factor()` is the opposite of `expand()`. `factor()` -uses a complete multivariate factorization algorithm over the rational -numbers, which means that each of the factors returned by `factor()` is -guaranteed to be irreducible. - -If you are interested in the factors themselves, `factor_list` returns a -more structured output. - -```python - >>> factor_list(x**2*z + 4*x*y*z + 4*y**2*z) - (1, [(z, 1), (x + 2⋅y, 2)]) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> factor_list(x^2*z + 4*x*y*z + 4*y^2*z) -(1, Tuple{Sym, Int64}[(z, 1), (x + 2*y, 2)]) -``` - ----- - -Note that the input to `factor` and `expand` need not be polynomials in -the strict sense. They will intelligently factor or expand any kind of -expression (though note that the factors may not be irreducible if the input -is no longer a polynomial over the rationals). - -```python - >>> expand((cos(x) + sin(x))**2) - 2 2 - sin (x) + 2⋅sin(x)⋅cos(x) + cos (x) - >>> factor(cos(x)**2 + 2*cos(x)*sin(x) + sin(x)**2) - 2 - (sin(x) + cos(x)) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand((cos(x) + sin(x))^2) |> string -"sin(x)^2 + 2*sin(x)*cos(x) + cos(x)^2" - -julia> factor(cos(x)^2 + 2*cos(x)*sin(x) + sin(x)^2) |> string -"(sin(x) + cos(x))^2" -``` - ----- - -### collect - - -`collect()` collects common powers of a term in an expression. For example - -```python - >>> expr = x*y + x - 3 + 2*x**2 - z*x**2 + x**3 - >>> expr - 3 2 2 - x - x ⋅z + 2⋅x + x⋅y + x - 3 - >>> collected_expr = collect(expr, x) - >>> collected_expr - 3 2 - x + x ⋅(-z + 2) + x⋅(y + 1) - 3 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expr = x*y + x - 3 + 2*x^2 - z*x^2 + x^3 - 3 2 2 -x - x ⋅z + 2⋅x + x⋅y + x - 3 - -julia> collected_expr = collect(expr, x) - 3 2 -x + x ⋅(2 - z) + x⋅(y + 1) - 3 -``` - ----- - -`collect()` is particularly useful in conjunction with the `.coeff()` -method. `expr.coeff(x, n)` gives the coefficient of `x**n` in `expr`: - -```python - >>> collected_expr.coeff(x, 2) - -z + 2 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> collected_expr.coeff(x, 2) -2 - z -``` - ----- - -!!! note "TODO" - - Discuss coeff method in more detail in some other section (maybe basic expression manipulation tools) - - -### cancel - -`cancel()` will take any rational function and put it into the standard -canonical form, $\frac{p}{q}$, where $p$ and $q$ are expanded polynomials with -no common factors, and the leading coefficients of $p$ and $q$ do not have -denominators (i.e., are integers). - - -```python - >>> cancel((x**2 + 2*x + 1)/(x**2 + x)) - x + 1 - ───── - x - - >>> expr = 1/x + (3*x/2 - 2)/(x - 4) - >>> expr - 3⋅x - ─── - 2 - 2 1 - ─────── + ─ - x - 4 x - >>> cancel(expr) - 2 - 3⋅x - 2⋅x - 8 - ────────────── - 2 - 2⋅x - 8⋅x - - >>> expr = (x*y**2 - 2*x*y*z + x*z**2 + y**2 - 2*y*z + z**2)/(x**2 - 1) - >>> expr - 2 2 2 2 - x⋅y - 2⋅x⋅y⋅z + x⋅z + y - 2⋅y⋅z + z - ─────────────────────────────────────── - 2 - x - 1 - >>> cancel(expr) - 2 2 - y - 2⋅y⋅z + z - ─────────────── - x - 1 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> cancel((x^2 + 2*x + 1)/(x^2 + x)) -x + 1 -───── - x - -julia> expr = 1/x + (3*x/2 - 2)/(x - 4) -3⋅x -─── - 2 - 2 1 -─────── + ─ - x - 4 x - -julia> cancel(expr) |> string -"(3*x^2 - 2*x - 8)/(2*x^2 - 8*x)" - -julia> expr = (x*y^2 - 2*x*y*z + x*z^2 + y^2 - 2*y*z + z^2)/(x^2 - 1) - 2 2 2 2 -x⋅y - 2⋅x⋅y⋅z + x⋅z + y - 2⋅y⋅z + z -─────────────────────────────────────── - 2 - x - 1 - -julia> cancel(expr) - 2 2 -y - 2⋅y⋅z + z -─────────────── - x - 1 -``` - ----- - -Note that since `factor()` will completely factorize both the numerator and -the denominator of an expression, it can also be used to do the same thing: - -```python - >>> factor(expr) - 2 - (y - z) - ──────── - x - 1 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> factor(expr) |> string -"(y - z)^2/(x - 1)" -``` - ----- - -However, if you are only interested in making sure that the expression is in -canceled form, `cancel()` is more efficient than `factor()`. - -### apart - - -`apart()` performs a `partial fraction decomposition -`_ on a rational -function. - -```python - >>> expr = (4*x**3 + 21*x**2 + 10*x + 12)/(x**4 + 5*x**3 + 5*x**2 + 4*x) - >>> expr - 3 2 - 4⋅x + 21⋅x + 10⋅x + 12 - ──────────────────────── - 4 3 2 - x + 5⋅x + 5⋅x + 4⋅x - >>> apart(expr) - 2⋅x - 1 1 3 - ────────── - ───── + ─ - 2 x + 4 x - x + x + 1 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expr = (4*x^3 + 21*x^2 + 10*x + 12)/(x^4 + 5*x^3 + 5*x^2 + 4*x); string(expr) -"(4*x^3 + 21*x^2 + 10*x + 12)/(x^4 + 5*x^3 + 5*x^2 + 4*x)" - -julia> apart(expr) - 2⋅x - 1 1 3 -────────── - ───── + ─ - 2 x + 4 x -x + x + 1 - -``` - ----- - -## Trigonometric Simplification - - -!!! note - - SymPy follows Python's naming conventions for inverse trigonometric - functions, which is to append an `a` to the front of the function's - name. For example, the inverse cosine, or arc cosine, is called `acos()`. - - -```python - >>> acos(x) - acos(x) - >>> cos(acos(x)) - x - >>> asin(1) - π - ─ - 2 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> acos(x) -acos(x) - -julia> cos(acos(x)) -x - -julia> asin(1) -1.5707963267948966 - -julia> sympy.asin(1) -π -─ -2 -``` - ----- - - -!!! note "TODO" - - Can we actually do anything with inverse trig functions, simplification wise? - - -### trigsimp - - -To simplify expressions using trigonometric identities, use `trigsimp()`. - -```python - >>> trigsimp(sin(x)^2 + cos(x)**2) - 1 - >>> trigsimp(sin(x)**4 - 2*cos(x)**2*sin(x)**2 + cos(x)**4) - cos(4⋅x) 1 - ──────── + ─ - 2 2 - >>> trigsimp(sin(x)*tan(x)/sec(x)) - 2 - sin (x) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> trigsimp(sin(x)^2 + cos(x)^2) -1 - -julia> trigsimp(sin(x)^4 - 2*cos(x)^2*sin(x)^2 + cos(x)^4) -cos(4⋅x) 1 -──────── + ─ - 2 2 - -julia> trigsimp(sin(x)*tan(x)/sec(x)) |> string -"sin(x)^2" -``` - ----- - -`trigsimp()` also works with hyperbolic trig functions. - -```python - >>> trigsimp(cosh(x)**2 + sinh(x)**2) - cosh(2⋅x) - >>> trigsimp(sinh(x)/tanh(x)) - cosh(x) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> trigsimp(cosh(x)^2 + sinh(x)^2) -cosh(2⋅x) - -julia> trigsimp(sinh(x)/tanh(x)) -cosh(x) -``` - ----- - -Much like `simplify()`, `trigsimp()` applies various trigonometric identities to -the input expression, and then uses a heuristic to return the "best" one. - -### expand_trig - - -To expand trigonometric functions, that is, apply the sum or double angle -identities, use `expand_trig()`. - -```python - >>> expand_trig(sin(x + y)) - sin(x)⋅cos(y) + sin(y)⋅cos(x) - >>> expand_trig(tan(2*x)) - 2⋅tan(x) - ───────────── - 2 - - tan (x) + 1 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_trig(sin(x + y)) -sin(x)⋅cos(y) + sin(y)⋅cos(x) - -julia> expand_trig(tan(2*x)) - 2⋅tan(x) -─────────── - 2 -1 - tan (x) -``` - ----- - -Because `expand_trig()` tends to make trigonometric expressions larger, and -`trigsimp()` tends to make them smaller, these identities can be applied in -reverse using `trigsimp()` - -```python - >>> trigsimp(sin(x)*cos(y) + sin(y)*cos(x)) - sin(x + y) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> trigsimp(sin(x)*cos(y) + sin(y)*cos(x)) -sin(x + y) -``` - ----- - - -!!! note "TODO" - - It would be much better to teach individual trig rewriting functions - here, but they don't exist yet. See - https://github.com/sympy/sympy/issues/3456. - - -## Powers - -Before we introduce the power simplification functions, a mathematical -discussion on the identities held by powers is in order. There are three -kinds of identities satisfied by exponents - -1. `x^ax^b = x^{a + b}` -2. `x^ay^a = (xy)^a` -3. `(x^a)^b = x^{ab}` - -Identity 1 is always true. - -Identity 2 is not always true. For example, if $x = y = -1$ and $a = -\frac{1}{2}$, then $x^ay^a = \sqrt{-1}\sqrt{-1} = i\cdot i = -1$, whereas -$(xy)^a = \sqrt{-1\cdot-1} = \sqrt{1} = 1$. However, identity 2 is true at -least if $x$ and $y$ are nonnegative and $a$ is real (it may also be true -under other conditions as well). A common consequence of the failure of -identity 2 is that $\sqrt{x}\sqrt{y} \neq \sqrt{xy}$. - -Identity 3 is not always true. For example, if $x = -1$, $a = 2$, and $b = -\frac{1}{2}$, then $(x^a)^b = {\left ((-1)^2\right )}^{1/2} = \sqrt{1} = 1$ -and $x^{ab} = (-1)^{2\cdot1/2} = (-1)^1 = -1$. However, identity 3 is true -when $b$ is an integer (again, it may also hold in other cases as well). Two -common consequences of the failure of identity 3 are that $\sqrt{x^2}\neq x$ -and that $\sqrt{\frac{1}{x}} \neq \frac{1}{\sqrt{x}}$. - -To summarize - - - -1. This: $x^ax^b = x^{a + b}$ is always true - -2. This: $x^ay^a = (xy)^a$ is true when $x, y \geq 0$ and $a \in \mathbb{R}$; but note $(-1)^{1/2}(-1)^{1/2} \neq (-1\cdot-1)^{1/2}$ and $\sqrt{x}\sqrt{y} \neq \sqrt{xy}$ in general - -3. This: $(x^a)^b = x^{ab}$ when $b \in \mathbb{Z}$; but note ${\left((-1)^2\right )}^{1/2} \neq (-1)^{2\cdot1/2}$ and $\sqrt{x^2}\neq x$ and $\sqrt{\frac{1}{x}}\neq\frac{1}{\sqrt{x}}$ in general - - - - - - -This is important to remember, because by default, SymPy will not perform -simplifications if they are not true in general. - -In order to make SymPy perform simplifications involving identities that are -only true under certain assumptions, we need to put assumptions on our -Symbols. We will undertake a full discussion of the assumptions system later, -but for now, all we need to know are the following. - -- By default, SymPy Symbols are assumed to be complex (elements of - $\mathbb{C}$). That is, a simplification will not be applied to an - expression with a given Symbol unless it holds for all complex numbers. - -- Symbols can be given different assumptions by passing the assumption to - `symbols()`. For the rest of this section, we will be assuming that `x` - and `y` are positive, and that `a` and `b` are real. We will leave - `z`, `t`, and `c` as arbitrary complex Symbols to demonstrate what - happens in that case. - -```python - >>> x, y = symbols('x y', positive=True) - >>> a, b = symbols('a b', real=True) - >>> z, t, c = symbols('z t c') -``` - -##### In `Julia`: - -The same notation can be used: - -```jldoctest simplification -julia> x, y = symbols("x y", positive=true) -(x, y) - -julia> a, b = symbols("a b", real=true) -(a, b) - -julia> z, t, c = symbols("z t c") -(z, t, c) -``` - -However, the recommended way is to use `@syms` for symbol construction with assumptions: - -```jldoctest simplification -julia> @syms x::positive, y::positive -(x, y) - -julia> @syms a::real, b::real -(a, b) - -julia> @syms z, t, c -(z, t, c) -``` - - ----- - -!!! note "TODO:" - - Rewrite this using the new assumptions - - -!!! note - - In SymPy, `sqrt(x)` is just a shortcut to `x**Rational(1, 2)`. They - are exactly the same object. - - -```python - >>> sqrt(x) == x**Rational(1, 2) - True -``` - -##### In `Julia`: - -* we can construction rational numbers with `//` - -```jldoctest simplification -julia> sqrt(x) == x^(1//2) -true -``` - ----- - -powsimp -------- - -`powsimp()` applies identities 1 and 2 from above, from left to right. - - -```python - >>> powsimp(x**a*x**b) - a + b - x - >>> powsimp(x**a*y**a) - a - (x⋅y) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powsimp(x^a*x^b) - a + b -x - -julia> powsimp(x^a*y^a) |> string -"(x*y)^a" -``` - ----- - -Notice that `powsimp()` refuses to do the simplification if it is not valid. - -```python - >>> powsimp(t**c*z**c) - c c - t ⋅z -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powsimp(t^c*z^c) - c c -t ⋅z -``` - ----- - -If you know that you want to apply this simplification, but you don't want to -mess with assumptions, you can pass the `force=True` flag. This will force -the simplification to take place, regardless of assumptions. - -```python - >>> powsimp(t**c*z**c, force=True) - c - (t⋅z) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powsimp(t^c*z^c, force=true) |> string -"(t*z)^c" -``` - ----- - -Note that in some instances, in particular, when the exponents are integers or -rational numbers, and identity 2 holds, it will be applied automatically. - -```python - >>> (z*t)**2 - 2 2 - t ⋅z - >>> sqrt(x*y) - √x⋅√y -``` - -##### In `Julia`: - -```jldoctest simplification -julia> (z*t)^2 - 2 2 -t ⋅z - -julia> sqrt(x*y) -√x⋅√y -``` - ----- - -This means that it will be impossible to undo this identity with -`powsimp()`, because even if `powsimp()` were to put the bases together, -they would be automatically split apart again. - -```python - >>> powsimp(z**2*t**2) - 2 2 - t ⋅z - >>> powsimp(sqrt(x)*sqrt(y)) - √x⋅√y -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powsimp(z^2*t^2) - 2 2 -t ⋅z - -julia> powsimp(sqrt(x)*sqrt(y)) -√x⋅√y -``` - ----- - -### `expand_power_exp` / `expand_power_base` - - -`expand_power_exp()` and `expand_power_base()` apply identities 1 and 2 -from right to left, respectively. - -```python - >>> expand_power_exp(x**(a + b)) - a b - x ⋅x - - >>> expand_power_base((x*y)**a) - a a - x ⋅y -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_power_exp(x^(a + b)) - a b -x ⋅x - -julia> expand_power_base((x*y)^a) - a a -x ⋅y -``` - ----- - -As with `powsimp()`, identity 2 is not applied if it is not valid. - -```python - >>> expand_power_base((z*t)**c) - c - (t⋅z) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_power_base((z*t)^c) |> string -"(t*z)^c" -``` - ----- - -And as with `powsimp()`, you can force the expansion to happen without -fiddling with assumptions by using `force=True`. - -```python - >>> expand_power_base((z*t)**c, force=True) - c c - t ⋅z -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_power_base((z*t)^c, force=true) - c c -t ⋅z -``` - ----- - -As with identity 2, identity 1 is applied automatically if the power is a -number, and hence cannot be undone with `expand_power_exp()`. - -```python - >>> x**2*x**3 - 5 - x - >>> expand_power_exp(x**5) - 5 - x -``` - -##### In `Julia`: - -```jldoctest simplification -julia> x^2*x^3 |> string -"x^5" - -julia> expand_power_exp(x^5) |> string -"x^5" -``` - ----- - -### powdenest - - -`powdenest()` applies identity 3, from left to right. - -```python - >>> powdenest((x**a)**b) - a⋅b - x -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powdenest((x^a)^b) - a⋅b -x -``` - ----- - -As before, the identity is not applied if it is not true under the given -assumptions. - -```python - >>> powdenest((z**a)**b) - b - ⎛ a⎞ - ⎝z ⎠ -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powdenest((z^a)^b) |> string -"(z^a)^b" -``` - ----- - -And as before, this can be manually overridden with `force=True`. - -```python - >>> powdenest((z**a)**b, force=True) - a⋅b - z -``` - -##### In `Julia`: - -```jldoctest simplification -julia> powdenest((z^a)^b, force=true) - a⋅b -z -``` - ----- - -## Exponentials and logarithms - - -!!! note - - In SymPy, as in Python and most programming languages, `log` is the - natural logarithm, also known as `ln`. SymPy automatically provides an - alias `ln = log` in case you forget this. - - -```python - >>> ln(x) - log(x) -``` - -##### In `Julia`: - -* `ln` is exported - -```jldoctest simplification -julia> ln(x) -log(x) -``` - ----- - -Logarithms have similar issues as powers. There are two main identities - -1. $\log{(xy)} = \log{(x)} + \log{(y)}$ -2. $\log{(x^n)} = n\log{(x)}$ - -Neither identity is true for arbitrary complex $x$ and $y$, due to the branch -cut in the complex plane for the complex logarithm. However, sufficient -conditions for the identities to hold are if $x$ and $y$ are positive and $n$ -is real. - -```python - >>> x, y = symbols('x y', positive=True) - >>> n = symbols('n', real=True) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> @syms x::positive, y::positive -(x, y) - -julia> @syms n::real -(n,) -``` - ----- - -As before, `z` and `t` will be Symbols with no additional assumptions. - -Note that the identity $\log{\left (\frac{x}{y}\right )} = \log(x) - \log(y)$ -is a special case of identities 1 and 2 by $\log{\left (\frac{x}{y}\right )} -=$ $\log{\left (x\cdot\frac{1}{y}\right )} =$ $\log(x) + \log{\left( -y^{-1}\right )} =$ $\log(x) - \log(y)$, and thus it also holds if `x` and `y` -are positive, but may not hold in general. - -We also see that $\log{\left( e^x \right)} = x$ comes from $\log{\left ( e^x -\right)} = x\log(e) = x$, and thus holds when $x$ is real (and it can be -verified that it does not hold in general for arbitrary complex $x$, for -example, $\log{\left (e^{x + 2\pi i}\right)} = \log{\left (e^x\right )} = x -\neq x + 2\pi i$). - -expand_log ----------- - -To apply identities 1 and 2 from left to right, use `expand_log()`. As -always, the identities will not be applied unless they are valid. - -```python - >>> expand_log(log(x*y)) - log(x) + log(y) - >>> expand_log(log(x/y)) - log(x) - log(y) - >>> expand_log(log(x**2)) - 2⋅log(x) - >>> expand_log(log(x**n)) - n⋅log(x) - >>> expand_log(log(z*t)) - log(t⋅z) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_log(log(x*y)) -log(x) + log(y) - -julia> expand_log(log(x/y)) -log(x) - log(y) - -julia> expand_log(log(x^2)) -2⋅log(x) - -julia> expand_log(log(x^n)) -n⋅log(x) - -julia> expand_log(log(z*t)) -log(t⋅z) - -``` - ----- - -As with `powsimp()` and `powdenest()`, `expand_log()` has a `force` -option that can be used to ignore assumptions. - -```python - >>> expand_log(log(z**2)) - ⎛ 2⎞ - log⎝z ⎠ - >>> expand_log(log(z**2), force=True) - 2⋅log(z) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_log(log(z^2)) - ⎛ 2⎞ -log⎝z ⎠ -``` - -```jldoctest simplification -julia> expand_log(log(z^2), force=true) -2⋅log(z) -``` - ----- - -logcombine ----------- - -To apply identities 1 and 2 from right to left, use `logcombine()`. - -```python - >>> logcombine(log(x) + log(y)) - log(x⋅y) - >>> logcombine(n*log(x)) - ⎛ n⎞ - log⎝x ⎠ - >>> logcombine(n*log(z)) - n⋅log(z) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> logcombine(log(x) + log(y)) -log(x⋅y) - -julia> logcombine(n*log(x)) - ⎛ n⎞ -log⎝x ⎠ - -julia> logcombine(n*log(z)) -n⋅log(z) -``` - ----- - -`logcombine()` also has a `force` option that can be used to ignore -assumptions. - -```python - >>> logcombine(n*log(z), force=True) - ⎛ n⎞ - log⎝z ⎠ -``` - -##### In `Julia`: - -```jldoctest simplification -julia> logcombine(n*log(z), force=true) - ⎛ n⎞ -log⎝z ⎠ -``` - ----- - -## Special Functions - - -SymPy implements dozens of special functions, ranging from functions in -combinatorics to mathematical physics. - -An extensive list of the special functions included with SymPy and their -documentation is at the :ref:`Functions Module ` page. - -For the purposes of this tutorial, let's introduce a few special functions in -SymPy. - -Let's define `x`, `y`, and `z` as regular, complex Symbols, removing any -assumptions we put on them in the previous section. We will also define `k`, -`m`, and `n`. - -```python - >>> x, y, z = symbols('x y z') - >>> k, m, n = symbols('k m n') -``` - -##### In `Julia`: - -```jldoctest simplification -julia> @syms x, y, z -(x, y, z) - -julia> @syms k, m, n -(k, m, n) -``` - ----- - -The `factorial `_ function is -`factorial`. `factorial(n)` represents $n!= 1\cdot2\cdots(n - 1)\cdot -n$. `n!` represents the number of permutations of `n` distinct items. - -```python - >>> factorial(n) - n! -``` - -##### In `Julia`: - -```jldoctest simplification -julia> factorial(n) -n! -``` - ----- - -The `binomial coefficient -`_ function is -`binomial`. `binomial(n, k)` represents $\binom{n}{k}$, the number of -ways to choose `k` items from a set of `n` distinct items. It is also often -written as `nCk`, and is pronounced "`n` choose `k`". - -```python - >>> binomial(n, k) - ⎛n⎞ - ⎜ ⎟ - ⎝k⎠ -``` - -##### In `Julia`: - -```jldoctest simplification -julia> binomial(n, k) -⎛n⎞ -⎜ ⎟ -⎝k⎠ -``` - ----- - -The factorial function is closely related to the [gamma function](http://en.wikipedia.org/wiki/Gamma_function). -$\Gamma(z) = \int_0^\infty t^{z - 1}e^{-t}dt$ is implemented in `gamma(z)`, which for positive integer has: -`z` is the same as `(z - 1)!`. - -```python - >>> gamma(z) - Γ(z) -``` - -##### In `Julia`: - -* recall, we need to load `SpecialFunctions` for `gamma` to be available - - -```jldoctest simplification -julia> gamma(z) -Γ(z) -``` - ----- - -The `generalized hypergeometric function -` is -`hyper`. `hyper([a_1, ..., a_p], [b_1, ..., b_q], z)` represents -${}_pF_q\left(\begin{matrix} a_1, \cdots, a_p \\ b_1, \cdots, b_q \end{matrix} -\middle| z \right)$. The most common case is ${}_2F_1$, which is often -referred to as the `ordinary hypergeometric function -`. - -```python - >>> hyper([1, 2], [3], z) - ┌─ ⎛1, 2 │ ⎞ - ├─ ⎜ │ z⎟ - 2╵ 1 ⎝ 3 │ ⎠ -``` - -##### In `Julia`: - -* as `[1,2]` is not symbolic, we qualify `hyper` - -```jldoctest simplification -julia> sympy.hyper([1, 2], [3], z) - ┌─ ⎛1, 2 │ ⎞ - ├─ ⎜ │ z⎟ -2╵ 1 ⎝ 3 │ ⎠ -``` - ----- - -rewrite -------- - -A common way to deal with special functions is to rewrite them in terms of one -another. This works for any function in SymPy, not just special functions. -To rewrite an expression in terms of a function, use -`expr.rewrite(function)`. For example, - -```python - >>> tan(x).rewrite(sin) - 2 - 2⋅sin (x) - ───────── - sin(2⋅x) - >>> factorial(x).rewrite(gamma) - Γ(x + 1) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> tan(x).rewrite(sin) |> string -"2*sin(x)^2/sin(2*x)" -``` - -```jldoctest simplification -julia> factorial(x).rewrite(gamma) -Γ(x + 1) -``` - ----- - -For some tips on applying more targeted rewriting, see the -:ref:`tutorial-manipulation` section. - -### expand_func - - -To expand special functions in terms of some identities, use -`expand_func()`. For example - -```python - >>> expand_func(gamma(x + 3)) - x⋅(x + 1)⋅(x + 2)⋅Γ(x) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> expand_func(gamma(x + 3)) -x⋅(x + 1)⋅(x + 2)⋅Γ(x) -``` - ----- - -hyperexpand ------------ - -To rewrite `hyper` in terms of more standard functions, use -`hyperexpand()`. - -```python - >>> hyperexpand(hyper([1, 1], [2], z)) - -log(-z + 1) - ───────────── - z -``` - -##### In `Julia`: - -* As `[1,1]` is not symbolic, we qualify `hyperexpand`: - -```jldoctest simplification -julia> sympy.hyperexpand(sympy.hyper([1, 1], [2], z)) --log(1 - z) -──────────── - z -``` - ----- - -`hyperexpand()` also works on the more general Meijer G-function (see -:py:meth:`its documentation ` for more -information). - -```python - >>> expr = meijerg([[1],[1]], [[1],[]], -z) - >>> expr - ╭─╮1, 1 ⎛1 1 │ ⎞ - │╶┐ ⎜ │ -z⎟ - ╰─╯2, 1 ⎝1 │ ⎠ - >>> hyperexpand(expr) - 1 - ─ - z - ℯ -``` - -##### In `Julia`: - -* again, we qualify `meijerg` - -```jldoctest simplification -julia> expr = sympy.meijerg([[1],[1]], [[1],[]], -z) -╭─╮1, 1 ⎛1 1 │ ⎞ -│╶┐ ⎜ │ -z⎟ -╰─╯2, 1 ⎝1 │ ⎠ -julia> hyperexpand(expr) |> string -"exp(1/z)" -``` - ----- - -### combsimp - - -To simplify combinatorial expressions, use `combsimp()`. - -```python - >>> n, k = symbols('n k', integer = True) - >>> combsimp(factorial(n)/factorial(n - 3)) - n⋅(n - 2)⋅(n - 1) - >>> combsimp(binomial(n+1, k+1)/binomial(n, k)) - n + 1 - ───── - k + 1 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> @syms n::integer, k::integer -(n, k) - -julia> combsimp(factorial(n)/factorial(n - 3)) -n⋅(n - 2)⋅(n - 1) - -julia> combsimp(binomial(n+1, k+1)/binomial(n, k)) -n + 1 -───── -k + 1 -``` - ----- - -### gammasimp - - -To simplify expressions with gamma functions or combinatorial functions with -non-integer argument, use `gammasimp()`. - -```python - >>> gammasimp(gamma(x)*gamma(1 - x)) - π - ──────── - sin(π⋅x) -``` - -##### In `Julia`: - -```jldoctest simplification -julia> gammasimp(gamma(x)*gamma(1 - x)) |> string -"pi/sin(pi*x)" -``` - ----- - -### Example: Continued Fractions - - -Let's use SymPy to explore continued fractions. A `continued fraction -`_ is an expression of the -form - -$$~ - a_0 + \cfrac{1}{a_1 + \cfrac{1}{a_2 + \cfrac{1}{ \ddots + \cfrac{1}{a_n} - }}} - ~$$ - -where $a_0, \ldots, a_n$ are integers, and $a_1, \ldots, a_n$ are positive. Acontinued fraction can also be infinite, but infinite objects are more -difficult to represent in computers, so we will only examine the finite case -here. - -A continued fraction of the above form is often represented as a list $[a_0; a_1, \ldots, a_n]$. Let's write a simple function that converts such a list -to its continued fraction form. The easiest way to construct a continued -fraction from a list is to work backwards. Note that despite the apparent -symmetry of the definition, the first element, `a_0`, must usually be handled -differently from the rest. - -```python - >>> def list_to_frac(l): - ... expr = Integer(0) - ... for i in reversed(l[1:]): - ... expr += i - ... expr = 1/expr - ... return l[0] + expr - >>> list_to_frac([x, y, z]) - 1 - x + ───── - 1 - y + ─ - z -``` - -##### In `Julia`: - -```jldoctest simplification -julia> function list_to_frac(l) - expr = Sym(0) - for i in reverse(l[2:end]) - expr += i - expr = 1/expr - end - l[1] + expr - end -list_to_frac (generic function with 1 method) - -julia> list_to_frac([x, y, z]) |> string -"x + 1/(y + 1/z)" -``` - ----- - -We use `Integer(0)` in `list_to_frac` so that the result will always be a -SymPy object, even if we only pass in Python ints. - -```python - >>> list_to_frac([1, 2, 3, 4]) - 43 - ── - 30 -``` - -##### In `Julia`: - -```jldoctest simplification -julia> list_to_frac([1, 2, 3, 4]) |> N -43//30 -``` - ----- - -Every finite continued fraction is a rational number, but we are interested in -symbolics here, so let's create a symbolic continued fraction. The -`symbols()` function that we have been using has a shortcut to create -numbered symbols. `symbols('a0:5')` will create the symbols `a0`, `a1`, -..., `a4`. - -```python - >>> syms = symbols('a0:5') - >>> syms - (a₀, a₁, a₂, a₃, a₄) - >>> a0, a1, a2, a3, a4 = syms - >>> frac = list_to_frac(syms) - >>> frac - 1 - a₀ + ───────────────── - 1 - a₁ + ──────────── - 1 - a₂ + ─────── - 1 - a₃ + ── - a₄ -``` - -##### In `Julia`: - -We can pass tensor-like notation to `@syms` to create containers of indexed variables - -```jldoctest simplification -julia> @syms a[0:4] -(Sym[a₀, a₁, a₂, a₃, a₄],) - -julia> frac = list_to_frac(a); string(frac) -"a₀ + 1/(a₁ + 1/(a₂ + 1/(a₃ + 1/a₄)))" -``` - ----- - -This form is useful for understanding continued fractions, but lets put it -into standard rational function form using `cancel()`. - -```python - >>> frac = cancel(frac) - >>> frac - a₀⋅a₁⋅a₂⋅a₃⋅a₄ + a₀⋅a₁⋅a₂ + a₀⋅a₁⋅a₄ + a₀⋅a₃⋅a₄ + a₀ + a₂⋅a₃⋅a₄ + a₂ + a₄ - ───────────────────────────────────────────────────────────────────────── - a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1 - -``` - -##### In `Julia`: - -```jldoctest simplification -julia> frac = cancel(frac) -a₀⋅a₁⋅a₂⋅a₃⋅a₄ + a₀⋅a₁⋅a₂ + a₀⋅a₁⋅a₄ + a₀⋅a₃⋅a₄ + a₀ + a₂⋅a₃⋅a₄ + a₂ + a₄ -───────────────────────────────────────────────────────────────────────── - a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1 -``` - ----- -Now suppose we were given `frac` in the above canceled form. In fact, we -might be given the fraction in any form, but we can always put it into the -above canonical form with `cancel()`. Suppose that we knew that it could be -rewritten as a continued fraction. How could we do this with SymPy? A -continued fraction is recursively $c + \frac{1}{f}$, where $c$ is an integer -and $f$ is a (smaller) continued fraction. If we could write the expression -in this form, we could pull out each $c$ recursively and add it to a list. We -could then get a continued fraction with our `list_to_frac()` function. - -The key observation here is that we can convert an expression to the form `c + -\frac{1}{f}` by doing a partial fraction decomposition with respect to -`c`. This is because `f` does not contain `c`. This means we need to use the -`apart()` function. We use `apart()` to pull the term out, then subtract -it from the expression, and take the reciprocal to get the `f` part. - -```python - >>> l = [] - >>> frac = apart(frac, a0) - >>> frac - a₂⋅a₃⋅a₄ + a₂ + a₄ - a₀ + ─────────────────────────────────────── - a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1 - >>> l.append(a0) - >>> frac = 1/(frac - a0) - >>> frac - a₁⋅a₂⋅a₃⋅a₄ + a₁⋅a₂ + a₁⋅a₄ + a₃⋅a₄ + 1 - ─────────────────────────────────────── - a₂⋅a₃⋅a₄ + a₂ + a₄ -``` - -##### In `Julia`: - -```jldoctest simplification -julia> l = Sym[] -Sym[] - -julia> a0,a1,a2,a3,a4 = a; # destructure - - -julia> frac = apart(frac, a0); string(frac) -"a₀ + (a₂*a₃*a₄ + a₂ + a₄)/(a₁*a₂*a₃*a₄ + a₁*a₂ + a₁*a₄ + a₃*a₄ + 1)" - -julia> push!(l, a0) -1-element Vector{Sym}: - a₀ - -julia> frac = 1/(frac - a0); string(frac) -"(a₁*a₂*a₃*a₄ + a₁*a₂ + a₁*a₄ + a₃*a₄ + 1)/(a₂*a₃*a₄ + a₂ + a₄)" -``` - ----- - -Now we repeat this process - -```python - >>> frac = apart(frac, a1) - >>> frac - a₃⋅a₄ + 1 - a₁ + ────────────────── - a₂⋅a₃⋅a₄ + a₂ + a₄ - >>> l.append(a1) - >>> frac = 1/(frac - a1) - >>> frac = apart(frac, a2) - >>> frac - a₄ - a₂ + ───────── - a₃⋅a₄ + 1 - >>> l.append(a2) - >>> frac = 1/(frac - a2) - >>> frac = apart(frac, a3) - >>> frac - 1 - a₃ + ── - a₄ - >>> l.append(a3) - >>> frac = 1/(frac - a3) - >>> frac = apart(frac, a4) - >>> frac - a₄ - >>> l.append(a4) - >>> list_to_frac(l) - 1 - a₀ + ───────────────── - 1 - a₁ + ──────────── - 1 - a₂ + ─────── - 1 - a₃ + ── - a₄ -``` - -##### In `Julia`: - -```jldoctest simplification -julia> frac = apart(frac, a1); - -julia> push!(l, a1); - -julia> frac = 1/(frac - a1); - -julia> frac = apart(frac, a2); - -julia> push!(l, a2); - -julia> frac = 1/(frac - a2); - -julia> frac = apart(frac, a3); - -julia> push!(l, a3); - -julia> frac = 1/(frac - a3); - -julia> frac = apart(frac, a4); - -julia> push!(l, a4); - -julia> list_to_frac(l) |> string -"a₀ + 1/(a₁ + 1/(a₂ + 1/(a₃ + 1/a₄)))" -``` - ----- - - - - -!!! note "Quick tip" - You can execute multiple lines at once in SymPy Live. Typing - `Shift-Enter` instead of `Enter` will enter a newline instead of - executing. - -Of course, this exercise seems pointless, because we already know that our -`frac` is `list_to_frac([a0, a1, a2, a3, a4])`. So try the following -exercise. Take a list of symbols and randomize them, and create the canceled -continued fraction, and see if you can reproduce the original list. For -example - -```python - >>> import random - >>> l = list(symbols('a0:5')) - >>> random.shuffle(l) - >>> orig_frac = frac = cancel(list_to_frac(l)) - >>> del l -``` - -##### In `Julia`: - -* `shuffle` from Python is `randperm` in the `Random` module - -```julia -julia> using Random - -julia> @syms a[0:4] -(Sym[a₀, a₁, a₂, a₃, a₄],) - -julia> a = a[randperm(length(a))] -5-element Vector{Sym}: - a₄ - a₀ - a₃ - a₂ - a₁ - -julia> orig_frac = frac = cancel(list_to_frac(a)) -a₀⋅a₁⋅a₂⋅a₃⋅a₄ + a₀⋅a₁⋅a₄ + a₀⋅a₃⋅a₄ + a₁⋅a₂⋅a₃ + a₁⋅a₂⋅a₄ + a₁ + a₃ + a₄ -───────────────────────────────────────────────────────────────────────── - a₀⋅a₁⋅a₂⋅a₃ + a₀⋅a₁ + a₀⋅a₃ + a₁⋅a₂ + 1 -``` - ----- - -Click on "Run code block in SymPy Live" on the definition of `list_to_frac()` -above, and then on the above example, and try to reproduce `l` from -`frac`. I have deleted `l` at the end to remove the temptation for -peeking (you can check your answer at the end by calling -`cancel(list_to_frac(l))` on the list that you generate at the end, and -comparing it to `orig_frac`. - -See if you can think of a way to figure out what symbol to pass to `apart()` -at each stage (hint: think of what happens to $a_0$ in the formula $a_0 + -\frac{1}{a_1 + \cdots}$ when it is canceled). - - -!!! note - - Answer: a0 is the only symbol that does not appear in the denominator diff --git a/docs/src/Tutorial/solvers.md b/docs/src/Tutorial/solvers.md deleted file mode 100644 index 6ac0e6b8..00000000 --- a/docs/src/Tutorial/solvers.md +++ /dev/null @@ -1,747 +0,0 @@ -# Solvers - -[From](https://docs.sympy.org/latest/tutorial/solvers.html) - - -```python - >>> from sympy import * - >>> x, y, z = symbols("x y z") - >>> init_printing(use_unicode=True) -``` - - -```@setup solvers -using SymPy -sympy.init_printing(use_unicode=True) -``` - -##### In `Julia`: - -```jldoctest solvers -julia> using SymPy - -julia> @syms x, y, z -(x, y, z) -``` - ----- - -## A Note about Equations - - -Recall from the :ref:`gotchas ` section of this -tutorial that symbolic equations in SymPy are not represented by `=` or -`==`, but by `Eq`. - - -```python - >>> Eq(x, y) - x = y -``` - -##### In `Julia`: - -```jldoctest solvers -julia> Eq(x, y) -x = y - -``` - ----- - -However, there is an even easier way. In SymPy, any expression not in an -`Eq` is automatically assumed to equal 0 by the solving functions. Since `a -= b` if and only if `a - b = 0`, this means that instead of using `x == y`, -you can just use `x - y`. For example - -```python - >>> solveset(Eq(x**2, 1), x) - {-1, 1} - >>> solveset(Eq(x**2 - 1, 0), x) - {-1, 1} - >>> solveset(x**2 - 1, x) - {-1, 1} -``` - -##### In `Julia`: - -```jldoctest solvers -julia> solveset(Eq(x^2, 1), x) -{-1, 1} - -julia> solveset(Eq(x^2 - 1, 0), x) -{-1, 1} - -julia> solveset(x^2 - 1, x) -{-1, 1} -``` - ----- - -This is particularly useful if the equation you wish to solve is already equal -to 0. Instead of typing `solveset(Eq(expr, 0), x)`, you can just use -`solveset(expr, x)`. - -## Solving Equations Algebraically - - -The main function for solving algebraic equations is `solveset`. -The syntax for `solveset` is `solveset(equation, variable=None, domain=S.Complexes)` -Where `equations` may be in the form of `Eq` instances or expressions -that are assumed to be equal to zero. - -Please note that there is another function called `solve` which -can also be used to solve equations. The syntax is `solve(equations, variables)` -However, it is recommended to use `solveset` instead. - -When solving a single equation, the output of `solveset` is a `FiniteSet` or -an `Interval` or `ImageSet` of the solutions. - -```python - >>> solveset(x**2 - x, x) - {0, 1} - >>> solveset(x - x, x, domain=S.Reals) - ℝ - >>> solveset(sin(x) - 1, x, domain=S.Reals) - ⎧ π ⎫ - ⎨2⋅n⋅π + ─ | n ∊ ℤ⎬ - ⎩ 2 ⎭ -``` - -##### In `Julia`: - -* `S` is not exported, as it is not a function, so we create an alias: - -```jldoctest solvers -julia> const S = sympy.S -PyObject S - -julia> solveset(x^2 - x, x) -{0, 1} - -julia> solveset(x - x, x, domain=S.Reals) -ℝ - -julia> solveset(sin(x) - 1, x, domain=S.Reals) -⎧ π │ ⎫ -⎨2⋅n⋅π + ─ │ n ∊ ℤ⎬ -⎩ 2 │ ⎭ -``` - ----- - - -If there are no solutions, an `EmptySet` is returned and if it -is not able to find solutions then a `ConditionSet` is returned. - -```python - >>> solveset(exp(x), x) # No solution exists - ∅ - >>> solveset(cos(x) - x, x) # Not able to find solution - {x | x ∊ ℂ ∧ -x + cos(x) = 0} -``` - -##### In `Julia`: - -```jldoctest solvers -julia> solveset(exp(x), x) # No solution exists -∅ - -julia> solveset(cos(x) - x, x) # Not able to find solution -{x │ x ∊ ℂ ∧ (-x + cos(x) = 0)} -``` - ----- - -In the `solveset` module, the linear system of equations is solved using `linsolve`. -In future we would be able to use linsolve directly from `solveset`. Following -is an example of the syntax of `linsolve`. - -* List of Equations Form: - -```python - >>> linsolve([x + y + z - 1, x + y + 2*z - 3 ], (x, y, z)) -``` - -##### In `Julia`: - -Rather than a vector, we pass a tuple: - - -```jldoctest solvers -julia> linsolve((x + y + z - 1, x + y + 2*z - 3), (x, y, z)) -{(-y - 1, y, 2)} - -``` - -A tuple -!!! note "Tuples" - The `linsolve` function expects a list of equations, whereas `PyCall` is instructed to promote the syntax to produce a list in `Python` into a `Array{Sym}` object. As such, we pass the equations in a tuple above. Similar considerations are necessary at times for the `sympy.Matrix` constructor. It is suggested, as in the next example, to work around this by passing `Julia`n arrays to the constructor or bypassing it altogether. - - ----- - -* Augmented -Matrix Form: - -```python ->>> M = Matrix(((1, 1, 1, 1), (1, 1, 2, 3))) ->>> system = A, b = M[:, :-1], M[:, -1] ->>> linsolve(system, x, y, z) -{(-y - 1, y, 2)} -``` - -##### In `Julia`: - -We use `Julia`n syntax for matrices: - -```jldoctest solvers -julia> A = [1 1 1; 1 1 2]; b = [1,3] -2-element Vector{Int64}: - 1 - 3 -``` - -The augmented form is not available -``` -julia> aug = [A b] -2×4 Array{Int64,2}: - 1 1 1 1 - 1 1 2 3 - -julia> linsolve(sympy.Matrix(aug), (x,y,z)) # not {(-y - 1, y, 2)}! -∅ - -``` - -In lieu of using `sympy.Matrix`, the matrix can be created symbolically, as: - -```jldoctest solvers -julia> A = Sym[1 1 1; 1 1 2]; b = [1,3] -2-element Vector{Int64}: - 1 - 3 - -julia> aug = [A b] -2×4 Matrix{Sym}: - 1 1 1 1 - 1 1 2 3 - -julia> linsolve(aug, (x,y,z)) # {(-y - 1, y, 2)}; -{(-y - 1, y, 2)} - -``` - -Finally, linear equations are solved in `Julia` with the `\` (backslash) operator: - -``` -A \ b -``` - -The variables are generated within `\` in the sequence `x1`, `x2`, ... - - ----- - -* A*x = b Form - -```python - >>> M = Matrix(((1, 1, 1, 1), (1, 1, 2, 3))) - >>> system = A, b = M[:, :-1], M[:, -1] - >>> linsolve(system, x, y, z) - {(-y - 1, y, 2)} -``` - -##### In `Julia`: - -We follow the syntax above to construct the matrix (tuple of tuples), but not the `Julia`n matrix construtor would be recommended: - -```jldoctest solvers -julia> M = sympy.Matrix(((1, 1, 1, 1), (1, 1, 2, 3))) -2×4 Matrix{Sym}: - 1 1 1 1 - 1 1 2 3 - -julia> system = A, b = M[:, 1:end-1], M[:, end] -(Sym[1 1 1; 1 1 2], Sym[1, 3]) - -julia> linsolve(system, x, y, z) -{(-y - 1, y, 2)} - -``` - - ----- - -!!! note - The order of solution corresponds the order of given symbols. - -In the `solveset` module, the non linear system of equations is solved using -`nonlinsolve`. Following are examples of `nonlinsolve`. - -1. When only real solution is present: - -```python - >>> a, b, c, d = symbols('a, b, c, d', real=True) - >>> nonlinsolve([a**2 + a, a - b], [a, b]) - {(-1, -1), (0, 0)} - >>> nonlinsolve([x*y - 1, x - 2], x, y) - {(2, 1/2)} -``` - -##### In `Julia`: - -* we pass `[a,b]` as either `a, b` or using a tuple, as in `(a,b)`, but *not* as a vector, as this gets mapped into a vector of symbolic objects which causes issues with `nonlinsolve`: - -```jldoctest solvers -julia> @syms a::real, b::real, c::real, d::real -(a, b, c, d) - -julia> nonlinsolve([a^2 + a, a - b], a, b) -{(-1, -1), (0, 0)} - -julia> nonlinsolve([x*y - 1, x - 2], x, y) -{(2, 1/2)} - -``` - ----- - -2. When only complex solution is present: - -```python - >>> nonlinsolve([x**2 + 1, y**2 + 1], [x, y]) - {(-ⅈ, -ⅈ), (-ⅈ, ⅈ), (ⅈ, -ⅈ), (ⅈ, ⅈ)} -``` - -##### In `Julia`: - -```jldoctest solvers -julia> nonlinsolve([x^2 + 1, y^2 + 1], (x, y)) -{(-ⅈ, -ⅈ), (-ⅈ, ⅈ), (ⅈ, -ⅈ), (ⅈ, ⅈ)} - -``` - ----- - -3. When both real and complex solution is present: - -```python - >>> from sympy import sqrt - >>> system = [x**2 - 2*y**2 -2, x*y - 2] - >>> vars = [x, y] - >>> nonlinsolve(system, vars) - {(-2, -1), (2, 1), (-√2⋅ⅈ, √2⋅ⅈ), (√2⋅ⅈ, -√2⋅ⅈ)} - - >>> n = Dummy('n') - >>> system = [exp(x) - sin(y), 1/y - 3] - >>> real_soln = (log(sin(S(1)/3)), S(1)/3) - >>> img_lamda = Lambda(n, 2*n*I*pi + Mod(log(sin(S(1)/3)), 2*I*pi)) - >>> complex_soln = (ImageSet(img_lamda, S.Integers), S(1)/3) - >>> soln = FiniteSet(real_soln, complex_soln) - >>> nonlinsolve(system, [x, y]) == soln - True -``` - -##### In `Julia`: - -* we must remove the spaces within `[]` -* we must pass vars as a tuple: - -```jldoctest solvers -julia> system = [x^2-2*y^2-2, x*y-2] -2-element Vector{Sym}: - x^2 - 2*y^2 - 2 - x⋅y - 2 - -julia> vars = (x, y) -(x, y) - -julia> nonlinsolve(system, vars) -{(-2, -1), (2, 1), (-√2⋅ⅈ, √2⋅ⅈ), (√2⋅ⅈ, -√2⋅ⅈ)} - -``` - -However, the next bit requires some modifications to run: - -* the `system` array definition must have extra spaces removed -* `Dummy`, `Mod`, `ImageSet`, `FiniteSet` aren't exported -* we need `PI`, not `pi` to have a symbolic value -* we compare manually - -```jldoctest solvers -julia> n = sympy.Dummy("n") -n - -julia> system = [exp(x)-sin(y), 1/y-3] -2-element Vector{Sym}: - exp(x) - sin(y) - -3 + 1/y - -julia> real_soln = (log(sin(S(1)/3)), S(1)/3) -(log(sin(1/3)), 1/3) - -julia> img_lamda = Lambda(n, 2*n*IM*PI + sympy.Mod(sin(S(1)/3), 2*IM*PI)) -n ↦ 2⋅n⋅ⅈ⋅π + (sin(1/3) mod 2⋅ⅈ⋅π) - -julia> complex_soln = (sympy.ImageSet(img_lamda, S.Integers), S(1)/3) -(ImageSet(Lambda(_n, 2*_n*I*pi + Mod(sin(1/3), 2*I*pi)), Integers), 1/3) - -julia> soln = sympy.FiniteSet(real_soln, complex_soln) -{(log(sin(1/3)), 1/3), ({2⋅n⋅ⅈ⋅π + (sin(1/3) mod 2⋅ⅈ⋅π) │ n ∊ ℤ}, 1/3)} - -julia> nonlinsolve(system, (x, y)) -{({2⋅n⋅ⅈ⋅π + log(sin(1/3)) │ n ∊ ℤ}, 1/3)} - -``` - ----- - -4. If non linear system of equations is Positive dimensional system (A system with -infinitely many solutions is said to be positive-dimensional): - -```python - >>> nonlinsolve([x*y, x*y - x], [x, y]) - {(0, y)} - - >>> system = [a**2 + a*c, a - b] - >>> nonlinsolve(system, [a, b]) - {(0, 0), (-c, -c)} -``` - -##### In `Julia`: - -* again, we use a tuple for the variables: - -```jldoctest solvers -julia> nonlinsolve([x*y, x*y-x], (x, y)) -{(0, y)} - -``` - -```jldoctest solvers -julia> system = [a^2+a*c, a-b] -2-element Vector{Sym}: - a^2 + a*c - a - b - -julia> nonlinsolve(system, (a, b)) -{(0, 0), (-c, -c)} - -``` - - ----- - -> Note: - - 1. The order of solution corresponds the order of given symbols. - - 2. Currently `nonlinsolve` doesn't return solution in form of `LambertW` (if there is solution present in the form of `LambertW`). - - `solve` can be used for such cases: - -```python - >>> solve([x**2 - y**2/exp(x)], [x, y], dict=True) - ⎡⎧ ⎛y⎞⎫⎤ - ⎢⎨x: 2⋅LambertW⎜─⎟⎬⎥ - ⎣⎩ ⎝2⎠⎭⎦ -``` - -##### In `Julia`: - -it is similar - -```jldoctest solvers -julia> u = solve([x^2 - y^2/exp(x)], [x, y], dict=true) -2-element Vector{Dict{Any, Any}}: - Dict(y => -x*sqrt(exp(x))) - Dict(y => x*sqrt(exp(x))) - -``` - -To get prettier output, the dict may be converted to have one with symbolic keys: - -```jldoctest solvers -julia> convert(Dict{SymPy.Sym, Any}, first(u)) -Dict{Sym, Any} with 1 entry: - y => -x*sqrt(exp(x)) - -``` - ----- - - 3. Currently `nonlinsolve` is not properly capable of solving the system of equations - having trigonometric functions. - - `solve` can be used for such cases(not all solution): - -```python - >>> solve([sin(x + y), cos(x - y)], [x, y]) - ⎡⎛-3⋅π 3⋅π⎞ ⎛-π π⎞ ⎛π 3⋅π⎞ ⎛3⋅π π⎞⎤ - ⎢⎜─────, ───⎟, ⎜───, ─⎟, ⎜─, ───⎟, ⎜───, ─⎟⎥ - ⎣⎝ 4 4 ⎠ ⎝ 4 4⎠ ⎝4 4 ⎠ ⎝ 4 4⎠⎦ -``` - -##### In `Julia`: - -```jldoctest solvers -julia> solve([sin(x + y), cos(x - y)], [x, y]) -4-element Vector{Tuple{Sym, Sym}}: - (-3*pi/4, 3*pi/4) - (-pi/4, pi/4) - (pi/4, 3*pi/4) - (3*pi/4, pi/4) - -``` - ----- - - -`solveset` reports each solution only once. To get the solutions of a -polynomial including multiplicity use `roots`. - -```python - >>> solveset(x**3 - 6*x**2 + 9*x, x) - {0, 3} - >>> roots(x**3 - 6*x**2 + 9*x, x) - {0: 1, 3: 2} -``` - -##### In `Julia`: - -```jldoctest solvers -julia> solveset(x^3 - 6*x^2 + 9*x, x) -{0, 3} - -``` - -```jldoctest solvers -julia> roots(x^3 - 6*x^2 + 9*x, x) |> d -> convert(Dict{Sym, Any}, d) # prettier priting -Dict{Sym, Any} with 2 entries: - 0 => 1 - 3 => 2 -``` - ----- - -The output `{0: 1, 3: 2}` of `roots` means that `0` is a root of -multiplicity 1 and `3` is a root of multiplicity 2. - -> Note: - - Currently `solveset` is not capable of solving the following types of equations: - - * Equations solvable by LambertW (Transcendental equation solver). - - `solve` can be used for such cases: - -```python - >>> solve(x*exp(x) - 1, x ) - [LambertW(1)] -``` - -##### In `Julia`: - -```jldoctest solvers -julia> solve(x*exp(x) - 1, x ) -1-element Vector{Sym}: - W(1) - -``` - ----- - - -## Solving Differential Equations - - -To solve differential equations, use `dsolve`. First, create an undefined -function by passing `cls=Function` to the `symbols` function. - - -```python - >>> f, g = symbols('f g', cls=Function) -``` - -##### In `Julia`: - -```jldoctest solvers -julia> @syms f() g() -(f, g) - -``` - ----- - -`f` and `g` are now undefined functions. We can call `f(x)`, and it -will represent an unknown function. - -```python - >>> f(x) - f(x) -``` - -##### In `Julia`: - -```jldoctest solvers -julia> f(x) -f(x) - -``` - ----- - -Derivatives of `f(x)` are unevaluated. - -```python - >>> f(x).diff(x) - d - ──(f(x)) - dx -``` - -##### In `Julia`: - -```jldoctest solvers -julia> f(x).diff(x) -d -──(f(x)) -dx - -``` - ----- - -(see the :ref:`Derivatives ` section for more on -derivatives). - -To represent the differential equation $f''(x) - 2f'(x) + f(x) = \sin(x)$, we -would thus use - -```python - >>> diffeq = Eq(f(x).diff(x, x) - 2*f(x).diff(x) + f(x), sin(x)) - >>> diffeq - 2 - d d - f(x) - 2⋅──(f(x)) + ───(f(x)) = sin(x) - dx 2 - dx -``` - -##### In `Julia`: - -```jldoctest solvers -julia> diffeq = Eq(f(x).diff(x, x) - 2*f(x).diff(x) + f(x), sin(x)); string(diffeq) -"Eq(f(x) - 2*Derivative(f(x), x) + Derivative(f(x), (x, 2)), sin(x))" - -``` - ----- - -To solve the ODE, pass it and the function to solve for to `dsolve`. - -```python - >>> dsolve(diffeq, f(x)) - x cos(x) - f(x) = (C₁ + C₂⋅x)⋅ℯ + ────── - 2 -``` - -##### In `Julia`: - -* we use `dsolve` for initial value proplems - -```jldoctest solvers -julia> dsolve(diffeq, f(x)) |> string -"Eq(f(x), (C1 + C2*x)*exp(x) + cos(x)/2)" - -``` - - ----- - -`dsolve` returns an instance of `Eq`. This is because in general, -solutions to differential equations cannot be solved explicitly for the -function. - -```python - >>> dsolve(f(x).diff(x)*(1 - sin(f(x))), f(x)) - f(x) + cos(f(x)) = C₁ -``` - -##### In `Julia`: - -```jldoctest solvers -julia> dsolve(f(x).diff(x)*(1 - sin(f(x))), f(x)) -f(x) = C₁ - -``` - ----- - -The arbitrary constants in the solutions from dsolve are symbols of the form -`C1`, `C2`, `C3`, and so on. - - -## Julia alternative interface - - - - -`SymPy.jl` adds a `SymFunction` class, that makes it a bit easier to set up a differential equation, though not as general. - -We use either the `SymFunction` constructor - -```jldoctest solvers -julia> f = SymFunction("f") -f - -``` - -or the `@syms` macro, as in `@syms f()` to define symbolic functions. The `Differential` function (who's functionality is lifted from `ModelingToolkit`). Can simplify things: - -```jldoctest solvers -julia> D = Differential(x); - -julia> diffeq = Eq(D(D(f))(x) - 2*D(f)(x) + f(x), sin(x)); string(diffeq) -"Eq(f(x) - 2*Derivative(f(x), x) + Derivative(f(x), (x, 2)), sin(x))" - -julia> dsolve(diffeq, f(x)) |> string -"Eq(f(x), (C1 + C2*x)*exp(x) + cos(x)/2)" - -``` - - -Or: - -```jldoctest solvers -julia> dsolve(D(f)(x)*(1 - sin(f(x))), f(x)) -f(x) = C₁ - -``` - -Initial conditions can be specified using a dictionary. - -For the initial condition `f'(x0) = y0`, this would be specified as `Dict(D(f)(x0) => y0)`. - -For example, to solve the exponential equation $f'(x) = f(x), f(0) = a$ we would have: - -```jldoctest solvers -julia> @syms x, a, f() -(x, a, f) - -julia> dsolve(D(f)(x) - f(x), f(x), ics = Dict(f(0) => a)) |> string -"Eq(f(x), a*exp(x))" - -``` - -To solve the simple harmonic equation, where two initial conditions are specified, we combine the tuple for each within another tuple: - -```jldoctest solvers -julia> ics = Dict(f(0) => 1, D(f)(0) => 2); - -julia> dsolve(D(D(f))(x) - f(x), f(x), ics=ics) |> string -"Eq(f(x), 3*exp(x)/2 - exp(-x)/2)" - -``` diff --git a/docs/src/index.md b/docs/src/index.md index d4ba4e48..01d9add9 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,91 +1,9 @@ -# SymPy.jl - -The `Julia` package [SymPy](https://github.com/JuliaPy/SymPy.jl) uses the `PyCall` package to interface `Julia` with the underlying SymPy Python package. SymPy ([http://sympy.org/](http://sympy.org/)) is a Python library for symbolic mathematics. The [Symbolics](https://symbolics.juliasymbolics.org/) package for `Julia` provides an alternative with some compelling performance improvements, though is not nearly as feature rich as the underlying SymPy Python library. - -## Using Python's SymPy with `PyCall` - -The `PyCall` package does the heavy lifting for `SymPy`. In fact, this package can be skipped altogether, if so inclined. For example, we can import the underlying Python module as follows: - -```julia -julia> using PyCall - -julia> sympy = pyimport("sympy") -[...] -``` - -Using the dot-call notation of Python, we can create a symbolic variable, and a symbolic expression: - -```julia -julia> x = sympy.symbols("x") -PyObject x - -julia> y = sympy.sin(x) -PyObject sin(x) +```@meta +CurrentModule = SymPy ``` -The symbolic expression can be evaluated at a symbolic value returning an object accessible via pycall. In the following, this is then converted to a floating point number in `Julia`: - -```julia -julia> z = y.subs(x, sympy.pi) -PyObject 0 - -julia> convert(Float64, z) -0.0 -``` - -Further, some arithmetic operations are already available, for example multiplication and powers: - -```{julia} -julia> y = sympy.sin(sympy.pi * x)^2 # ^ in Julia, ** in Python -PyObject sin(pi*x)**2 - -julia> z = y.subs(x,2) -PyObject 0 -``` - -## Using SymPy with `SymPy` - -The `SymPy` package provides a more Julian interface to such tasks; +# SymPy -```julia -julia> using SymPy -WARNING: using SymPy.sympy in module Main conflicts with an existing identifier. - -julia> @syms x -(x,) - -julia> ex = sin(pi*x)^2 # generic method overload - 2 -sin (π⋅x) - -julia> z = ex(x => 1) # Julia style, use ex.subs((x, PI)) for Python style -0 - -julia> N(z) # z refers to a python value; N converts this symbolic number to a number in Julia -0 -``` - -As seen in the warning, the `SymPy` package also exposes the underlying `sympy` module. In addition, the basic usage follows these points: - -* generic methods from `Julia` and imported functions in the `sympy` namespace are called through `fn(object)`. Method overloading for basic generics usually expects the first argument to be symbolic for dispatch. - -* SymPy methods of an object are called through Python's dot-call syntax: `object.fn(...)`. - -* Constructors, like `sympy.Symbol`, and other non-function calls from `sympy` are qualified with `sympy.Constructor(...)`. Such qualified calls are also useful when the first argument is not symbolic. - - -## Installation - -This package can be installed through the usual means (`using SymPy` or `add SymPy`). - -Installation of the package will also install `PyCall`, if needed; a `python` interpreter, if needed; and then use the `Conda` package to install the underlying SymPy library, if needed. - -For the `Conda` installation, the following commands can be used to update the underlying python library: - -``` -using Conda # may need to be installed -Conda.update() -``` +This is a package providing access to the SymPy library of `Python` from within `Julia` utilizing `PyCall` as the glue package between the two systems. -!!! warning - If installation does not work, there may be a conflict with another `python` installation. There have been reports for Mac OS systems that a Conda installed python and a brew installed python do not work well together, with the solution being to delete the brew installed version. +See docs at [SymPyCore](https://github.com/jverzani/SymPyCore.jl). diff --git a/docs/src/introduction.md b/docs/src/introduction.md deleted file mode 100644 index 2da60795..00000000 --- a/docs/src/introduction.md +++ /dev/null @@ -1,2993 +0,0 @@ -# A SymPy introduction - -This document provides an introduction to using `SymPy` within `Julia`. -It owes an enormous debt to SymPy documentation and the tutorial for using SymPy within Python which may be found -[here](http://docs.sympy.org/dev/tutorial/index.html). - -Here we first load the package into `Julia`: - - -```@setup introduction -using SymPy -sympy.init_printing(use_latex=true) -``` - -```jldoctest introduction -julia> using SymPy - -``` - - -## Symbols - -At the core of `SymPy` is the introduction of symbolic variables that -differ quite a bit from `Julia`'s variables. Symbolic variables do not -immediately evaluate to a value, rather the "symbolicness" propagates -when interacted with. To keep the resulting expressions manageable, -SymPy does some simplifications along the way. - - -The `@syms` macro makes creating one or more symbolic variables very easy: - -```jldoctest introduction -julia> @syms x -(x,) - -``` - -```jldoctest introduction -julia> @syms a b c -(a, b, c) - -``` - -This macro creates variables in the local scope, no assignment is needed. - -This example shows that symbols may be Unicode: - -```jldoctest introduction -julia> @syms α, β, γ -(α, β, γ) - -``` - -Additionally you can specify how the variables are displayed using pair notation: - -``` -julia> @syms a1=>"α₁" a2=>"α₂" -(α₁, α₂) - -``` - -The user can type `a1`, say, but, as seen, the variable prints as `α₁`. - - -There are other means, described in the following, to create symbolic variables, but the `@syms` macro is the suggested one to use. - -The `sympy.symbols` constructor is exported as `symbols`, as it is a common means for creating symbolic variables in other documentation. Here the variables are passed as a string with names separated by space or commas, allowing the -creation of one or more variables: - -```jldoctest introduction -julia> a = symbols("a") -a - -julia> a, b, c = symbols("a b c") -(a, b, c) - -``` - -The documentation for the Python function is available from within `Julia` using the `SymPy.@doc` macro, as in `SymPy.@doc sympy.symbols`. - -In `Julia`, the symbolic expressions are primarily instances of the `Sym` type, which holds an instance to an underlying `PyObject`. This type can be used as a constructor in the standard way: - -```jldoctest introduction -julia> x = Sym("x") -x - -julia> a,b,c = Sym("a, b, c") -(a, b, c) - -``` - - -The `sympify` function is used by SymPy to convert arbitrary expressions to a type useful within SymPy. It too can be used to create variables (and more): - -```{julia} -sympify("x") # sympy.sympify is exported -``` - -### Ranges of symbolic variables - -Here are two ways to make sequenced variables: - -```jldoctest introduction -julia> @syms xs[1:5] -(Sym[xs₁, xs₂, xs₃, xs₄, xs₅],) - -julia> ys = [Sym("y$i") for i in 1:5] -5-element Vector{Sym}: - y₁ - y₂ - y₃ - y₄ - y₅ -``` - -The former much more succinct, but the latter pattern of use when the number of terms is a variable. - -The `symbols` constructor also has non-Julian range patterns available: - -```jldoctext introduction -julia> symbols("x:3") -(x0, x1, x2) - -julia> symbols("x:2:2") -(x00, x01, x10, x11) -``` - - -### Assumptions - -SymPy has "core assumptions" that can be asserted for a variable. These include being real, positive, etc. A list of possible assumptions is -[here](http://docs.sympy.org/dev/modules/core.html#module-sympy.core.assumptions). - -The `@syms` macro allows annotations, akin to type annotations, to specify assumptions on new variables: - -```jldoctest introduction -julia> @syms u1::positive u2::(real, nonzero) -(u1, u2) -``` - -Skipping ahead, there are some functions from the `assumptions` module of `SymPy` to query these properties: - -```jldoctest introduction -julia> ask(𝑄.positive(u1)), ask(𝑄.positive(u2)), ask(𝑄.positive(u2^2)), ask(𝑄.real(u2)) -(true, nothing, nothing, true) - -``` - -Despite it easy to see that a non-zero real variable (`u2`) when squared will be positive, this is not identified in the assumptions. - - -The `symbols` constructor uses keyword arguments to pass in assumptions. It is illustrated below as they apply to all the variables that are created, which can be more convenient at times. - -```jldoctest introduction -julia> u = symbols("u") -u - -julia> x = symbols("x", real=true) -x - -julia> y1, y2 = symbols("y1, y2", positive=true) -(y1, y2) - -julia> alpha = symbols("alpha", integer=true, positive=true) -α - -``` - -We jump ahead for a second to illustrate, but here we see that `solve` will respect these assumptions, by failing to find solutions to these equations: - -```jldoctest introduction -julia> @syms x::real -(x,) - -julia> solve(x^2 + 1) # ±i are not real -Any[] - -``` - -```jldoctest introduction -julia> @syms x::positive -(x,) - -julia> solve(x + 1) # -1 is not positive -Any[] - -``` - -!!! warning - SymPy can easily create two variables with the same stringified name but different assumptions and will treat these as distinct. - -```jldoctest introduction -julia> @syms x::real=>"x" y=>"x" -(x, x) - -julia> x, y -(x, x) - -julia> string(x) == string(y) -true - -julia> x == y -false - -julia> hash(x) == hash(y) -false - -``` - - - -### Special constants - -`Julia` has its math constants, like `pi` and `e`, `SymPy` as well. A few of these have `Julia` counterparts provided by `SymPy`. For example, these two constants are defined (where `oo` is for infinity): - -```jldoctest introduction -julia> PI, oo -(pi, oo) - -``` - -(The pretty printing of SymPy objects does not work for tuples.) - -These are aliases to `sympy.pi` and `sympy.oo`. There are a few others. - -Numeric values themselves can be symbolic. This example shows the -difference. The first `asin` call dispatches to `Julia`'s `asin` -function, the second to `SymPy`'s: - -```jldoctest introduction -julia> [asin(1), asin(Sym(1))] -2-element Vector{Sym}: - 1.57079632679490 - pi/2 - -``` - -## Basics of SymPy - -As seen, SymPy has symbolic variables and numbers can be symbolic. These are used to build symbolic expressions. In `SymPy` most all of these have the `Sym` type, a subtype of the `SymbolicObject` type. The `Sym` type is immutable; assignment is necessary to modify a variable. - -In the Python library, there are two basic method types for interacting with expressions: ones that are defined in the `sympy` module and called using `fn(args...)` style, and ones that are methods of the object and called using Python's dot call notation `obj.fn(args...; kwargs...)`. - -The `SymPy` package essentially merges the Python style and `Julia`'s style as follows: - -* Promotion rules are established to promote `Julia` number types to symbolic values so that expressions like `pi * x` will first promote `pi` and `x` to two symbolic values (with `pi` being converted to the exact `sympy.pi`) and then the SymPy multiplication operator is called on the two symbolic values. The result is wrapped in the `Sym` type. -* For generic methods in `Julia`, such as `sin`, a new method specialized on the first argument being symbolic is defined to call the underlying `sympy` function. (Essentially, the method `sin(x::Sym) = sympy.sin(x)` is added.) -* For a selection of `sympy` methods, an exported function is defined. (For example a method like `simplify(x::Sym) = sympy.simplify(x)` is defined and exported.) -* For object methods and properties in Python, the dot-call notation is used to call these methods. There is a selection of methods for which a `Julia`n style is also added. For example, `ex.subs(...)` and `subs(ex, ...)` are both calling styles for the `subs` method of the expression `ex`. -* When a SymPy function is called, `PyCall` converts the arguments to a Python object, which works as desired in most all cases. - -Expressions in SymPy are simplified to some extent but are not immediately evaluated if there are symbols involved. Expressions are stored in a tree-like manner that can be explored using some basic functions. Here we look at `x*sin(x^2)`: - -```jldoctest introduction -julia> @syms x -(x,) - -julia> ex = x * sin(x^2); println(ex) -x*sin(x^2) - -julia> SymPy.Introspection.func(ex) -PyObject - -julia> a,b = SymPy.Introspection.args(ex) -(x, sin(x^2)) - -julia> a.is_Atom -true - -julia> SymPy.Introspection.func(b) -PyObject sin - -julia> b.is_Atom -false - -julia> SymPy.Introspection.args(b) -(x^2,) - -julia> SymPy.Introspection.func(x^2) -PyObject -``` - -The point is not to discuss the introspection functions, but rather to show the tree-like structure: - -``` -x*sin(x^2) - / \ - x sin - | - Pow - / \ - x 2 -``` - -SymPy uses the term `Atom` to describe the leaves of this tree. An atom is an expression with no subexpressions. - - -## Substitution - -SymPy provides several means to substitute values in for the symbolic expressions: - -* `subs`: substitution of subexpressions as defined by the objects themselves -* `replace`: replace matching subexpressions of `self` with `value` -* `xreplace`: exact node replacement in expr tree; also capable of using matching rules - -We discuss `subs` here which uses "old/new" pairs to indicate the substitution. - - -The method `subs` is a method of an object, but for this widely used task, a `Julia`n method `subs` is also defined. Consider the simplest case: - -```jldoctest introduction -julia> ex = x * sin(x^2); println(ex) -x*sin(x^2) - -julia> subs(ex, x^2, x) -x⋅sin(x) - -``` - -Above, the old/new pair is separated by a comma. Pairs notation is suggested instead: - -```jldoctest introduction -julia> subs(ex, x => x^2) |> println -x^2*sin(x^4) - -``` - -`Julia`'s call notation is used for substitution, so we can more directly write: - -```jldoctest introduction -julia> ex = x^2 + x^4; println(ex) -x^4 + x^2 - -julia> ex(x^2 => x^5) |> println -x^10 + x^5 - -``` - -Of some note, above `x^4` is treated like `(x^2)^2` and so an `x^10` term is returned after the substitution. The `subs` method simplifies arguments; `xreplace` method only replaces exact expressions in syntax tree: - -```jldoctest introduction -julia> ex.xreplace(Dict(x^2 => x^5)) |> println -x^5 + x^4 - -``` - -For example, this is one way to make a polynomial in a new variable: - -```jldoctest introduction -julia> @syms x y -(x, y) - -julia> ex = x^2 + 2x + 1 - 2 -x + 2⋅x + 1 - -julia> ex(x => y) - 2 -y + 2⋅y + 1 - -``` - - -Substitution can also be numeric: - -```jldoctest introduction -julia> ex(x => 0) -1 - -``` - -The output has no free variables, but is still a symbolic quantity. - -Expressions with more than one variables can have multiple substitutions (performed from left to right): - -```jldoctest introduction -julia> @syms x,y,z -(x, y, z) - -julia> ex = x + y + z -x + y + z - -julia> ex(x => 1, y=> PI) -z + 1 + π - -``` - -A straight call (e.g. `ex(1, PI)`) is also possble, where the order of the variables is determined by `free_symbols`. While useful for expressions of a single variable, being explicit through the use of paired values is recommended. - - - -## Simplification - -The `simplify` function in SymPy "simplifies" a given expression. As mentioned in the SymPy documentation, simplification is not a well-defined term and strategies employed may differ between versions. The `simplify` function depends on over a dozen other functions, such as `powsimp`, `trigsimp`, `radsimp`, `logcombine`, and `together`. The `simplify` function is exported by the `SymPy` package, the others called through the `sympy` module. - - -To illustrate, we have: - -```jldoctest introduction -julia> @syms x y; - -julia> q = x*y + x*y^2 + x^2*y + x; - -julia> simplify(q) - ⎛ 2 ⎞ -x⋅⎝x⋅y + y + y + 1⎠ - -``` - -The above uses factoring. Other simplifications are possible. For example, powers: - -```jldoctest introduction -julia> @syms m::integer n::integer -(m, n) - -julia> x^m / x^n - m -n -x ⋅x - -julia> simplify(x^m / x^n) - m - n -x - -``` - -Simplification rules can also be made by hand using `Wild` to create wild cards. This shows a simple trigonometric substitution: - -```jldoctest introduction -julia> x_ = Wild("x") -x - -julia> ex = sin(2x) -sin(2⋅x) - -julia> replace(ex, sin(2x_) => 2sin(x_)*cos(x_)) -2⋅sin(x)⋅cos(x) - -``` - -!!! note - With version 1.9 of `Julia` an extension for the `TermInterface` package is provided which allows `Metatheory` rules to be applied to symbolic expressions. - -### Trigsimp - -For trigonometric expressions, `simplify` will use `trigsimp` to simplify: - -```jldoctest introduction -julia> @syms theta::real -(theta,) - -julia> p = cos(theta)^2 + sin(theta)^2 - 2 2 -sin (θ) + cos (θ) - -``` - -Calling either `simplify` or `trigsimp` will apply the Pythagorean identity: - -```jldoctest introduction -julia> simplify(p) -1 - -``` - -While often forgotten, the `trigsimp` function is, of course, aware of the double angle formulas: - -```jldoctest introduction -julia> simplify(sin(2theta) - 2sin(theta)*cos(theta)) -0 - -``` - -Unlike the example above, `trigsimp` replaces the product with the double angle: - -```jldoctest introduction -julia> sympy.trigsimp(2* sin(theta) * cos(theta)) -sin(2⋅θ) - -``` - -The `expand_trig` function will expand such expressions: - -```jldoctest introduction -julia> sympy.expand_trig(sin(2theta)) -2⋅sin(θ)⋅cos(θ) - -``` - -## Conversion from symbolic to numeric - -SymPy provides two identical means to convert a symbolic math -expression to a number. One is the `evalf` method, the other the `N` -function. Within `Julia` we decouple these, using `N` to also convert -to a `Julian` value and; `evalf` leaving the conversion as a symbolic -object. - -The `N` function converts symbolic integers, rationals, -irrationals, and complex values, while attempting to find an -appropriate `Julia` type for the value. - -To see the difference, we use both on `PI`: - -```jldoctest introduction -julia> N(PI) # converts to underlying pi Irrational -π = 3.1415926535897... - -``` - -Whereas, `evalf` will produce a symbolic numeric value: - -```jldoctest introduction -julia> PI.evalf() -3.14159265358979 - -``` - -The `evalf` call allows for a precision argument to be passed through the second argument. This is how $30$ digits of $\pi$ can be extracted: - -```jldoctest introduction -julia> PI.evalf(30) -3.14159265358979323846264338328 - -``` - -The output of an `evalf` call is is a symbolic number, not a `Julia` object. Composing with `N` we can see the difference: - -```jldoctest introduction -julia> N(PI.evalf(30)) -3.141592653589793238462643383279999999999999999999999999999999999999999999999985 - -``` - -Explicit conversion via the `convert(T, ex)` pattern can also be used to convert a symbolic number to a `Julia`n one. This is -necessary at times when `N` does not give the desired type. - - -## Algebraic expressions - -As mentioned, `SymPy` overloads many of `Julia`'s functions to work with symbolic objects, such as seen above with `sin` and `asin`. The usual mathematical operations such as `+`, `*`, `-`, `/` etc. work through `Julia`'s promotion mechanism, where numbers are promoted to symbolic objects, others dispatch internally to related `SymPy` functions. - -In most all cases, thinking about this distinction between numbers and symbolic numbers is unnecessary, as numeric values passed to `SymPy` functions are typically promoted to symbolic expressions. This conversion will take math constants to their corresponding `SymPy` counterpart, rational expressions to rational expressions, and floating point values to floating point values. However there are edge cases. An expression like `1//2 * pi * x` will differ from the seemingly identical `1//2 * (pi * x)`. The former will produce a floating point value from `1//2 * pi` before being promoted to a symbolic instance. Using the symbolic value `PI` makes this expression work either way. - -!!! note - The `sympy.nsimplify` function can be used to convert floating point values to rational values, but it is suggested to convert rational value in `Julia` to symbolic values, rather than rely on later conversions. - -Most of `Julia`'s -[mathematical](http://julia.readthedocs.org/en/latest/manual/mathematical-operations/#elementary-functions) -functions are overloaded to work with symbolic expressions. `Julia`'s -generic definitions are used, as possible. This also introduces some -edge cases. For example, `x^(-2)` will work, but `k=-2; x^l` will balk due to the non-literal, negative, integer exponent. However, either `k = -2//1` or `k = Sym(-2)` will work as -expected with `x^k`, as the former call first dispatches to a generic definition not defined for negative integer exponents unless `x` is one, but the latter two expressions do not. - - -## Polynomial and rational expressions - -`SymPy` makes it very easy to work with polynomial and rational expressions, as illustrated in the following section. - - -### The expand, factor, and collect functions - -A typical polynomial expression in a single variable can be written in two common ways, expanded or factored form. Using `factor` and `expand` can move between the two. - -For example, - -```jldoctest introduction -julia> @syms x y z -(x, y, z) - -``` - -```jldoctest introduction -julia> p = x^2 + 3x + 2; println(p) -x^2 + 3*x + 2 - -julia> factor(p) -(x + 1)⋅(x + 2) - - -``` - -Or - -```jldoctest introduction -julia> expand(prod((x-i) for i in 1:5)) |> println -x^5 - 15*x^4 + 85*x^3 - 225*x^2 + 274*x - 120 - -``` - -The `factor` function factors over the rational numbers, so something like this with obvious factors is not finished: - -```jldoctest introduction -julia> factor(x^2 - 2) |> println -x^2 - 2 - -``` - -When expressions involve one or more variables, it can be convenient to be able to manipulate them. For example, -if we define `q` by: - -```jldoctest introduction -julia> q = x*y + x*y^2 + x^2*y + x - 2 2 -x ⋅y + x⋅y + x⋅y + x - -``` - -Then we can collect the terms by the variable `x`: - -```jldoctest introduction -julia> collect(q, x) - 2 ⎛ 2 ⎞ -x ⋅y + x⋅⎝y + y + 1⎠ - -``` - -or the variable `y`: - -```jldoctest introduction -julia> collect(q, y) - 2 ⎛ 2 ⎞ -x⋅y + x + y⋅⎝x + x⎠ - -``` - -These are identical expressions, though viewed differently. - -The SymPy tutorial illustrates that `expand` can also result in simplifications through this example: - -```jldoctest introduction -julia> expand((x + 1)*(x - 2) - (x - 1)*x) --2 - -``` - - -These methods are not restricted to polynomial expressions and will -work with other expressions. For example, `factor` identifies the -following as a factorable object in terms of the variable `exp(x)`: - -```jldoctest introduction -julia> factor(exp(2x) + 3exp(x) + 2) -⎛ x ⎞ ⎛ x ⎞ -⎝ℯ + 1⎠⋅⎝ℯ + 2⎠ - -``` - -## Rational expressions: apart, together, cancel - -When working with rational expressions, SymPy does not do much -simplification unless asked. For example this expression is not -simplified: - -```jldoctest introduction -julia> r = 1/x + 1/x^2 -1 1 -─ + ── -x 2 - x - -``` - -To put the terms of `r` over a common denominator, the `together` function is available: - -```jldoctest introduction -julia> together(r) -x + 1 -───── - 2 - x - -``` - -The `apart` function does the reverse, creating a partial fraction decomposition from a ratio of polynomials: - -```jldoctest introduction -julia> apart( (4x^3 + 21x^2 + 10x + 12) / (x^4 + 5x^3 + 5x^2 + 4x)) - 2⋅x - 1 1 3 -────────── - ───── + ─ - 2 x + 4 x -x + x + 1 - -``` - -Some times SymPy will cancel factors, as here: - -```jldoctest introduction -julia> top = (x-1)*(x-2)*(x-3) -(x - 3)⋅(x - 2)⋅(x - 1) - -julia> bottom = (x-1)*(x-4) -(x - 4)⋅(x - 1) - -julia> top/bottom -(x - 3)⋅(x - 2) -─────────────── - x - 4 - -``` - -(This might make math faculty a bit upset, but it is in line with student thinking.) - -However, with expanded terms, the common factor of `(x-1)` is not cancelled: - -```jldoctest introduction -julia> r = expand(top) / expand(bottom) - 3 2 -x - 6⋅x + 11⋅x - 6 -──────────────────── - 2 - x - 5⋅x + 4 - -``` - -The `cancel` function instructs SymPy to cancel common factors in a rational expression. - -```jldoctest introduction -julia> cancel(r) - 2 -x - 5⋅x + 6 -──────────── - x - 4 - -``` - -## Powers - -The SymPy [tutorial](http://docs.sympy.org/dev/tutorial/simplification.html#powers) offers a thorough explanation on powers and how the rules of powers are applied during simplification. Basically - -* $x^a x^b = x^{a+b}$ is always true. However - -* $x^a y^a=(xy)^a$ is only true with assumptions, such as $x,y \geq 0$ and $a$ is real, but not in general. For example, $x=y=-1$ and $a=1/2$ has $x^a \cdot y^a = i \cdot i = -1$, where as $(xy)^a = 1$. - -* $(x^a)^b = x^{ab}$ is only true with assumptions. For example $x=-1, a=2$, and $b=1/2$ gives $(x^a)^b = 1^{1/2} = 1$, whereas $x^{ab} = -1^1 = -1$. - - -We see that with assumptions, the following expression does simplify to $0$: - -```jldoctest introduction -julia> @syms x::nonnegatve y::nonnegative a::real -(x, y, a) - -julia> simplify(x^a * y^a - (x*y)^a) -0 - -``` - -However, without assumptions this is not the case - -```jldoctest introduction -julia> @syms x, y, a -(x, y, a) - -julia> simplify(x^a * y^a - (x*y)^a) - a a a -x ⋅y - (x⋅y) - -``` - -The `simplify` function calls `powsimp` to simplify powers, as above. The `powsimp` function has the keyword argument `force=true` to force simplification even if assumptions are not specified: - -```jldoctest introduction -julia> powsimp(x^a * y^a - (x*y)^a, force=true) -0 - -``` - -## More on polynomials - -(The following section is borrowed almost directly from the SymPy documentation [Examples from Wester's Article](https://docs.sympy.org/latest/modules/polys/wester.html)) and shows more methods for working with polynomial expressions. - -The following are reasonably high-degree polynomials: - -```jldoctest introduction -julia> @syms x -(x,) - -julia> f = 64*x^34 - 21*x^47 - 126*x^8 - 46*x^5 - 16*x^60 - 81 - 60 47 34 8 5 -- 16⋅x - 21⋅x + 64⋅x - 126⋅x - 46⋅x - 81 - -julia> g = 72*x^60 - 25*x^25 - 19*x^23 - 22*x^39 - 83*x^52 + 54*x^10 + 81 - 60 52 39 25 23 10 -72⋅x - 83⋅x - 22⋅x - 25⋅x - 19⋅x + 54⋅x + 81 - -julia> h = 34*x^19 - 25*x^16 + 70*x^7 + 20*x^3 - 91*x - 86 - 19 16 7 3 -34⋅x - 25⋅x + 70⋅x + 20⋅x - 91⋅x - 86 -``` - -There are no common divisors of `f` and `g`, as `gcd` illustrates: - -```jldoctest introduction -julia> gcd(f,g) -1 - -``` - -Multiplying both terms by `h` and expanding gives a known g.c.d. of `h`, verified through: - -```jldoctest introduction -julia> gcd(expand(f*h), expand(g*h)) - h -0 - -``` - -The resultant of two polynomials is a polynomial expression of their coefficients that is equal to zero if and only if the polynomials have a common root : - -```jldoctest introduction -julia> sympy.resultant(expand(f*h), expand(g*h)) -0 -``` - -The following shows high-degree polynomials can easily be factored: - -```jldoctest introduction -julia> factor(expand(f*g)) |> println --(16*x^60 + 21*x^47 - 64*x^34 + 126*x^8 + 46*x^5 + 81)*(72*x^60 - 83*x^52 - 22*x^39 - 25*x^25 - 19*x^23 + 54*x^10 + 81) - -``` - - -Similar functionality extends to multivariable polynomials - -```jldoctest introduction -julia> @syms x y z -(x, y, z) - -julia> f = 24*x*y^19*z^8 - 47*x^17*y^5*z^8 + 6*x^15*y^9*z^2 - 3*x^22 + 5 - 22 17 5 8 15 9 2 19 8 -- 3⋅x - 47⋅x ⋅y ⋅z + 6⋅x ⋅y ⋅z + 24⋅x⋅y ⋅z + 5 - -julia> g = 34*x^5*y^8*z^13 + 20*x^7*y^7*z^7 + 12*x^9*y^16*z^4 + 80*y^14*z - 9 16 4 7 7 7 5 8 13 14 -12⋅x ⋅y ⋅z + 20⋅x ⋅y ⋅z + 34⋅x ⋅y ⋅z + 80⋅y ⋅z - -julia> h = 11*x^12*y^7*z^13 - 23*x^2*y^8*z^10 + 47*x^17*y^5*z^8 - 17 5 8 12 7 13 2 8 10 -47⋅x ⋅y ⋅z + 11⋅x ⋅y ⋅z - 23⋅x ⋅y ⋅z - -julia> gcd(expand(f*h), expand(g*h)) - h -0 - -julia> factor(expand(f*g)) |> println --2*y^7*z*(6*x^9*y^9*z^3 + 10*x^7*z^6 + 17*x^5*y*z^12 + 40*y^7)*(3*x^22 + 47*x^17*y^5*z^8 - 6*x^15*y^9*z^2 - 24*x*y^19*z^8 - 5) -``` - -Symbolic powers also can be used: - -```jldoctest introduction -julia> @syms x::real, n::integer -(x, n) - -julia> gcd(x^n - x^(2*n), x^n) |> println -x^n - -julia> sympy.resultant(3*x^4 + 3*x^3 + x^2 - x - 2, x^3 - 3*x^2 + x + 5) -0 - -``` - -Factoring can be done over different fields, not just the rationals. This shows factoring over complex terms: - -```jldoctest introduction -julia> f = 4*x^4 + 8*x^3 + 77*x^2 + 18*x + 153 - 4 3 2 -4⋅x + 8⋅x + 77⋅x + 18⋅x + 153 - -julia> factor(f, gaussian=true) - ⎛ 3⋅ⅈ⎞ ⎛ 3⋅ⅈ⎞ -4⋅⎜x - ───⎟⋅⎜x + ───⎟⋅(x + 1 - 4⋅ⅈ)⋅(x + 1 + 4⋅ⅈ) - ⎝ 2 ⎠ ⎝ 2 ⎠ - -julia> factor(f, extension=sympy.I) - ⎛ 3⋅ⅈ⎞ ⎛ 3⋅ⅈ⎞ -4⋅⎜x - ───⎟⋅⎜x + ───⎟⋅(x + 1 - 4⋅ⅈ)⋅(x + 1 + 4⋅ⅈ) - ⎝ 2 ⎠ ⎝ 2 ⎠ -``` - -In the following we make a variable for a symbolic $\sqrt{2}$: - -```jldoctest introduction -julia> const φ = sqrt(Sym(2)) -√2 - -julia> f = x^3 + (φ - 2)*x^2 - (2*φ + 3)*x - 3*φ - 3 2 -x + x ⋅(-2 + √2) - x⋅(2⋅√2 + 3) - 3⋅√2 - -julia> g = x^2 - 2 - 2 -x - 2 -``` - -Trying to cancel `f/g` leaves the expression unchanged; to make `cancel` recognize algebraic properties of $\sqrt{2}$ -the `extension` keyword is needed: - -```jldoctest introduction -julia> cancel(f/g) |> println -(x^3 - 2*x^2 + sqrt(2)*x^2 - 3*x - 2*sqrt(2)*x - 3*sqrt(2))/(x^2 - 2) - -julia> cancel(f/g, extension=true) |> println -(x^2 - 2*x - 3)/(x - sqrt(2)) - -julia> cancel(f/g, extension=φ) |> println -(x^2 - 2*x - 3)/(x - sqrt(2)) - -``` - -Factoring over modular integers (prime only) is possible using the keyword `modulus`: - -```jldoctest introduction -julia> f = x^4 - 3*x^2 + 1; - -julia> factor(f) |> println -(x^2 - x - 1)*(x^2 + x - 1) - -julia> factor(f, modulus=2) |> println -(x^2 + x + 1)^2 - -julia> factor(f, modulus=5) |> println -(x - 2)^2*(x + 2)^2 - -``` - -## The Poly class in SymPy - -The expression `x^4 - 3x^2 + 1` is stored internally as other expressions are, using the expression tree to build up from the atoms. However, for polynomials, more efficient and advantageous representations are possible. The dense polynomial representation is possible by storing just the coefficients relative to a known basis. For example: - -```julia -julia> f = x^4 - 2x^2 + 1 - 4 2 -x - 2⋅x + 1 - -julia> println(sympy.srepr(f)) -Add(Pow(Symbol('x'), Integer(4)), Mul(Integer(-1), Integer(2), Pow(Symbol('x'), Integer(2))), Integer(1)) - -julia> p = sympy.Poly(f, x) -Poly(x**4 - 2*x**2 + 1, x, domain='ZZ') - -julia> p.rep -PyObject DMP([mpz(1), mpz(0), mpz(-2), mpz(0), mpz(1)], ZZ, None) - -``` - -Storing `p` using just coefficients may be more efficient for many task, such as addition and multiplication, but not, as it will be much more efficient to store `f^100` than `p^100`. - -One advantage of the `Poly` class is specific methods become available as the structure is assumed. For example, we will see the `coeffs` method used to extract the coefficients. - -The domain shown when `p` is displayed refers to assumptions on the coefficients. Above is a symbol for integers, `QQ` is for rational numbers, `RR` for real numbers, `CC` for complex (along with other variants, like `ZZ_I` for complex integers. - - - -## Coefficients - -Returning to polynomials, there are a few functions to find various pieces of the polynomials. First we make a general quadratic polynomial: - -```jldoctest introduction -julia> @syms a,b,c,x -(a, b, c, x) - -julia> p = a*x^2 + b*x + c - 2 -a⋅x + b⋅x + c - -``` - -If given a polynomial, like `p`, there are different means to extract the coefficients: - -* SymPy provides a `coeffs` method for `Poly` objects, but `p` must first be converted to one. - -* SymPy provides the `coeff` method for expressions, which allows extration of a coeffiecient for a given monomial - - - - -The `ex.coeff(monom)` call will return the corresponding coefficient of the monomial: - -```jldoctest introduction -julia> p.coeff(x^2) # a -a - -julia> p.coeff(x) # b -b - -``` - -The constant can be found through substitution: - -```jldoctest introduction -julia> p(x=>0) -c - -``` - -Though one could use some trick like this to find all the coefficients: - -```jldoctest introduction -julia> Sym[[p.coeff(x^i) for i in N(degree(p,gen=x)):-1:1]; p(x=>0)] -3-element Vector{Sym}: - a - b - c - -``` - -that is cumbersome, at best. SymPy has a function `coeffs`, but it is defined for polynomial types, so will fail on `p`: - - -```jldoctest introduction -julia> try p.coeffs() catch err "ERROR: KeyError: key `coeffs` not found" end # wrap p.coeffs() for doctest of error -"ERROR: KeyError: key `coeffs` not found" -``` - -Polynomials are a special class in SymPy and must be constructed. The `Poly` constructor can be used. As there is more than one free variable in `p`, we specify the variable `x` below: - -```jldoctest introduction -julia> q = sympy.Poly(p, x) -Poly(a*x**2 + b*x + c, x, domain='ZZ[a,b,c]') - -julia> q.coeffs() -3-element Vector{Sym}: - a - b - c - -``` - -!!! note - - The `Poly` constructor from SymPy is *not* a function, so is not exported when `SymPy` is loaded. To access it, the object must be qualified by its containing module, in this case `Poly`. Were it to be used frequently, an alias could be used, as in `const Poly=sympy.Poly` *or* the `import_from` function, as in `import_from(sympy, :Poly)`. The latter has some attempt to avoid naming collisions. - - -## Polynomial roots: solve, real_roots, polyroots, nroots - -SymPy provides functions to find the roots of a polynomial. In -general, a polynomial with real coefficients of degree $n$ will have -$n$ roots when multiplicities and complex roots are accounted for. The -number of real roots is consequently between $0$ and $n$. - -For a *univariate* polynomial expression (a single variable), the real -roots, when available, are returned by `real_roots`. For example, - -```jldoctest introduction -julia> real_roots(x^2 - 2) -2-element Vector{Sym}: - -√2 - √2 - -``` - -Unlike `factor` -- which only factors over rational factors -- -`real_roots` finds the two irrational roots here. It is well known -(the -[Abel-Ruffini theorem](http://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem)) -that for degree 5 polynomials, or higher, it is not always possible to -express the roots in terms of radicals. However, when the roots are -rational `SymPy` can have success: - - -```jldoctest introduction -julia> p = (x-3)^2*(x-2)*(x-1)*x*(x+1)*(x^2 + x + 1); println(p) -x*(x - 3)^2*(x - 2)*(x - 1)*(x + 1)*(x^2 + x + 1) - -julia> real_roots(p) -6-element Vector{Sym}: - -1 - 0 - 1 - 2 - 3 - 3 - -``` - -!!! note "Why `println`?" - The uses of `println(p)` above and elsewhere throughout the introduction is only for technical reasons related to doctesting and how `Documenter.jl` parses the expected output. This usage is not idiomatic, or suggested; it only allows the cell to be tested programatically for regressions. Similarly, expected errors are wrapped in `try`-`catch` blocks just for testing purposes. - - -In this example, the degree of `p` is 8, but only the 6 real roots -returned, the double root of $3$ is accounted for. The two complex -roots of `x^2 + x+ 1` are not considered by this function. The complete set -of distinct roots can be found with `solve`: - -```jldoctest introduction -julia> solve(p) -7-element Vector{Sym}: - -1 - 0 - 1 - 2 - 3 - -1/2 - sqrt(3)*I/2 - -1/2 + sqrt(3)*I/2 - -``` - -This finds the complex roots, but does not account for the double -root. The `roots` function of SymPy does. - - - -The output of calling `roots` will be a dictionary whose keys are the roots and values the multiplicity. - -```julia -julia> roots(p) -Dict{Any, Any} with 7 entries: - -1 => 1 - 3 => 2 - 1 => 1 - 0 => 1 - -1/2 - sqrt(3)*I/2 => 1 - 2 => 1 - -1/2 + sqrt(3)*I/2 => 1 - -``` - -When exact answers are not provided, the `roots` call is contentless: - -```jldoctest introduction -julia> p = x^5 - x + 1 - 5 -x - x + 1 - -julia> sympy.roots(p) -Dict{Any, Any}() - -``` - -Calling `solve` seems to produce very little as well: - -```jldoctest introduction -julia> rts = solve(p) -5-element Vector{Sym}: - CRootOf(x^5 - x + 1, 0) - CRootOf(x^5 - x + 1, 1) - CRootOf(x^5 - x + 1, 2) - CRootOf(x^5 - x + 1, 3) - CRootOf(x^5 - x + 1, 4) - -``` - -But in fact, `rts` contains lots of information. We can extract numeric values quite easily with `N`: - -```jldoctest introduction -julia> N.(rts) -5-element Vector{Number}: - -1.167303978261418684256045899854842180720560371525489039140082449275651903429536 - -0.18123244446987538 - 1.0839541013177107im - -0.18123244446987538 + 1.0839541013177107im - 0.7648844336005847 - 0.35247154603172626im - 0.7648844336005847 + 0.35247154603172626im - -``` - -These are numeric approximations to irrational values. For numeric -approximations to polynomial roots, the `nroots` function is also -provided. The answers are still symbolic: - -```jldoctest introduction -julia> nroots(p) -5-element Vector{Sym}: - -1.16730397826142 - -0.181232444469875 - 1.08395410131771⋅ⅈ - -0.181232444469875 + 1.08395410131771⋅ⅈ - 0.764884433600585 - 0.352471546031726⋅ⅈ - 0.764884433600585 + 0.352471546031726⋅ⅈ - -``` - -## The solve function - -The `solve` function is more general purpose than just finding roots of univariate polynomials. The function tries to solve for when an expression is 0, or a set of expressions are all 0. - -For example, it can be used to solve when $\cos(x) = \sin(x)$: - -```jldoctest introduction -julia> solve(cos(x) - sin(x)) -1-element Vector{Sym}: - pi/4 - -``` - -Though there are infinitely many correct solutions, these are within a certain range. - -!!! note "Using `~` to specify an equation" - The above solved an equation $\cos(x) = \sin(x)$ by subtracting and solving the mathematically equivalent $\cos(x) - \sin(x) = 0$, the $0$ being assumed by `SymPy` when an explicit equation is not specified. Alternatively, the `~` can be used in place of `=` when representing an equation, as in `solve(cos(x) ~ sin(x), x)`. - - - -The -[solveset](http://docs.sympy.org/latest/modules/solvers/solveset.html) -function appeared in version 1.0 of SymPy and is an intended -replacement for `solve`. Here we see it describes all solutions: - -```jldoctest introduction -julia> u = solveset(cos(x) ~ sin(x)) -⎧ 5⋅π │ ⎫ ⎧ π │ ⎫ -⎨2⋅n⋅π + ─── │ n ∊ ℤ⎬ ∪ ⎨2⋅n⋅π + ─ │ n ∊ ℤ⎬ -⎩ 4 │ ⎭ ⎩ 4 │ ⎭ - -``` - -The output of `solveset` is a set, rather than a vector or -dictionary. To get the values requires some work. For *finite sets* we collect the elements -with `collect`, but first we must convert to a `Julia` `Set`: - -```jldoctest introduction -julia> v = solveset(x^2 ~ 4, x) -{-2, 2} - -julia> collect(Set(v...)) -2-element Vector{Any}: - 2 - -2 - -``` - -This composition is done in the `elements` function: - -```jldoctest introduction -julia> elements(v) -2-element Vector{Sym}: - 2 - -2 - -``` - -The `elements` function does not work for more complicated (non-finite) sets, such as `u`. For these, the `contains` method may be useful to query the underlying elements. - -```jldoctest introduction -julia> solveset(cos(x) ~ sin(x), x).contains(PI/4) -True -``` - -The output is a symbolic `True`. This can be converted via `N` or compared to `rrue`, but not directly used within a conditional argument: - -```jldoctest introduction -julia> N(True), True == true, True === true -(true, true, false) - -``` - ----- - -Solving within Sympy has limits. For example, there is no symbolic solution here: - -```jldoctest introduction -julia> try solve(cos(x) - x) catch err "error" end # wrap command for doctest of error -"error" -``` - -(And hence the error message generated.) - -For such an equation, a numeric method would be needed, similar to the `Roots` package. For example: - -```jldoctest introduction -julia> nsolve(cos(x) - x, 1) ≈ 0.73908513321516064165 -true -``` - -Though it can't solve everything, the `solve` function can also solve -equations of a more general type. For example, here it is used to -derive the quadratic equation: - -```jldoctest introduction -julia> @syms a::real, b::real, c::real -(a, b, c) - -julia> p = a*x^2 + b*x + c - 2 -a⋅x + b⋅x + c -``` - -``` -julia> solve(p, x) -2-element Vector{Sym}: - (-b + sqrt(-4*a*c + b^2))/(2*a) - -(b + sqrt(-4*a*c + b^2))/(2*a) - -``` - -The extra argument `x` is passed to `solve` so that `solve` knows -which variable to solve for. - -The `solveset` function is similar: - -```jldoctest introduction -julia> solveset(p, x) -⎧ _____________ _____________⎫ -⎪ ╱ 2 ╱ 2 ⎪ -⎨ b ╲╱ -4⋅a⋅c + b b ╲╱ -4⋅a⋅c + b ⎬ -⎪- ─── - ────────────────, - ─── + ────────────────⎪ -⎩ 2⋅a 2⋅a 2⋅a 2⋅a ⎭ - -``` - - -If the `x` value is not given, `solveset` will error and `solve` will try to find a -solution over all the free variables: - -``` -julia> solve(p) -1-element Vector{Dict{Any, Any}}: - Dict(a => -(b*x + c)/x^2) -``` - -Systems of equations can be solved as well. For example, to solve this -linear system: $2x + 3y = 6, 3x - 4y=12$, we have: - -```jldoctest introduction -julia> @syms x::real, y::real -(x, y) - -julia> exs = (2x+3y ~ 6, 3x-4y ~ 12) -(Eq(2*x + 3*y, 6), Eq(3*x - 4*y, 12)) - -``` - -```jldoctest introduction -julia> d = solve(exs); # Dict(x=>60/17, y=>-6/17) -``` - -!!! note - We formed a tuple of equations above. A matrix of equations is now deprecated within SymPy and the automatic conversion of `PyCall` does that conversion. - - -We can "check our work" by plugging into each equation. We take advantage of how the `subs` function, used implicitly, allows us to pass in a dictionary: - -```jldoctest introduction -julia> [ex(d) for ex ∈ exs] -2-element Vector{Sym}: - True - True - -``` - -The more `Julia`n way to solve a linear equation, like this would be as follows: - -```jldoctest introduction -julia> A = Sym[2 3; 3 -4]; b = Sym[6, 12] -2-element Vector{Sym}: - 6 - 12 - -julia> A \ b -2-element Vector{Sym}: - 60/17 - -6/17 -``` - -!!! note - Rather than use a generic `lu` solver through `Julia` (which proved slow for larger systems), the `\` operator utilizes `solve` to perform this computation. - - - -In the previous example, the system had two equations and two -unknowns. When that is not the case, one can specify the variables to -solve for in a tuple. In this example, we find a quadratic polynomial -that approximates $\cos(x)$ near $0$: - -```julia -julia> a,b,c,h = symbols("a,b,c,h", real=true) -(a, b, c, h) - -julia> p = a*x^2 + b*x + c - 2 -a⋅x + b⋅x + c - -julia> fn = cos -cos (generic function with 14 methods) - -julia> exs = [fn(0*h)-p(x => 0), fn(h)-p(x => h), fn(2h)-p(x => 2h)] -3-element Vector{Sym}: - 1 - c - -a*h^2 - b*h - c + cos(h) - -4*a*h^2 - 2*b*h - c + cos(2*h) - -julia> d = solve(exs, (a,b,c)) -Dict{Any, Any} with 3 entries: - a => -cos(h)/h^2 + cos(2*h)/(2*h^2) + 1/(2*h^2) - c => 1 - b => 2*cos(h)/h - cos(2*h)/(2*h) - 3/(2*h) - -``` - -Again, a dictionary is returned. The polynomial itself can be found by -substituting back in for `a`, `b`, and `c`: - -```julia -julia> quad_approx = p.subs(d); println(quad_approx) -x^2*(-cos(h)/h^2 + cos(2*h)/(2*h^2) + 1/(2*h^2)) + x*(2*cos(h)/h - cos(2*h)/(2*h) - 3/(2*h)) + 1 - -``` - -Taking the "limit" as $h$ goes to $0$ produces the answer $1 - x^2/2$, as will be shown later. - -Finally for `solve`, we show one way to re-express the polynomial $a_2x^2 + a_1x + a_0$ -as $b_2(x-c)^2 + b_1(x-c) + b_0$ using `solve` (and not, say, an -expansion theorem.) - -```julia -julia> n = 3 -3 - -julia> @syms x, c -(x, c) - -julia> @syms as[1:3] -(Sym[as₁, as₂, as₃],) - -julia> @syms bs[1:3] -(Sym[bs₁, bs₂, bs₃],) - -julia> p = sum([as[i+1]*x^i for i in 0:(n-1)]); - -julia> q = sum([bs[i+1]*(x-c)^i for i in 0:(n-1)]); - -julia> solve(p ~ q, bs) -Dict{Any, Any} with 3 entries: - bs₁ => as₁ + as₂*c + as₃*c^2 - bs₂ => as₂ + 2*as₃*c - bs₃ => as₃ - -``` - - -### Solving using logical operators - -The `solve` function does not need to just solve `ex = 0`. There are other means to specify an equation. Ideally, it would be nice to say `ex1 == ex2`, but the interpretation of `==` is not for this. Rather, `SymPy` introduces `Eq` for equality. So this expression - -```jldoctest introduction -julia> solve(Eq(x, 1)) -1-element Vector{Sym}: - 1 - -``` - -gives 1, as expected from solving `x == 1`. - -The previously used infix operator `~` simply calls `Eq` to construct an equality. - -In addition to `Eq`, there are `Lt`, `Le`, `Ge`, `Gt`. The Unicode -operators (e.g., `\leq` and not `\leq`) are not aliased to these, but there are alternatives -`\ll[tab]`, `\leqq[tab]`, `\Equal[tab]`, `\geqq[tab]`, `\gg[tab]` and -`\neg[tab]` to negate. - -So, the above could have been written with the following nearly identical expression, though it is entered with `\Equal[tab]`: - -```jldoctest introduction -julia> solve(x ⩵ 1) -1-element Vector{Sym}: - 1 - -``` - -The infix tilde, `~`, consistent with the interface from `Symbolics` , is recommended for readability over `⩵`. - -## Plotting - -The `Plots` package allows many 2-dimensional plots of `SymPy` objects -to be agnostic as to a backend plotting package. `SymPy` provides -recipes that allow symbolic expressions to be used where functions are -part of the `Plots` interface. -[See the help page for `sympy_plotting`.] - -In particular, the following methods of `plot` are defined: - -* `plot(ex::Sym, a, b)` will plot the expression of single variable over the interval `[a,b]` -* `plot!(ex::Sym, a, b)` will add to the current plot a plot of the expression of single variable over the interval `[a,b]`, or, when not specified, the current plotting limits -* `plot(ex1, ex2, a, b)` will plot a parametric plot of the two expressions over the interval `[a,b]`. -* `contour(xs, ys, ex::Sym)` will make a contour plot of the expression of two variables over the grid specifed by the `xs` and `ys`. -* `surface(xs, ys, ex::Sym)` will make a surface plot of the expression of two variables over the grid specifed by the `xs` and `ys`. - - -For example: - -```@example plots -using SymPy, Plots -@syms x -plot(x^2 - 2, -2, 2) -savefig("plot-1.svg"); nothing # hide -``` - -![](plot-1.svg) - -Or a parametric plot: - -```@example plots -plot(sin(2x), cos(3x), 0, 4pi); -savefig("plot-2.svg"); nothing # hide -``` - -![](plot-2.svg) - -For plotting with other plotting packages, it is generally faster to -first call `lambdify` on the expression and then generate `y` values -with the resulting `Julia` function. An example might follow this pattern: - -```@example plots -ex = cos(x)^2 + cos(x^2) -fn = lambdify(ex) -xs = range(0, stop=10, length=256) -plot(xs, fn.(xs)) -savefig("plot-3.svg"); nothing #hide -``` - -![](plot-3.svg) - ----- - -In addition, with `PyPlot` a few other plotting functions from `SymPy` are available from its interface to `MatplotLib`: - -* `plot3d_parametric_surface(ex1::Sym, ex2::Sym, ex3::Sym), (uvar, a0, - b0), (vvar, a1, b1))` -- make a surface plot of the expressions - parameterized by the region `[a0,b0] x [a1,b1]`. The default region - is `[-5,5]x[-5,5]` where the ordering of the variables is given by - `free_symbols(ex)`. - -* `plot_implicit(predictate, (xvar, a0, b0), (yvar, a1, b1))` -- make -an implicit equation plot of the expressions over the region `[a0,b0] -x [a1,b1]`. The default region is `[-5,5]x[-5,5]` where the ordering -of the variables is given by `free_symbols(ex)`. To create predicates -from the variable, the functions `Lt`, `Le`, `Eq`, `Ge`, and `Gt` can -be used, as with `Lt(x*y, 1)`. For infix notation, unicode operators -can be used: `\ll`, `\leqq`, `\Equal`, `\geqq`, and -`\gg`. For example, `x*y ≪ 1`. To combine terms, the unicode -`\vee` (for "or"), `\wedge` (for "and") can be used. - - - -## Calculus - -`SymPy` has many of the basic operations of calculus provided through a relatively small handful of functions. - -### Limits - -Limits are computed by the `limit` function which takes an expression, a variable and a value, and optionally a direction specified by either `dir="+"` or `dir="-"`. - -For example, this shows Gauss was right: - -```jldoctest introduction -julia> limit(sin(x)/x, x, 0) -1 - -``` - -Alternatively, the second and third arguments can be specified as a pair: - -```jldoctest introduction -julia> limit(sin(x)/x, x => 0) -1 - -``` - -Limits at infinity are done by using `oo` for $\infty$: - -```jldoctest introduction -julia> limit((1+1/x)^x, x => oo) -ℯ - -``` - - -This example computes what L'Hopital reportedly paid a Bernoulli for - -```jldoctest introduction -julia> @syms a::positive -(a,) - -julia> ex = (sqrt(2a^3*x-x^4) - a*(a^2*x)^(1//3)) / (a - (a*x^3)^(1//4)); println(ex) -(-a^(5/3)*x^(1/3) + sqrt(2*a^3*x - x^4))/(-a^(1/4)*(x^3)^(1/4) + a) - -``` - -Substituting $x=a$ gives an indeterminate form: - -```jldoctest introduction -julia> ex(x=>a) -nan - -``` - -We can see it is of the form $0/0$: - -```jldoctest introduction -julia> denom(ex)(x => a), numer(ex)(x => a) -(0, 0) - -``` - -And we get - -```jldoctest introduction -julia> limit(ex, x => a) -16⋅a -──── - 9 - -``` - -In a previous example, we defined `quad_approx`: - -```julia -julia> quad_approx |> println -x^2*(-cos(h)/h^2 + cos(2*h)/(2*h^2) + 1/(2*h^2)) + x*(2*cos(h)/h - cos(2*h)/(2*h) - 3/(2*h)) + 1 - -``` - -The limit as `h` goes to $0$ gives `1 - x^2/2`, as expected: - -```julia -julia> limit(quad_approx, h => 0) - 2 - x -1 - ── - 2 - -``` - -#### Left and right limits - -The limit is defined when both the left and right limits exist and are equal. But left and right limits can exist and not be equal. The `sign` function is $1$ for positive $x$, $-1$ for negative $x$ and $0$ when $x$ is 0. It should not have a limit at $0$: - -```jldoctest introduction -julia> limit(sign(x), x => 0) -1 - -``` - -Oops. Well, the left and right limits are different anyways: - -```jldoctest introduction -julia> limit(sign(x), x => 0, dir="-"), limit(sign(x), x => 0, dir="+") -(-1, 1) - -``` - -The `limit` function finds the *right* limit by default. The direction `"+-"` will check both, erroring if the two do not agree, as below with a message of "ValueError('The limit does not exist since left hand limit = -1 and right hand limit = 1')" - -```jldoctest introduction -julia> try(limit(sign(x), x => 0, dir="+-")) catch err "error" end -"error" - -``` - - -#### Numeric limits - -The `limit` function uses the -[Gruntz](http://docs.sympy.org/latest/modules/series.html#the-gruntz-algorithm) -algorithm. It is far more reliable then simple numeric attempts at -limits. An example of Gruntz is the right limit at $0$ of the -function: - -```jldoctest introduction -julia> j(x) = 1/x^(log(log(log(log(1/x)))) - 1) -j (generic function with 1 method) - -``` - -A numeric attempt might be done along these lines: - -```julia -julia> hs = [10.0^(-i) for i in 6:16] -11-element Vector{Float64}: - 1.0e-6 - 1.0e-7 - 1.0e-8 - 1.0e-9 - 1.0e-10 - 1.0e-11 - 1.0e-12 - 1.0e-13 - 1.0e-14 - 1.0e-15 - 1.0e-16 - -julia> ys = [r(h) for h in hs] -11-element Vector{Float64}: - 6.146316238971239e-7 - 1.4298053954169988e-7 - 3.4385814272678773e-8 - 8.529918929292077e-9 - 2.176869418153584e-9 - 5.700972891527026e-10 - 1.528656750900649e-10 - 4.188388514215749e-11 - 1.1705748589577942e-11 - 3.331965462828263e-12 - 9.64641441953344e-13 - -julia> [hs ys] -11×2 Matrix{Float64}: - 1.0e-6 6.14632e-7 - 1.0e-7 1.42981e-7 - 1.0e-8 3.43858e-8 - 1.0e-9 8.52992e-9 - 1.0e-10 2.17687e-9 - 1.0e-11 5.70097e-10 - 1.0e-12 1.52866e-10 - 1.0e-13 4.18839e-11 - 1.0e-14 1.17057e-11 - 1.0e-15 3.33197e-12 - 1.0e-16 9.64641e-13 - -``` - -With a values appearing to approach $0$. However, in fact these values will ultimately head off to $\infty$: - -```jldoctest introduction -julia> limit(j(x), x => 0, dir="+") -∞ - -``` - - -### Derivatives - -One *could* use limits to implement the definition of a derivative: - -```jldoctest introduction -julia> @syms x::real, h::real -(x, h) - -julia> j(x) = x * exp(x) -j (generic function with 1 method) - -julia> limit((j(x+h) - j(x)) / h, h => 0) |> println -x*exp(x) + exp(x) - -``` - -However, it would be pretty inefficient, as `SymPy` already does a great job with derivatives. The `diff` function implements this. The basic syntax is `diff(ex, x)` to find the first derivative in `x` of the expression in `ex`, or its generalization to $k$th derivatives with `diff(ex, x, k)`. - -The same derivative computed above by a limit could be found with: - -```jldoctest introduction -julia> diff(j(x), x) - x x -x⋅ℯ + ℯ - -``` - -Similarly, we can compute other derivatives: - -```jldoctest introduction -julia> diff(x^x, x) - x -x ⋅(log(x) + 1) - -``` - -Or, higher order derivatives: - -```jldoctest introduction -julia> diff(exp(-x^2), (x, 2)) |> println -2*(2*x^2 - 1)*exp(-x^2) - -``` - -As an alternate to specifying the number of derivatives, multiple variables can be passed to `diff`: - -```jldoctest introduction -julia> diff(exp(-x^2), x, x, x) |> println # same as diff(..., (x, 3)) -4*x*(3 - 2*x^2)*exp(-x^2) - -``` - -This could include variables besides `x`, as is needed with mixed partial derivatives. - - -The output is a simple expression, so `diff` can be composed with -other functions, such as `solve`. For example, here we find the -critical points where the derivative is $0$ of some rational function: - -```jldoctest introduction -julia> j(x) = (12x^2 - 1) / (x^3) -j (generic function with 1 method) - -julia> diff(j(x), x) |> solve -2-element Vector{Sym}: - -1/2 - 1/2 - -``` - - -#### Partial derivatives - -The `diff` function makes finding partial derivatives as easy as specifying the variable to differentiate in. This example computes the mixed partials of an expression in `x` and `y`: - -```jldoctest introduction -julia> @syms x y -(x, y) - -julia> ex = x^2*cos(y) - 2 -x ⋅cos(y) - -julia> [diff(ex, v1, v2) for v1 in [x,y], v2 in [x,y]] # also hessian(ex, (x,y)) -2×2 Matrix{Sym}: - 2⋅cos(y) -2⋅x⋅sin(y) - -2⋅x⋅sin(y) -x^2*cos(y) - -``` - - -#### Unevaluated derivatives - -The `Derivative` constructor provides unevaluated derivatives, useful with differential equations and the output for unknown functions. Here is an example: - -```jldoctest introduction -julia> ex = sympy.Derivative(exp(x*y), x, (y, 2)) - 3 - ∂ ⎛ x⋅y⎞ -──────⎝ℯ ⎠ - 2 -∂y ∂x - -``` - -These expressions are evaluated with the `doit` method: - -```jldoctest introduction -julia> ex.doit() |> println -x*(x*y + 2)*exp(x*y) - -``` - -#### Implicit derivatives - -SymPy can be used to find derivatives of implicitly defined -functions. For example, the task of finding $dy/dx$ for the equation: - -$$~ -y^4 - x^4 -y^2 + 2x^2 = 0 -~$$ - -As with the mathematical solution, the key is to treat one of the variables as depending on the other. In this case, we think of $y$ locally as a function of $x$. SymPy allows us to create symbolic functions, and we will use one to substitute in for `y`. - - -In SymPy, symbolic functions use the class name "Function", but in `SymPy` we use `SymFunction` to avoid a name collision with one of `Julia`'s primary types. The constructor can be used as `SymFunction(:F)`: - -```jldoctest introduction -julia> F, G = SymFunction("F"), SymFunction("G") -(F, G) - -``` - -The `@syms` macro can also more naturally be used, in place of `SymFunction`: - -```jldoctest introduction -julia> @syms F(), G() -(F, G) - -``` - - -We can call these functions, but we get a function expression: - -```jldoctest introduction -julia> F(x) -F(x) - -``` - -SymPy can differentiate symbolically, again with `diff`: - -```jldoctest introduction -julia> diff(F(x)) -d -──(F(x)) -dx - -``` - -To get back to our problem, we have our expression: - -```jldoctest introduction -julia> @syms x, y -(x, y) - -julia> ex = y^4 - x^4 - y^2 + 2x^2 - 4 2 4 2 -- x + 2⋅x + y - y - -``` - -Now we substitute: - -```jldoctest introduction -julia> ex1 = ex(y=>F(x)) - 4 2 4 2 -- x + 2⋅x + F (x) - F (x) - -``` - -We want to differentiate "both" sides. As the right side is just $0$, there isn't anything to do here, but mentally keep track. As for the left we have: - -```jldoctest introduction -julia> ex2 = diff(ex1, x) - 3 3 d d -- 4⋅x + 4⋅x + 4⋅F (x)⋅──(F(x)) - 2⋅F(x)⋅──(F(x)) - dx dx - -``` - -Now we collect terms and solve in terms of $F'(x)$ - -```jldoctest introduction -julia> ex3 = solve(ex2, F'(x))[1] - 3 - 2⋅x - 2⋅x -────────────── - 3 -2⋅F (x) - F(x) - -``` - -Finally, we substitute back into the solution for $F(x)$: - -```jldoctest introduction -julia> ex4 = ex3(F(x) => y) - 3 -2⋅x - 2⋅x -────────── - 3 - 2⋅y - y - -``` - -###### Example: A Norman Window - -A classic calculus problem is to maximize the area of a -[Norman window](http://en.wiktionary.org/wiki/Norman_window) (in the -shape of a rectangle with a half circle atop) when the perimeter is -fixed to be $P \geq 0$. - - -Label the rectangle with $w$ and $h$ for width and height and then the -half circle has radius $r=w/2$. With this, we can see that the area is -$wh+(1/2)\pi r^2$ and the perimeter is $w + 2h + \pi r$. This gives: - -```jldoctest introduction -julia> @syms w::nonnegative, h::nonnegative, P::nonnegative -(w, h, P) - -julia> r = w/2 -w -─ -2 - -julia> A = w*h + 1//2 * (pi * r^2); println(A) -h*w + pi*w^2/8 - -julia> p = w + 2h + pi*r; println(p) -2*h + w + pi*w/2 - -``` - -(There is a subtlety above: using `1//2*pi*r^2` will lose exactness, as -the products will be done left to right, and `1//2*pi` will be -converted to an approximate floating point value before multiplying -`r^2`. As such we rewrite the terms. It may be easier to use `PI` -instead of `pi`.) - -We want to solve for `h` from when `p=P` (our fixed value) and -substitute back into `A`. We solve taking the first solution. - -```jldoctest introduction -julia> h0 = solve(p ~ P, h)[1] -P π⋅w w -─ - ─── - ─ -2 4 2 - -julia> A1 = A(h => h0) - 2 -π⋅w ⎛P π⋅w w⎞ -──── + w⋅⎜─ - ─── - ─⎟ - 8 ⎝2 4 2⎠ - -``` - -Now we note this is a parabola in `w`, so any maximum will be at an -endpoint or the vertex, provided the leading term is negative. -The leading term can be found through: - -```jldoctest introduction -julia> sympy.Poly(A1, w).coeffs() -2-element Vector{Sym}: - -1/2 - pi/8 - P/2 - -``` - -Or without using the `Poly` methods, we could do this: - -```jldoctest introduction -julia> collect(expand(A1), w).coeff(w^2) - 1 π -- ─ - ─ - 2 8 - -``` - -Either way, the leading coefficient, $-1/2 - \pi/8$, is negative, so -the maximum can only happen at an endpoint or the vertex of the -parabola. Now we check that when $w=0$ (the left endpoint) the area is -$0$: - -```jldoctest introduction -julia> A1(w => 0) -0 - -``` - -The other endpoint is when $h=0$, or - -```jldoctest introduction -julia> b = solve((P-p)(h => 0), w)[1] - 2⋅P -───── -2 + π - -``` - -We will need to check the area at `b` and at the vertex. - -To find the vertex, we can use calculus -- it will be when the derivative in `w` is $0$: - -```jldoctest introduction -julia> c = solve(diff(A1, w), w)[1] - 2⋅P -───── -π + 4 - -``` - -The answer will be the larger of `A1` at `b` or `c`: - -```jldoctest introduction -julia> atb = A1(w => b); println(atb) -pi*P^2/(2*(2 + pi)^2) + 2*P*(-pi*P/(2*(2 + pi)) - P/(2 + pi) + P/2)/(2 + pi) - -julia> atc = A1(w => c); println(atc) -pi*P^2/(2*(pi + 4)^2) + 2*P*(-pi*P/(2*(pi + 4)) - P/(pi + 4) + P/2)/(pi + 4) - -``` - -A simple comparison isn't revealing: - -```jldoctest introduction -julia> atc - atb |> println --pi*P^2/(2*(2 + pi)^2) + pi*P^2/(2*(pi + 4)^2) - 2*P*(-pi*P/(2*(2 + pi)) - P/(2 + pi) + P/2)/(2 + pi) + 2*P*(-pi*P/(2*(pi + 4)) - P/(pi + 4) + P/2)/(pi + 4) - -``` - -But after simplifying, we can see that this expression is positive if $P$ is: - -```jldoctest introduction -julia> simplify(atc - atb) |> println -2*P^2/(16 + pi^3 + 20*pi + 8*pi^2) - -``` - -With this observation, we conclude the maximum area happens at `c` with area `atc`. - -### Integrals - -Integration is implemented in SymPy through the `integrate` function. There are two basic calls: -`integrate(f(x), x)` will find the indefinite integral ($\int f(x) dx$) and when endpoints are specified through `integrate(f(x), (x, a, b))` the definite integral will be found ($\int_a^b f(x) dx$). The special form `integrate(ex, x, a, b)` can be used for single integrals, but the specification through a tuple is needed for multiple integrals, so isn't illustrated here. - -Basic integrals are implemented: - -```jldoctest introduction -julia> integrate(x^3, x) - 4 -x -── -4 - -``` - -Or in more generality: - -```jldoctest introduction -julia> @syms n::real -(n,) - -julia> ex = integrate(x^n, x) -⎧ n + 1 -⎪x -⎪────── for n ≠ -1 -⎨n + 1 -⎪ -⎪log(x) otherwise -⎩ - -``` - -The output here is a *piecewise function*, performing a substitution will choose a branch in this case: - -```jldoctest introduction -julia> ex(n => 3) - 4 -x -── -4 - -``` - -Definite integrals are just as easy. Here is Archimedes' answer: - -```jldoctest introduction -julia> integrate(x^2, (x, 0, 1)) -1/3 - -``` - - -Tedious problems, such as those needing multiple integration-by-parts steps can be done easily: - -```julia -julia> integrate(x^5 * sin(x), x) - 5 4 3 2 -- x ⋅cos(x) + 5⋅x ⋅sin(x) + 20⋅x ⋅cos(x) - 60⋅x ⋅sin(x) - 120⋅x⋅cos(x) + 120⋅sin(x) - -``` - -The SymPy tutorial says: - -> "`integrate` uses powerful algorithms that are always improving to compute both definite and indefinite integrals, including heuristic pattern matching type algorithms, a partial implementation of the Risch algorithm, and an algorithm using Meijer G-functions that is useful for computing integrals in terms of special functions, especially definite integrals." - -The tutorial gives the following example: - -```julia -julia> ex = (x^4 + x^2*exp(x) - x^2 - 2*x*exp(x) - 2*x - exp(x))*exp(x)/((x - 1)^2*(x + 1)^2*(exp(x) + 1)) -⎛ 4 2 x 2 x x⎞ x -⎝x + x ⋅ℯ - x - 2⋅x⋅ℯ - 2⋅x - ℯ ⎠⋅ℯ -──────────────────────────────────────── - 2 2 ⎛ x ⎞ - (x - 1) ⋅(x + 1) ⋅⎝ℯ + 1⎠ - -``` - -With indefinite integral: - -```julia -julia> integrate(ex, x) |> println -log(exp(x) + 1) + exp(x)/(x^2 - 1) - -``` - -#### Multiple integrals - -The `integrate` function uses a tuple, `(var, a, b)`, to specify the limits of a definite integral. This syntax lends itself readily to multiple integration. - -For example, the following computes the integral of $xy$ over the unit square: - -```jldoctest introduction -julia> @syms x, y -(x, y) - -julia> integrate(x*y, (y, 0, 1), (x, 0, 1)) -1/4 - -``` - -The innermost terms can depend on outer ones. For example, the following integrates $x^2y$ over the upper half of the unit circle: - -```jldoctest introduction -julia> integrate(x^2*y, (y, 0, sqrt(1 - x^2)), (x, -1, 1)) -2/15 - -``` - - -#### Unevaluated integrals - -The `Integral` constructor can stage unevaluated integrals that will be evaluated by calling `doit`. It is also used when the output is unknown. This example comes from the tutorial: - -```jldoctest introduction -julia> integ = sympy.Integral(sin(x^2), x) -⌠ -⎮ ⎛ 2⎞ -⎮ sin⎝x ⎠ dx -⌡ - -``` - -```jldoctest introduction -julia> integ.doit() |> println -3*sqrt(2)*sqrt(pi)*fresnels(sqrt(2)*x/sqrt(pi))*gamma(3/4)/(8*gamma(7/4)) - -``` - - -### Taylor series - -The `series` function can compute series expansions around a point to a specified order. For example, -the following command finds four terms of the series expansion of `exp(sin(x))` in `x` about $c=0$: - -```jldoctest introduction -julia> s1 = series(exp(sin(x)), x, 0, 4); println(s1) -1 + x + x^2/2 + O(x^4) - -``` - -The coefficients are from the Taylor expansion ($a_i=f^{i}(c)/i!$). The -[big "O"](http://en.wikipedia.org/wiki/Big_O_notation) term indicates -that the remainder is no bigger in size than a constant times $x^4$, as $x\rightarrow 0$. - - -Consider what happens when we multiply series of different orders: - -```jldoctest introduction -julia> s2 = series(cos(exp(x)), x, 0, 6); println(s2) -cos(1) - x*sin(1) + x^2*(-sin(1)/2 - cos(1)/2) - x^3*cos(1)/2 + x^4*(-cos(1)/4 + 5*sin(1)/24) + x^5*(-cos(1)/24 + 23*sin(1)/120) + O(x^6) - -``` - -```jldoctest introduction -julia> simplify(s1 * s2) |> println -cos(1) + sqrt(2)*x*cos(pi/4 + 1) - 3*x^2*sin(1)/2 - sqrt(2)*x^3*sin(pi/4 + 1) + O(x^4) - -``` - -The big "O" term is $x^4$, as smaller order terms in `s2` are covered in this term. The big "O" notation is sometimes not desired, in which case the `removeO` function can be employed: - -```jldoctest introduction -julia> s1.removeO() |> println -x^2/2 + x + 1 - -``` - - - -### Sums - -`SymPy` can do sums, including some infinite ones. The `summation` function performs this task. For example, we have - -```jldoctest introduction -julia> @syms i, n -(i, n) - -julia> summation(i^2, (i, 1, n)) |> println -n^3/3 + n^2/2 + n/6 - -``` - -Like `Integrate` and `Derivative`, there is also a `Sum` function to stage the task until the `doit` function is called to initiate the sum. - - -Some famous sums can be computed: - -```jldoctest introduction -julia> sn = sympy.Sum(1/i^2, (i, 1, n)); println(sn) -Sum(i^(-2), (i, 1, n)) - -julia> sn.doit() -harmonic(n, 2) - -``` - -And from this a limit is available: - -```jldoctest introduction -julia> limit(sn.doit(), n, oo) |> println -pi^2/6 - -``` - -This would have also been possible through `summation(1/i^2, (i, 1, oo))`. - -### Vector-valued functions - -Julia makes constructing a vector of symbolic objects easy: - -```jldoctest introduction -julia> @syms x,y -(x, y) - -julia> v = [1,2,x] -3-element Vector{Sym}: - 1 - 2 - x - -julia> w = [1,y,3] -3-element Vector{Sym}: - 1 - y - 3 - -``` - -The generic definitions of vector operations will work as expected with symbolic objects: - -```jldoctest introduction -julia> using LinearAlgebra - -julia> dot(v,w) |> println -2*y + 3*conjugate(x) + 1 - -``` - -Or - -```jldoctest introduction -julia> cross(v,w) -3-element Vector{Sym}: - -x⋅y + 6 - x - 3 - y - 2 - -``` - -Finding gradients can be done using a comprehension. - -```jldoctest introduction -julia> ex = x^2*y - x*y^2 - 2 2 -x ⋅y - x⋅y - -julia> Sym[diff(ex,var) for var in (x,y)] -2-element Vector{Sym}: - 2*x*y - y^2 - x^2 - 2*x*y - -``` - - -Or through broadcasting: - -```jldoctest introduction -julia> diff.(ex, (x,y)) -(2*x*y - y^2, x^2 - 2*x*y) -``` - -The mixed partials is similarly done by passing two variables to differentiate in to `diff`, as illustrated previously: - -```jldoctest introduction -julia> Sym[diff(ex, v1, v2) for v1 in (x,y), v2 in (x,y)] -2×2 Matrix{Sym}: - 2⋅y 2⋅(x - y) - 2⋅(x - y) -2⋅x - -``` - -For this task, SymPy provides the `hessian` method: - -```jldoctest introduction -julia> hessian(ex, (x,y)) -2×2 Matrix{Sym}: - 2⋅y 2⋅x - 2⋅y - 2⋅x - 2⋅y -2⋅x - -``` - -## Matrices - -`Julia` has excellent infrastructure to work with generic matrices, -such as `Matrix{Sym}` objects (matrices with symbolic entries). As -well, SymPy has a class for matrices. `SymPy`, through `PyCall`, automatically maps mutable SymPy matrices into `Julia`n matrices of type `Array{Sym}`. - - -Constructing matrices with symbolic entries follows `Julia`'s conventions: - -```jldoctest introduction -julia> @syms x,y -(x, y) - -julia> M = [1 x; x 1] -2×2 Matrix{Sym}: - 1 x - x 1 - -``` - -!!! note - However, SymPy may throw a deprecation order when mapping a `Matrix{Sym}` argument for a function, as the underlying `sympy.Matrix` class is for symbolic numbers only since this [change](https://github.com/sympy/sympy/issues/21497). The suggestion is to use a "list of lists, TableForm, NumPy array, or some -other data structure instead". A tuple of tuples will map to a list of lists. - -Here is an example of a tuple of tuples forming a matrix: - -```jldoctest introduction -julia> A = sympy.Matrix( ((1,x), (y,2)) ) -2×2 Matrix{Sym}: - 1 x - y 2 -``` - -This is useful if copying SymPy examples, but otherwise unneccesary, these are immediately mapped into `Julia` arrays by `PyCall` -- -**unless** an immutable array is desired, and then the `sympy.ImmutableMatrix` constructor is used. - - -Some more examples: - -```jldoctest introduction -julia> diagm(0=>ones(Sym, 5)) -5×5 Matrix{Sym}: - 1 0 0 0 0 - 0 1 0 0 0 - 0 0 1 0 0 - 0 0 0 1 0 - 0 0 0 0 1 - -julia> M = [1 x; x 1] -2×2 Matrix{Sym}: - 1 x - x 1 - -julia> M^2 -2×2 Matrix{Sym}: - x^2 + 1 2⋅x - 2⋅x x^2 + 1 - -julia> det(M) - 2 -1 - x -``` - - -We can call `Julia`'s generic matrix functions in the usual manner, e.g: - -```jldoctest introduction -julia> det(A) --x⋅y + 2 - -``` - -We can also call SymPy's matrix methods using the dot-call syntax: - - -```jldoctest introduction -julia> A.det() --x⋅y + 2 - -``` - -!!! note - Actually, `det(A)` avoids the generic `Julia` implementation. A better example might be `qr(M)` versus `M.QRdecomposition()` to see a generic `Julia` implementation for `Matrix{Sym}` against SymPy's method for matrices. - - -Occasionally, the SymPy method has more content: - -```jldoctest introduction -julia> eigvecs(M) -2×2 Matrix{Sym}: - 1 -1 - 1 1 - -``` - -As compared to SymPy's `eigenvects` which yields: - -```jldoctest introduction -julia> A.eigenvects() -2-element Vector{Tuple{Sym, Int64, Vector{Matrix{Sym}}}}: - (3/2 - sqrt(4*x*y + 1)/2, 1, [[(3/2 - sqrt(4*x*y + 1)/2)/y - 2/y; 1;;]]) - (sqrt(4*x*y + 1)/2 + 3/2, 1, [[(sqrt(4*x*y + 1)/2 + 3/2)/y - 2/y; 1;;]]) - -``` - -(This is a bit misleading, as the generic `eigvecs` fails on `M`, so the value is basically just repackaged from `A.eigenvects()`.) - -This example from the SymPy tutorial shows the `nullspace` method: - -```jldoctest introduction -julia> A = Sym[1 2 3 0 0; 4 10 0 0 1] -2×5 Matrix{Sym}: - 1 2 3 0 0 - 4 10 0 0 1 - -julia> vs = A.nullspace() -3-element Vector{Matrix{Sym}}: - [-15; 6; … ; 0; 0;;] - [0; 0; … ; 1; 0;;] - [1; -1/2; … ; 0; 1;;] - -``` - -This confirms that values found are indeed in the null space of `A`: - -```jldoctest introduction -julia> [A*vs[i] for i in 1:3] -3-element Vector{Matrix{Sym}}: - [0; 0;;] - [0; 0;;] - [0; 0;;] - -``` - -Symbolic expressions can be included in the matrices: - -```jldoctest introduction -julia> A = [1 x; x 1] -2×2 Matrix{Sym}: - 1 x - x 1 - -julia> P, D = A.diagonalize() # M = PDP^-1 -(Sym[-1 1; 1 1], Sym[1 - x 0; 0 x + 1]) - -julia> A - P*D*inv(P) -2×2 Matrix{Sym}: - 0 0 - 0 0 - -``` - - -## Differential equations - -SymPy has facilities for solving ordinary differential -[equations](http://docs.sympy.org/latest/modules/solvers/ode.html). The -key is to create a symbolic function expression using -`SymFunction`. Again, this may be done through: - -```jldoctest introduction -julia> @syms F() -(F,) - -``` - -With this, we can construct a differential equation. Following the SymPy tutorial, we solve $f''(x) - 2f'(x) + f(x) = \sin(x)$: - -```jldoctest introduction -julia> diffeq = diff(F(x), x, 2) - 2*diff(F(x)) + F(x) ~ sin(x); println(diffeq) -Eq(F(x) - 2*Derivative(F(x), x) + Derivative(F(x), (x, 2)), sin(x)) - -``` - - -With this, we just need the `dsolve` function. This is called as `dsolve(eq)` or `dsolve(eq, F(x))`: - -```jldoctest introduction -julia> ex = dsolve(diffeq, F(x)); println(ex) -Eq(F(x), (C1 + C2*x)*exp(x) + cos(x)/2) - -``` - - -The `dsolve` function in SymPy has an extensive list of named -arguments to control the underlying algorithm. These can be passed -through with the appropriate keyword arguments. - -The definition of the differential equation expects the cumbersome `diff(ex, var)` to provide the derivative. The `Differential` function lessens the visual noise (with a design taken from `ModelingToolkit`). The above would be: - -```jldoctest introduction -julia> D = Differential(x) -Differential(x) - -julia> diffeq = D(D(F))(x) - 2D(F)(x) + F(x) ~ sin(x); println(diffeq) -Eq(F(x) - 2*Derivative(F(x), x) + Derivative(F(x), (x, 2)), sin(x)) - -julia> sympy.dsolve(diffeq, F(x)) |> println -Eq(F(x), (C1 + C2*x)*exp(x) + cos(x)/2) -``` - - -This solution has two constants, $C_1$ and $C_2$, that would be found from initial conditions. Say we know $F(0)=0$ and $F'(0)=1$, can we find the constants? To work with the returned expression, it is most convenient to get just the right hand side. The `rhs` method will return the right-hand side of a relation: - -```jldoctest introduction -julia> ex1 = rhs(ex); println(ex1) -(C1 + C2*x)*exp(x) + cos(x)/2 - -``` - -(The -[args](http://docs.sympy.org/dev/modules/core.html#sympy.core.basic.Basic.args) -function also can be used to break up the expression into parts.) - -With this, we can solve for `C1` through substituting in $0$ for $x$: - -```jldoctest introduction -julia> C1 = first(free_symbols(ex1)) -C₁ - -julia> solve(ex1(x => 0), C1) -1-element Vector{Sym}: - -1/2 - -``` - -We see that $C1=-1/2$, which we substitute in: - -```jldoctest introduction -julia> ex2 = ex1(C1 => -Sym(1//2)); println(ex2) -(C2*x - 1/2)*exp(x) + cos(x)/2 - -``` - -We know that $F'(0)=1$ now, so we solve for `C2` through - -```jldoctest introduction -julia> C2 = free_symbols(ex1)[2] -C₂ - -julia> solve( diff(ex2, x)(x => 0) - 1, C2 ) -1-element Vector{Sym}: - 3/2 -``` - -This gives `C2=3/2`. Again we substitute in to get our answer: - -```jldoctest introduction -julia> ex3 = ex2(Sym("C2") => 3//2); println(ex3) -(3*x/2 - 1/2)*exp(x) + cos(x)/2 - -``` - - -The `dsolve` function has an `ics` argument that allows most of the above to be done internally: - -```jldoctest introduction -julia> ex4 = dsolve(diffeq, F(x), ics=Dict(F(0)=>0, D(F)(0)=>1)); - -julia> ex3 - rhs(ex4) # need rhs to extract the solution -0 - -``` - -###### Example - -This example is borrowed from [here](http://nbviewer.ipython.org/github/garth-wells/IA-maths-Ipython/blob/master/notebooks/Lecture1.ipynb). - -> Find the variation of speed with time of a parachutist subject to a drag force of $k\cdot v^2$. - -The equation is - -$$~ -\frac{m}{k} \frac{dv}{dt} = \alpha^2 - v^2. -~$$ - -We proceed through: - -```jldoctest introduction -julia> @syms t, m, k, alpha=>"α", v() -(t, m, k, α, v) - -julia> D = Differential(t); - -julia> ex = (m/k)*D(v)(t) ~ alpha^2 - v(t)^2; println(ex) -Eq(m*Derivative(v(t), t)/k, α^2 - v(t)^2) - -``` - -We can "classify" this ODE with the method `classify_ode` function. - -``` -julia> sympy.classify_ode(ex) -("separable", "1st_exact", "1st_power_series", "lie_group", "separable_Integral", "1st_exact_Integral") - -``` - -It is linear, but not solvable. Proceeding with `dsolve` gives: - -``` -julia> u = dsolve(ex, v(t)); - -julia> println(rhs(u)) --α/tanh(log(exp(α*(C1*m - 2*k*t)))/(2*m)) - -``` - - -###### Example - -We follow an example from -[Wolfram](https://reference.wolfram.com/language/tutorial/DSolveLinearBVPs.html), solving first order ODE: $y'(t) - 3t\cdot y(t) = 1$: - -```jldoctest introduction -julia> @syms y(), a, x -(y, a, x) - -julia> D = Differential(x); - -julia> eqn = D(y)(x) - 3*x*y(x) - 1; println(eqn) --3*x*y(x) + Derivative(y(x), x) - 1 - -``` - -We solve the initial value problem with $y(0) = 4$ as follows: - -```jldoctest introduction -julia> x0, y0 = 0, 4 -(0, 4) - -julia> ics = Dict(y(x0) => y0); - -julia> out = dsolve(eqn, ics = ics); - -julia> println(rhs(out)) -(sqrt(6)*sqrt(pi)*erf(sqrt(6)*x/2)/6 + 4)*exp(3*x^2/2) - -``` - -Verifying this requires combining some operations: - -```jldoctest introduction -julia> u = rhs(out); - -julia> diff(u, x) - 3*x*u - 1 -0 - -``` - -To solve with a general initial condition is similar: - -```jldoctest introduction -julia> x0, y0 = 0, a -(0, a) - -julia> ics = Dict(y(x0) => y0); - -julia> out = dsolve(eqn, ics=ics); - -julia> println(rhs(out)) -(a + sqrt(6)*sqrt(pi)*erf(sqrt(6)*x/2)/6)*exp(3*x^2/2) - -``` - - -To plot this over a range of values for `a` we would have: - -```@example plots -@syms a x y() ; nothing # hide -D = Differential(x); nothing # hide -eqn = D(y)(x) - 3*x*y(x) - 1; nothing #hide -x0, y0 = 0, a; nothing #hide -out = dsolve(eqn, ics = (y, x0, y0)); nothing #hide - -as = -2:0.6:2 -fna = lambdify(subs(rhs(out), a=>first(as))) -xs = range(-1.8, 1.8, length=500) -p = plot(xs, fna.(xs), legend=false, ylim=(-4,4)) -for aᵢ in as[2:end] - fni = lambdify(subs(rhs(out), a=>aᵢ)) - plot!(p, xs, fni.(xs)) -end -p -savefig("plot-9.svg"); nothing #hide -``` - -![](plot-9.svg) - -The comment from the example is "This plots several integral curves of the equation for different values of $a$. The plot shows that the solutions have an inflection point if the parameter lies between $-1$ and $1$ , while a global maximum or minimum arises for other values of $a$." - - -##### Example - -We continue with another example from the Wolfram documentation: -solving $y'' + 5y' + 6y=0$ with values prescribed for both $y$ and -$y'$ at $x_0=0$. - -```jldoctest introduction -julia> @syms y(), x -(y, x) - -julia> D = Differential(x); D2 = D ∘ D -Differential(x) ∘ Differential(x) - -julia> eqn = D2(y)(x) + 5D(y)(x) + 6y(x); println(eqn) -6*y(x) + 5*Derivative(y(x), x) + Derivative(y(x), (x, 2)) - -``` - -To solve with $y(0) = 1$ and $y'(0) = 1$ we have: - -```jldoctest introduction -julia> ics=Dict(y(0) => 1, D(y)(0) => 1); - -julia> out = dsolve(eqn, ics=ics); println(rhs(out)) -(4 - 3*exp(-x))*exp(-2*x) - -``` - -To make a plot, we only need the right-hand-side of the answer: - -```@example plots -@syms y() x; nothing #hide -D = Differential(x); nothing # hide -eqn = D(D(y))(x) + 5D(y)(x) + 6y(x); nothing #hide -out = dsolve(eqn, ics=Dict(y(0) => 1, D(y)(0) => 1)); nothing #hide -plot(rhs(out), -1/3, 2) -savefig("plot-10.svg"); nothing # hide -``` - -![](plot-10.svg) - -##### Example - -Boundary value problems can be solved for, as well, through a similar -syntax. Continuing with examples from the -[Wolfram](https://reference.wolfram.com/language/tutorial/DSolveLinearBVPs.html) -page, we solve $y''(x) +y(x) = e^x$ over $[0,1]$ with conditions -$y(0)=1$, $y(1) = 1/2$: - -```jldoctest introduction -julia> eqn = D(D(y))(x) + y(x) - exp(x); println(eqn) -y(x) - exp(x) + Derivative(y(x), (x, 2)) - -julia> ics = Dict(y(0)=>1, y(1) => Sym(1//2)); - -julia> dsolve(eqn, ics=ics) |> println -Eq(y(x), exp(x)/2 + (-E - cos(1) + 1)*sin(x)/(2*sin(1)) + cos(x)/2) -``` - -!!! note - The wrapping of `Sym(1//2)` is necessary to avoid a premature conversion to floating point when the dictionary, `ics`, is converted to a Python dictionary by `PyCall`. - -## The lambdify function - -The `Symbolics` documentation describes its `build_function` method as follows: "`build_function` is kind of like if `lambdify` ate its spinach." This is true, but what is `lambdify` in the comparison? - -The SymPy docs say it can transform SymPy expressions to lambda functions which can be used to calculate numerical values very fast. - -The typical way to evaluate a symbolic expression at some value and gather the output as a number in Julia would follow this pipeline: - -``` -x |> ex(x) |> N -``` - -The first step requires a conversion of the value in `Julia` to a Python object, this is handled by `PyCall` and is essentially zero-cost. - -The substitution step, is done within SymPy and runs at the speed of Python. - -The last step converts the resulting python object computed by SymPy into a value on the `Julia` side. The `N` call is just one way to do this. By default, a mapping takes basic SymPy objects and wraps them in the `Sym` type for dispatch. The `N` method makes a `Julia`n numeric type, essentially calling `convert(T,x)` for a run-time determined type `T`. - -While two steps are mostly zero cost, it can be much more performant to create a native `Julia` function to do the work directly. - -The `SymPy` version in this package does not utilize the underlying SymPy `lambdify` function, rather it walks the expression tree in SymPy, creates a corresponding `Julia` expression, and then creates a function in `Julia` from that. - - -To see the creation of an expression, we have: - -```jldoctest introduction -julia> @syms x; ex = x*sin(x - 2) -x⋅sin(x - 2) - -julia> convert(Expr, ex) -:(x * sin(-2 + x)) - -``` - -The `sympy.julia_code` function is SymPy's means to stringify an expression into `Julia` code, in combination with `Meta.parse`, this can also create the expression. - -Converting the expression to a function can be done many different ways, some more performant than what is the default with `lambdify`. - -First, the variables in the expression must be identified, these can be passed in via the `vars` keyword or will be determined by default with the `free_symbols` function. The order of the variables is important when calling the create function. Finally, `invokelatest` is used to create a function object made by combining the variables and the function body. - -```jldoctest introduction -julia> l = lambdify(ex); - -julia> l(3), ex(3) -(2.5244129544236893, 3*sin(1)) - -``` - -## Using other SymPy modules - -The SymPy library has numerous external modules beyond those exposed immediately by `SymPy`. - -```jldoctest introduction -julia> stats = SymPy.PyCall.pyimport_conda("sympy.stats", "sympy"); - -``` - -The `stats` module holds several probability functions, similar to the `Distributions` package of `Julia`. This set of commands creates a normally distributed random variable, `X`, with symbolic parameters: - -```jldoctest introduction -julia> @syms μ, σ::positive; - -julia> X = stats.Normal("X", μ, σ) -X - -julia> stats.E(X) -μ - -julia> stats.E(X^2) - 2 2 -μ + σ - -julia> stats.variance(X) - 2 -σ - -``` - -The methods in the `stats` module are qualified with the module name above. `SymPy` provides the `import_from` function to import all such methods creating functions which dispatch on a symbolic first argument. This is not shown. - -Next we see that statements like $P(X > \mu)$ can be answered specifying the inequality using `Gt` in the following: - -```jldoctest introduction -julia> stats.P(Gt(X, μ)) -1/2 -``` - -A typical calculation for the normal distribution is the area one or more standard deviations larger than the mean: - -```jldoctest introduction -julia> stats.P(Gt(X, μ + 1σ)) |> println -sqrt(2)*(-sqrt(2)*pi*exp(1/2)*erf(sqrt(2)/2)/2 + sqrt(2)*pi*exp(1/2)/2)*exp(-1/2)/(2*pi) -``` - -The familiar answer could be found by calling `N` or `evalf`. - -We show one more distribution, the uniform over $[a,b]$: - -```jldoctest introduction -julia> @syms a::real b::real -(a, b) - -julia> U = stats.Uniform("U", a, b) -U - -julia> stats.E(U) |> simplify |> println -a/2 + b/2 - -julia> stats.variance(U) |> simplify |> factor |> println -(a - b)^2/12 -``` - - -Not all modules are so simple to incorporate. PyCall does a good job of converting the arguments from `Julia` to Python, but the conversion from a Python (SymPy) structure back to a workable `Julia` structure can require some massaging. - -For example, the return value of `solveset` offers a challenge: - -```jldoctest introduction -julia> @syms x -(x,) - -julia> u = solveset(x^2 - 2, x) -{-√2, √2} -``` - -Here `u` has the Python class `FiniteSet`: - -```jldoctest introduction -julia> u.__class__ -PyObject - -``` - -PyCall is instructed in `SymPy` to map SymPy objects as `Sym` objects, so `FiniteSet` is only relevant when the object is being interacted with using `Julia` methods, Here `collect` will fail on `u`, but `Set` will work with splatting, as the finite set is iterable. This set can then be collected: - -```julia -julia> collect(Set(u...)) -2-element Vector{Any}: - -√2 - √2 - -``` - -The output of many integration problems is a piecewise function: - -```jldoctest introduction -julia> @syms n::integer x::real -(n, x) - -julia> u = integrate(x^n, x) -⎧ n + 1 -⎪x -⎪────── for n ≠ -1 -⎨n + 1 -⎪ -⎪log(x) otherwise -⎩ - -``` - -The conversion to a `Julia` object is a bit cumbersome, in the following we work through the `args` of the expression and pull our the condition and arguments as an underlying `ExprCondPair` in SymPy for which `cond` and `args` are useful properties: - - -```jldoctest introduction -julia> [c.cond => c.args for c ∈ u.args] -2-element Vector{Pair{Sym, Tuple{Sym, Sym}}}: - Ne(n, -1) => (x^(n + 1)/(n + 1), Ne(n, -1)) - True => (log(x), True) - -``` - -Such is not automated. However some conversions are. `PyCall` allows the definition of a mapping between a python type and a `Julia` type (`pytype_mapping`). -By default, python objects with class `sympy_core.basic.Basic` are mapped to `Sym` objects in `Julia`, a simple wrapper to control dispatch. -More germaine to this example, the `combinatorics` module is imported by default in `SymPy`. It required managing the automatic mapping of types so that different dispatch rules could be used to follow the syntax of the module. The rules were: - -``` -pytype_mapping(sympy.combinatorics.permutations.Permutation, SymPermutation) -pytype_mapping(combinatorics.perm_groups.PermutationGroup, SymPermutationGroup) -``` - -Here, `SymPermutation` and `SymPermutationGroup` are subtypes of the abstract `SymbolicObject` type. diff --git a/docs/src/reference.md b/docs/src/reference.md deleted file mode 100644 index 11bb4809..00000000 --- a/docs/src/reference.md +++ /dev/null @@ -1,18 +0,0 @@ -# Reference/API - - -```@index -Pages = ["reference.md"] -``` - - -```@meta -DocTestSetup = quote - using SymPy -end -``` - -```@autodocs -Modules = [SymPy, SymPy.𝑄, SymPy.Introspection] -Order = [:function, :constant, :macro, :type, :module] -``` diff --git a/ext/SymPySymbolicUtilsExt.jl b/ext/SymPySymbolicUtilsExt.jl deleted file mode 100644 index a2c36c6e..00000000 --- a/ext/SymPySymbolicUtilsExt.jl +++ /dev/null @@ -1,52 +0,0 @@ -module SymPySymbolicUtilsExt - -using SymPy -import SymbolicUtils - -#== -Check if x represents an expression tree. If returns true, it will be assumed that operation(::T) and arguments(::T) methods are defined. Definining these three should allow use of SymbolicUtils.simplify on custom types. Optionally symtype(x) can be defined to return the expected type of the symbolic expression. -==# -function SymbolicUtils.istree(x::SymPy.SymbolicObject) - !(x.is_Atom) -end - -#== -f x is a term as defined by istree(x), exprhead(x) must return a symbol, corresponding to the head of the Expr most similar to the term x. If x represents a function call, for example, the exprhead is :call. If x represents an indexing operation, such as arr[i], then exprhead is :ref. Note that exprhead is different from operation and both functions should be defined correctly in order to let other packages provide code generation and pattern matching features. -function SymbolicUtils.exprhead(x::SymPy.SymbolicObject) # deprecated - :call # this is not right -end -==# - -#== -Returns the head (a function object) performed by an expression tree. Called only if istree(::T) is true. Part of the API required for simplify to work. Other required methods are arguments and istree -==# -function SymbolicUtils.operation(x::SymPy.SymbolicObject) - @assert SymbolicUtils.istree(x) - nm = Symbol(SymPy.Introspection.funcname(x)) - - λ = get(SymPy.Introspection.funcname2function, nm, nothing) - if isnothing(λ) - return getfield(Main, nm) - else - return λ - end -end - - -#== -Returns the arguments (a Vector) for an expression tree. Called only if istree(x) is true. Part of the API required for simplify to work. Other required methods are operation and istree -==# -function SymbolicUtils.arguments(x::SymPy.SymbolicObject) - collect(SymPy.Introspection.args(x)) -end - -#== -Construct a new term with the operation f and arguments args, the term should be similar to t in type. if t is a SymbolicUtils.Term object a new Term is created with the same symtype as t. If not, the result is computed as f(args...). Defining this method for your term type will reduce any performance loss in performing f(args...) (esp. the splatting, and redundant type computation). T is the symtype of the output term. You can use SymbolicUtils.promote_symtype to infer this type. The exprhead keyword argument is useful when creating Exprs. -==# -function SymbolicUtils.similarterm(t::SymPy.SymbolicObject, f, args, symtype=nothing; - metadata=nothing, exprhead=:call) - f(args...) # default -end - - -end diff --git a/src/SymPy.jl b/src/SymPy.jl index 057e2631..d01caadc 100644 --- a/src/SymPy.jl +++ b/src/SymPy.jl @@ -1,271 +1,24 @@ """ - -`SymPy` package to interface with Python's [SymPy library](http://www.sympy.org) through `PyCall`. - -The basic idea is that a new type -- `Sym` -- is made to hold symbolic -objects. For this type, the basic functions from SymPy and appropriate functions -of `Julia` are overloaded for `Sym` objects so that the expressions -are treated symbolically and not evaluated immediately. Instances of -this type are created by the constructor `Sym`, the function `symbols` or the macro -`@vars`. - -On loading, a priviledged set of the functions from the `sympy` module -are defined as generic functions with their first argument narrowed to -symbolic types. Others may be accessed by qualification, as in -`sympy.trigsimp`. Calling `import_from(sympy)` will import the -rest. SymPy methods are called through Python's dot-call syntax. To -find documentation on SymPy functions and methods, one should refer to -SymPy's [website](http://docs.sympy.org/latest/index.html). - -Plotting is provided through `Plots` recipes. For details, see -the help page for [`sympy_plotting`](@ref). - -The package [documentation](https://docs.juliahub.com/SymPy/) provides many examples. - -Access to SymPy's help system for most functions is available through `SymPy.Doc`. - - - +`SymPy` package to interface with Python's [SymPy library](http://www.sympy.org) through `SymPyCore` and `PyCall`. """ module SymPy -if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel")) - @eval Base.Experimental.@optlevel 1 -end -## minimal version of conveniently using SymPy in Julia -## uses getfield overloading to access sympy methods of Sym object +using SymPyCore using PyCall -using SpecialFunctions -using LinearAlgebra -using Markdown -import CommonSolve -import CommonSolve: solve -import CommonEq -import CommonEq: Eq, Lt, Le, Ne, Ge, Gt, ⩵, ≪, ≦, ≧,≫ - -import Base: show -import Base: convert, promote_rule -import Base: getproperty -import Base: hash, == -import Base: length, size -import Base.iterate -import Base: +, -, *, /, //, \, ^ - -export @vars, Sym, sympify, symbols, @symfuns, @syms -export SymMatrix, SymFunction -export PI, IM, oo, zoo, True, False -export N, subs, doit - -export sympy, sympy_core, sympy_matrices, import_from#, import_sympy -export free_symbols - - -include("types.jl") -include("decl.jl") -include("constructors.jl") -include("utils.jl") -include("numbers.jl") -include("mathops.jl") -include("mathfuns.jl") -include("generic.jl") -include("matrix.jl") -include("sets.jl") -include("symfunction.jl") -include("assumptions.jl") -include("lambdify.jl") -include("patternmatch.jl") -include("permutations.jl") -include("plot_recipes.jl") -include("latexify_recipe.jl") - -include("deprecated.jl") -################################################## - -pynull() = PyCall.PyNULL() -""" - sympy - -Variable from `pyimport("sympy")`. Numerous methods are available through Python's dot-call syntax. -""" -const sympy = PyCall.PyNULL() -""" - sympy_core - -Variable from `pyimport("sympy.core")`. -""" -const sympy_core = PyCall.PyNULL() -""" - sympy_matrices - -Variable from `pyimport("sympy.matrices")`. -""" -const sympy_matrices = PyCall.PyNULL() -const mpmath = PyCall.PyNULL() -const combinatorics = PyCall.PyNULL() - -# core.sympy.numbers.* -"PI is symbolic `pi`" -global PI = Sym(pynull()) -"IM is a symbolic `im`" -global IM = Sym(pynull()) -"oo is a symbolic infinity. Example: `integrate(exp(-x), x, 0, oo)`." -global oo = Sym(pynull()) -"zoo complex inifinity" -global zoo = Sym(pynull()) -"True from SymPy" -global True = Sym(pynull()) -"False from SymPy" -global False = Sym(pynull()) - - - - -# Can not actually initiate many things until `sympy` is defined, so not until runtime -function __init__() - - ## Define sympy, mpmath, ... - copy!(sympy, PyCall.pyimport_conda("sympy", "sympy")) - copy!(sympy_core, PyCall.pyimport("sympy.core")) - copy!(sympy_matrices, PyCall.pyimport("sympy.matrices")) - copy!(PI.__pyobject__, sympy.pi) - copy!(IM.__pyobject__, sympy.I) - copy!(oo.__pyobject__, sympy.oo) - copy!(zoo.__pyobject__, sympy.zoo) - copy!(True.__pyobject__, PyCall.PyObject(true)) - copy!(False.__pyobject__, PyCall.PyObject(false)) - - - # mpmath - try - PyCall.mpmath_init() - copy!(mpmath, PyCall.pyimport_conda("mpmath", "mpmath")) - catch err - # can't load - end - - # pull in a library - copy!(combinatorics, PyCall.pyimport_conda("sympy.combinatorics", "sympy")) - - - ## mappings from PyObjects to types. - ## order here is fussy, as we needed ImmutableMatrix before Basic - pytype_mapping(sympy_matrices.ImmutableMatrix, SymMatrix) - pytype_mapping(sympy_matrices.ImmutableDenseMatrix, SymMatrix) - - basictype = sympy_core.basic.Basic - pytype_mapping(basictype, Sym) - - pytype_mapping(sympy_matrices.Matrix, Matrix{Sym}) - pytype_mapping(sympy_matrices.MatrixBase, Matrix{Sym}) - pytype_mapping(sympy_matrices.MutableDenseMatrix, Matrix{Sym}) - - pytype_mapping(sympy.FiniteSet, Set) - - pytype_mapping(sympy.combinatorics.permutations.Permutation, SymPermutation) - pytype_mapping(combinatorics.perm_groups.PermutationGroup, SymPermutationGroup) - - if mpmath != PyCall.PyNULL() - ## ignore warnings for now... - mpftype = mpmath."mpf" - pytype_mapping(mpftype, BigFloat) ## Raises warning!!! - mpctype = mpmath."mpc" - pytype_mapping(mpctype, Complex{BigFloat}) - end - - - - ## We *would* like to import some methods from the `sympy` module, but we can't as calling - ## @eval to import from a different package causes precompilation issues. - ## Rather, we only call `import_sympy` to print a list of commands to add to this file (as - ## are added below). This is done by uncommenting the `println` statements in `import_from`. - #import_sympy() - -end - - -## On load, generic functions in the base modules that match a function in sympy.* are -## defined on `Sym` objects, as are those listed here: -## -priviledged = (:And, :Or, :Not, :Xor, - # - :apart, :cancel, :cse, :expand, :factor, :flatten, :nsimplify, - :isolate, :simplify, :together, :unflatten, - # - :srepr,:doit, - # - :integrate, :line_integrate, :interpolate, :limit, :series, :summation, - :hessian, - # - :prime, :multiplicity, :degree, :coeffs, - # - :DiracDelta, :Heaviside, - # - :linsolve, :nonlinsolve, :nroots, :nsolve, :pdsolve, :real_root, - :real_roots, :root, :rootof, :roots, :rsolve, :solve, :solveset, - :ode_order, - # - :Min, :Max, :Abs,:numer, :denom, :conjugate, :ln, - # - :intersection, :intervals, :isprime - - ) - - -function in_base(uv) - u = Symbol(uv[1]) - for M in base_Ms - isdefined(M, u) && return true - end - false -end -is_function(uv) = SymPy.pycall_hasproperty(uv[2], :__class__) && occursin("unction", uv[2].__class__.__name__) - - - -""" - import_sympy - -This method imports all functions from `mpmath` and a priviledged set -of functions from `sympy`, as well as the relational operators. - -These functions are narrowed on their first argument being of type `SymbolicObject`. - -A few modules are checked for namespace collisions. - -If a function naturally takes an non-Symbolic argument as a first argument, then it can be qualified: e.g. -`sympy.sin(2)` (as opposed to `sin(Sym(2))`). - -If a constructor is needed (which is not a function) then it must be -qualified. (E.g. `sympy.Function("F")`, though for this particular -case, there is `SymFunction` defined for convenience.) - -""" -function import_sympy() - if mpmath != PyCall.PyNULL() - mps = _get_member_functions(mpmath, base_exclude) - sps = Introspection.getmembers(sympy) - imp = Tuple(Symbol.(setdiff(keys(mps), keys(sps)))) - import_from(mpmath, imp) - end - ## import from - ## import_from(sympy) - d = Introspection.getmembers(sympy) - d1 = filter(uv -> in_base(uv) && is_function(uv), d) - import_from(sympy, setdiff(Symbol.(collect(keys(d1))), Symbol.(base_exclude))) - import_from(sympy, priviledged) - import_from(sympy, (:Ne, :Lt, :Le, :Eq, :Ge, :Gt, - :GreaterThan, :LessThan, - :StrictGreaterThan, :StrictLessThan, - :Equality, :Unequality - ), typ=:Number) -end - +const _PyType = PyCall.PyObject +_pynull() = PyCall.PyNULL() # for more generic usage +_copy!(a, b) = PyCall.copy!(a,b) # for more generic usage +_pyimport(a) = PyCall.pyimport(a) +_pyimport_conda(a,b) = PyCall.pyimport_conda(a, b) +_pyobject(x) = PyCall.PyObject(x) +_pytype_mapping(typ,a) = PyCall.pytype_mapping(typ, a) +core_src_path = joinpath(pathof(SymPyPyCall.SymPyCore), "../../src/SymPy") +include(joinpath(core_src_path, "sympy.jl")) -### Add generic methods and new methods -include("importexport.jl") +include("python_connection.jl") end # module diff --git a/src/assumptions.jl b/src/assumptions.jl deleted file mode 100644 index e6447bbc..00000000 --- a/src/assumptions.jl +++ /dev/null @@ -1,367 +0,0 @@ -## Assumptions -## http://docs.sympy.org/0.7.2/modules/assumptions/index.html - -""" - refine - -Simplify an expression using assumptions; [refine](https://docs.sympy.org/dev/modules/assumptions/refine.html). -""" -refine(ex, assumpts...) = sympy.refine(ex, assumpts...) -export refine - -""" - ask(query) - -Returns `true`, `false`, or `nothing`; [ask](https://docs.sympy.org/dev/modules/assumptions/ask.html) - -Example: - -```jldoctest ask -julia> using SymPy - -julia> @vars x y integer=true -(x, y) - -julia> ask(𝑄.integer(x*y), And(𝑄.integer(x), 𝑄.integer(y))) -true - -julia> ## really slow isprime: - filter(x -> ask(𝑄.prime(x)), 1:10) -4-element Vector{Int64}: - 2 - 3 - 5 - 7 -``` - -""" -ask(x::Sym, args...) = sympy.ask(x, args...) -ask(x::Bool, args...) = x -ask(x::Nothing, args...) = x -export ask - -## should we support & and | for (sym,sym) pairs? Not sure -## dependso on what happens to | (x==0) and ex == x usage for == -## for now, we can combine terms logically with And, Or, Not... -## these are in logic module - - - -## We make a module Q to hold the assumptions -## this follows this page http://docs.sympy.org/0.7.5/_modules/sympy/assumptions/ask.html -""" - 𝑄 - SymPy.Q - -The`SymPy.𝑄` module adds features of the `sympy.Q` module. Also accesible through `SymPy.Q`. - -SymPy allows for -[assumptions](https://docs.sympy.org/latest/modules/assumptions/index.html) -on variables. These may be placed on free sympols at construction. - -For example, the following creates a real value variable `x` and a postive, real variable `y`: - -```jldoctest 𝑄 -julia> using SymPy - -julia> @vars x real=true -(x,) - -julia> @vars y real=true positive=true -(y,) -``` - -The `𝑄` module exposes a means to *q*uery the assumptions on a -variable. For example, - -```jldoctest 𝑄 -julia> ask(𝑄.positive(y)) # true -true - -julia> ask(𝑄.negative(y)) # false -false - -julia> ask(SymPy.Q.positive(x)) # `nothing` - -julia> ask(SymPy.Q.positive(x^2)) # `nothing` -- might be 0 - -julia> ask(SymPy.Q.positive(1 + x^2)) # true -- must be postive now. -true -``` - -The ask function uses tri-state logic, returning one of 3 values: -`true`; `false`; or `nothing`, when the query is indeterminate. - -The construction of predicates is done through `Q` methods. These can -be combined logically. For example, this will be `true`: - -```jldoctest 𝑄 -julia> ask(𝑄.positive(y) & 𝑄.negative(-x^2 - 1)) - -``` - -The above use `&` as an infix operation for the binary operator -`And`. Values can also be combined with `Or`, `Not`, `Xor`, `Nand`, -`Nor`, `Implies`, `Equivalent`, and `satisfiable`. - -!!! note "typing `𝑄`" - 𝑄 is entered as [slash]itQ[tab]) or `SymPy.Q.query(value)` *but not* as `sympy.Q.query(value)` - -!!! note "Matrix predicates" - As `SymPy.jl` converts symbolic matrices into Julia's `Array` -type and not as matrices within Python, the predicate functions from SymPy for -matrices are not used, though a replacement is given. -""" -module 𝑄 -import SymPy -import PyCall -import LinearAlgebra: det, norm - -##http://docs.sympy.org/dev/_modules/sympy/assumptions/ask.html#ask -Q_predicates = (:antihermitian, - :bounded, :finite, # bounded deprecated - :commutative, - :complex, - :composite, - :even, - :extended_real, - :hermitian, - :imaginary, - :infinitesimal, - :infinity, :infinite, # :infinity deprecated - :integer, - :irrational, - :rational, - :algebraic, - :transcendental, - :negative, - :nonzero, :zero, - :positive, - :prime, - :real, - :odd, - :is_true, - :nonpositive, - :nonnegative -# :symmetric, -# :invertible, -# :singular, -# :orthogonal, -# :unitary, -# :normal, -# :positive_definite, -# :upper_triangular, -# :lower_triangular, -# :diagonal, -# :triangular, -# :unit_triangular, -# :fullrank, -# :square, -# :real_elements, -# :complex_elements, -# :integer_elements -) - -for meth in Q_predicates - nm = string(meth) - @eval begin - ($meth)(x) = PyCall.pycall(SymPy.sympy.Q.$nm, SymPy.Sym, x)::SymPy.Sym - end -end - - -symmetric(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.issymmetric(M) -function invertible(M::Array{T,2}) where {T <: SymPy.Sym} - d = det(M) - pos = SymPy.ask(positive(d)) - if pos == nothing - return nothing - elseif pos == true - return true - end - neg = SymPy.ask(negative(d)) - if neg == nothing - return nothing - end - z = SymPy.ask(zero(d)) - if z == nothing - return nothing - elseif z == true - return false - end - - return true -end - -function singular(M::Array{T,2}) where {T <: SymPy.Sym} - !invertible(M) -end - -function orthogonal(M::Array{T,2}) where {T <: SymPy.Sym} - vals = SymPy.simplify.(SymPy.simplify.(M*transpose(M)) .== one(T)) - no_nothing = 0 - for val in vals - a = SymPy.ask(zero(val)) - if a == nothing - no_nothing += 1 - elseif a == false - return false - end - end - - no_nothing > 0 && return nothing - return true -end - - -function unitary(M::Array{T,2}) where {T <: SymPy.Sym} - vals = SymPy.simplify.(SymPy.simplify.(M*ctranspose(M)) .== one(T)) - no_nothing = 0 - for val in vals - a = SymPy.ask(zero(val)) - if a == nothing - no_nothing += 1 - elseif a == false - return false - end - end - - no_nothing > 0 && return nothing - return true -end - -function normal(M::Array{T,2}) where {T <: SymPy.Sym} - lhs = ctranspose(M) * M - rhs = M * ctranspose(M) - vals = zero.(SymPy.simplify.(lhs - rhs)) - no_nothing = 0 - for val in vals - a = SymPy.ask(val) - if a == nothing - no_nothing += 1 - elseif a == false - return false - end - end - - no_nothing > 0 && return nothing - return true -end - -# Use [Sylvester's](https://en.wikipedia.org/wiki/Sylvester%27s_criterion) Criteria -function positive_definite(M::Array{T,2}) where {T <: SymPy.Sym} - !SymPy.ask(square(M)) && return false - !SymPy.ask(symmetric(M)) && return false - m, n = size(M) - no_false = 0 - no_nothing = 0 - for i in 1:m - a = SymPy.ask(𝑄.positive(det(M[1:i, 1:i]))) - if a == nothing no_nothing += 1 end - if a == false no_false += 1 end - end - if no_false > 0 - return false - elseif no_nothing > 0 - return nothing - else - return true - end -end - - - -upper_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istriu(M) -lower_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istril(M) -diagonal(M::Array{T,2}) where {T <: SymPy.Sym} = upper_triangular(M) && lower_triangular(M) -triangular(M::Array{T,2}) where {T <: SymPy.Sym} = upper_triangular(M) || lower_triangular(M) - -## This is likely not the best way as it is a bit fidgety due -## to the call to rref. -function full_rank(M::Array{T,2}) where {T <: SymPy.Sym} - m,n = size(M) - m <= n || return full_rank(transpose(M)) - - - rr, p = SymPy.rref(M) - lr = rr[end, :] # is this zero? - no_nothing = 0 - no_nonzero = 0 - for val in lr - a = SymPy.ask(nonzero(val)) - if a == nothing - no_nothing += 1 - end - if a == true - no_nonzero += 1 - end - end - if no_nothing > 0 - return nothing - elseif no_nonzero == 0 - return false - else - return true - end - -end - - -function square(M::Array{T,2}) where {T <: SymPy.Sym} - m,n = SymPy.size(M) - m == n -end - - -function real_elements(M::Array{T,2}) where {T <: SymPy.Sym} - vals = real.(M) - for val in vals - a = SymPy.ask(real(val)) - (a == nothing || a == false) && return false - end - return true -end - - -function complex_elements(M::Array{T,2}) where {T <: SymPy.Sym} - vals = real.(M) - for val in vals - a = SymPy.ask(SymPy.sympy."Q".complex(val)) - (a == nothing || a == false) && return false - end - return true - -end - - -function integer_elements(M::Array{T,2}) where {T <: SymPy.Sym} - vals = real.(M) - for val in vals - a = SymPy.ask(integer(val)) - (a == nothing || a == false) && return false - end - return true -end - - - - - -end -export 𝑄 - -## Issue #354; request to *not* export Q -## export -#export Q - -# """ -# Q - -# Unexported symbol for [`SymPy.𝑄`](@ref), a Julia module implementing `sympy.Q`. "Questions" can be asked through the patterns -# `𝑄.query(value)` - -# !!! note -# At one time, the symbol `Q` was exported. To avoid namespace clutter, the unicode alternative is now used. Legacy code would need a definition like `import SymPy: Q` to work. - -# """ -const Q = 𝑄 diff --git a/src/constructors.jl b/src/constructors.jl deleted file mode 100644 index da75f7ba..00000000 --- a/src/constructors.jl +++ /dev/null @@ -1,169 +0,0 @@ -################################################## -""" - symbols(name(s), assumptions...) - -Calls `sympy.symbols` to produce symbolic variables and symbolic functions. An alternate to the recommended `@syms`, (when applicable) - -In sympy `sympy.symbols` and `sympy.Symbol` both allow the construction of symbolic variables and functions. The `Julia` function `symbols` is an alias for `sympy.symbols`. - -* Variables are created through `x=symbols("x")`; -* Assumptions on variables by `x=symbols("x", real=true)`; -* Multiple symbols `x1,x2 = symbols("x[1:3]")` can be created. Unlike `@syms`, the number of variables can be specified with a variable through interpolation. -* Symbolic functions can be created py passing `cls=sympy.Function`, `symbols("F", cls=sympy.Function, real=true)` - -""" -function symbols(x::AbstractString; kwargs...) - out = sympy.symbols(x; kwargs...) -end -symbols(x::Symbol; kwargs...) = symbols(string(x); kwargs...) -symbols(xs::T...; kwargs...) where {T <: SymbolicObject} = xs - -""" - @vars x y z - -Define symbolic values, possibly with names and assumptions - -Examples: -``` -@vars x y -@vars a1=>"α₁" -@vars a b real=true -``` -!!! Note: - The `@vars` macro is deprecated and will be removed. Use `@syms`. -""" -macro vars(x...) - q = Expr(:block) - as = [] # running list of assumptions to be applied - ss = [] # running list of symbols created - for s in reverse(x) - if isa(s, Expr) # either an assumption or a named variable - if s.head == :(=) - s.head = :kw - push!(as, s) - elseif s.head == :call && s.args[1] == :(=>) - push!(ss, s.args[2]) - push!(q.args, Expr(:(=), esc(s.args[2]), Expr(:call, :symbols, s.args[3], map(esc,as)...))) - end - elseif isa(s, Symbol) # raw symbol to be created - push!(ss, s) - push!(q.args, Expr(:(=), esc(s), Expr(:call, :symbols, Expr(:quote, s), map(esc,as)...))) - else - throw(AssertionError("@vars expected a list of symbols and assumptions")) - end - end - push!(q.args, Expr(:tuple, map(esc,reverse(ss))...)) # return all of the symbols we created - q -end - -""" - @syms a n::integer x::(real,positive)=>"x₀" y[-1:1] u() v()::real w()::(real,positive) y()[1:3]::real - -Construct symbolic variables or functions along with specified assumptions. Similar to `@vars`, `sympy.symbols`, and `sympy.Function`, but the specification of the assumptions is more immediate than those interfaces which follow sympy's constructors. - -Allows the specification of assumptions on the variables and functions. - -* a type-like annontation, such as `n::integer` is equivalent to `sympy.symbols("n", integer=true)`. Multiple assumptions are combined using parentheses (e.g., `n::(integer,nonnegative)`. - -The possible [values](https://docs.sympy.org/latest/modules/core.html#module-sympy.core.assumptions) for assumptions are: "commutative", "complex", "imaginary", "real", "integer", "odd", "even", "prime", "composite", "zero", "nonzero", "rational", "algebraic", "transcendental", "irrational", "finite", "infinite", "negative", "nonnegative", "positive", "nonpositive", "hermitian", "antihermetian". - -* a tensor declaration form is provided to define arrays of variables, e.g. `x[-1:1]` or `y[1:4, 2:5]`. - -* a symbolic function can be specified using a pair of parentheses after the name, as in `u()`. - -* The return type of a function can have assumptions specified, as with a variable. E.g., `h()::complex`. How the symbolic function prints can be set as with a variable, e.g. `h()::complex=>"h̄"`. - -* multiple definitions can be separated by commas - -* How the symbol prints (the `__str__()` value) can be specified using the syntax `=>"name"`, as in `x=>"xₒ"` - -## Examples: - -```jldoctest constructors -julia> using SymPy - -julia> @syms a b::nonnegative -(a, b) - -julia> sqrt(a^2), sqrt(b^2) -(sqrt(a^2), b) -``` - -```jldoctest constructors -julia> @syms x::prime -(x,) - -julia> ask(𝑄.negative(x)), ask(𝑄.integer(x)), ask(𝑄.even(x)) # (false, true, nothing) -(false, true, nothing) -``` - -```jldoctest constructors -julia> @syms a[0:5], x -(Sym[a₀, a₁, a₂, a₃, a₄, a₅], x) - -julia> sum( aᵢ*x^(i) for (i,aᵢ) ∈ zip(0:5, a)) |> print -a₀ + a₁*x + a₂*x^2 + a₃*x^3 + a₄*x^4 + a₅*x^5 -``` - - -```jldoctest constructors -julia> @syms x u() v()::nonnegative -(x, u, v) - -julia> sqrt(u(x)^2), sqrt(v(x)^2) # sqrt(u(x)^2), Abs(v(x)) -(sqrt(u(x)^2), Abs(v(x))) -``` - -!!! Note: - Many thanks to `@matthieubulte` for this contribution. -""" -macro syms(xs...) - # If the user separates declaration with commas, the top-level expression is a tuple - if length(xs) == 1 && isa(xs[1], Expr) && xs[1].head == :tuple - _gensyms(xs[1].args...) - elseif length(xs) > 0 - _gensyms(xs...) - end -end - -function _gensyms(xs...) - asstokw(a) = Expr(:kw, esc(a), true) - - # Each declaration is parsed and generates a declaration using `symbols` - symdefs = map(xs) do expr - decl = parsedecl(expr) - symname = sym(decl) - symname, gendecl(decl) - end - syms, defs = collect(zip(symdefs...)) - - # The macro returns a tuple of Symbols that were declared - Expr(:block, defs..., :(tuple($(map(esc,syms)...)))) -end - - -## avoid PyObject conversion as possible -Sym(x::T) where {T <: Number} = sympify(x) -Sym(x::Bool) = Sym(PyObject(x)) -Sym(x::Rational{T}) where {T} = Sym(numerator(x))/Sym(denominator(x)) -function Sym(x::Complex{Bool}) - !x.re && x.im && return IM - !x.re && !x.im && return zero(Sym) - x.re && !x.im && return Sym(1) - x.re && x.im && return Sym(1) + IM -end -Sym(x::Complex{T}) where {T} = Sym(real(x)) + Sym(imag(x)) * IM -Sym(xs::Symbol...) = Tuple(Sym.((string(x) for x in xs))) -Sym(x::AbstractString) = sympy.symbols(x) -Sym(s::SymbolicObject) = s -Sym(x::Irrational{T}) where {T} = convert(Sym, x) - -convert(::Type{Sym}, s::AbstractString) = Sym(s) - -sympify(s, args...; kwargs...) = pycall(sympy.sympify::PyCall.PyObject, Sym, s) #sympy.sympify(s, args...; kwargs...) - - - -SymMatrix(s::SymMatrix) = s -SymMatrix(s::Sym) = sympy.ImmutableMatrix([s]) -SymMatrix(A::Matrix) = sympy.ImmutableMatrix([A[i,:] for i in 1:size(A)[1]]) diff --git a/src/decl.jl b/src/decl.jl deleted file mode 100644 index 758077e8..00000000 --- a/src/decl.jl +++ /dev/null @@ -1,145 +0,0 @@ -# The map_subscripts function is stolen from Symbolics.jl -const IndexMap = Dict{Char,Char}( - '-' => '₋', - '0' => '₀', - '1' => '₁', - '2' => '₂', - '3' => '₃', - '4' => '₄', - '5' => '₅', - '6' => '₆', - '7' => '₇', - '8' => '₈', - '9' => '₉') - -function map_subscripts(indices) - str = string(indices) - join(IndexMap[c] for c in str) -end - -# Define a type hierarchy to describe a variable declaration. This is mainly for convenient pattern matching later. -abstract type VarDecl end - -struct SymDecl <: VarDecl - sym :: Symbol -end - -struct NamedDecl <: VarDecl - name :: String - rest :: VarDecl -end - -struct FunctionDecl <: VarDecl - rest :: VarDecl -end - -struct TensorDecl <: VarDecl - ranges :: Vector{AbstractRange} - rest :: VarDecl -end - -struct AssumptionsDecl <: VarDecl - assumptions :: Vector{Symbol} - rest :: VarDecl -end - -# Transform a Decl struct in an Expression that calls SymPy to declare the corresponding symbol -function gendecl(x::VarDecl) - asstokw(a) = Expr(:kw, esc(a), true) - val = :($(ctor(x))($(name(x, missing)), $(map(asstokw, assumptions(x))...))) - :($(esc(sym(x))) = $(genreshape(val, x))) -end - -# Transform an expression in a Decl struct -function parsedecl(expr) - # @syms x - if isa(expr, Symbol) - return SymDecl(expr) - - # @syms x::assumptions, where assumption = assumptionkw | (assumptionkw...) - elseif isa(expr, Expr) && expr.head == :(::) - symexpr, assumptions = expr.args - assumptions = isa(assumptions, Symbol) ? [assumptions] : assumptions.args - return AssumptionsDecl(assumptions, parsedecl(symexpr)) - - # @syms x=>"name" - elseif isa(expr, Expr) && expr.head == :call && expr.args[1] == :(=>) - length(expr.args) == 3 || parseerror() - isa(expr.args[3], String) || parseerror() - - expr, strname = expr.args[2:end] - return NamedDecl(strname, parsedecl(expr)) - - # @syms x() - elseif isa(expr, Expr) && expr.head == :call && expr.args[1] != :(=>) - length(expr.args) == 1 || parseerror() - return FunctionDecl(parsedecl(expr.args[1])) - - # @syms x[1:5, 3:9] - elseif isa(expr, Expr) && expr.head == :ref - length(expr.args) > 1 || parseerror() - ranges = map(parserange, expr.args[2:end]) - return TensorDecl(ranges, parsedecl(expr.args[1])) - else - parseerror() - end -end - -function parserange(expr) - range = eval(expr) - isa(range, AbstractRange) || parseerror() - range -end - -sym(x::SymDecl) = x.sym -sym(x::NamedDecl) = sym(x.rest) -sym(x::FunctionDecl) = sym(x.rest) -sym(x::TensorDecl) = sym(x.rest) -sym(x::AssumptionsDecl) = sym(x.rest) - -ctor(::SymDecl) = :symbols -ctor(x::NamedDecl) = ctor(x.rest) -ctor(::FunctionDecl) = :SymFunction -ctor(x::TensorDecl) = ctor(x.rest) -ctor(x::AssumptionsDecl) = ctor(x.rest) - -assumptions(::SymDecl) = [] -assumptions(x::NamedDecl) = assumptions(x.rest) -assumptions(x::FunctionDecl) = assumptions(x.rest) -assumptions(x::TensorDecl) = assumptions(x.rest) -assumptions(x::AssumptionsDecl) = x.assumptions - -# Reshape is not used by most nodes, but TensorNodes require the output to be given -# the shape matching the specification. For instance if @syms x[1:3, 2:6], we should -# have size(x) = (3, 5) -genreshape(expr, ::SymDecl) = expr -genreshape(expr, x::NamedDecl) = genreshape(expr, x.rest) -genreshape(expr, x::FunctionDecl) = genreshape(expr, x.rest) -genreshape(expr, x::TensorDecl) = let - shape = tuple(length.(x.ranges)...) - :(reshape(collect($(expr)), $(shape))) -end -genreshape(expr, x::AssumptionsDecl) = genreshape(expr, x.rest) - -# To find out the name, we need to traverse in both directions to make sure that each node can get -# information from parents and children about possible name. -# This is done because the expr tree will always look like NamedDecl -> ... -> TensorDecl -> ... -> SymDecl -# and the TensorDecl node will need to know if it should create names base on a NamedDecl parent or -# based on the SymDecl leaf. -name(x::SymDecl, parentname) = coalesce(parentname, String(x.sym)) -name(x::NamedDecl, parentname) = coalesce(name(x.rest, x.name), x.name) -name(x::FunctionDecl, parentname) = name(x.rest, parentname) -name(x::AssumptionsDecl, parentname) = name(x.rest, parentname) -name(x::TensorDecl, parentname) = let - basename = name(x.rest, parentname) - # we need to double reverse the indices to make sure that we traverse them in the natural order - namestensor = map(Iterators.product(x.ranges...)) do ind - sub = join(map(map_subscripts, ind), "_") - string(basename, sub) - end - join(namestensor[:], ", ") -end - -function parseerror() - error("Incorrect @syms syntax. Try `@syms x::(real,positive)=>\"x₀\" y() z::complex n::integer` for instance.") -end diff --git a/src/deprecated.jl b/src/deprecated.jl deleted file mode 100644 index eae25771..00000000 --- a/src/deprecated.jl +++ /dev/null @@ -1,136 +0,0 @@ -# This file contains numerous deprecations for `SymPy` -# -# `SymPy` took a pretty lenient approach to what it wrapped -- it if was a member -# of the `sympy` object, essentially it got wrapped. (cf. `importexport.jl`) -# The `SymPyCore` backend takes a much more selective approach, consequently not -# adding `Julia` methods for many `sympy` functions. -# -# This file contains many deprecations. There are a few others utilizing `Base.depwarn` -# elswhere. -# -# The following are to be deprecated, but didn't easily fit with `Julia`'s deprecations: -# -# * `True` and `False` are deprecated in favor of `Sym(true)` and `Sym(false)`. (`SymPy` didn't properly wrap `BooleanTrue` or `BooleanFalse`. -# -# * The python object `sympy_core` can just be `sympy.core`. Similarly `sympy_matrices` is sjust `sympy.matrices`. -# -# * The macro `@vars` is deprecated in favor of `@syms`; `@symfuns` is deprecated, as `@syms` can be used. -# -# * the `import_from` method to import all functions and wrap them from some module is deprecated. -# -# That should be it, but if not. Apologies. - -Base.@deprecate conjugate(x::Sym, args...; kwargs...) conj(x, args...; kwargs...) true - -Base.@deprecate cse(ex::SymbolicObject, args...; kwargs...) sympy.cse(ex, args...; kwargs...) true -Base.@deprecate denom(ex::SymbolicObject, args...; kwargs...) sympy.denom(ex, args...; kwargs...) true -Base.@deprecate flatten(ex::SymbolicObject, args...; kwargs...) sympy.flatten(ex, args...; kwargs...) true -Base.@deprecate unflatten(ex::SymbolicObject, args...; kwargs...) sympy.unflatten(ex, args...; kwargs...) true -Base.@deprecate interpolate(ex::SymbolicObject, args...; kwargs...) sympy.interpolate(ex, args...; kwargs...) true -Base.@deprecate intervals(ex::SymbolicObject, args...; kwargs...) sympy.intervals(ex, args...; kwargs...) true -Base.@deprecate isolate(ex::SymbolicObject, args...; kwargs...) sympy.isolate(ex, args...; kwargs...) true -Base.@deprecate isprime(ex::SymbolicObject, args...; kwargs...) sympy.isprime(ex, args...; kwargs...) true -Base.@deprecate line_integrate(ex::SymbolicObject, args...; kwargs...) sympy.line_integrate(ex, args...; kwargs...) true -Base.@deprecate ln(ex::SymbolicObject, args...; kwargs...) sympy.ln(ex, args...; kwargs...) true -Base.@deprecate prime(ex::SymbolicObject, args...; kwargs...) sympy.prime(ex, args...; kwargs...) true -Base.@deprecate real_root(ex::SymbolicObject, args...; kwargs...) sympy.real_root(ex, args...; kwargs...) true -Base.@deprecate root(ex::SymbolicObject, args...; kwargs...) sympy.root(ex, args...; kwargs...) true -Base.@deprecate rootof(ex::SymbolicObject, args...; kwargs...) sympy.rootof(ex, args...; kwargs...) true -Base.@deprecate rsolve(ex::SymbolicObject, args...; kwargs...) sympy.rsolve(ex, args...; kwargs...) true -Base.@deprecate srepr(ex::SymbolicObject, args...; kwargs...) sympy.srepr(ex, args...; kwargs...) true -Base.@deprecate multiplicity(ex::SymbolicObject, args...; kwargs...) sympy.multiplicity(ex, args...; kwargs...) true -Base.@deprecate nsimplify(ex::SymbolicObject, args...; kwargs...) sympy.nsimplify(ex, args...; kwargs...) true -Base.@deprecate numer(ex::SymbolicObject, args...; kwargs...) sympy.numer(ex, args...; kwargs...) true -Base.@deprecate ode_order(ex::SymbolicObject, args...; kwargs...) sympy.ode_order(ex, args...; kwargs...) true -Base.@deprecate pdsolve(ex::SymbolicObject, args...; kwargs...) sympy.pdsolve(ex, args...; kwargs...) true -Base.@deprecate Abs(ex::SymbolicObject, args...; kwargs...) sympy.Abs(ex, args...; kwargs...) true -Base.@deprecate And(ex::SymbolicObject, args...; kwargs...) sympy.And(ex, args...; kwargs...) true -Base.@deprecate DiracDelta(ex::SymbolicObject, args...; kwargs...) sympy.DiracDelta(ex, args...; kwargs...) true -Base.@deprecate Equality(ex::SymbolicObject, args...; kwargs...) sympy.Equality(ex, args...; kwargs...) true -Base.@deprecate GreaterThan(ex::SymbolicObject, args...; kwargs...) sympy.GreaterThan(ex, args...; kwargs...) true -Base.@deprecate LessThan(ex::SymbolicObject, args...; kwargs...) sympy.LessThan(ex, args...; kwargs...) true -Base.@deprecate Max(ex::SymbolicObject, args...; kwargs...) sympy.Max(ex, args...; kwargs...) true -Base.@deprecate Min(ex::SymbolicObject, args...; kwargs...) sympy.Min(ex, args...; kwargs...) true -Base.@deprecate Not(ex::SymbolicObject, args...; kwargs...) sympy.Not(ex, args...; kwargs...) true -Base.@deprecate Or(ex::SymbolicObject, args...; kwargs...) sympy.Or(ex, args...; kwargs...) true -Base.@deprecate StrictGreaterThan(ex::SymbolicObject, args...; kwargs...) sympy.StrictGreaterThan(ex, args...; kwargs...) true -Base.@deprecate StrictLessThan(ex::SymbolicObject, args...; kwargs...) sympy.StrictLessThan(ex, args...; kwargs...) true -Base.@deprecate Unequality(ex::SymbolicObject, args...; kwargs...) sympy.Unequality(ex, args...; kwargs...) true -Base.@deprecate Xor(ex::SymbolicObject, args...; kwargs...) sympy.Xor(ex, args...; kwargs...) true - -Base.@deprecate plot_parametric_surface(exs, args...; kwargs...) sympy.plotting.plot3d_parametric_surface(exs..., args...; kwargs...) true -Base.@deprecate plot_implicit(ex, args...; kwargs...) sympy.plotting.plot_implicit(ex, args...; kwargs...) true - - -# mpmath @deprecate -function expj(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The expj function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.expj(...)`.", :expj) - mpmath.expj(ex, args...; kwargs...) -end -export expj - -function expjpi(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The expjpi function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.expjpi(...)`.", :expjpi) - mpmath.expjpi(ex, args...; kwargs...) -end -export expjpi - -function fac(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The fac function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.fac(...)`.", :fac) - mpmath.fac(ex, args...; kwargs...) -end -export fac - -function nint(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The nint function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.nint(...)`.", :nint) - mpmath.nint(ex, args...; kwargs...) -end -export nint - -function fib(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The fib function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.fib(...)`.", :fib) - mpmath.fib(ex, args...; kwargs...) -end -export fib - -function monitor(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The monitor function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.monitor(...)`.", :monitor) - mpmath.monitor(ex, args...; kwargs...) -end -export monitor - -function bernfrac(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The bernfrac function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.bernfrac(...)`.", :bernfrac) - mpmath.bernfrac(ex, args...; kwargs...) -end -export bernfrac - -function doctests(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The doctests function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.doctests(...)`.", :doctests) - mpmath.doctests(ex, args...; kwargs...) -end -export doctests - -function ei(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The ei function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.ei(...)`.", :ei) - mpmath.ei(ex, args...; kwargs...) -end -export ei - -function timing(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The timing function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.timing(...)`.", :timing) - mpmath.timing(ex, args...; kwargs...) -end -export timing - -function rgamma(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The rgamma function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.rgamma(...)`.", :rgamma) - mpmath.rgamma(ex, args...; kwargs...) -end -export rgamma - -function e1(ex::SymbolicObject, args...; kwargs...) - Base.depwarn("The e1 function is deprecated. To use this feature, you would need to import the python library `mpmath` and then call as `mpmath.e1(...)`.", :e1) - mpmath.e1(ex, args...; kwargs...) -end -export e1 diff --git a/src/generic.jl b/src/generic.jl deleted file mode 100644 index 3ae184b8..00000000 --- a/src/generic.jl +++ /dev/null @@ -1,117 +0,0 @@ -## functions for generic programming within SymPy -## if a generic method should apply to a sympy variable, it would -## be defined here. - - - -## Iterator for Sym -import Base.iterate -iterate(x::Sym) = (x.__pyobject__, 0) -iterate(x::Sym, state) = nothing - - -Base.isless(a::Sym, b::Sym) = (a != sympy.nan && b != sympy.nan) && sympy.Lt(a,b) == Sym(true) -Base.isless(a::Sym, b::Number) = isless(promote(a,b)...) -Base.isless(a::Number, b::Sym) = isless(promote(a,b)...) - -Base.isequal(a::Sym, b::Sym) = Eq(a,b) == Sym(true) -Base.isequal(a::Sym, b::Number) = Eq(promote(a,b)...) == Sym(true) -Base.isequal(a::Number, b::Sym) = Eq(promote(a,b)...) == Sym(true) - - -# Floating point bits -Base.eps(::Type{Sym}) = zero(Sym) -Base.eps(::Sym) = zero(Sym) -Base.signbit(x::Sym) = x < 0 -Base.copysign(x::Sym, y::Number) = abs(x) * sign(y) -Base.flipsign(x::Sym, y) = signbit(y) ? -x : x -Base.typemax(::Type{Sym}) = oo -Base.typemin(::Type{Sym}) = -oo - -Base.fld(x::SymbolicObject, y) = floor(x/y) -Base.cld(x::SymbolicObject, y) = ceil(x/y) -Base.mod(x::SymbolicObject, args...)= Mod(x, args...) -#Base.mod1 -#Base.mod2pi -#Base.fldmod - -# so we can compare numbers with ≈ -Base.rtoldefault(::Type{<:SymbolicObject}) = eps() - -function Base.round(x::Sym; kwargs...) - length(free_symbols(x)) > 0 && throw(ArgumentError("can't round a symbolic expression")) - round(N(x); kwargs...) -end - -function Base.trunc(x::Sym; kwargs...) - length(free_symbols(x)) > 0 && throw(ArgumentError("can't truncate a symbolic expression")) - trunc(N(x); kwargs...) -end - -# check on type of number -# these are boolean: true/false; not tru/false/nothing,as in SymPy -Base.isfinite(x::Sym) = !is_(:infinite, x) -Base.isinf(x::Sym) = is_(:infinite, x) -Base.isnan(x::Sym) = x == sympy.nan -Base.isinteger(x::Sym) = is_(:integer, x) -Base.iseven(x::Sym) = is_(:even, x) -Base.isreal(x::Sym) = is_(:real, x) -Base.isodd(x::Sym) = is_(:odd, x) - - - - -## zero and one (zeros?) -Base.zero(x::Sym) = Sym(0) -Base.zero(::Type{Sym}) = Sym(0) - -Base.one(x::Sym) = Sym(1) -Base.one(::Type{Sym}) = Sym(1) - - - - -## float, complex, real, imag, angle -Base.float(x::Sym) = _float(N(x)) -_float(x::Sym) = throw(ArgumentError("variable must have no free symbols")) -_float(x) = float(x) -Base.Float64(x::Sym) = _Float64(N(x)) -_Float64(x::Sym) = throw(ArgumentError("variable must have no free symbols")) -_Float64(x) = Float64(x) - -Base.Integer(x::Sym) = is_integer(x) ? N(x) : throw(DomainError("x can not be converted to an integer")) - -Base.complex(::Type{Sym}) = Sym -Base.complex(r::Sym) = real(r) + imag(r) * im -function Base.complex(r::Sym, i) - isreal(r) || throw(ArgumentError("r and i must not be complex")) - isreal(i) || throw(ArgumentError("r and i must not be complex")) - N(r) + N(i) * im -end -Base.complex(xs::AbstractArray{Sym}) = complex.(xs) # why is this in base? - -Base.conj(x::SymbolicObject) = x.conjugate() -function Base.transpose(f::Sym)::Sym - if pycall_hasproperty(PyObject(f), :transpose) - f.transpose() - else - f - end -end - -Base.real(::Type{Sym}) = Sym -Base.real(x::Sym) = sympy.re(x) -Base.imag(x::Sym) = sympy.im(x) - -Base.angle(z::SymPy.SymbolicObject) = atan(sympy.im(z), sympy.re(z)) - - -# sympy.div for poly division -Base.divrem(x::Sym, y) = sympy.div(x, y) -# needed for #390; but odd -Base.div(x::Sym, y::Union{Sym,Number}) = convert(Sym, sympy.floor(x/convert(Sym,y))) -Base.rem(x::Sym, y::Union{Sym,Number}) = x-Sym(y)*Sym(sympy.floor.(x/y)) - - -Base.denominator(x::SymbolicObject) = denom(x) -Base.numerator(x::SymbolicObject) = numer(x) diff --git a/src/importexport.jl b/src/importexport.jl deleted file mode 100644 index e00a739d..00000000 --- a/src/importexport.jl +++ /dev/null @@ -1,152 +0,0 @@ -### These are generated by a) uncommenting import_sympy() in __init__ b) uncomment println parts in import_from -#XX expj(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :expj)(ex, Sym.(args)...; kwargs...); export expj -#fac(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :fac)(ex, Sym.(args)...; kwargs...); export fac -#nint(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :nint)(ex, Sym.(args)...; kwargs...); export nint -Base.ceil(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :ceil)(ex, Sym.(args)...; kwargs...) -#fib(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :fib)(ex, Sym.(args)...; kwargs...); export fib -#monitor(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :monitor)(ex, Sym.(args)...; kwargs...); export monitor -Base.cospi(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :cospi)(ex, Sym.(args)...; kwargs...) -#bernfrac(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :bernfrac)(ex, Sym.(args)...; kwargs...); export bernfrac -#doctests(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :doctests)(ex, Sym.(args)...; kwargs...); export doctests -#ei(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :ei)(ex, Sym.(args)...; kwargs...); export ei -Base.sinpi(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :sinpi)(ex, Sym.(args)...; kwargs...) -#timing(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :timing)(ex, Sym.(args)...; kwargs...); export timing -#rgamma(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :rgamma)(ex, Sym.(args)...; kwargs...); export rgamma -#expjpi(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :expjpi)(ex, Sym.(args)...; kwargs...); export expjpi -#e1(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :e1)(ex, Sym.(args)...; kwargs...); export e1 - -SpecialFunctions.ellipk(ex::SymbolicObject, args...; kwargs...)=getproperty(mpmath, :ellipk)(ex, Sym.(args)...; kwargs...); -SpecialFunctions.digamma(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :digamma)(ex, Sym.(args)...; kwargs...) -#Base.rem(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :rem)(ex, Sym.(args)...; kwargs...) -Base.asin(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :asin)(ex, Sym.(args)...; kwargs...) -Base.acosh(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :acosh)(ex, Sym.(args)...; kwargs...) -Base.im(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :im)(ex, Sym.(args)...; kwargs...) -Base.collect(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :collect)(ex, Sym.(args)...; kwargs...) -Base.Function(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Function)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.erfi(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :erfi)(ex, Sym.(args)...; kwargs...) -Base.floor(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :floor)(ex, Sym.(args)...; kwargs...) -Base.product(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :product)(ex, Sym.(args)...; kwargs...) -Base.gcd(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :gcd)(ex, Sym.(args)...; kwargs...) -Base.atan(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :atan)(ex, Sym.(args)...; kwargs...) -Base.sqrt(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :sqrt)(ex, Sym.(args)...; kwargs...) -Base.acsch(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :acsch)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.besselj(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :besselj)(ex, Sym.(args)...; kwargs...) -Base.adjoint(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :adjoint)(ex, Sym.(args)...; kwargs...) -Base.asinh(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :asinh)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.besselk(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :besselk)(ex, Sym.(args)...; kwargs...) -Base.binomial(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :binomial)(ex, Sym.(args)...; kwargs...) -Base.asec(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :asec)(ex, Sym.(args)...; kwargs...) -Base.exp(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :exp)(ex, Sym.(args)...; kwargs...) -Base.sech(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :sech)(ex, Sym.(args)...; kwargs...) -Base.cbrt(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :cbrt)(ex, Sym.(args)...; kwargs...) -Base.acsc(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :acsc)(ex, Sym.(args)...; kwargs...) -Base.factorial(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :factorial)(ex, Sym.(args)...; kwargs...) -Base.trunc(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :trunc)(ex, Sym.(args)...; kwargs...) -Base.acos(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :acos)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.polygamma(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :polygamma)(ex, Sym.(args)...; kwargs...) -Base.tanh(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :tanh)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.erfinv(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :erfinv)(ex, Sym.(args)...; kwargs...) -Base.sinh(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :sinh)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.airybi(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :airybi)(ex, Sym.(args)...; kwargs...) -Base.asech(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :asech)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.erfcinv(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :erfcinv)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.besseli(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :besseli)(ex, Sym.(args)...; kwargs...) -Base.acot(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :acot)(ex, Sym.(args)...; kwargs...) -Base.coth(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :coth)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.airyai(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :airyai)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.erf(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :erf)(ex, Sym.(args)...; kwargs...) -Base.cosh(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :cosh)(ex, Sym.(args)...; kwargs...) -LinearAlgebra.diag(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :diag)(ex, Sym.(args)...; kwargs...) -Base.lcm(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :lcm)(ex, Sym.(args)...; kwargs...) -Base.zeros(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :zeros)(ex, Sym.(args)...; kwargs...) -Base.cot(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :cot)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.zeta(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :zeta)(ex, Sym.(args)...; kwargs...) -Base.sign(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :sign)(ex, Sym.(args)...; kwargs...) -Base.permutedims(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :permutedims)(ex, Sym.(args)...; kwargs...) -Base.cos(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :cos)(ex, Sym.(args)...; kwargs...) -Base.transpose(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :transpose)(ex, Sym.(args)...; kwargs...) -Base.MathConstants.catalan(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :catalan)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.erfc(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :erfc)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.bessely(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :bessely)(ex, Sym.(args)...; kwargs...) -Base.diff(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :diff)(ex, Sym.(args)...; kwargs...) -Base.tan(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :tan)(ex, Sym.(args)...; kwargs...) -Base.decompose(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :decompose)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.airyaiprime(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :airyaiprime)(ex, Sym.(args)...; kwargs...) -Base.csch(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :csch)(ex, Sym.(args)...; kwargs...) -Base.csc(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :csc)(ex, Sym.(args)...; kwargs...) -Base.ones(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :ones)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.trigamma(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :trigamma)(ex, Sym.(args)...; kwargs...) -Base.prod(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :prod)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.beta(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :beta)(ex, Sym.(args)...; kwargs...) -Base.sec(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :sec)(ex, Sym.(args)...; kwargs...) -Base.acoth(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :acoth)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.airybiprime(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :airybiprime)(ex, Sym.(args)...; kwargs...) -Base.atanh(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :atanh)(ex, Sym.(args)...; kwargs...) -LinearAlgebra.det(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :det)(ex, Sym.(args)...; kwargs...) -SpecialFunctions.gamma(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :gamma)(ex, Sym.(args)...; kwargs...) -Base.reshape(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :reshape)(ex, Sym.(args)...; kwargs...) -Base.sin(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :sin)(ex, Sym.(args)...; kwargs...) -simplify(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :simplify)(ex, Sym.(args)...; kwargs...); export simplify -summation(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :summation)(ex, Sym.(args)...; kwargs...); export summation -solve(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :solve)(ex, Sym.(args)...; kwargs...); export solve -#Max(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Max)(ex, Sym.(args)...; kwargs...); export Max -#XXXunflatten(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :unflatten)(ex, Sym.(args)...; kwargs...); export unflatten -#denom(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :denom)(ex, Sym.(args)...; kwargs...); export denom -nonlinsolve(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :nonlinsolve)(ex, Sym.(args)...; kwargs...); export nonlinsolve -cancel(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :cancel)(ex, Sym.(args)...; kwargs...); export cancel -solveset(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :solveset)(ex, Sym.(args)...; kwargs...); export solveset -#DiracDelta(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :DiracDelta)(ex, Sym.(args)...; kwargs...); export DiracDelta -#Or(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Or)(ex, Sym.(args)...; kwargs...); export Or -conjugate(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :conjugate)(ex, Sym.(args)...; kwargs...); export conjugate -#XXX flatten(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :flatten)(ex, Sym.(args)...; kwargs...); export flatten -#Not(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Not)(ex, Sym.(args)...; kwargs...); export Not -integrate(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :integrate)(ex, Sym.(args)...; kwargs...); export integrate -roots(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :roots)(ex, Sym.(args)...; kwargs...); export roots -factor(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :factor)(ex, Sym.(args)...; kwargs...); export factor -#pdsolve(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :pdsolve)(ex, Sym.(args)...; kwargs...); export pdsolve -#Abs(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Abs)(ex, Sym.(args)...; kwargs...); export Abs -Heaviside(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Heaviside)(ex, Sym.(args)...; kwargs...); export Heaviside -#nsimplify(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :nsimplify)(ex, Sym.(args)...; kwargs...); export nsimplify -#isprime(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :isprime)(ex, Sym.(args)...; kwargs...); export isprime -apart(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :apart)(ex, Sym.(args)...; kwargs...); export apart -#Min(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Min)(ex, Sym.(args)...; kwargs...); export Min -#intervals(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :intervals)(ex, Sym.(args)...; kwargs...); export intervals -#intersection(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :intersection)(ex, Sym.(args)...; kwargs...); export intersection -#line_integrate(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :line_integrate)(ex, Sym.(args)...; kwargs...); export line_integrate -real_roots(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :real_roots)(ex, Sym.(args)...; kwargs...); export real_roots -#isolate(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :isolate)(ex, Sym.(args)...; kwargs...); export isolate -linsolve(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :linsolve)(ex, Sym.(args)...; kwargs...); export linsolve -#Xor(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :Xor)(ex, Sym.(args)...; kwargs...); export Xor -#real_root(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :real_root)(ex, Sym.(args)...; kwargs...); export real_root -nsolve(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :nsolve)(ex, Sym.(args)...; kwargs...); export nsolve -#ln(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :ln)(ex, Sym.(args)...; kwargs...); export ln -#rsolve(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :rsolve)(ex, Sym.(args)...; kwargs...); export rsolve -degree(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :degree)(ex, Sym.(args)...; kwargs...); export degree -#prime(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :prime)(ex, Sym.(args)...; kwargs...); export prime -limit(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :limit)(ex, Sym.(args)...; kwargs...); export limit -#And(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :And)(ex, Sym.(args)...; kwargs...); export And -#root(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :root)(ex, Sym.(args)...; kwargs...); export root -#rootof(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :rootof)(ex, Sym.(args)...; kwargs...); export rootof -#ode_order(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :ode_order)(ex, Sym.(args)...; kwargs...); export ode_order -#multiplicity(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :multiplicity)(ex, Sym.(args)...; kwargs...); export multiplicity -series(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :series)(ex, Sym.(args)...; kwargs...); export series -expand(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :expand)(ex, Sym.(args)...; kwargs...); export expand -hessian(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :hessian)(ex, Sym.(args)...; kwargs...); export hessian -#srepr(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :srepr)(ex, Sym.(args)...; kwargs...); export srepr -nroots(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :nroots)(ex, Sym.(args)...; kwargs...); export nroots -#interpolate(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :interpolate)(ex, Sym.(args)...; kwargs...); export interpolate -#numer(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :numer)(ex, Sym.(args)...; kwargs...); export numer -#XXX cse(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :cse)(ex, Sym.(args)...; kwargs...); export cse -together(ex::SymbolicObject, args...; kwargs...)=getproperty(sympy, :together)(ex, Sym.(args)...; kwargs...); export together -#Equality(ex::Number, args...; kwargs...)=getproperty(sympy, :Equality)(ex, Sym.(args)...; kwargs...); export Equality -Ne(ex::Number, args...; kwargs...)=getproperty(sympy, :Ne)(ex, Sym.(args)...; kwargs...); export Ne -#LessThan(ex::Number, args...; kwargs...)=getproperty(sympy, :LessThan)(ex, Sym.(args)...; kwargs...); export LessThan -Gt(ex::Number, args...; kwargs...)=getproperty(sympy, :Gt)(ex, Sym.(args)...; kwargs...); export Gt -Eq(ex::Number, args...; kwargs...)=getproperty(sympy, :Eq)(ex, Sym.(args)...; kwargs...); export Eq -#GreaterThan(ex::Number, args...; kwargs...)=getproperty(sympy, :GreaterThan)(ex, Sym.(args)...; kwargs...); export GreaterThan -Le(ex::Number, args...; kwargs...)=getproperty(sympy, :Le)(ex, Sym.(args)...; kwargs...); export Le -Lt(ex::Number, args...; kwargs...)=getproperty(sympy, :Lt)(ex, Sym.(args)...; kwargs...); export Lt -#Unequality(ex::Number, args...; kwargs...)=getproperty(sympy, :Unequality)(ex, Sym.(args)...; kwargs...); export Unequality -Ge(ex::Number, args...; kwargs...)=getproperty(sympy, :Ge)(ex, Sym.(args)...; kwargs...); export Ge -#StrictLessThan(ex::Number, args...; kwargs...)=getproperty(sympy, :StrictLessThan)(ex, Sym.(args)...; kwargs...); export StrictLessThan -#StrictGreaterThan(ex::Number, args...; kwargs...)=getproperty(sympy, :StrictGreaterThan)(ex, Sym.(args)...; kwargs...); export StrictGreaterThan diff --git a/src/lambdify.jl b/src/lambdify.jl deleted file mode 100644 index 1110d78e..00000000 --- a/src/lambdify.jl +++ /dev/null @@ -1,300 +0,0 @@ -# lambdify an expression - - - -## Mapping of SymPy Values into julia values -val_map = Dict( - "Zero" => :(0), - "One" => :(1), - "NegativeOne" => :(-1), - "Half" => :(1/2), - "Pi" => :pi, - "Exp1" => :ℯ, - "Infinity" => :Inf, - "NegativeInfinity" => :(-Inf), - "ComplexInfinity" => :Inf, # error? - "ImaginaryUnit" => :im, - "BooleanTrue" => :true, - "BooleanFalse" => :false - ) - -function _piecewise(args...) - as = copy([args...]) - val, cond = pop!(as) - ex = Expr(:call, :ifelse, cond, convert(Expr,val), :nothing) - while length(as) > 0 - val, cond = pop!(as) - ex = Expr(:call, :ifelse, cond, convert(Expr,val), convert(Expr, ex)) - end - ex -end - - -## Mapping of Julia function names into julia ones -## most are handled by Symbol(fnname), the following catch exceptions -## Hack to avoid Expr(:call, :*,2, x) being 2x and not 2*x -## As of newer sympy versions, this is no longer needed. -__PROD__(args...) = prod(args) - -__ANY__(xs...) = any(xs) -__ALL__(xs...) = all(xs) -__ZERO__(xs...) = 0 -# not quite a match; NaN not θ(0) when evaluated at 0 w/o second argument -__HEAVISIDE__ = (a...) -> (a[1] < 0 ? 0 : (a[1] > 0 ? 1 : (length(a) > 1 ? a[2] : NaN))) -__POW__(x, y::Int) = Base.literal_pow(^, x, Val(y)) # otherwise -__POW__(a,b) = (@show a, b; (a)^(b)) -# __SYMPY__ALL__, -fn_map = Dict( - "Add" => :+, - "Sub" => :-, - "Mul" => :*, # :(SymPy.__PROD__) - "Div" => :/, - "Pow" => :^, - #"Pow" => :(SymPy.__POW__), - "re" => :real, - "im" => :imag, - "Abs" => :abs, - "Min" => :min, - "Max" => :max, - "Poly" => :identity, - "Piecewise" => :(SymPy._piecewise), - "Order" => :(SymPy.__ZERO__), # :(as...) -> 0, - "And" => :(SymPy.__ALL__), #:((as...) -> all(as)), #:(&), - "Or" => :(SymPy.__ANY__), #:((as...) -> any(as)), #:(|), - "Less" => :(<), - "LessThan" => :(<=), - "StrictLessThan" => :(<), - "Equal" => :(==), - "Equality" => :(==), - "Unequality" => :(!==), - "StrictGreaterThan" => :(>), - "GreaterThan" => :(>=), - "Greater" => :(>), - "conjugate" => :conj, - "atan2" => :atan, - "TupleArg" => :tuple, - "Heaviside" => :(SymPy.__HEAVISIDE__), -) - -map_fn(key, fn_map) = haskey(fn_map, key) ? fn_map[key] : Symbol(key) - -Base.convert(::Type{Expr}, x::SymbolicObject) = walk_expression(x) - -""" - walk_expression(ex; values=Dict(), fns=Dict()) - -Convert a symbolic SymPy expression into a `Julia` expression. This is needed to use functions in external packages in lambdified functions. - -## Example -``` -using SymPy -@syms x y -ex = sympy.hyper((2,2),(3,3),x) * y -``` - -Calling `lambdify(ex)` will fail to make a valid function, as `hyper` is implemented in `HypergeometricFunctions.pFq`. So, we have: - -``` -using HypergeometricFunctions -d = Dict("hyper" => :pFq) -body = SymPy.walk_expression(ex, fns=d) -syms = Symbol.(free_symbols(ex)) -fn = eval(Expr(:function, Expr(:call, gensym(), syms...), body)); -fn(1,1) # 1.6015187080185656 -``` - -""" -function walk_expression(ex; values=Dict(), fns=Dict()) - - fns_map = merge(fn_map, fns) - vals_map = merge(val_map, values) - - fn = Introspection.funcname(ex) - - # special case `F(t) = ...` output from ODE - # this may be removed if it proves a bad idea.... - if fn == "Equality" && lhs(ex).is_Function - return walk_expression(rhs(ex), values=values, fns=fns) - end - - if fn == "Symbol" || fn == "Dummy" || fn == "IndexedBase" - str_ex = string(ex) - return get(vals_map, str_ex, Symbol(str_ex)) - elseif fn in ["Integer" , "Float"] - return N(ex) - elseif fn == "Rational" - return convert(Int, numer(ex))//convert(Int, denom(ex)) - ## piecewise requires special treatment - elseif fn == "Piecewise" - return _piecewise([walk_expression(cond, values=values, fns=fns) for cond in Introspection.args(ex)]...) - elseif fn == "ExprCondPair" - val, cond = Introspection.args(ex) - return (val, walk_expression(cond, values=values, fns=fns)) - elseif fn == "Tuple" - return walk_expression.(Introspection.args(ex), values=values, fns=fns) - elseif fn == "Indexed" - return Expr(:ref, [walk_expression(a, values=values, fns=fns) for a in Introspection.args(ex)]...) - elseif fn == "Pow" - a, b = Introspection.args(ex) - b == 1//2 && return Expr(:call, :sqrt, walk_expression(a, values=values, fns=fns)) - b == 1//3 && return Expr(:call, :cbrt, walk_expression(a, values=values, fns=fns)) - return Expr(:call, :^, [walk_expression(aᵢ, values=values, fns=fns) for aᵢ in (a,b)]...) - elseif haskey(vals_map, fn) - return vals_map[fn] - end - - as = Introspection.args(ex) - Expr(:call, map_fn(fn, fns_map), [walk_expression(a, values=values, fns=fns) for a in as]...) -end - -""" - lambdify(ex, vars=free_symbols(); - fns=Dict(), values=Dict, use_julia_code=false, - invoke_latest=true) - -Take a symbolic expression and return a `Julia` function or expression to build a function. - -* `ex::Sym` a symbolic expression with 0, 1, or more free symbols - -* `vars` a container of symbols to use for the function arguments. The default is `free_symbols` which has a specific ordering. Specifying `vars` allows this default ordering of arguments to be customized. If `vars` is empty, such as when the symbolic expression has *no* free symbols, a variable arg constant function is returned. - -* `fns::Dict`, `vals::Dict`: Dictionaries that allow customization of the function that walks the expression `ex` and creates the corresponding AST for a Julia expression. See `SymPy.fn_map` and `SymPy.val_map` for the default mappings of sympy functions and values into `Julia`'s AST. - -* `use_julia_code::Bool`: use SymPy's conversion to an expression, the default is `false` - -* `invoke_latest=true`: if `true` will call `eval` and `Base.invokelatest` to return a function that should not have any world age issue. If `false` will return a Julia expression that can be `eval`ed to produce a function. - -Example: - -```jldoctest -julia> using SymPy - -julia> @syms x y z -(x, y, z) - -julia> ex = x^2 * sin(x) - 2 -x ⋅sin(x) - -julia> fn = lambdify(ex); - -julia> fn(pi) -0.0 - -julia> ex = x + 2y + 3z -x + 2⋅y + 3⋅z - -julia> fn = lambdify(ex); - -julia> fn(1,2,3) # order is by free_symbols -14 - -julia> ex(x=>1, y=>2, z=>3) -14 - -julia> fn = lambdify(ex, (y,x,z)); - -julia> fn(1,2,3) -13 -``` - -!!! Note: - -The default produces slower functions due to the calls to `eval` and -`Base.invokelatest`. In the following `g2` (which, as seen, requires -additional work to compute) is as fast as calling `f` (on non symbolic -types), whereas `g1` is an order of magnitude slower in this example. - -``` -julia> @vars x -(x,) - -julia> f(x) = exp(cot(x)) -f (generic function with 1 method) - -julia> g1 = lambdify(f(x)) -#88 (generic function with 1 method) - -julia> ex = lambdify(f(x), invoke_latest=false) -:(function var"##271"(x) - exp(cot(x)) - end) - -julia> @eval g2(x) = (\$ex)(x) -g2 (generic function with 1 method) -``` - -An alternative, say, is to use `GeneralizedGenerated`'s `mk_function`, as follows: - -``` -julia> using GeneralizedGenerated - -julia> body = convert(Expr, f(x)) -:(exp(cot(x))) - -julia> g3 = mk_function((:x,), (), body) -function = (x;) -> begin - (Main).exp((Main).cot(x)) -end -``` - -This function will be about 2-3 times slower than `f`. - -""" -function lambdify(ex::Sym, vars=free_symbols(ex); - fns=Dict(), values=Dict(), - use_julia_code=false, - invoke_latest=true) - if isempty(vars) - # can't call N(ex) here... - v = ex.evalf() - if v.is_real == Sym(true) - val = convert(Real, v) - else - val = Complex(convert(Real, real(v)), convert(Real, imag(v))) - end - return (ts...) -> val - end - body = convert_expr(ex, fns=fns, values=values, use_julia_code=use_julia_code) - ex = expr_to_function(body, vars) - if invoke_latest - fn = eval(ex) - return (args...) -> Base.invokelatest(fn, args...) - else - ex - end -end - -# convert symbolic expression to julia AST -# more flexibly than `convert(Expr, ex)` -function convert_expr(ex::Sym; - fns=Dict(), values=Dict(), - use_julia_code=false) - if use_julia_code - body = Meta.parse(sympy.julia_code(ex)) # issue here with 2.*... - else - body = walk_expression(ex, fns=fns, values=values) - end - body -end - -# take an expression and arguments and return an Expr of a generic function -function expr_to_function(body, vars) - syms = Symbol.(vars) - Expr(:function, Expr(:call, gensym(), syms...), body) -end - -# from @mistguy cf. https://github.com/JuliaPy/SymPy.jl/issues/218 -# T a data type to convert to, when specified -function lambdify(exs::Array{S, N}, vars = union(free_symbols.(exs)...); T::DataType=Nothing, kwargs...) where {S <: Sym, N} - f = lambdify.(exs, (vars,)) # prevent broadcast in vars - if T == Nothing - (args...) -> map.(f, args...) - else - (args...) -> convert(Array{T,N}, map.(f, args...)) - end -end - -Base.convert(::Type{Function}, ex::Sym) = lambdify(ex) - -export lambdify diff --git a/src/latexify_recipe.jl b/src/latexify_recipe.jl deleted file mode 100644 index 35184a80..00000000 --- a/src/latexify_recipe.jl +++ /dev/null @@ -1,12 +0,0 @@ - -using Latexify: Latexify - -# Recipe to hook into Latexify.jl's `latexify` function for `Sym`s. -# We do not use SymPy's `latex` function here since it behaves a bit different -# from Latexify.jl. Thus, we let Latexify.jl handle everything for the sake of -# consistency. -# For example, SymPy doesn't print a multiplication symbol by default while -# Latexify uses a `"\\cdot"` (unless the keyword argument `cdot=false` is set). -Latexify.@latexrecipe function _(x::Sym) - return string(x) -end diff --git a/src/mathfuns.jl b/src/mathfuns.jl deleted file mode 100644 index 14654cba..00000000 --- a/src/mathfuns.jl +++ /dev/null @@ -1,355 +0,0 @@ -## these are differently named than SymPy or missing or ... - - -Base.abs(x::SymbolicObject) = sympy.Abs(x) -Base.abs2(x::SymbolicObject) = x * conj(x) -Base.max(x::Sym, a) = sympy.Max(x, a) -Base.min(x::Sym, a) = sympy.Min(x, a) - - -Base.cbrt(x::Sym) = x^(1//3) -Base.ceil(x::Sym) = sympy.ceiling(x) - -## Trig -Base.asech(z::Sym) = log(sqrt(1/z-1)*sqrt(1/z+1) + 1/z) -Base.acsch(z::Sym) = log(sqrt(1+1/z^2) + 1/z) ## http://mathworld.wolfram.com/InverseHyperbolicCosecant.html -Base.atan(y::Sym, x) = sympy.atan2(y,x) - - -Base.sinc(x::Sym) = iszero(x) ? one(x) : sin(PI*x)/(PI*x) -cosc(x::Sym) = diff(sinc(x)) - -Base.sincos(x::Sym) = (sin(x), cos(x)) -Base.sinpi(x::Sym) = sympy.sin(x*PI) -Base.cospi(x::Sym) = sympy.cos(x*PI) -degree_variants = (:sind, :cosd, :tand, :cotd, :secd, :cscd, - :asind, :acosd, :atand, :acotd, :asecd, :acscd) - -for methvar in degree_variants - meth = Symbol(String(methvar)[1:end-1]) - @eval begin - (Base.$methvar)(ex::SymbolicObject) = ($meth)((PI/180)*ex) - end -end - -Base.rad2deg(x::Sym) = (x * 180) / PI -Base.deg2rad(x::Sym) = (x * PI) / 180 - -Base.hypot(x::Sym, y::Number) = hypot(promote(x,y)...) -Base.hypot(xs::Sym...) = sqrt(sum(abs(xᵢ)^2 for xᵢ ∈ xs)) - -## exponential -Base.log1p(x::Sym) = sympy.log(1 + x) -Base.log(x::Sym) = sympy.log(x) -Base.log(b::Number, x::Sym) = sympy.log(x, b) -Base.log2(x::SymbolicObject) = log(2,x) -Base.log10(x::SymbolicObject) = log(10,x) - - - - -## calculus. -## use a pair for limit x=>0 -limit(x::SymbolicObject, xc::Pair, args...;kwargs...) = limit(x, xc[1], xc[2], args...;kwargs...) -## allow a function -limit(f::Function, x::Sym, c;kwargs...) = limit(Sym(f(x)), x, c; kwargs...) -function limit(f::Function, c;kwargs...) - @vars x - limit(f, x, c; kwargs...) -end - -## This is type piracy and a bad idea -function Base.diff(f::Function, n::Integer=1) - @vars x - sympy.diff(f(x), x, n) -end - -## integrate(ex,a,b) -function integrate(ex::SymbolicObject, a::Number, b::Number) - fs = free_symbols(ex) - if length(fs) !== 1 - @warn "Need exactly on free symbol. Use `integrate(ex, (x, a, b))` instead" - return - end - integrate(ex, (fs[1], a, b)) -end -function integrate(f::Function, a::Number, b::Number) - @vars x - sympy.integrate(f(x), (x, a, b)) -end -function integrate(f::Function) - @syms x - sympy.integrate(f(x), x) -end - - -## Add interfaces for solve, nonlinsolve when vector of equations passed in - -## An alternative to Eq(lhs, rhs) following Symbolics.jl -""" - lhs ~ rhs - -Specify an equation. - -Alternative syntax to `Eq(lhs, rhs)` or `lhs ⩵ rhs` (`\\Equal[tab]`) following `Symbolics.jl`. -""" -Base.:~(lhs::Number, rhs::SymbolicObject) = Eq(lhs, rhs) -Base.:~(lhs::SymbolicObject, rhs::Number) = Eq(lhs, rhs) -Base.:~(lhs::SymbolicObject, rhs::SymbolicObject) = Eq(lhs, rhs) - - -""" - solve - -Use `solve` to solve algebraic equations. - -Examples: - -```julia -julia> using SymPy - -julia> @syms x y a b c d -(x, y, a, b, c, d) - -julia> solve(x^2 + 2x + 1, x) # [-1] -1-element Vector{Sym}: - -1 - -julia> solve(x^2 + 2a*x + a^2, x) # [-a] -1-element Vector{Sym}: - -a - -julia> solve([a*x + b*y-3, c*x + b*y - 1], [x,y]) # Dict(y => (a - 3*c)/(b*(a - c)),x => 2/(a - c)) -Dict{Any, Any} with 2 entries: - y => (a - 3*c)/(a*b - b*c) - x => 2/(a - c) - -``` - -!!! note - A very nice example using `solve` is a [blog](https://newptcai.github.io/euclidean-plane-geometry-with-julia.html) entry on [Napolean's theorem](https://en.wikipedia.org/wiki/Napoleon%27s_theorem) by Xing Shi Cai. -""" -solve() = () - - -""" - nonlinsolve - -Note: if passing variables in use a tuple (e.g., `(x,y)`) and *not* a vector (e.g., `[x,y]`). -""" -nonlinsolve() - - -## dsolve allowing initial condiation to be specified - -""" - dsolve(eqn, var, args..,; ics=nothing, kwargs...) - -Call `sympy.dsolve`. - -The initial conditions are specified with a dictionary. - -Example: - -```jldoctest dsolve -julia> using SymPy - -julia> @syms α, x, f(), g() -(α, x, f, g) - -julia> ∂ = Differential(x) -Differential(x) - -julia> eqn = ∂(f(x)) ~ α * x -d -──(f(x)) = x⋅α -dx -``` - -```julia -julia> dsolve(eqn) - 2 - x ⋅α -f(x) = C₁ + ──── - 2 -``` - -```jldoctest dsolve -julia> dsolve(eqn(α=>2); ics=Dict(f(0)=>1)) |> print # fill in parameter, initial condition -Eq(f(x), x^2 + 1) - -julia> eqn = ∂(∂(f(x))) ~ -f(x); print(eqn) -Eq(Derivative(f(x), (x, 2)), -f(x)) - -julia> dsolve(eqn) -f(x) = C₁⋅sin(x) + C₂⋅cos(x) - -julia> dsolve(eqn; ics = Dict(f(0)=>1, ∂(f)(0) => -1)) -f(x) = -sin(x) + cos(x) - -julia> eqn = ∂(∂(f(x))) - f(x) - exp(x); - -julia> dsolve(eqn, ics=Dict(f(0) => 1, f(1) => Sym(1//2))) |> print # not just 1//2 -Eq(f(x), (x/2 + (-exp(2) - 2 + E)/(-2 + 2*exp(2)))*exp(x) + (-E + 3*exp(2))*exp(-x)/(-2 + 2*exp(2))) -``` - -Systems. Use a tuple, not a vector, of equations, as such are now deprecated by SymPy. - -```jldoctest dsolve -julia> @syms x() y() t g -(x, y, t, g) - -julia> ∂ = Differential(t) -Differential(t) - -julia> eqns = (∂(x(t)) ~ y(t), ∂(y(t)) ~ x(t)) -(Eq(Derivative(x(t), t), y(t)), Eq(Derivative(y(t), t), x(t))) - -julia> dsolve(eqns) -2-element Vector{Sym}: - Eq(x(t), -C1*exp(-t) + C2*exp(t)) - Eq(y(t), C1*exp(-t) + C2*exp(t)) - -julia> dsolve(eqns, ics = Dict(x(0) => 1, y(0) => 2)) -2-element Vector{Sym}: - Eq(x(t), 3*exp(t)/2 - exp(-t)/2) - Eq(y(t), 3*exp(t)/2 + exp(-t)/2) - -julia> eqns = (∂(∂(x(t))) ~ 0, ∂(∂(y(t))) ~ -g) -(Eq(Derivative(x(t), (t, 2)), 0), Eq(Derivative(y(t), (t, 2)), -g)) - -julia> dsolve(eqns) # can't solve for initial conditions though! (NotAlgebraic) -2-element Vector{Sym}: - x(t) = C₁ + C₂⋅t - Eq(y(t), C3 + C4*t - g*t^2/2) - -julia> @syms t x() y() -(t, x, y) - -julia> eq = (∂(x)(t) ~ x(t)*y(t)*sin(t), ∂(y)(t) ~ y(t)^2 * sin(t)) -(Eq(Derivative(x(t), t), x(t)*y(t)*sin(t)), Eq(Derivative(y(t), t), y(t)^2*sin(t))) -``` - -```julia -julia> dsolve(eq) # returns a set to be `collect`ed: -PyObject {Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t))), Eq(y(t), -1/(C1 - cos(t)))} -``` - -```julia -julia> dsolve(eq) |> collect -2-element Vector{Any}: - Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t))) - Eq(y(t), -1/(C1 - cos(t))) -``` - -""" -function dsolve(eqn, args...; - ics::Union{Nothing, AbstractDict, Tuple}=nothing, - kwargs...) - if isa(ics, Tuple) # legacy - _dsolve(eqn, args...; ics=ics, kwargs...) - else - sympy.dsolve(eqn, args...; ics=ics, kwargs...) - end -end - -rhs(x::SymbolicObject) = pycall_hasproperty(x, :rhs) ? x.rhs : x -lhs(x::SymbolicObject) = pycall_hasproperty(x, :lhs) ? x.lhs : x - - -export dsolve, rhs, lhs - -## ---- - -## Add methods for "solve functions" -for meth ∈ (:solve, :linsolve, :nonlinsolve, :nsolve, :dsolve) - m = Symbol(meth) - @eval begin - ($meth)(V::AbstractArray{T,N}, args...; kwargs...) where {T <: SymbolicObject, N} = sympy.$meth(V, args...; kwargs...) - ($meth)(Ts::NTuple{N,T}, args...; kwargs...) where {N, T <: SymbolicObject} = - sympy.$meth(Ts, args...; kwargs...) - ($meth)(Ts::Tuple, args...; kwargs...) = - sympy.$meth(Ts, args...; kwargs...) - end -end - - - -## ---- deprecate ---- - -## used with ics=(u,0,1) style -function _dsolve(eqn::Sym, args...; ics=nothing, kwargs...) - - Base.depwarn("Use of tuple(s), `(u, x₀, u₀)`, to specify initial conditions is deprecated. Use a dictionary: `ics=Dict(u(x₀) => u₀)`.", :_dsolve) - - if isempty(args) - var = first(free_symbols(eqn)) - else - var = first(args) - end - # var might be f(x) or x, we want `x` - if Introspection.classname(var) != "Symbol" - var = first(var.args) - end - ## if we have one initial condition, can be passed in a (u,x0,y0) *or* ((u,x0,y0),) - ## if more than oneq a tuple of tuples - if eltype(ics) <: Tuple - __dsolve(eqn, var, ics; kwargs...) - else - __dsolve(eqn, var, (ics,); kwargs...) - end -end - -function __dsolve(eqn::Sym, var::Sym, ics; kwargs...) - if length(ics) == 0 - throw(ArgumentError("""Some initial value specification is needed. -Specifying the function, as in `dsolve(ex, f(x))`, is deprecated. -Use `sympy.dsolve(ex, f(x); kwargs...)` directly for that underlying interface. -""")) - end - - out = sympy.dsolve(eqn; kwargs...) - ord = sympy.ode_order(eqn, var) - - ## `out` may be an array of solutions. If so we do each one. - ## we want to use an array for output only if needed - if !isa(out, Array) - return _solve_ivp(out, var, ics,ord) - else - output = Sym[] - for o in out - a = _solve_ivp(o, var, ics,ord) - a != nothing && push!(output, a) - end - return length(output) == 1 ? output[1] : output - end -end - -## Helper. -## out is an equation in var with constants. Args are intial conditions -## Return `nothing` if initial condition is not satisfied (found by `solve`) -function _solve_ivp(out, var, args, o) - - eqns = Sym[(diff(out.rhs(), var, f.n))(var=>x0) - y0 for (f, x0, y0) in args] - sols = solve(eqns, Sym["C$i" for i in 1:o], dict=true) - if length(sols) == 0 - return nothing - end - - ## massage output - ## Might have more than one solution, though unlikely. But if we substitute a variable - ## for y0 we will get an array back from solve which may have length 1. - if isa(sols, Array) - if length(sols) == 1 - sols = sols[1] - else - return [out([Pair(k,v) for (k,v) in sol]...) for sol in sols] - end - end - - out([Pair(k,v) for (k,v) in sols]...) -end - -## For System Of Ordinary Differential Equations -## may need to collect return values -# dsolve(eqs::Union{Array, Tuple}, args...; kwargs...) = sympy.dsolve(eqs, args...; kwargs...) diff --git a/src/mathops.jl b/src/mathops.jl deleted file mode 100644 index 1a4172e4..00000000 --- a/src/mathops.jl +++ /dev/null @@ -1,36 +0,0 @@ - - -################################################## -## evaluate binary operations of symbolic objects -## XXX -- this may prove too narryw with the use of ::Sym - - - -+(x::Sym, y::Sym)::Sym = x.__add__(y) -*(x::Sym, y::Sym)::Sym = x.__mul__(y) --(x::Sym, y::Sym)::Sym = x.__sub__(y) -(-)(x::Sym)::Sym = x.__neg__() -/(x::Sym, y::Sym)::Sym = x.__truediv__(y) -^(x::Sym, y::Sym)::Sym = x.__pow__(y) -^(x::Sym, y::Rational) = x^convert(Sym,y) -#^(x::Sym, y::Integer) = x^convert(Sym,y) # no Union{Integer, Rational}, as that has ambiguity -//(x::Sym, y::Int) = x / Sym(y) -//(x::Sym, y::Rational) = x / Sym(y) -//(x::Sym, y::Sym) = x / y - -\(x::Sym, y::Sym) = (y'/x')' # ? - -Base.inv(x::Sym) = x.__pow__(Sym(-1)) - -# special case Boolean; issue 351 -# promotion for Boolean here is to 0 or 1, not False, True -+(x::Bool, y::Sym)::Sym = Sym(Int(x)).__add__(y) -*(x::Bool, y::Sym)::Sym = Sym(Int(x)).__mul__(y) --(x::Bool, y::Sym)::Sym = Sym(Int(x)).__sub__(y) -/(x::Bool, y::Sym)::Sym = Sym(Int(x)).__truediv__(y) -^(x::Bool, y::Sym)::Sym = Sym(Int(x)).__pow__(y) -+(x::Sym, y::Bool)::Sym = x.__add__(Int(y)) -*(x::Sym, y::Bool)::Sym = x.__mul__(Int(y)) --(x::Sym, y::Bool)::Sym = x.__sub__(Int(y)) -/(x::Sym, y::Bool)::Sym = x.__truediv__(Int(y)) -^(x::Sym, y::Bool)::Sym = x.__pow__(Int(y)) diff --git a/src/matrix.jl b/src/matrix.jl deleted file mode 100644 index 716b07ad..00000000 --- a/src/matrix.jl +++ /dev/null @@ -1,197 +0,0 @@ -## Matrix operations on SymMatrix - -## * we support Array{Sym} using Julia's generic matrix functions -## * Array{Sym} allows calling of SymPy methods via the dot-call syntax (A.det()) -## * we support SymPy's immutable matrices via the SymMatrix type. These use dot call style. - - - - -## for mapping sympy.Matrix -> Array{Sym} this is used -function Base.convert(::Type{Array{Sym,N}}, x::PyCall.PyObject) where {N} - if PyCall.hasproperty(x, :__class__) - nm = Symbol(x.__class__.__name__) - _convert(Val(nm), x) - else - @show x - ## error? - x - end -end - -function _convert(::Val{:MutableDenseMatrix}, x) - MM = x.tolist() - length(size(MM)) == 2 && return MM - sh = x.shape - reshape(MM, sh) -end - -function _convert(::Type{Val{T}}, x) where {T} - @show :T - x -end - -function Base.convert(::Type{Array{T,N}}, M::SymMatrix) where {T <: SymbolicObject, N} - MM = M.tolist() - length(size(MM)) == N && return MM - sh = M.shape - reshape(MM,sh) -end - - - -## This allows abstract arrays of Sym Objects to slip through sympy.meth() calls -PyCall.PyObject(A::AbstractArray{Sym,2}) = - PyCall.pycall(sympy.Matrix, PyCall.PyObject, [PyCall.PyObject.(A[i,:]) for i in 1:size(A)[1]]) - -PyCall.PyObject(V::AbstractArray{Sym,1}) = - PyCall.pycall(sympy.Matrix, PyCall.PyObject,[[PyCall.PyObject(v)] for v in V]) - - - -# call SymMatrix method on Matrix{Sym} -## Eg. A.norm() where A = [x 1; 1 x], say -function Base.getproperty(A::AbstractArray{T}, k::Symbol) where {T <: SymbolicObject} - if k in fieldnames(typeof(A)) - return getfield(A,k) - else - M1 = getproperty(PyCall.PyObject(A), k) - M1 - end -end - - -################################################## -## -## special case generic methods that fail on Array{Sym}: - -## inv, det may fail as lu need no pivoting specified in general -function Base.inv(A::Matrix{T}) where {T <: SymbolicObject} - A.inv() -end - -function Base.exp(A::Matrix{T}) where {T <: Sym} - A.exp() -end - -function LinearAlgebra.det(A::Matrix{T}) where {T <: Sym} - A.det() -end - -function LinearAlgebra.norm(A::AbstractArray{T,N}) where {T <: SymbolicObject,N} - A.norm() -end - -function _eigenvects(a::Matrix{Sym}) - ds = a.eigenvects() - ks = getindex.(ds, 1) - d = Dict(u[1] => u[2:3] for u ∈ ds) - ((k,d[k]...) for k ∈ sympy.ordered(ks)) -end - -function LinearAlgebra.eigvals(a::Matrix{Sym}) - out = Sym[] - for eiv ∈ _eigenvects(a) - for k ∈ 1:eiv[2] - tmp = eiv[1] - push!(out, tmp) - end - end - out -end - - -function LinearAlgebra.eigvecs(a::Matrix{Sym}) - m = similar(a) - j = 1 - for eiv ∈ _eigenvects(a) - for k ∈ 1:eiv[2] - m[:,j] = eiv[3][k] - j += 1 - end - end - m -end - -# solve Ax=b for x, avoiding generic `lu`, which can be very slow for bigger n values -# fix suggested by @olof3 in issue 355 -function LinearAlgebra.:\(A::AbstractArray{Sym,2}, b::AbstractArray{S,1}) where {S} - - m,n = size(A) - x = Sym["x$i" for i in 1:n] - out = solve(A*x-b, x) - isempty(out) && throw(SingularException(0)) # Could also return out here? - ret = Vector{Sym}(undef, n) - for (i,xᵢ) in enumerate(x) - ret[i] = get(out, xᵢ, xᵢ) - end - - return ret - -end - -function LinearAlgebra.:\(A::AbstractArray{T,2}, B::AbstractArray{S,2}) where {T <: Sym, S} - hcat([A \ bⱼ for bⱼ in eachcol(B)]...) -end - -## Issue #359 so that A + λI is of type Sym -Base.:+(A::AbstractMatrix{T}, J::UniformScaling) where {T <: SymbolicObject} = (n=LinearAlgebra.checksquare(A); A .+ J.λ*I(n)) -Base.:+(A::AbstractMatrix, J::UniformScaling{T}) where {T <: SymbolicObject} = (n=LinearAlgebra.checksquare(A); A .+ J.λ*I(n)) -Base.:+(A::AbstractMatrix{T}, J::UniformScaling{T}) where {T <: SymbolicObject} = (n=LinearAlgebra.checksquare(A); A .+ J.λ*I(n)) - -Base.:-(J::UniformScaling, A::AbstractMatrix{T}) where {T <: SymbolicObject} = (-A) + J -Base.:-(J::UniformScaling{T}, A::AbstractMatrix) where {T <: SymbolicObject} = (-A) + J -Base.:-(J::UniformScaling{T}, A::AbstractMatrix{T}) where {T <: SymbolicObject} = (-A) + J - - -# Issue 397 so that A' infers correctly -Base.adjoint(x::Sym)::Sym = x.adjoint() - -################################################## -## -## Support for ImmutableMatrix via SymMatrix - -#### basic matrix operations must be delegated - -## literal powers are tricky without this -Base.inv(x::SymMatrix) = x.inv() -*(x::SymMatrix, y::SymbolicObject) = x.multiply(y) -*(x::SymbolicObject, y::SymMatrix) = y.multiply(x) -*(x::SymMatrix, y::Number) = x.multiply(y) -*(x::Number, y::SymMatrix) = y.multiply(x) -*(x::SymMatrix, y::SymMatrix) = x.multiply(y) -/(x::SymMatrix, y::Number) = x.multiply(1/Sym(y)) -/(x::SymMatrix, y::SymbolicObject) = x.multiply(1/y) -+(x::SymMatrix, y::SymMatrix) = x.add(y) --(x::SymMatrix, y::SymMatrix) = x + y.multiply(-1) -^(x::SymMatrix, y::Union{Int, SymbolicObject}) = pycall(sympy.Pow, SymMatrix, x, y) - -""" - M[i,j] - -Define `getindex` for SymPy's `ImmutableMatrix` class. - -SymMatrix is 0-based, like python, not Julia. Use Matrix{Sym} for that. - -""" -Base.getindex(M::SymMatrix, i::Int, j::Int) = M.__getitem__((i,j)) -Base.getindex(V::SymMatrix, i::Int) = V.__getitem__((i,0)) -Base.getindex(M::SymMatrix) = M - -function Base.convert(::Type{SymMatrix}, M::AbstractArray{T, N}) where {T <: Number, N} - m,n = size(M) - sympy.ImmutableMatrix([PyCall.PyObject.(M[i,:]) for i in 1:m]) -end - -function Base.convert(::Type{SymMatrix}, V::Vector{T}) where {T <: Number} - convert(SymMatrix, hcat(V)) -end - -## # Functions to convert between Matrix{Sym} and SymMatrix -function Base.convert(::Type{Array{Sym,N}}, M::SymMatrix) where {N} - reshape(M.tolist(), M.shape) -end - -function Base.convert(::Type{SymMatrix}, x::PyCall.PyObject) - SymMatrix(x) -end diff --git a/src/numbers.jl b/src/numbers.jl deleted file mode 100644 index e5a97a5c..00000000 --- a/src/numbers.jl +++ /dev/null @@ -1,346 +0,0 @@ -################################################## - -## promote up to symbolic so that math ops work -promote_rule(::Type{T}, ::Type{S}) where {T<:SymbolicObject, S<:Number}= T -Base.promote_type(::Type{Irrational{T}}, ::Type{Sym}) where {T} = Sym -Base.promote_op(::T, ::Type{S}, ::Type{Sym}) where {T, S <: Number} = Sym -Base.promote_op(::T, ::Type{Sym}, ::Type{S}) where {T, S <: Number} = Sym -Base.promote_op(::T, ::Type{Sym}, ::Type{Sym}) where {T} = Sym # This helps out linear algebra conversions - -## Conversion -convert(::Type{T}, o::PyCall.PyObject) where {T <: SymbolicObject} = T(o) -convert(::Type{PyObject}, s::Sym) = s.__pyobject__ - -## rational -convert(::Type{T}, x::Rational) where {T<:SymbolicObject} = sympy.Rational(x.num, x.den)::T - -## big. Need mpmath installed separately -- not as a SymPy module as that is how it is called in PyCall -convert(::Type{T}, x::BigFloat) where {T<:SymbolicObject} = Sym(PyCall.PyObject(x))::T -convert(::Type{Sym}, x::Complex{BigFloat}) = Sym(PyCall.PyObject(x))::Sym - -## real -convert(::Type{S}, x::T) where {S<:SymbolicObject, T <: Real}= sympify(x)::S -convert(::Type{T}, x::Sym) where {T <: Real} = convert(T, PyObject(x)) - - -## complex -## cf math.jl for `complex` of a value -## IM is SymPy's "i" (synpy.I, not Python's -## Sym(PyCall.PyObject(im)) which gives 1j. -function convert(::Type{Sym}, x::Complex) - y = ifelse(isa(x, Complex{Bool}), real(x) + imag(x) * im, x) - real(y) + imag(y) * IM -end -convert(::Type{Complex{T}}, x::Sym) where {T} = complex(map(x -> convert(T, x), x.as_real_imag())...) - - -## Irrationals -Base.convert(::Type{Sym}, x::Irrational{:π}) = PI -Base.convert(::Type{Sym}, x::Irrational{:ℯ}) = sympy.exp(1) -Base.convert(::Type{Sym}, x::Irrational{:γ}) = Sym(sympy.EulerGamma) -Base.convert(::Type{Sym}, x::Irrational{:catalan}) = Sym(sympy.Catalan) -Base.convert(::Type{Sym}, x::Irrational{:φ}) = (1 + Sym(5)^(1//2))/2 - -## utility functions to test type of value -function is_integer(x::Sym) - - pycall_hasproperty(x, :is_integer) && return x.is_integer - x.__class__.__name__ == "int" && return true - - return false -end - -function is_rational(x::Sym) - pycall_hasproperty(x, :is_rational) && return x.is_rational - - return false -end - -function is_real(x::Sym) - pycall_hasproperty(x, :is_real) && return x.is_real - x.__class__.__name__ == "real" && return true - x.__class__.__name__ == "mpg" && return true - - return false -end - -function is_complex(x::Sym) - pycall_hasproperty(x, :is_complex) && return x.is_complex - x.__class__.__name__ == "complex" && return true - x.__class__.__name__ == "mpc" && return true - - return false -end - -""" - N(ex) - -Convert a `Sym` value to a numeric Julian value. - -In SymPy, `N(ex, options...)` is identifcal to `ex.evalf(options...)` -and is used to convert expressions into floating-point -approximations. A positional precision argument indicates the number -of digits, keyword arguments `chop` can be used to trim floating point -roundoff errors and `subs` for free variable substitution prior to -conversions. - -For example, symbolic roots can be computed numerically, even if not -available symbolically, by calling `N` on the values. - -Using `SymPy` within `Julia` makes having two such functions useful: - -* one to do the equivalent of SymPy's `evalf` call -* one to convert these expressions back into `Julia` objects (like `convert(T, ex)`) - -We use `N` to return a `Julia` object and `evalf` to return a symbolic -object. The type of `Julia` object is heurisitically identified. - -Examples: - -```jldoctest -julia> using SymPy - -julia> x = Sym("x") -x - -julia> p = subs(x, x, pi) -π - -julia> N(p) # float version of pi -π = 3.1415926535897... - -julia> p.evalf(60) # 60 digits of pi, as a symbolic value -3.14159265358979323846264338327950288419716939937510582097494 - -julia> N(p, 60) # when a precision is given, "Big" values are returned -3.141592653589793238462643383279502884197169399375105820974939 - -julia> r = subs(x,x,1.2) -1.20000000000000 - -julia> N(r) # float -1.2 - -julia> q = subs(x, x, 1//2) -1/2 - -julia> N(q) # 1//2 -1//2 - -julia> z = solve(x^2 + 1)[1] # -ⅈ --ⅈ - -julia> N(z) # 0 - 1im -0 - 1im - -julia> z.evalf() --1.0⋅ⅈ - -julia> rts = solve(x^5 - x + 1) -5-element Vector{Sym}: - CRootOf(x^5 - x + 1, 0) - CRootOf(x^5 - x + 1, 1) - CRootOf(x^5 - x + 1, 2) - CRootOf(x^5 - x + 1, 3) - CRootOf(x^5 - x + 1, 4) - -julia> [r.evalf() for r in rts] # numeric solutions to quintic -5-element Vector{Sym}: - -1.16730397826142 - -0.181232444469875 - 1.08395410131771⋅ⅈ - -0.181232444469875 + 1.08395410131771⋅ⅈ - 0.764884433600585 - 0.352471546031726⋅ⅈ - 0.764884433600585 + 0.352471546031726⋅ⅈ - -julia> [N(r) for r in rts] -5-element Vector{Number}: - -1.167303978261418684256045899854842180720560371525489039140082449275651903429536 - -0.18123244446987538 - 1.0839541013177107im - -0.18123244446987538 + 1.0839541013177107im - 0.7648844336005847 - 0.35247154603172626im - 0.7648844336005847 + 0.35247154603172626im -``` - - -`N` returns the value unchanged when it has free symbols. -""" -function N(x::Sym) - - length(free_symbols(x)) > 0 && return x - # many different possible types, and not all have some nice property - # python int - # mpmath int - # SymPy big int - # python float - # Pi, Half, Rational - - - for (u,v) in sympy_core_numbers - if pycall_hasproperty(x, :__class__) - if x.__class__.__name__ == string(u) - return v - end - end - end - if is_(:real, x) - if is_(:zero, x) - return 0 - elseif is_(:infinite, x) - return (x.is_negative ? -1 : 1) * Inf - elseif is_(:integer, x) - u = abs(x) - if sympy.Le(u, typemax(Int)) == Sym(true) - return convert(Int, x) -# elseif sympy.Le(u, typemax(Int128)) == Sym(true) -# # no PyCall support for this conversion? -# return convert(Int128, x) - else - return convert(BigInt, x) - end - elseif x.__class__.__name__ == "Float" - if x._prec <= 64 - return convert(Float64, x) - else - return convert(BigFloat, x) - end - elseif is_(:rational, x) - return N(numer(x)) // N(denom(x)) - elseif pycall_hasproperty(x, :args) && length(x.args) > 1 - def_precision_decimal = ceil(Int, log10(big"2"^Base.MPFR.DEFAULT_PRECISION.x)) - convert(BigFloat, x.evalf(def_precision_decimal)) - elseif length(x.args) > 0 - return N(x.evalf()) - else - return convert(BigFloat, x) - end - elseif x.__class__.__name__ == "ComplexRootOf" - u = x.evalf(16) - return N(u) - elseif is_(:complex, x) - return complex(N(sympy.re(x)), N(sympy.im(x))) - elseif is_(:imaginary, x) - return complex(0, N(sympy.im(x))) - elseif x.__class__.__name__ == "int" - convert(Int, x) - elseif x.__class__.__name__ == "float" - convert(Float64, x) - elseif x.__class__.__name__ == "complex" - return complex(N(sympy.re(x)), N(sympy.im(x))) - elseif x.__class__.__name__ == "mpf" - convert(Float64, x) - elseif x.__class__.__name__ == "mpc" - return complex(N(sympy.re(x)), N(sympy.im(x))) - elseif x.__class__.__name__ == "Infinity" - return Inf - elseif x.__class__.__name__ == "NegativeInfinity" - return -Inf - elseif x.__class__.__name__ == "ComplexInfinity" - return complex(Inf) - elseif Eq(x,Sym(true)) == Sym(true) - return true - elseif Eq(x, Sym(false)) == Sym(true) - return false - else - try - lambdify(x)() - catch err - @info "FAILED to find type for $x. Please report" - x - end - end -end -N(x::Number) = x # implies N(x::Sym) = x if ... -N(m::AbstractArray{Sym}) = map(N, m) - -# special case numbers in sympy.core.numbers -sympy_core_numbers = ((:Zero, 0), - (:One, 1), - (:NegativeOne, -1), - (:Half, 1//2), - (:NaN, NaN), - (:Exp1, ℯ), - (:ImaginaryUnit, im), - (:Pi, pi), - (:EulerGamma, Base.MathConstants.eulergamma), - (:Catalan, Base.MathConstants.catalan), - (:GoldenRation, Base.MathConstants.golden), - (:TribonacciConstant, big(1)/3 + (-big(3)*sqrt(big(33)) + 19)^(1//3)/3 + (3*sqrt(big(33)) + 19)^(1//3)/3)) - - -# fix me XXX -""" - N(x::Sym, digits::Int) - -`N` can take a precision argument, whichm when given as an integer greater than 16, we try to match the digits of accuracy using `BigFloat` precision on conversions to floating point. - -""" -function N(x::Sym, digits::Int; kwargs...) - - ## check - digits <= 16 && return(N(x)) - if is_integer(x) == nothing - out = x.evalf(digits; kwargs...) - return( N(out, digits) ) - end - - ex = x.evalf(digits) - if is_integer(x) - return(convert(BigInt, x)) - elseif is_rational(x) - p = round(Int, log2(10)*digits) - ex = x.evalf(1+digits) - out = setprecision(p) do - convert(BigFloat, ex) - end - return(out) - elseif is_real(x) == true - p = round(Int, log2(10)*digits) - out = setprecision(p) do - convert(BigFloat, ex) - end - return(out) - elseif is_complex(x) == true - r, i = ex.as_real_imag() - u, v = promote(N(r, digits), N(i, digits)) - return(Complex(u, v)) - end - - N(x.evalf(digits; kwargs...)) - #throw(DomainError()) -end - - - - - - -################################################## -## -## infix logical operators - -## XXX Experimental! Not sure these are such a good idea ... -## but used with piecewise -import Base: &, |, ! -Base.:&(x::Sym, y::Sym) = PyCall.pycall(PyObject(x).__and__, Sym, y) -Base.:|(x::Sym, y::Sym) = PyCall.pycall(PyObject(x).__or__, Sym, y) -!(x::Sym) = PyCall.pycall(PyObject(x).__invert__, Sym)::Sym - -## use ∨, ∧, ¬ for |,&,! (\vee, \wedge, \neg) -∨(x::Sym, y::Sym) = x | y -∧(x::Sym, y::Sym) = x & y -¬(x::Sym) = !x -export ∨, ∧, ¬ - - -## In SymPy, symbolic equations are not represented by `=` or `==` -## rather ther function `Eq` is used. Here we use the unicode -## `\Equal` for an infix operator. There are also unicode values to represent analogs of `<`, `<=`, `>=`. `>`. These are - -## * `<` is `\ll` -## * `<=` is `\leqq -## * `==` is `\Equal` -## * `>=` is `\geqq` -## * `>` is `\gg` - - -export ≪,≦,⩵,≧,≫, ≶, ≷ # from CommonEq diff --git a/src/patternmatch.jl b/src/patternmatch.jl deleted file mode 100644 index 8dee66a2..00000000 --- a/src/patternmatch.jl +++ /dev/null @@ -1,211 +0,0 @@ -## Pattern matching modifications - - -""" - Wild(x) - -create a "wild card" for pattern matching -""" -Wild(x::AbstractString) = pycall(sympy.Wild, PyAny, x) -Wild(x::Symbol) = Wild(string(x)) -export Wild - - -""" - match(pattern, expression, ...) - -Match a pattern against an expression; returns a dictionary of matches. - -If a match is unsuccesful, returns an *empty* dictionary. (SymPy returns "nothing") - -The order of the arguments follows `Julia`'s `match` function, not `sympy.match`, which can be used directly, otherwise. -""" -function Base.match(pat::Sym, ex::Sym, args...; kwargs...) - out = ex.match(pat, args...; kwargs...) - out == nothing && return Dict() - out -end - -""" - replace(expression, pattern, value, ...) - replace(expression, pattern => value; kwargs...) - -In the expression replace a mathcing pattern with the value. Returns the modified expression. - -# Extended help - -From: [SymPy Docs](http://docs.sympy.org/dev/modules/core.html) - -Traverses an expression tree and performs replacement of matching -subexpressions from the bottom to the top of the tree. The default -approach is to do the replacement in a simultaneous fashion so changes -made are targeted only once. If this is not desired or causes -problems, `simultaneous` can be set to `false`. In addition, if an -expression containing more than one `Wild` symbol is being used to match -subexpressions and the `exact` flag is `true`, then the match will only -succeed if non-zero values are received for each `Wild` that appears in -the match pattern. - - -Differences from SymPy: - -* "types" are specified via calling `func` on the head of an expression: `func(sin(x))` -> `sin`, or directly through `sympy.sin` - -* functions are supported, but only with `PyCall` commands. - - -Examples (from the SymPy docs) - -```jldoctest replace -julia> using SymPy - -julia> x, y, z = symbols("x, y, z") -(x, y, z) - -julia> f = log(sin(x)) + tan(sin(x^2)); string(f) # `string(f)` only so doctest can run -"log(sin(x)) + tan(sin(x^2))" - -``` - -## "type" -> "type" - -Types are specified through `func`: - -```jldoctest replace -julia> func = SymPy.Introspection.func -func (generic function with 1 method) - -julia> replace(f, func(sin(x)), func(cos(x))) |> string # type -> type -"log(cos(x)) + tan(cos(x^2))" - -julia> replace(f, sympy.sin, sympy.cos) |> string -"log(cos(x)) + tan(cos(x^2))" - -julia> sin(x).replace(sympy.sin, sympy.cos, map=true) -(cos(x), Dict{Any, Any}(sin(x) => cos(x))) - -``` - -The `func` function finds the head of an expression (`sin` and `cos` above). This could also have been written (perhaps more directly) as: - -```jldoctest replace -julia> replace(f, sympy.sin, sympy.cos) |> string -"log(cos(x)) + tan(cos(x^2))" - -``` - -## "type" -> "function" - -To replace with a more complicated function, requires some assistance from `Python`, as an anonymous function must be defined witin Python, not `Julia`: - -```julia -julia> import PyCall - -julia> ## Anonymous function a -> sin(2a) - PyCall.py\"\"\" - from sympy import sin, Mul - def anonfn(*args): - return sin(2*Mul(*args)) - \"\"\") - - -julia> replace(f, sympy.sin, PyCall.py"anonfn") - ⎛ ⎛ 2⎞⎞ -log(sin(2⋅x)) + tan⎝sin⎝2⋅x ⎠⎠ -``` - -## "pattern" -> "expression" - -Using "`Wild`" variables allows a pattern to be replaced by an expression: - -```jldoctest replace -julia> a, b = Wild("a"), Wild("b") -(a_, b_) - -julia> replace(f, sin(a), tan(2a)) |> string -"log(tan(2*x)) + tan(tan(2*x^2))" - -julia> replace(f, sin(a), tan(a/2)) |> string -"log(tan(x/2)) + tan(tan(x^2/2))" - -julia> f.replace(sin(a), a) |> string -"log(x) + tan(x^2)" - -julia> (x*y).replace(a*x, a) -y - -``` - -In the SymPy docs we have: - -Matching is exact by default when more than one Wild symbol is used: matching fails unless the match gives non-zero values for all Wild symbols." - -```jldoctest replace -julia> replace(2x + y, a*x+b, b-a) # y - 2 -y - 2 - -julia> replace(2x + y, a*x+b, b-a, exact=false) # y + 2/x - 2 -y + ─ - x -``` - -## "pattern" -> "func" - -The function is redefined, as a fixed argument is passed: - -```julia -julia> PyCall.py\"\"\" - from sympy import sin - def anonfn(a): - return sin(2*a) - \"\"\" - -julia> replace(f, sin(a), PyCall.py"anonfn") - ⎛ ⎛ 2⎞⎞ -log(sin(2⋅x)) + tan⎝sin⎝2⋅x ⎠⎠ -``` - -## "func" -> "func" - -```julia - -julia> PyCall.py\"\"\" - def fn1(expr): - return expr.is_Number - - def fn2(expr): - return expr**2 - \"\"\" - -julia> replace(2*sin(x^3), PyCall.py"fn1", PyCall.py"fn2") - ⎛ 9⎞ -4⋅sin⎝x ⎠ -``` - -```julia -julia> PyCall.py\"\"\" - def fn1(x): - return x.is_Mul - - def fn2(x): - return 2*x - \"\"\" - -julia> replace(x*(x*y + 1), PyCall.py"fn1", PyCall.py"fn2") -2⋅x⋅(2⋅x⋅y + 1) -``` -""" -function Base.replace(ex::Sym, query::Sym, fn::Function; exact=true, kwargs...) - ## XXX this is failing! - ex.replace(query, PyCall.PyObject((args...) ->fn(args...)); exact=exact, kwargs...) -end - - -function Base.replace(ex::Sym, query::Any, value; exact=true, kwargs...) - ex.replace(query, value; exact=exact, kwargs...) -end - -function Base.replace(ex::Sym, qv::Pair; kwargs...) - replace(ex, first(qv), last(qv); kwargs...) -end diff --git a/src/permutations.jl b/src/permutations.jl deleted file mode 100644 index be0be89d..00000000 --- a/src/permutations.jl +++ /dev/null @@ -1,255 +0,0 @@ -function _check_permutation_format(x::Vector{Vector{T}}) where {T} - nothing -end -_check_permutation_format(x) = nothing - -""" - Permutation(args...) - -This module mostly implements SymPy's [Permutation](http://docs.sympy.org/latest/modules/combinatorics/permutations.html) module. - -A permuation can be represented in different ways. Here a permutation is a reaarangment of the values 0, 1, ..., n. For example, the mapping `0->2, 1->3, 2->0, 3->1` can be presented by a vector: `sigma = [2,3,0,1]` where `sigma[i] = j` when `i -> j`. Otheriwse, it can be presented as a product of cycles: `(0 2)(1 3)` which reads 0 goes to 2 which goes to 0 (wrapping) and 1 goes to 3 and 3 goes to 1. - -Either representation can be passed through the `Permutation` constructor. - -For the vector notation -- 0-based -- it is passed directly to the constructor: - -```jldoctest permutations -julia> using SymPy - -julia> p = Permutation([2,3,0,1]) -(0 2)(1 3) - -``` - -If a range describes a permutation, it can be used as well: - -```jldoctest permutations -julia> id = Permutation(10:-1:0) -(0 10)(1 9)(2 8)(3 7)(4 6) - -``` - -Cycle notation can more compactly describe a permuation, it can be passed in as a container of cycles specified through tuples or vectors: - -```jldoctest permutations -julia> p = Permutation([(0,2), (1,3)]) -(0 2)(1 3) - -``` - -The latter can be be expresed more quickly as - -```jldoctest permutations -julia> p = Permutation(0,2)(1,3) -(0 2)(1 3) - -``` - -This works as a single cycle can be passed to the `Permutation` -constructor with values separated by commas and the "call" method for -`Permuation` objects is overloaded: for a single argument, the mapping -`i -> j` is created (also the notation `i^p` returns this) *but* if -more than one argument is given, a cycle is created and multiplied on -the *right* by `p`, so that the above becomes `(0,2) * (1,3)`. - -Here are two permutations forming the symmetries of square, naturally represented in the two ways: - -```jldoctest permutations -julia> flip = Permutation([[0,1],[2,3]]) # or Permutation(0,1)(2,3) -(0 1)(2 3) - -julia> rotate = Permutation([1,2,3,0]) # or Permutation(0,1,2,3) in cycle notation -(0 1 2 3) - -``` - -Operations on permutations include: - -* a function call, `p(i)` to recover `j` where `i -> j`, also `i^p`. -* `*` for multiplication. The convention is `(p*q)(i) = q(p(i))` or with the `^` notation: `i^(p*q) = (i^p)^q`. -* `+` for multiplication when `p` and `q` commute, where a check on commuting is performed. -* `inv` for the inverse permutation. -* `/`, where `p/q` is `p * inv(q)`. -* `p^n` for powers. We have `inv(p) = p^(-1)` and `p^order(p)` is the identity. -* `p^q` for conjugate, defined by `inv(q) * p * q`. - - -We can see that a flip is an involution through: - -```jldoctest permutations -julia> flip^2 # the identity -() - -``` - -whereas a rotation is not (as it has order 4) - -```jldoctest permutations -julia> rotate * rotate -(0 2)(1 3) - -julia> rotate.order() -4 - -``` - -These two operations do not commute: - -```jldoctest permutations -julia> flip * rotate -(0 2) - -``` - -```jldoctest permutations -julia> rotate * flip # (1 3) -(1 3) - -``` - -We can see this is the correct mapping `1 -> 3` with - -```jldoctest permutations -julia> (1^rotate)^flip, 1^(rotate*flip), flip(rotate(1)) -(3, 3, 3) - -``` - -We can check that `flip` and `rotate^2` do commute: - -```jldoctest permutations -julia> id = Permutation(3) # (n) is the identify -() - -julia> flip.commutator(rotate^2) == id -true - -``` - -The conjugate for flip and rotate does the inverse of the flip, then rotates, then flips: - -```jldoctest permutations -julia> rotate^flip -(0 3 2 1) - -``` - -This is different than `flip^rotate`. As `flip` commutes with `rotate^2` this will return `rotate^2`: - -```jldoctest permutations -julia> (rotate^2)^flip -(0 2)(1 3) - -``` - -!!! note "Differences" - There is no support currently for the `Cycle` class - -""" -function Permutation(x; kwargs...) - if typeof(x) <: UnitRange - x = collect(x) - end - _check_permutation_format(x) - SymPermutation(sympy.combinatorics.permutations.Permutation(x; kwargs...)) -end -function Permutation(i, j, xs...; kwargs...) - Permutation([vcat(i,j,xs...)]; kwargs...) -end -Permutation(;kwargs...) = SymPermutation(sympy.combinatorics.permutations.Permutation(; kwargs...)) -export Permutation - -# left right -# check commutative -*(p::SymPermutation, q::SymPermutation)::SymPermutation = PyCall.py"$p * $q" -+(p::SymPermutation, i::Integer)::SymPermutation = PyCall.py"$p + $i" --(p::SymPermutation, i::Integer)::SymPermutation = PyCall.py"$p - $i" -^(p::SymPermutation, i::Integer)::SymPermutation = PyCall.py"$p**$i" -^(p::SymPermutation, i::Sym) = p^N(i) -^(i::Integer, p::SymPermutation) = p(i) # python has i^p = p(i) -^(i::Sym, p::SymPermutation) = p(i) # SymPy has i^p = p(i) -^(p::SymPermutation, q::SymPermutation)::SymPermutation = PyCall.py"$p^$q" # conjugate -inv(p::SymPermutation)::SymPermutation = PyCall.py"$p**(-1)" -/(p::SymPermutation, q::SymPermutation)::SymPermutation = p * inv(q) -import Base: ~ -~(p::SymPermutation) = SymPermutation(~(PyCall.PyObject(p))) -Base.inv(p::SymPermutation) = ~p - -function +(p::SymPermutation, q::SymPermutation)::SymPermutation - !is_Identity(commutator(p,q)) && throw(DomainError("p and q do not commute")) - p * q -end - -# evaluation p:i -> j -(p::SymPermutation)(i::Integer) = p.__pyobject__(i) -function (p::SymPermutation)(i::Integer, j::Integer, xs...) - q = Permutation([vcat(i, j, xs...)]) - p * q -end -(p::SymPermutation)(v::Vector) = PyCall.py"$p($v)" -(p::SymPermutation)(s::String) = p([s[i:i] for i in eachindex(s)]) - -==(p::SymPermutation, q::SymPermutation) = PyCall.py"$p == $q" -hash(p::SymPermutation) = hash(array_form(p)) - - -Base.length(p::SymPermutation) = p.length -Base.max(p::SymPermutation) = p.max -Base.min(p::SymPermutation) = p.min -rank(p::SymPermutation) = p.rank - - -## we want perms, not tuples -function transpositions(p::SymPermutation) - Base.depwarn("This function is deprecated", - "Use `map(x -> Permutation([x]), object_meth(p, :transpositions))`", - :transpositions) - map(x -> Permutation([x]), object_meth(p, :transpositions)) -end -export transpositions - - - - -""" - PermutationGroup() - -Create Permutation group from group generators - -A PermutationGroup is one generated by a collection of permutations. - -Some pre-defined groups are built-in: - -* `SymmetricgGroup(n)`: S_n or all symmetries of an n-gon -* `CyclicGroup`: the group Z_n -* `DihedralGroup`: Group formed by a flip and rotation -* `AlternativeGroup`: Subgroup of S_n of even elements -* `AbelianGroup`: Returns the direct product of cyclic groups with the given orders. - - -Differences: - -* use `collect(generate(G))` in place of `list(G.generate())` - -""" -PermutationGroup(args...; kwargs...) = combinatorics.perm_groups.PermutationGroup(args...; kwargs...) -export PermutationGroup - -## Algebra therof -import Base: * -*(G1::SymPermutationGroup, G2::SymPermutationGroup)::SymPermutationGroup = PyCall.py"$G1*$G2" - -## Indexing into group -Base.getindex(Gp::SymPermutationGroup, i::Int) = PyCall.py"$(Gp.__pyobject__)[$(i-1)]" # 1-base - -elements(gp::SymPermutationGroup) = [a for a in gp.elements] - -Base.length(G::SymPermutationGroup) = PyCall.py"len($(G.__pyobject__))" - -""" - occursin(x, G::SymPermutationGroup) - -Does G contain x. (In SymPy, this is `contains.) -""" -Base.occursin(x, G::SymPermutationGroup; kwargs...) = (G.contains(x; kwargs...) == Sym(true)) # was contains diff --git a/src/physics.jl b/src/physics.jl deleted file mode 100644 index 320fa730..00000000 --- a/src/physics.jl +++ /dev/null @@ -1,10 +0,0 @@ - -import PyCall -PyCall.pyimport_conda("sympy.physics.wigner", "sympy") -PyCall.pyimport_conda("sympy.physics.optics", "sympy") -PyCall.pyimport_conda("sympy.physics.quantum.spin", "sympy") - -## to use -## import_from(sympy.physics.wigner) -## wigner3j(sympy.S(6),0,4,0,2,0) -## Wigner3j(sympy.S(6),0,4,0,2,0).doit() diff --git a/src/plot_recipes.jl b/src/plot_recipes.jl deleted file mode 100644 index 259db58b..00000000 --- a/src/plot_recipes.jl +++ /dev/null @@ -1,262 +0,0 @@ -""" - -Plotting of symbolic objects. - -The `Plots` package provide a uniform interface to many of `Julia`'s -plotting packages. `SymPy` plugs into `Plots`' "recipes." - -The basic goal is that when `Plots` provides an interface for function -objects, this package extends the interface to symbolic expressions. - -In particular: - - -* `plot(ex::Sym, a, b; kwargs...)` will plot a function evaluating `ex` over [a,b] - -Example. Here we use the default backend for `Plots` to make a plot: - -``` -using Plots -@syms x -plot(x^2 - 2x, 0, 4) -``` - - - -* `plot(ex1, ex2, a, b; kwargs...)` will plot the two expressions in a parametric plot over the interval `[a,b]`. - -Example: - -``` -@syms x -plot(sin(2x), cos(3x), 0, 4pi) ## also -``` - -For a few backends (those that support `:path3d`) a third symbolic -expression may be added to have a 3d parametric plot rendered: - -``` -plot(sin(x), cos(x), x, 0, 4pi) # helix in 3d -``` - -* `plot(xs, ys, expression)` will make a contour plot (for many backends). - -``` -@syms x y -plot(range(0,stop=5, length=50), range(0,stop=5, length=50), x*y) -``` - - - -* To plot the surface `z=ex(x,y)` over a region we have `Plots.surface`. For example, - -``` -@syms x y -surface(-5:5, -5:5, 25 - x^2 - y^2) -``` - -* a vectorfield plot can (inefficiently but directly) be produced following this example: - -``` -function vfieldplot(fx, fy; xlim=(-5,5), ylim=(-5,5), n=8) - xs = range(xlim[1], stop=xlim[2], length=n) - ys = range(ylim[1], stop=ylim[2], length=n) - - us = vec([x for x in xs, y in ys]) - vs = vec([y for x in xs, y in ys]) - fxs = vec([fx(x,y) for x in xs, y in ys]) - fys = vec([fy(x,y) for x in xs, y in ys]) - - mxs = maximum(abs.(filter(!isinf, filter(!isnan, fxs)))) - mys = maximum(abs.(filter(!isinf, filter(!isnan, fys)))) - d = 1/2 * max((xlim[2]-xlim[1])/mxs, (ylim[2]-ylim[1])/mys) / n - - quiver(us, vs, quiver=(fxs.*d, fys.*d)) - -end -fx = (x + y) / sqrt(x^2 + y^2) -fy = (x - y) / sqrt(x^2 + y^2) -vfieldplot(fx, fy) - - -``` - -* To plot two or more functions at once, the style `plot([ex1, ex2], a, b)` does not work. Rather, use - `plot(ex1, a, b); plot!(ex2)`, as in: - -``` -@syms x -plot(sin(x), 0, 2pi) -plot!(cos(x)) -``` ----- - -Some graphics provided by `SymPy` are available if `PyPlot` is installed, such as: - -* `sympy.plotting.plot3d_parametric_surface` -* `sympy.plotting.plot_implicit` - - -Plot the parametrically defined surface `[exs[1](u,v), exs[2](u,v), exs[3](u,v)]` over `[a0,a1] x -[b0,b1]`. The specification of the variables uses a tuple of the form -`(Sym, Real, Real)` following the style of SymPy in `integrate`, say, -where disambiguation of variable names is needed. - -``` -@syms theta, phi -r = 1 -sympy.plotting.plot3d_parametric_surface((r*sin(theta)*sin(phi), r*sin(theta)*cos(phi), r*cos(theta)), - (theta, 0, pi), (phi, 0, pi/2)) -``` - - -* `sympy.plotting.plot_implicit(equation, (xvar, x0, x1), (yvar, y0, y1))` will plot implicitly the equation. - -``` -@syms x y -sympy.plotting.plot_implicit(Eq(x^2+ y^2,3), (x, -2, 2), (y, -2, 2)) # draw a circle -``` - - -""" -sympy_plotting = nothing -export sympy_plotting - - -## Recipes for hooking into Plots - -using RecipesBase - -## -@recipe f(::Type{T}, v::T) where {T<:Sym} = lambdify(v) - -## for vectors of expressions -## This does not work. See: https://github.com/JuliaPlots/RecipesBase.jl/issues/19 -#@recipe f(ss::AbstractVector{Sym}) = lambdify.(ss) -#@recipe function f{T<:Array{Sym,1}}(::Type{T}, ss::T) Function[lambdify(s) for s in ss] end - -## A vector field plot can be visualized as an n × n collection of arrows -## over the region xlims × ylims -## These arrows are defined by: -## * fx, fy giving the components of each. These are callable objects, such as -## (x,y) -> sin(y) -## * Fyx A function F(y,x), useful for visualizing first-order ODE y'=F(y(x),x). -## note reverse order of y and x. -## The vectors are scaled so as not to overlap. - -""" -`VectorField(fx, fy`): create an object that can be `plot`ted as a vector field. - -A vectorfield plot draws arrows at grid points proportional to `[fx(x_i,y_i), fy(x_i,y_i)]` to visualize the field generated by `[fx, fy]`. - -The plot command: `plot(VectorField(fx, fy), xlims=(-5,5), ylims=(-5,5), n=8)` will draw the vectorfield. This uses the default values, so the same graph would be rendered by `plot(VectorField(fx,fy))`. - -To faciliate the visualization of solution to the ODE y' = F(x, y(x)), the call `plot(VectorField(F))` will work. (The order is x then y, though often this is written as F(y(x),x).) - -`SymPy` objects can be passed to `VectorField`, but this is a bit -fragile, as they must each have two variables so that they can be -called with two variables. (E.g., `y(1,2)` will be `1` not `2`, as -might be intended.) - -Examples: -``` -using Plots - -fx(x,y) = sin(y); fy(x,y) = cos(y) -plot(VectorField(fx, fy), xlims=(-2pi, 2pi), ylims=(-2pi,2pi)) - -# plot field of y' = 3y*x over (-5,5) x (-5,5) -F(x,y) = 3*y*x -plot(VectorField(F)) - -# plot field and solution u' = u*(1-u) -@syms x u() -F(x,y) = y*(1-y) -out = dsolve(u'(x) - F(x, u(x)), x, (u, 0, 1)) -plot(VectorField(F), xlims=(0,5), ylims=(0,2)) -plot!(rhs(out)) -``` - -""" -struct VectorField - fx - fy - function VectorField(fx, fy) - Base.depwarn("Vectorfield is deprecated", :VectorField) - new(fx, fy) - end -end -VectorField(f) = VectorField((x,y) -> 1.0, f) -export VectorField - -@recipe function f(F::VectorField; n=8) - - xlims = get(plotattributes,:xlims, (-5,5)) - ylims = get(plotattributes, :ylims, (-5,5)) - - xs = repeat(range(xlims[1], stop=xlims[2], length=n), inner=(n,)) - ys = repeat(range(ylims[1], stop=ylims[2], length=n), outer=(n,)) - - us, vs = broadcast(F.fx, xs, ys), broadcast(F.fy, xs, ys) - - delta = min((xlims[2]-xlims[1])/n, (ylims[2]-ylims[1])/n) - m = maximum([norm([u,v]) for (u,v) in zip(us, vs)]) - - lambda = delta / m - - x := xs - y := ys - quiver := (lambda*us, lambda*vs) - seriestype := :quiver - () -end - - -## --------------------- - - - -## These functions give acces to SymPy's plotting module. They will work if PyPlot is installed, but may otherwise cause an error - -## surface plot xvar = Tuple(Sym, Real, Real) -## -# """ - -# Render a parametrically defined surface plot. - -# Example: -# ``` -# @syms u, v -# plot_parametric_surface((u*v,u-v,u+v), (u,0,1), (v,0,1)) -# ``` - -# This uses `PyPlot`, not `Plots` for now. -# """ -# function plot_parametric_surface(exs::Tuple{Sym,Sym,Sym}, -# xvar=(-5.0, 5.0), -# yvar=(-5.0, 5.0), -# args...; -# kwargs...) - -# sympy.plotting.plot3d_parametric_surface(exs..., args...; kwargs...) - -# end -# export plot_parametric_surface - - - - - -# """ -# Plot an implicit equation - -# ``` -# @syms x y -# plot_implicit(Eq(x^2+ y^2,3), (x, -2, 2), (y, -2, 2)) -# ``` - -# """ -# function plot_implicit(ex, args...; kwargs...) -# sympy.plotting.plot_implicit(ex, args...; kwargs...) -# end -# export plot_implicit diff --git a/src/python_connection.jl b/src/python_connection.jl new file mode 100644 index 00000000..df1fe6c9 --- /dev/null +++ b/src/python_connection.jl @@ -0,0 +1,83 @@ +## PyCall specific usage +Base.convert(::Type{S}, x::Sym{T}) where {T<:PyCall.PyObject, S<:Sym} = x +Base.convert(::Type{S}, x::T) where {T<:PyCall.PyObject, S <: SymbolicObject} = Sym(x) + +SymPyCore._convert(::Type{T}, x) where {T} = convert(T, x) + + +## Modifications for ↓, ↑ +Sym(x::Nothing) = Sym(PyCall.PyObject(nothing)) +SymPyCore.:↓(x::PyCall.PyObject) = x +SymPyCore.:↓(d::Dict) = Dict(↓(k) => ↓(v) for (k,v) ∈ pairs(d)) +SymPyCore.:↓(x::Set) = _sympy_.sets.FiniteSet((↓(xi) for xi ∈ x)...) + +SymPyCore.:↑(::Type{<:AbstractString}, x) = PyObject(x) +# Create a symbolic type. There are various containers to recurse in to be +# caught here +function SymPyCore.:↑(::Type{PyCall.PyObject}, x) + class_nm = SymPyCore.classname(x) + class_nm == "set" && return Set(Sym.(collect(x))) + class_nm == "tuple" && return Tuple(↑(xᵢ) for xᵢ ∈ x) + class_nm == "list" && return [↑(xᵢ) for xᵢ ∈ x] + class_nm == "dict" && return Dict(↑(k) => ↑(x[k]) for k ∈ x) + + class_nm == "FiniteSet" && return Set(Sym.(collect(x))) + class_nm == "MutableDenseMatrix" && return _up_matrix(x) #map(↑, x.tolist()) + + # others ... more hands on than pytype_mapping + + Sym(x) +end + +# ↑ for matrices +_up_matrix(m) = map(↑, m.tolist()) + + + +# hash (work around pending PyCall release with its own hash) +function Base.hash(x::SymbolicObject{T}, h::UInt) where {T <: PyCall.PyObject} + o = ↓(x) + px = ccall((PyCall.@pysym :PyObject_Hash), PyCall.Py_hash_t, (PyCall.PyPtr,), o) # from PyCall.jl + reinterpret(UInt, Int(px)) - 3h # from PythonCall.jl +end + + +# should we also have different code path for a::String like PyCall? +function Base.getproperty(x::SymbolicObject{T}, a::Symbol) where {T <: PyCall.PyObject} + a == :o && return getfield(x,a) + val = ↓(x) + if hasproperty(val, a) + meth = PyCall.__getproperty(val, a) + (meth == PyObject(nothing)) && return nothing + + if hasproperty(meth, :is_Boolean) + o = Sym(meth.is_Boolean) + o == Sym(true) && return true + a == :is_Boolean && return o == Sym(False) ? false : nothing + end + + if hasproperty(meth, :__class__) && meth.__class__.__name__ == "bool" + a = Sym(meth) + return a == Sym(true) ? true : + a == Sym(false) ? false : nothing + end + + # treat modules, callsm others differently + if hasproperty(meth, :__class__) && meth.__class__.__name__ == "module" + return Sym(meth) + end + + if hasproperty(meth, :__call__) + # meth = getproperty(meth, "__call__") + + return SymPyCore.SymbolicCallable(meth) + end + return ↑(convert(PyCall.PyAny, meth)) + end + # nothing? + nothing +end + + +# do we need this conversion? +#Base.convert(::Type{T}, o::Py) where {T <: Sym} = T(o) diff --git a/src/sets.jl b/src/sets.jl deleted file mode 100644 index df5f0007..00000000 --- a/src/sets.jl +++ /dev/null @@ -1,26 +0,0 @@ -## sets - - -## for finite set Set(s...) will work too, but -## may have wider type, e.g. Any -function Base.convert(::Type{Set}, s::Sym) - is_(:FiniteSet,s) || throw(ArgumentError("`s` must be a finite set")) - s1 = Set(u for u in s.__pyobject__) - - s1 -end - -""" - elements(s) - -return elements of a set s as an array, unlike `convert(Set,s)` -""" -function elements(s::Sym) - Base.depwarn("`elements` is deprecated", :elements) - collect(convert(Set, s)) -end -export elements - -# is x in set; avoid ambiguity -Base.in(x::Sym, I::Sym) = I.contains(x) == Sym(true) -Base.in(x::Number, I::Sym) = Sym(x) in I diff --git a/src/symfunction.jl b/src/symfunction.jl deleted file mode 100644 index 3b835ece..00000000 --- a/src/symfunction.jl +++ /dev/null @@ -1,197 +0,0 @@ -################################################## - -""" - SymFunction - -A type and constructor to create symbolic functions. Such objects can be used -for specifying differential equations. The macro [`@syms`](@ref) is also available for constructing `SymFunction`s (`@syms f()`) - -## Examples: - -```jldoctest symfunction -julia> using SymPy - -julia> u = SymFunction("u"); - -julia> @syms v(); - -``` - -Alternatively, we can pass a comma separated string of variable names to create -more than one at a time. - -```jldoctest symfunction -julia> F,G,H = SymFunction("F, G, H") -3-element Vector{SymFunction}: - F - G - H -``` - - -For symbolic functions *not* wrapped in the `SymFunction` type, the `sympy.Function` constructor can be used, as can the [`symbols`](@ref) function to construct symbolic functions (`F=sympy.Function("F", real=true)`; `F = sympy.symbols("F", cls=sympy.Function, real=true)`). - -```jldoctest symfunction -julia> @syms u(), v()::real, t -(u, v, t) - -julia> sqrt(u(t)^2), sqrt(v(t)^2) # real values have different simplification rules -(sqrt(u(t)^2), Abs(v(t))) - -``` - -Such functions are undefined functions in SymPy, and can be used symbolically, such as with taking derivatives: - -```jldoctest symfunction -julia> @syms x y u() -(x, y, u) - -julia> diff(u(x), x) |> string -"Derivative(u(x), x)" - -julia> diff(u(x, y), x) |> string -"Derivative(u(x, y), x)" -``` - - -Here is one way to find the second derivative of an inverse function to `f`, utilizing the `SymFunction` class and the convenience `Differential` function: - -``` -@syms f() f⁻¹() x -D = Differential(x) # ∂(f) is diff(f(x),x) -D² = D∘D -u1 = solve(diff((f⁻¹∘f)(x), x) ~ 1, D(f⁻¹)(f(x)))[1] -u2 = solve(diff((f⁻¹∘f)(x), x,2) ~ 0, D²(f⁻¹)(f(x)))[1] -u2(D(f⁻¹)(f(x)) => u1) # f''/[f']^3 -``` - -""" -SymFunction - -## SymFunction has a `n` field to allow u', u'', etc to work -## However, it seems better to be more consistent with ModelingToolkit -## and use `D = Differential(x)` to inicate derivatives, e.g. `D(u)` -## This `n` will be deprecated -mutable struct SymFunction <: SymbolicObject - __pyobject__::PyCall.PyObject - n::Int # <-- remove -end - - -function SymFunction(x::T; kwargs...) where {T<:AbstractString} - us = split(x, r",\s*") - length(us) > 1 ? SymFunction.(us; kwargs...) : - SymFunction(sympy.Function(x; kwargs...), 0) - # SymFunction(sympy.Function(x; kwargs...)) # if n removed -end - - -# Steal this idea from ModelingToolkit -# better than the **hacky** f'(0) stuff -""" - Differential(x) - -Use to find (partial) derivatives. - -## Example -``` -@syms x y u() -Dx = Differential(x) -Dx(u(x,y)) # resolves to diff(u(x,y),x) -Dx(u) # will evaluate diff(u(x), x) -``` -""" -struct Differential - x::Sym -end -(∂::Differential)(u::Sym) = diff(u, ∂.x) -(∂::Differential)(u::SymFunction) = diff(u(∂.x), ∂.x) -export Differential - -# after deprecation, these two methods need to be exposed: -# Base.show(io::IO, u::SymFunction) = print(io, "$(string(Sym(u.__pyobject__)))") -#(u::SymFunction)(args...) = u.__pyobject__(PyObject.(args)...) - -## ---- deprecate. Remove once u'(x) syntax is removed ---- -## support legacy - -# Hacky way to streamline expressions like -# dsolve(diff(u(x),x) ~ u(x), ics=Dict(diff(u(x),x)(0) => 1)) -function Base.adjoint(x::SymFunction) - Base.depwarn("""Use of `'` to indicate a derivative of a symbolic function will be deprecated. Use `diff(u(x),x)` or `D=Differential(x); D(u)`""", :adjoint) - SymFunction(x.__pyobject__, x.n + 1) -end - - -Base.:(==)(x::SymFunction, y::SymFunction) = x.__pyobject__ == y.__pyobject__ && x.n == y.n -Base.hash(x::SymFunction) = hash((hash(x.__pyobject__),x.n)) - -# these are from https://github.com/OptMist-Tokyo/DAEPreprocessor.jl/blob/sympy_warning/src/symbolic.jl -derivative(x::SymFunction, d::Int = 1) = SymFunction(x.__pyobject__, x.n + d) - -Base.show(io::IO, u::SymFunction) = print(io, "$(string(Sym(u.__pyobject__)))" * repeat("'", u.n)) -Base.show(io::IO, ::MIME"text/plain", u::SymFunction) = print(io, "$(string(Sym(u.__pyobject__)))" * repeat("'", u.n)) -Base.show(io::IO, ::MIME"text/latex", x::SymFunction) = print(io, as_markdown("\\begin{align*}" * latex(x) * "\\end{align*}")) -function Base.show(io::IO, ::MIME"text/latex", x::AbstractArray{SymFunction, 1}) - print(io, as_markdown("\\begin{align*}\\left[\\begin{array}{c}" * join(latex.(x), "\\\\") * "\\end{array}\\right]\\end{align*}")) -end - -latex(x::SymFunction) = latex(Sym(x.__pyobject__)) * repeat("'", x.n) - -function PyCall.PyObject(f::SymFunction) - f.n == 0 && return f.__pyobject__ - z = symbols(gensym()) - f(z).__pyobject__ # diff(f(__x__), __x__, f.n).__pyobject__ -end - - -(u::SymFunction)(x::Base.Dict) = throw(ArgumentError("IVPsolutions can only be called with symbolic objects")) -(u::SymFunction)(x::Base.Pair) = throw(ArgumentError("IVPsolutions can only be called with symbolic objects")) - -function (u::SymFunction)(x) - if u.n == 0 - PyObject(u)(x) #u.__pyobject__(PyObject(x)) - else - __x = Sym("__x") - diff(u.__pyobject__(PyObject(__x)), __x, u.n)(__x => x) - end -end - -(u::SymFunction)(x, y...) = u.n== 0 ? u.__pyobject__(map(PyObject, vcat(x, y...))...) : error("Need to implement derivatives of symbolic functions of two or more variables") - - -## ---- deprecate macro; use @syms ---- - -""" - @symfuns - -Thanks to `@alhirzel` for the contribution. - -!!! Note: - The `@symfuns` macro is deprecated. The more general [`@syms`](@ref) macro should be used for constructing symbolic functions of type `SymFunction` and `symbols` can be used to construct symbolic functions in general. - -""" -macro symfuns(x...) - q = Expr(:block) - as = [] # running list of assumptions to be applied - fs = [] # running list of symbols created - for s in reverse(x) - if isa(s, Expr) # either an assumption or a named variable - if s.head == :(=) - s.head = :kw - push!(as, s) - elseif s.head == :(=>) - push!(fs, s.args[1]) - push!(q.args, Expr(:(=), s.args[1], Expr(:call, :SymFunction, s.args[2], map(esc,as)...))) - end - elseif isa(s, Symbol) # raw symbol to be created - push!(fs, s) - # @show s - push!(q.args, Expr(:(=), esc(s), Expr(:call, :SymFunction, string(s), map(esc,as)...))) - else - throw(AssertionError("@symfuns expected a list of symbols and assumptions")) - end - end - push!(q.args, Expr(:tuple, map(esc,reverse(fs))...)) # return all of the symbols we created - q -end diff --git a/src/types.jl b/src/types.jl deleted file mode 100644 index 1ee2cce6..00000000 --- a/src/types.jl +++ /dev/null @@ -1,175 +0,0 @@ -################################################## -## SymbolicObject types have field x::PyCall.PyObject - -## Symbol class for controlling dispatch -abstract type SymbolicObject <: Number end -struct Sym <: SymbolicObject - __pyobject__::PyCall.PyObject -end - - - - -## Matrices -## We use this class for `ImmutableMatrices` -## Mutable matrices are mapped to `AbstractArray{Sym,N}` -## cf. matrix.jl -""" - SymMatrix - -Type to store a SymPy matrix, as created by `sympy.ImmutableMatrix`. - -These have 0-based indexing defined for them to match SymPy - -The traditional infix mathmatical operations are defined, but no dot broadcasting. - -The `convert(Matrix{Sym}, M)` call is useful to covert to a Julia matrix - -""" -mutable struct SymMatrix <: SymbolicObject - __pyobject__::PyCall.PyObject - function SymMatrix(o) - Base.depwarn("The `SymMatrix` wrapper type is deprecated", :SymPermuation) - new(o) - end - -end - -## Permutations -## A permutation of {0, 1, 2, ..., n} -- 0-based -struct SymPermutation <: SymbolicObject - __pyobject__::PyCall.PyObject - function SymPermutation(o) - Base.depwarn("The `SymPermutation` wrapper type is deprecated", :SymPermuation) - new(o) - end -end -export SymPermutation -Base.convert(::Type{SymPermutation}, o::PyCall.PyObject) = SymPermutation(o) - - -## A permutation of {0, 1, 2, ..., n} -- 0-based -struct SymPermutationGroup <: SymbolicObject - __pyobject__::PyCall.PyObject - function SymPermutationGroup(o) - Base.depwarn("The `SymPermutationGroup` wrapper type is deprecated", :SymPermuation) - new(o) - end - -end -export SymPermutationGroup -Base.convert(::Type{SymPermutationGroup}, o::PyCall.PyObject) = SymPermutationGroup(o) - -# a Lambda function -struct Lambda <: SymbolicObject - __pyobject__::PyCall.PyObject - function Lambda(o) - Base.depwarn("`Lambda` is deprecated; use `sympy.Lambda(args,expression)`", :Lambda) - new(o) - end -end -Lambda(args, expression) = Lambda(sympy.Lambda(args, expression).__pyobject__) -(λ::Lambda)(args...; kwargs...) = λ.__pyobject__(args...; kwargs...) -export Lambda - - -################################################## - -## important override -## this allows most things to flow though PyCall -PyCall.PyObject(x::SymbolicObject) = x.__pyobject__ -## Override this so that using symbols as keys in a dict works -function Base.hash(x::SymbolicObject, h::UInt) - o = PyObject(x) - px = ccall((PyCall.@pysym :PyObject_Hash), PyCall.Py_hash_t, (PyCall.PyPtr,), o) # from PyCall.jl - reinterpret(UInt, Int(px)) - 3h # from PythonCall.jl -end -#hash(x::SymbolicObject) = hash(PyObject(x)) -==(x::SymbolicObject, y::SymbolicObject) = PyObject(x) == PyObject(y) - -################################################## - - -## Show methods -"create basic printed output" -function jprint(x::SymbolicObject) - out = PyCall.pycall(pybuiltin("str"), String, PyObject(x)) - out = replace(out, r"\*\*" => "^") - out -end -jprint(x::AbstractArray) = map(jprint, x) - -## text/plain -Base.show(io::IO, s::Sym) = print(io, jprint(s)) -Base.show(io::IO, ::MIME"text/plain", s::SymbolicObject) = print(io, sympy.pretty(s)) - -## latex enhancements: Sym, array, Dict -#Base.show(io::IO, ::MIME"text/latex", x::SymbolicObject) = print(io, sympy.latex(x, mode="equation*")) - -as_markdown(x) = Markdown.parse("``$x``") -function Base.show(io::IO, ::MIME"text/latex", x::SymbolicObject) - print(io, sympy.latex(x, mode="inline",fold_short_frac=false)) # plain? equation*? -# #print(io, as_markdown(sympy.latex(x, mode="equation*"))) -# #print(io, as_markdown(sympy.latex(x, mode="plain",fold_short_frac=false))) # inline? -end -function show(io::IO, ::MIME"text/latex", x::AbstractArray{Sym}) - function toeqnarray(x::Vector{Sym}) - a = join([sympy.latex(x[i]) for i in 1:length(x)], "\\\\") - """\\left[ \\begin{array}{r}$a\\end{array} \\right]""" -# "\\begin{bmatrix}$a\\end{bmatrix}" - end - function toeqnarray(x::AbstractArray{Sym,2}) - sz = size(x) - a = join([join(map(sympy.latex, x[i,:]), "&") for i in 1:sz[1]], "\\\\") - "\\left[ \\begin{array}{" * repeat("r",sz[2]) * "}" * a * "\\end{array}\\right]" -# "\\begin{bmatrix}$a\\end{bmatrix}" - end - print(io, as_markdown(toeqnarray(x))) -end -function show(io::IO, ::MIME"text/latex", d::Dict{T,S}) where {T<:SymbolicObject, S<:Any} - Latex(x::Sym) = sympy.latex(x) - Latex(x) = sprint(io -> show(IOContext(io, :compact => true), x)) - - out = "\\begin{equation*}\\begin{cases}" - for (k,v) in d - out = out * Latex(k) * " & \\text{=>} &" * Latex(v) * "\\\\" - end - out = out * "\\end{cases}\\end{equation*}" - print(io, as_markdown(out)) -end - -latex(x::Sym) = sympy.latex(x) - -# (this is used by Polynomials.jl) -function Base.show_unquoted(io::IO, pj::SymbolicObject, indent::Int, prec::Int) - if Base.operator_precedence(:+) <= prec - print(io, "(") - show(io, pj) - print(io, ")") - else - show(io, pj) - end -end - - - -## Following recent changes to PyCall where: -# For o::PyObject, make o["foo"], o[:foo], and o.foo equivalent to o.foo in Python, -# with the former returning an raw PyObject and the latter giving the PyAny -# conversion. -# We do something similar to SymPy -# -# We only implement for symbols here, not strings -function Base.getproperty(o::T, s::Symbol) where {T <: SymbolicObject} - if (s in fieldnames(T)) - getfield(o, s) - else - getproperty(PyCall.PyObject(o), s) - end -end - -# XXX Needs version v1.2+ -#function Base.hasproperty(o::T, s::Symbol) where {T <: SymbolicObject} -# s ∈ fieldnames(T) && return true -# hasproperty(PyCall.PyObject(o), s) -#end diff --git a/src/utils.jl b/src/utils.jl deleted file mode 100644 index e1720128..00000000 --- a/src/utils.jl +++ /dev/null @@ -1,534 +0,0 @@ -## Getindex default. -## Is this the proper default? -""" - x[i] - -Some SymPy Python objects have index notation provided for them through `__getitem__`. This allows Julia's `getindex` to dispatch to Python's `__getitem__`. The index (indices) must be symbolic. This will use 0-based indexing, as it is a simple pass through to Python. - -Examples: - -```jldoctest utils -julia> using SymPy - -julia> i,j = sympy.symbols("i j", integer=true) -(i, j) - -julia> x = sympy.IndexedBase("x") -x - -julia> a = sympy.Sum(x[i], (i, 1, j)) - j - ___ - ╲ - ╲ - ╱ x[i] - ╱ - ‾‾‾ -i = 1 -``` - -""" -function Base.getindex(x::Sym, xs::Sym...) - if pycall_hasproperty(x, :__getitem__) - return x.__getitem__(xs) - elseif pycall_hasproperty(x, :__getslice__) - return x.__getslice__(xs) - else - - # no indexing defined, but this won't effect broadcasting - x - end -end - - -## Call -## Call symbolic object with natural syntax -## ex(x=>val) -## how to do from any symbolic object? -(ex::Sym)() = ex - -## without specification, variables to substitute for come from ordering of `free_symbols`: -function (ex::Sym)(args...) - xs = free_symbols(ex) - for (var, val) in zip(xs, args) - ex = ex.subs(var, Sym(val)) - end - ex -end - -## can use a Dict or pairs to specify: -function (ex::Sym)(x::Dict) - for (k,v) in x - ex = ex.subs(k, Sym(v)) - end - ex -end -function (ex::Sym)(kvs::Pair...) - for (k,v) in kvs - ex = ex.subs(k, Sym(v)) - end - ex -end - -# useful to convert sympy.core.containers.Tuple -convert(::Type{Tuple}, b::Sym) = Tuple(b) -function Base.Tuple(b::Sym) - Tuple(bᵢ for bᵢ ∈ b.__pyobject__) -end -################################################## -## subs -## -""" -`subs` is used to subsitute a value in an expression with another -value. -Examples: - -```jldoctest subs -julia> using SymPy - -julia> x,y = symbols("x,y") -(x, y) - -julia> ex = (x-y)*(x+2y) -(x - y)⋅(x + 2⋅y) - -julia> subs(ex, (y, y^2)) -⎛ 2⎞ ⎛ 2⎞ -⎝x - y ⎠⋅⎝x + 2⋅y ⎠ - -julia> subs(ex, (x,1), (y,2)) --5 - -julia> subs(ex, (x,y^3), (y,2)) -72 - -julia> subs(ex, y, 3) -(x - 3)⋅(x + 6) -``` - -There is a curried form of `subs` to use with the chaining `|>` operator - -```jldoctest subs -julia> ex |> subs(x,ℯ) -(ℯ - y)⋅(2⋅y + ℯ) -``` -The use of pairs gives a convenient alternative: - -```jldoctest subs -julia> subs(ex, x=>1, y=>2) --5 - -julia> ex |> subs(x=>1, y=>2) --5 -``` - - -""" -subs(ex::T, y::Tuple{Any, Any}; kwargs...) where {T <: SymbolicObject} = ex.subs(y[1], Sym(y[2]), kwargs...) -subs(ex::T, y::Tuple{Any, Any}, args...; kwargs...) where {T <: SymbolicObject} = subs(subs(ex, y), args...) -subs(ex::T, y::S, val; kwargs...) where {T <: SymbolicObject, S<:SymbolicObject} = subs(ex, (y,val)) -subs(ex::T, dict::Dict; kwargs...) where {T <: SymbolicObject} = subs(ex, dict...) -subs(ex::T, d::Pair...; kwargs...) where {T <: SymbolicObject} = subs(ex, ((p.first, p.second) for p in d)...) -subs(exs::Tuple{T, N}, args...; kwargs...) where {T <: SymbolicObject, N} = map(u -> subs(u, args...;kwargs...), exs) -subs(x::Number, args...; kwargs...) = x - -## curried versions to use with |> -subs(x::SymbolicObject, y; kwargs...) = ex -> subs(ex, x, y; kwargs...) -subs(;kwargs...) = ex -> subs(ex; kwargs...) -subs(dict::Dict; kwargs...) = ex -> subs(ex, dict...; kwargs...) -subs(d::Pair...; kwargs...) = ex -> subs(ex, [(p.first, p.second) for p in d]...; kwargs...) - - -################################################## -## doit -## -""" -`doit` evaluates objects that are not evaluated by default. - -Examples: - -```jldoctest doit -julia> using SymPy - -julia> @syms x f() -(x, f) - -julia> D = Differential(x) -Differential(x) - -julia> df = D(f(x)) -d -──(f(x)) -dx - -julia> dfx = subs(df, (f(x), x^2)) -d ⎛ 2⎞ -──⎝x ⎠ -dx - -julia> doit(dfx) -2⋅x -``` - -Set `deep=true` to apply `doit` recursively to force evaluation of nested expressions: - -```jldoctest doit -julia> @syms g() -(g,) - -julia> dgfx = g(dfx) - ⎛d ⎛ 2⎞⎞ -g⎜──⎝x ⎠⎟ - ⎝dx ⎠ - -julia> doit(dgfx) - ⎛d ⎛ 2⎞⎞ -g⎜──⎝x ⎠⎟ - ⎝dx ⎠ - -julia> doit(dgfx, deep=true) -g(2⋅x) -``` - -There is also a curried form of `doit`: -```jldoctest doit -julia> dfx |> doit -2⋅x - -julia> dgfx |> doit(deep=true) -g(2⋅x) -``` - -""" -doit(ex::T; deep::Bool=false) where {T<:SymbolicObject} = ex.doit(deep=deep) -doit(; deep::Bool=false) = ((ex::T) where {T<:SymbolicObject}) -> doit(ex, deep=deep) - -## simplify(ex::SymbolicObject, ...) is exported -""" - simplify - -SymPy has dozens of functions to perform various kinds of simplification. There is also one general function called `simplify` that attempts to apply all of these functions in an intelligent way to arrive at the simplest form of an expression. (See [Simplification](https://docs.sympy.org/latest/tutorial/simplification.html) for details on `simplify` and other related functionality). - -For non-symbolic expressions, `simplify` returns its first argument. -""" -simplify(x,args...;kwargs...) = x - -################################################## -## -## access documentation of SymPy -""" - SymPy.Doc(f::Symbol, [module=sympy]) - -Return docstring of `f` found within the specified module. - -Examples -``` -SymPy.Doc(:sin) -SymPy.Doc(:det, sympy.matrices) -## add module to query -SymPy.pyimport_conda("sympy.crypto.crypto", "sympy") -SymPy.Doc(:padded_key, sympy.crypto) -``` -""" -struct Doc - u - m -end -Doc(u::Union{Symbol, Function}) = Doc(Symbol(u), sympy) -function Base.show(io::IO, u::Doc) - v = getproperty(u.m, Symbol(u.u)).__doc__ - print(io, v) -end - -################################################## -# avoid type piracy. After we call `pytype` mappings, some -# objects are automatically converted and no longer PyObjects -function pycall_hasproperty(x::PyCall.PyObject, k) - PyCall.hasproperty(x, k) && (getproperty(x,k) != nothing) -end - -pycall_hasproperty(x::SymbolicObject, k) = pycall_hasproperty(PyCall.PyObject(x), k) -pycall_hasproperty(x, k) = false - -# simple helper for boolean properties -# x.is_finite -> is_(:finite, x) -# e.g.: is_(:FiniteSet, x) = hasproperty && get property -function is_(k::Symbol, x::Sym)::Bool - key = Symbol("is_$k") - pycall_hasproperty(x, key) && getproperty(x, key) == Sym(true) -end - - -_get_members(sm) = PyCall.inspect.getmembers(sm) -function _get_member_functions(sm, exclude=()) - mems = _get_members(sm) - fns = Dict() - for (u, v) in mems - # special cases - u in exclude && continue - - pycall_hasproperty(v, :deprecated) && continue - !isa(v, PyCall.PyObject) && continue - - ## we grab functions or FunctionClass objects only - if PyCall.hasproperty(v, :__class__) && - v.__class__.__name__ in ("function", "FunctionClass") - fns[u] = v - end - end - fns -end - -function _is_module(x::Symbol) - try - typeof(eval(x)) <: Module && x ≠ :Main - catch err - false - end -end - -function loaded_modules() - nms = names(Main,imported=true) - nms = filter(nm -> nm != :SymPy, nms) - Ms = filter(_is_module, nms) - eval.(Ms) -end - -# default list of modules to search for namespace collicsions -const base_Ms = (Base, SpecialFunctions, Base.MathConstants, - LinearAlgebra - ) - -# default list of methods to exclude from importing -# -# In addition to issues (such as "C") this should list -# -# * julia methods for which the sympy method is different from julia's generic usage (e.g. `div`) -# * `julia` methods that are clearly issues with the package system, though not in base_Ms (e.g., `plot`, `latex`) -# * sympy methods for which a substitute is used (e.g. `lambdify`) -# -# these are still accesible through dot-call syntax. -# -const base_exclude=("C", "lambdify", - "latex", "eye", "sympify","symbols", "subs", - "div", "rem", "log", "sinc", - "dsolve", - "ask", - "plot") - -""" - import_from(module, meths; kwargs...) - -Import methods from a python module. Implements functionality of `from module import function` in Python. - -* `module`: a python module, such as `sympy` -* `meths`: nothing or a tuple of symbols to import. If `nothing`, then all member functions of the module are imported (but not constructors or other objects) -* `Ms`: additional Julia Modules to import from. By default, a few base modules are searched for to avoid namespace collisions. -* `typ`: a symbol indicating variable type for first argument that the new function should be restricted to. For most, the default, `:SymbolicObject` will be appropriate -* `exclude`: when importing all (`meths=nothing`), this can be used to avoid importing some methods by name. The default has a few to avoid. - -Examples: - -``` -import_from(sympy) # bring in functions from sympy (done `import_sympy`) -import_from(sympy, (:sin, :cos)) # just bring in a few methods -import_from(sympy , (:Wild,), typ=:Any) # Allows `Wild("x")` -# -import PyCall -PyCall.pyimport_conda("sympy.physics.wigner", "sympy") -import_from(sympy.physics.wigner) -``` - -""" -function import_from(sm, meths=nothing; - Ms::Tuple=(), - typ::Symbol=:SymbolicObject, - exclude::Union{Nothing, NTuple{N,Symbol}}=nothing - ) where {N} - - Base.depwarn("`import_from` is deprecated", :import_from) - - if meths == nothing - _exclude = isa(exclude, Nothing) ? base_exclude : exclude - fns = _get_member_functions(sm, _exclude) - else - mems = PyCall.inspect.getmembers(sm) - fns = Dict() - for (m,p) in mems - if Symbol(m) in meths - fns[m] = p - end - end - end - for (k,v) in fns - meth = Symbol(k) - inMs = false - for M in union(base_Ms, Ms) - M1 = replace("$M",r"^.*\."=>"") - if isdefined(M, meth) - inMs = true - ## @show "import", M, k -## println("$M.$k(ex::$typ, args...; kwargs...)=getproperty($(sm.__name__), :$k)(ex, args...; kwargs...)") - @eval begin - ($M.$meth)(ex::($typ), args...; kwargs...) = - getproperty($sm,$k)(ex, Sym.(args)...; kwargs...) - end - break - end - end - if !inMs - ## need to export - ## @show "export", k -## println("$k(ex::$typ, args...; kwargs...)=getproperty($(sm.__name__), :$k)(ex, args...; kwargs...); export $k") - @eval begin - ($meth)(ex::($typ), args...; kwargs...) = - getproperty($sm,$k)(ex, args...; kwargs...) - end - eval(Expr(:export, meth)) - end - end - -end - - - -""" - free_symbols(ex) - free_symbols(ex::Vector{Sym}) - -Return vector of free symbols of expression or vector of expressions. The results are orderded by -`sortperm(string.(fs))`. - -Example: - -```jldoctest -julia> using SymPy - -julia> @syms x y z a -(x, y, z, a) - -julia> free_symbols(2*x + a*y) # [a, x, y] -3-element Vector{Sym}: - a - x - y -``` -""" -function free_symbols(ex::Union{T, Vector{T}}) where {T<:SymbolicObject} - pex = PyObject(ex) - #fs.__class__.__name__ == "set" - if PyCall.hasproperty(pex, :free_symbols) - fs = collect(Sym, pex.free_symbols) - fs[sortperm(string.(fs))] # some sorting order to rely on - else - Sym[] - end -end - - - -## Module for Introspection -module Introspection - -import SymPy -import SymPy: Sym -import PyCall: PyObject, hasproperty, PyNULL, inspect -export args, func, funcname, class, classname, getmembers - - -# utilities - -""" - funcname(x) - -Return name or "" -""" -function funcname(x::Sym) - y = PyObject(x) - if hasproperty(y, :func) - return y.func.__name__ - else - return "" - end -end - -""" - func(x) - -Return function head from an expression - -[Invariant:](http://docs.sympy.org/dev/tutorial/manipulation.html) - -Every well-formed SymPy expression `ex` must either have `length(args(ex)) == 0` or -`func(ex)(args(ex)...) = ex`. -""" -func(ex::Sym) = return ex.func - -""" - args(x) - -Return arguments of `x`, as a tuple. (Empty if no `:args` property.) -""" -function args(x::Sym) - if hasproperty(PyObject(x), :args) - return x.args - else - return () - end -end - -function class(x::T) where {T <: Union{Sym, PyObject}} - if hasproperty(PyObject(x), :__class__) - return x.__class__ - else - return PyNull() - end -end - -function classname(x::T) where {T <: Union{Sym, PyObject}} - cls = class(x) - if cls == PyNULL() - "NULL" - else - cls.__name__ - end -end - -function getmembers(x::T) where {T <: Union{Sym, PyObject}} - Dict(u=>v for (u,v) in inspect.getmembers(x)) -end - - -## Map to get function object from type information -funcname2function = ( - Add = +, - Sub = -, - Mul = *, - Div = /, - Pow = ^, - re = real, - im = imag, - Abs = abs, - Min = min, - Max = max, - Poly = identity, - Piecewise = error, # replace - Order = (as...) -> 0, - And = (as...) -> all(as), - Or = (as...) -> any(as), - Less = <, - LessThan = <=, - StrictLessThan = <, - Equal = ==, - Equality = ==, - Unequality = !==, - StrictGreaterThan = >, - GreaterThan = >=, - Greater = >, - conjugate = conj, - atan2 = atan, - TupleArg = tuple, - Heaviside = (a...) -> (a[1] < 0 ? 0 : (a[1] > 0 ? 1 : (length(a) > 1 ? a[2] : NaN))), -) - -end diff --git a/test/runtests.jl b/test/runtests.jl index 6e1edb03..3ddd9ed6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,17 +2,8 @@ if lowercase(get(ENV, "CI", "false")) == "true" include("install_dependencies.jl") end + using SymPy -using Test -import SymbolicUtils # test loading extension -include("tests.jl") -include("test-math.jl") -include("test-matrix.jl") -include("test-logical.jl") -#include("test-specialfuncs.jl") ## XXX NEEDS WORK -#include("test-permutations.jl") -include("test-physics.jl") -include("test-external-module.jl") -include("test-latexify.jl") -include("test-ode.jl") +path = joinpath(pathof(SymPy.SymPyCore), "../../test") +include(joinpath(path, "runtests-sympycore.jl")) diff --git a/test/test-external-module.jl b/test/test-external-module.jl deleted file mode 100644 index 9baccb1b..00000000 --- a/test/test-external-module.jl +++ /dev/null @@ -1,48 +0,0 @@ -using SymPy -import PyCall -using Test - -## Test of importing an external module using `pyimport_conda` and `import_from`. -## from issue #262 - -stats = PyCall.pyimport_conda("sympy.stats", "sympy") - -# this brings in all functions into the session using introspection, specialized on a -# symbolic first argument -import_from(stats) - - -@testset "Test stats module" begin - - @vars mu z x - @vars sigma positive=true - X = stats.Normal("x", mu, sigma) - - # some operations: - @test variance(X) == sigma^2 - @test E(X) == mu - P(Gt(X,3)) ## or P(X ≫ 3) - - Z = stats.Normal("Z", 0, 1) # needs `stats.` as "Z" is not symbolic, - # could also do this: - @vars Z - Z = Normal(Z, 0, 1) - @test E(Z) == 0 - @test variance(Z) == 1 - - simplify(P(Gt(Z, 1))) - - @test 0.68 <= 1 - 2*N(P(Gt(Z, 1))) <= 0.69 - - - @vars Y - @vars a b c - Y = DiscreteUniform(Y, (a,b,c)) - density(Y).dict - @test density(Y).dict[a] == 1//3 - - # And density - density(X) #NormalDistribution(mu, sigma) - density(X).pdf(z) # need the `pdf` call here - -end diff --git a/test/test-latexify.jl b/test/test-latexify.jl deleted file mode 100644 index f1d1d0fe..00000000 --- a/test/test-latexify.jl +++ /dev/null @@ -1,9 +0,0 @@ -using SymPy -using Test - -@testset "Latexify" begin - @vars α - @test_nowarn SymPy.Latexify.latexify(- 3/(8*α) + 1/(8*α^2)) - @test occursin("\\cdot", SymPy.Latexify.latexify(- 3/(8*α) + 1/(8*α^2), cdot=true)) - @test !occursin("\\cdot", SymPy.Latexify.latexify(- 3/(8*α) + 1/(8*α^2), cdot=false)) -end diff --git a/test/test-logical.jl b/test/test-logical.jl deleted file mode 100644 index a34afbe0..00000000 --- a/test/test-logical.jl +++ /dev/null @@ -1,8 +0,0 @@ -using SymPy -using Test - -@testset "Logical" begin - ## More logical expressions - @vars x y - (x ≪ 0) ∧ Le(x*y,1) ∨ (x ⩵ y) ∧ (¬(x ≫ 3)) -end diff --git a/test/test-math.jl b/test/test-math.jl deleted file mode 100644 index 3aee29be..00000000 --- a/test/test-math.jl +++ /dev/null @@ -1,57 +0,0 @@ -using SymPy -using Test -using SpecialFunctions - -@testset "Math" begin - ρ = symbols("rho", positive=true) - ϕ = symbols("phi", real=true) - x = symbols("x") - - @test simplify(hypot(ρ*cos(ϕ), ρ * sin(ϕ))) == ρ - @test simplify(hypot(ρ*cos(ϕ), 3)) == sqrt(ρ^2*cos(ϕ)^2 + 9) - - @test real(sqrt(Sym(5))+im) == sqrt(Sym(5)) - @test real(sqrt(Sym(5))+im) isa Sym - @test imag(sqrt(Sym(5))+im) == 1 - @test imag(sqrt(Sym(5))+im) isa Sym - - @test atan(Sym(1), 1) == PI/4 - @test atan(Sym(1), -1) == 3PI/4 - - @test N(angle(Sym(1) + Sym(2)*IM)) ≈ atan(2,1) - - @test factorial(Sym(0)) == 1 - @test factorial(Sym(7)) == 5040 - - @test sympy.factorial2(Sym(5)) == 15 - @test sympy.factorial2(Sym(-5)) == Sym(1)/3 - - @test erf(Sym(0)) == 0 - @test erf(Sym(oo)) == 1 - @test diff(erf(x), x) == 2*exp(-x^2)/sqrt(PI) - - - @test sinc(Sym(0)) == 1 - # test consistency with Julia's sinc - @test sinc(Sym(1)) == 0 - @test N(sinc(Sym(0.2))) ≈ sinc(0.2) - - @test flipsign(Sym(3), 2.0) == 3 - @test flipsign(Sym(3), 0.0) == 3 - @test flipsign(Sym(3), -0.0) == -3 - @test flipsign(Sym(3), -2.0) == -3 - - @test eps(Sym) == 0 - #@test rewrite(sinc(x), "jn") == jn(0, PI * x) -end - -@testset "nan" begin - # issue 346 - a=sympy.nan - a′ = NaN - - @test (a < 0) == (a′ < 0) - @test (a > 0) == (a′ > 0) - @test (a == 0) == (a′ == 0) - -end diff --git a/test/test-matrix.jl b/test/test-matrix.jl deleted file mode 100644 index 1f81dc55..00000000 --- a/test/test-matrix.jl +++ /dev/null @@ -1,196 +0,0 @@ -using SymPy -using Test -using LinearAlgebra - -@testset "Matrix" begin - ## matrices - x, y = @vars x y - A = [x 1; 1 x] - B = [x 1; 0 2x] - v = [x, 2] - - ## These fail for older installations of SymPy - @test simplify(det(A)) == x^2 - 1 - - ## we use inverse for A[:inv]() - - # aliased to use inverse - @test simplify.(inv(A) * A) == [1 0; 0 1] - @test simplify.(A * inv(A)) == [1 0; 0 1] - ##XXX @test simplify.(A[:inv]() - inv(A)) == zeros(2, 2) - - @test SymPy.adjoint(B) == [adjoint(x) 0; 1 adjoint(2x)] - @test SymPy.adjoint(B) == B' - @test A.dual() == sympy.zeros(2, 2) - - - A1 = Sym[25 15 -5; 15 18 0; -5 0 11] - r = A1.cholesky() - @test r*r.transpose() == A1 - - - # s = LUsolve(A, v) - s = A.LUsolve(B) - @test simplify.(A * s) == B - - # norm - @test norm(A) == sqrt(2 * abs(x)^2 + 2) - # test norm for different subtypes of AbstractArray - ## XXX @test norm(A) == norm(Symmetric(A)) LinearAlgebra.Symmetric no long works - @test norm(A) == norm(view(A, :, :)) - - # abs - @test all(convert.(Bool, abs.(A) .≧ 0)) - @test abs.(A) == abs.(view(A, :, :)) - - # is_lower, is_square, is_symmetric much slower than julia only counterparts. May deprecate, but for now they are here - @test A.is_lower == istril(A) - @test A.is_square == true - @test A.is_symmetric() != issymmetric(A) - - @vars x real=true - A = [x 1; 1 x] - @test A.is_symmetric() == issymmetric(A) - - @test Set(eigvals(A)) == Set([x-1, x+1]) - - # issue with transpose being non-typestable - @test eltype(transpose(A)) == Sym - @test eltype(Symmetric(A)) == Sym - - #numerical tests - M = Sym[1 0 0; 0 1 0; 0 0 x] - evecs = eigvecs(M) - @test evecs[:,1] == [1, 0, 0] - - - A = Sym[1 2 3; 3 6 2; 2 0 1] - q, r = A.QRdecomposition() - @test q * r == A - @test abs(det(q)) == 1 - - - # for v0.4, the vector type is not correctly inferred - #L = Vector{Sym}[Sym[2,3,5], Sym[3,6,2], Sym[8,3,6]] - # L = collect(sympy.Matrix.(([[2,3,5]], [[3,6,2]], [[8,3,6]]])) - L = collect(sympy.Matrix.([[2,3,5], [3,6,2], [8,3,6]])) - out = sympy.GramSchmidt(L, true) # qualify, as L not SymbolicObject type - for i = 1:3, j = i:3 - @test out[i].dot(out[j]) == (i == j ? 1 : 0) - end - - A = Sym[4 3; 6 3] - L, U, _ = A.LUdecomposition() - @test L == Sym[1 0; 3//2 1] - - A = Sym[1 0; 0 1] * 2 - B = Sym[1 2; 3 4] - @test A.diagonal_solve(B) == B/2 - - M = Sym[1 2 0; 0 3 0; 2 -4 2] - P, D = M.diagonalize() - @test D == [1 0 0; 0 2 0; 0 0 3] - @test P == [-1 0 -1; 0 0 -1; 2 1 2] - @test D == inv(P) * M * P - - # test SymPy's expm against Julia's expm - A = [1 2 0; 0 3 0; 2 -4 2] - M = Sym.(A) - ## no exp(M)! - U = M.exp() - exp(A) - @test maximum(abs.(N.(U))) <= 1e-12 - - M = [x y; 1 0] - @test integrate.(M, x) == [x^2/2 x*y; x 0] - @test integrate.(M, Ref((x, 0, 2))) == [2 2y; 2 0] - - - M = Sym[1 3 0; -2 -6 0; 3 9 6] - @test M.nullspace()[1] == reshape(Sym[-3, 1, 0], 3, 1) - - - M = Sym[1 2 0; 0 3 0; 2 -4 2] - # M.cofactor uses 0-based indexing! - i, j = 1, 2 - @test M.cofactor(i, j) == (-1)^(i+j) * det(M[setdiff(1:3, i+1), setdiff(1:3, j+1)]) - @test M.adjugate() / M.det() == M.inv() - - M = Sym[ 6 5 -2 -3; - -3 -1 3 3; - 2 1 -2 -3; - -1 1 5 5] - - P, J = M.jordan_form() - @test J == [2 1 0 0; - 0 2 0 0; - 0 0 2 1; - 0 0 0 2] - @test J == inv(P) * M * P - - ρ, ϕ = symbols("rho, phi") - X = [ρ*cos(ϕ), ρ*sin(ϕ), ρ^2] - Y = [ρ, ϕ] - @test X.jacobian(Y) == [cos(ϕ) -ρ*sin(ϕ); - sin(ϕ) ρ*cos(ϕ); - 2ρ 0] - X = [ρ*cos(ϕ), ρ*sin(ϕ)] - @test convert(Matrix{Sym}, X.jacobian(Y)) == [cos(ϕ) -ρ*sin(ϕ); - sin(ϕ) ρ*cos(ϕ)] - @test X.jacobian(Y) == view(X, :, :).jacobian(view(Y, :, :)) - - ## Issue #359 - if VERSION >= v"1.2" - @syms a - A1 = [a 1; 1 a] - # The first six cases work - @test typeof(A1 + a*I) == Matrix{Sym} - @test typeof(A1 - a*I) == Matrix{Sym} - @test typeof(-A1 + a*I) == Matrix{Sym} - @test typeof(-A1 - a*I) == Matrix{Sym} - @test typeof(-a*I + A1) == Matrix{Sym} - @test typeof(a*I + A1) == Matrix{Sym} - @test typeof(a*I - A1) == Matrix{Sym} - @test typeof(-a*I - A1) == Matrix{Sym} - - A2 = [1 2; 2 1] - @test typeof(A2 + a*I) == Matrix{Sym} - @test typeof(A2 - a*I) == Matrix{Sym} - @test typeof(-A2 + a*I) == Matrix{Sym} - @test typeof(-A2 - a*I) == Matrix{Sym} - @test typeof(-a*I + A2) == Matrix{Sym} - @test typeof(a*I + A2) == Matrix{Sym} - @test typeof(a*I - A2) == Matrix{Sym} - @test typeof(-a*I - A2) == Matrix{Sym} - - end - - ## Issue #397 adjoint losing type - A = ones(Sym, 1, 1) - @test eltype(A * A') == Sym - - ## Issue 413 wth matrix exponential; SymMatrix multiplication - @vars a - A,A1 = [1 0; 0 a], [1 1; a 2] - @test exp(A) == [exp(Sym(1)) 0; 0 exp(a)] - B,B1 = convert(SymMatrix,A), convert(SymMatrix,A1) - @test B*B1 == convert(SymMatrix, A*A1) - - ## Issue #462 missing pytypemapping - U = sympy.Matrix(sympy.MatrixSymbol("U",2,2)) - @test U isa Matrix{Sym} - - ## Issue 495 with eigenvals - a = Sym[-5 -6 3; 3 4 -3; 0 0 -2] - ls = eigvals(a) - @test ls == sort(ls) - vs = eigvecs(a) - for i ∈ 1:3 - λ, v = ls[i], vs[:,i] - @test (a * v - λ * v == 0 *v) - end - - a=Sym["a" "b"; "c" "d"] - ls = eigvals(a) - vs = eigvecs(a) - @test simplify.(vs * Diagonal(ls) * inv(vs) -a) == 0*a -end diff --git a/test/test-ode.jl b/test/test-ode.jl deleted file mode 100644 index f9047020..00000000 --- a/test/test-ode.jl +++ /dev/null @@ -1,86 +0,0 @@ -using SymPy -using Test - -@testset "ODes" begin - ## ODEs - x, a = Sym("x, a") - F = SymFunction("F") - ex = diff(F(x), x) - a*F(x) - ex1 = dsolve(ex) - ex2 = ex1.rhs()(Sym("C1") => 1, a => 2) - @test ex2 == exp(2x) - - t, = @vars t - X, Y = SymFunction("X, Y") - eq = (Eq(diff(X(t),t), 12*t*X(t) + 8*Y(t)), Eq(diff(Y(t),t), 21*X(t) + 7*t*Y(t))) - sympy.dsolve(eq) # use tuple, not array is not SymbolicObject - - - ## Removed - ## version 0.4+ allow use of u'(x) in lieu of diff(u(x), x) and `ivpsolve` - ## This will be deprecated in favor of Differential(x) - u = SymFunction("u") - a, x, y, y0, y1 = symbols("a, x, y, y0, y1") - - @test dsolve(u'(x) - a*u(x), u(x), ics=(u, 0, 1)) == Eq(u(x), exp(a*x)) - @test dsolve(u'(x) - a*u(x), u(x), ics=(u, 0, y1)) == Eq(u(x), y1*exp(a*x)) - dsolve(u'(x) - a*u(x), u(x), ics=(u, y0, y1)) # == Eq(u(x), y1 * exp(a*(x - y0))) - dsolve(x*u'(x) + x*u(x) + 1, u(x), ics=(u, 1, 1)) - dsolve((u'(x))^2 - a*u(x), u(x), ics=(u, 0, 1)) - dsolve(u''(x) - a * u(x), u(x), ics=((u, 0, 1), (u', 0, 0))) - - F, G, K = SymFunction("F, G, K") - eqn = F(x)*u'(y)*y + G(x)*u(y) + K(x) - dsolve(eqn, u(y), ics=(u, 1, 0)) - - ## dsolve eqn has two answers, but we want to eliminate one - # based on initial condition - dsolve(u'(x) - (u(x)-1)*u(x)*(u(x)+1), u(x), ics=(u, 0, 1//2)) - - ## --- - - ## use Differential, not u' or u'' - ## use Dict to specify ics from SymPy, not internal one - @syms a x y0 y1 u() - ∂ = Differential(x) - - @test dsolve(∂(u)(x) - a*u(x), u(x), ics=Dict(u(0) => 1)) == Eq(u(x), exp(a*x)) - @test dsolve(∂(u)(x) - a*u(x), u(x), ics=Dict(u(0) => y1)) == Eq(u(x), y1*exp(a*x)) - dsolve(∂(u)(x) - a*u(x), u(x), ics=Dict(u(y0)=>y1)) # == Eq(u(x), y1 * exp(a*(x - y0))) - dsolve(x*∂(u)(x) + x*u(x) + 1, u(x), ics=Dict(u(1) => 1)) - 𝒂 = 2 - dsolve((∂(u)(x))^2 - 𝒂 * u(x), u(x), ics=Dict(u(0) => 0, ∂(u)(0) => 0)) - dsolve(∂(∂(u))(x) - 𝒂 * u(x), u(x), ics=Dict(u(0)=> 1, ∂(u)(0) => 0)) - - F, G, K = SymFunction("F, G, K") - eqn = F(x)*∂(u)(y)*y + G(x)*u(y) + K(x) - dsolve(eqn, u(y), ics=Dict(u(1) => 0)) - - ## dsolve eqn has two answers, but we want to eliminate one - # based on initial condition - dsolve(∂(u)(x) - (u(x)-1)*u(x)*(u(x)+1), u(x), ics=Dict(u(0)=> Sym(1//2))) - - ## ---- - ## rhs works - u = SymFunction("u") - @syms x y a::positive - eqn = ∂(u)(x) - a * u(x) * (1 - u(x)) - out = dsolve(eqn) - eq = rhs(out) # just the right hand side - C1 = first(setdiff(free_symbols(eq), (x,a))) - c1 = solve(eq(x=>0) - 1//2, C1) - @test c1[1] == Sym(1) - - - ## dsolve and system of equations issue #291 - @syms t x() y() - ∂ = Differential(t) - eq1 = ∂(x(t)) ~ x(t)*y(t)*sin(t) - eq2 = ∂(y(t)) ~ y(t)^2*sin(t) -# eq1 = Eq(diff(x(t),t),x(t)*y(t)*sin(t)) -# eq2 = Eq(diff(y(t),t),y(t)^2*sin(t)) -# out = dsolve([eq1, eq2]) # vector -- deprecated - out = dsolve((eq1, eq2)) # tuple - Dict(lhs.(collect(out)) .=> rhs.(collect(out))) # turn python set into a dictionary - -end diff --git a/test/test-physics.jl b/test/test-physics.jl deleted file mode 100644 index ff9693b8..00000000 --- a/test/test-physics.jl +++ /dev/null @@ -1,48 +0,0 @@ -using SymPy -using PyCall: PyError -using Test - - -import PyCall -PyCall.pyimport_conda("sympy.physics.wigner", "sympy") -PyCall.pyimport_conda("sympy.physics.optics", "sympy") -PyCall.pyimport_conda("sympy.physics.quantum.spin", "sympy") - -import_from(sympy.physics.wigner) -import_from(sympy.physics.optics) -import_from(sympy.physics.quantum.spin) - -@testset "Physics" begin - @test clebsch_gordan(Sym(3)/2, Sym(1)/2, 2, Sym(3)/2, Sym(1)/2, 2) == 1 - @test clebsch_gordan(Sym(3)/2, Sym(1)/2, 1, Sym(3)/2, -Sym(1)/2, 1) == sqrt(Sym(3))/2 - @test clebsch_gordan(Sym(3)/2, Sym(1)/2, 1, -Sym(1)/2, Sym(1)/2, 0) == -sqrt(Sym(2))/2 - - θ, ϕ = symbols("theta, phi") - @test dot_rot_grad_Ynm(Sym(3), 2, 2, 0, θ, ϕ).doit() == 3*sqrt(Sym(55))*Ynm(Sym(5), 2, θ, ϕ)/(11*sqrt(PI)) - - @test gaunt(Sym(1),0,1,1,0,-1) == -1/(2*sqrt(PI)) - @test N(gaunt(Sym(1000),1000,1200,9,3,-12)) ≈ 0.00689500421922113448 # takes forever to compute - - @test_throws PyError gaunt(Sym(1),2,0,1.2,0,0,0) - @test_throws PyError gaunt(Sym(1),0,1,1.1,0,-1.1) - - @test racah(Sym(3),3,3,3,3,3) == -1//14 - - - @test wigner_3j(Sym(2), 6, 4, 0, 0, 0) == sqrt(Sym(715))/143 - @test wigner_3j(Sym(2), 6, 4, 0, 0, 1) == 0 - - @test wigner_6j(Sym(3),3,3,3,3,3) == -1//14 - @test wigner_6j(Sym(5),5,5,5,5,5) == 1//52 - - - # Optics - # sympy.physics.optics.RayTransferMatrix not a functino - @test sympy.physics.optics.RayTransferMatrix(1,2,3,4) == convert(SymMatrix,Sym[1 2; 3 4]) - @test sympy.physics.optics.FlatMirror() == convert(SymMatrix,Sym[1 0; 0 1]) - - # Spin - import_from(sympy.physics.quantum.spin, (:WignerD,), typ=:Any) - @test WignerD(Sym(3), Sym(2), Sym(1), PI, PI/2, -PI).doit() == -sqrt(Sym(10))/8 - @test WignerD(Sym(1), Sym(1), Sym(0), 0, θ, 0).doit() == -sin(θ)/sqrt(Sym(2)) -end diff --git a/test/test-plots.jl b/test/test-plots.jl deleted file mode 100644 index f1efb386..00000000 --- a/test/test-plots.jl +++ /dev/null @@ -1,48 +0,0 @@ -## code to test plotting -## not part of headless test suite -## has dependencies not in Test.REQUIRES - -using SymPy -using Plots -plotly() - -@vars x y - -info("expression") -plot(x^2, 0, 3) # single expression -plot!(9-x^2, 0, 3, color=:green) # plot! - -info("two graphs") -plot([x^2, 1 - x^2/2], 0, 3) # two graphs at once - -plot(x^2, 0, 3) -plot!(1 - x^2/2, 0, 3) - - -info("parametric plot") -plot(sin(x), cos(x), 0, 2pi) # parametric - - -info("3d parameteric") -plot(cos(x), sin(x), x, 0, pi, linewidth=5) - - -#info("vectorplot") -#vectorfieldplot([cos(x) sin(y)], (x,0,2pi), (y,0,2pi)) - -info("contour plot") -contour(linspace(-5,5,25), linspace(-5,5,25), x^2 - y^2) - -info("surface") -surface(-5:5, -5:5, 25 - x^2 - y^2) - - - -## PyPlot Only for now. - -info("parametric surface") -plot_parametric_surface((sin(x)*sin(y), sin(x)*cos(y), cos(x)), (x,0,pi/2), (y,0,pi)) - - -info("implicit") -plot_implicit(x^2 + y^2 <= 1, (x,-5, 5), (y, 0,5)) diff --git a/test/test-specialfuncs.jl b/test/test-specialfuncs.jl deleted file mode 100644 index ae882366..00000000 --- a/test/test-specialfuncs.jl +++ /dev/null @@ -1,180 +0,0 @@ -using SymPy: Sym, sqrt, conjugate, symbols, PI, simplify, - expand_func, rewrite, N -using LinearAlgebra -using Test - -using SymPy.SpecialFuncs -using SpecialFunctions - - -@testset "SpecialFuns" begin - a, b, x, y = symbols("a, b, x, y") - n, m, θ, ϕ = symbols("n, m, theta, phi") - ν = symbols("nu", integer=true) - - - @test fresnels(Sym(0)) == 0 - @test fresnels(Sym(oo)) == Sym(1)/2 - @test diff(fresnels(x), x) == sin(PI*x^2/2) - #@test evalf(fresnels(Sym(2)), 30) == Sym(parse(BigFloat, "0.343415678363698242195300815958")) - - - @test diff(fresnelc(x), x) == cos(PI*x^2/2) - - - @test Ei(Sym(-1)) == Ei(exp(1im*PI)) - @test diff(Ei(x), x) == exp(x)/x - @test diff(Si(x), x) == sin(x)/x - @test diff(Ci(x), x) == cos(x)/x - - - @test airyai(Sym(0)) == 3^(Sym(1)/3)/(3*gamma(Sym(2)/3)) - @test airybi(Sym(0)) == 3^(Sym(5)/6)/(3*gamma(Sym(2)/3)) - - - @test jacobi(0, a, b, x) == 1 - @test jacobi(1, a, b, x) == a/2 - b/2 + x*(a/2 + b/2 + 1) - @test jacobi(n, 0, 0, x) == legendre(n, x) - @test jacobi(n, a, b, -x) == (-1)^n*jacobi(n, b, a, x) - @test conjugate(jacobi(n, a, b, x)) == jacobi(n, conjugate(a), conjugate(b), conjugate(x)) - - - @test gegenbauer(0, a, x) == 1 - @test gegenbauer(1, a, x) == 2a*x - @test gegenbauer(2, a, x) == -a + x^2*(2a^2 + 2a) - @test conjugate(gegenbauer(n, a, x)) == gegenbauer(n, conjugate(a), conjugate(x)) - @test diff(gegenbauer(n, a, x), x) == 2a*gegenbauer(n-1, a+1, x) - - - @test chebyshevt(0, x) == 1 - @test chebyshevt(1, x) == x - @test chebyshevt(2, x) == 2x^2-1 - - - @test chebyshevu(0, x) == 1 - @test chebyshevu(1, x) == 2x - @test chebyshevu(2, x) == 4x^2 - 1 - @test chebyshevu(n, 0) == cos(π*n/2) - @test chebyshevu(n, 1) == n + 1 - - - @test legendre(0, x) == 1 - @test legendre(1, x) == x - @test legendre(2, x) == 3x^2/2 - 1/2 - @test legendre(n, x) == legendre(n, x) - @test diff(legendre(n,x), x) == n*(x*legendre(n, x) - legendre(n - 1, x))/(x^2 - 1) - - - @test assoc_legendre(0,0, x) == 1 - @test assoc_legendre(1,0, x) == x - @test assoc_legendre(1,1, x) == -sqrt(-x^2 + 1) - #@test assoc_legendre(n,m,x) == assoc_legendre(n, m, x) - - - @test hermite(0, x) == 1 - @test hermite(1, x) == 2x - @test hermite(2, x) == 4x^2 - 2 - @test hermite(n, x) == hermite(n, x) - @test diff(hermite(n,x), x) == 2n*hermite(n - 1, x) - @test hermite(n, -x) == (-1)^n*hermite(n, x) - - - @test laguerre(0, x) == 1 - @test laguerre(1, x) == -x + 1 - @test laguerre(2, x) == x^2/2 - 2x + 1 - @test laguerre(3, x) == -x^3/6 + 3x^2/2 - 3x + 1 - @test diff(laguerre(n, x), x) == -assoc_laguerre(n - 1, 1, x) - - - @test assoc_laguerre(0, a, x) == 1 - @test assoc_laguerre(1, a, x) == a - x + 1 - @test assoc_laguerre(2, a, x) == a^2/2 + 3a/2 + x^2/2 + x*(-a - 2) + 1 - @test assoc_laguerre(n, 0, x) == laguerre(n, x) - @test diff(assoc_laguerre(n, a, x), x) == -assoc_laguerre(n - 1, a + 1, x) - - - @test Ynm(n, m, θ, ϕ) == Ynm(n, m, θ, ϕ) - @test Ynm(n, -m, θ, ϕ) == (-1)^m*exp(-2im*m*ϕ)*Ynm(n, m, θ, ϕ) - @test Ynm(n, m, -θ, ϕ) == Ynm(n, m, θ, ϕ) - @test Ynm(n, m, θ, -ϕ) == exp(-2im*m*ϕ)*Ynm(n, m, θ, ϕ) - @test SymPy.expand(simplify(Ynm(0, 0, θ, ϕ)), func=true) == 1/(2*sqrt(PI)) - - @test Ynm_c(n, m, θ, ϕ) == conjugate(Ynm(n, m, θ, ϕ)) - - @test diff(hankel1(n, x), x) == hankel1(n - 1, x)/2 - hankel1(n + 1, x)/2 - @test diff(hankel2(n, x), x) == hankel2(n - 1, x)/2 - hankel2(n + 1, x)/2 - - - @test expand_func(jn(0, x)) == sin(x)/x - @test expand_func(jn(1, x)) == sin(x)/x^2 - cos(x)/x - @test rewrite(jn(ν, x), "besselj") == sqrt(2PI/x)*besselj(ν + Sym(1)/2, x)/2 - VERSION < v"0.6.0-dev" && @test rewrite(jn(ν, x), "bessely") == (-1)^ν*sqrt(2PI/x)*bessely(-ν - Sym(1)/2, x)/2 - u = N(jn(2, 5.2+0.3im), 20) - @test norm(real(u) - 0.099419756723640344491) <= 1e-15 && norm(imag(u) + 0.054525080242173562897) <= 1e-15 - - - @test expand_func(yn(0, x)) == -cos(x)/x - @test expand_func(yn(1, x)) == -cos(x)/x^2-sin(x)/x - VERSION < v"0.6.0-dev" && @test rewrite(yn(ν, x), "besselj") == (-1)^(ν + 1)*sqrt(2PI/x)*besselj(-ν - Sym(1)/2, x)/2 - @test rewrite(yn(ν, x), "bessely") == sqrt(2PI/x)*bessely(ν + Sym(1)/2, x)/2 - @test N(yn(2, 5.2+0.3im)) ≈ 0.18525034196069722536 + 0.014895573969924817587im - - - # gamma, beta and related functions - @test gamma(Sym(1)) == 1 - @test gamma(Sym(3)/2) == sqrt(PI)/2 - - - @test diff(polygamma(Sym(0), x), x) == polygamma(Sym(1), x) - @test diff(polygamma(Sym(0), x), x, 2) == polygamma(Sym(2), x) - - - @test diff(beta(x, y), x) == (polygamma(Sym(0), x) - polygamma(Sym(0), x + y)) * beta(x, y) - @test diff(beta(x, y), y) == (polygamma(Sym(0), y) - polygamma(Sym(0), x + y)) * beta(x, y) - - - # test numerical consistency with Julia functions - @test N(gamma(Sym(4.1))) ≈ gamma(4.1) - @test N(polygamma(Sym(2), Sym(3.2))) ≈ polygamma(2, 3.2) - VERSION >= v"0.5.0" && @test N(beta(Sym(1)+1im, Sym(1)+1im)) ≈ beta(1.0+1im, 1.0+1im) - - - # Elliptic-type functions - - @test elliptic_k(Sym(0)) == PI/2 - @test N(elliptic_k(Sym(1.0 + im))) ≈ 1.50923695405127 + 0.625146415202697im - - @test N(elliptic_f(Sym(3.0 + im/2), Sym(1.0 + im))) ≈ 2.909449841483 + 1.74720545502474im - - @test elliptic_e(Sym(0)) == PI/2 - @test N(elliptic_e(Sym(2.0 - im))) ≈ 0.991052601328069 + 0.81879421395609im - - @test elliptic_pi(Sym(0), Sym(0)) == PI/2 - @test N(elliptic_pi(Sym(1.0 - im/3), Sym(2.0 + im))) ≈ 3.29136443417283 + 0.32555634906645im - - - # Bessel-type functions - @test diff(besselj(n, x), x) == (besselj(n - 1, x) - besselj(n + 1, x))/2 - @test rewrite(besselj(n, x), "jn") == sqrt(2x/PI)*jn(n - 1/2, x) - - - @test diff(bessely(n, x), x) == (bessely(n - 1, x) - bessely(n + 1, x))/2 - @test rewrite(bessely(n, x), "yn") == sqrt(2x/PI)*yn(n - 1/2, x) - - - @test diff(besseli(n, x), x) == (besseli(n - 1, x) + besseli(n + 1, x))/2 - - - @test diff(besselk(n, x), x) == -(besselk(n - 1, x) + besselk(n + 1, x))/2 - - - # test numerical consistency with Julia functions - if VERSION < v"0.6.0-dev" - @test N(besselj(3.2, Sym(1.5))) ≈ besselj(3.2, 1.5) - @test N(bessely(3.2, Sym(1.5))) ≈ bessely(3.2, 1.5) - @test N(besseli(3.2, Sym(1.5))) ≈ besseli(3.2, 1.5) - @test N(besselk(3.2, Sym(1.5))) ≈ besselk(3.2, 1.5) - end - - @test expand_func(x*hyper([1, 1], [2], -x)) == log(x + 1) -end diff --git a/test/tests.jl b/test/tests.jl deleted file mode 100644 index 3ec17b78..00000000 --- a/test/tests.jl +++ /dev/null @@ -1,862 +0,0 @@ -using SpecialFunctions -using Test -using LinearAlgebra -using Base.MathConstants -using SymPy -using SymPy.Introspection -import PyCall - -@testset "Core" begin - ## Symbol creation - x = Sym("x") - #x = sym"x" # deprecated - x = Sym(:x) - x,y = Sym(:x, :y) - x,y = symbols("x,y") - - @vars u1 u2 u3 - @vars u positive=true - @test length(solve(u+1)) == 0 - # make sure @vars defines in a local scope - let - @vars w - end - @test_throws UndefVarError isdefined(w) - @vars a b c - - # Renaming with @vars - @vars a=>"α₁" - @test a.name == "α₁" - - ## extract symbols - @vars z - ex = x*y*z - @test isa(free_symbols(ex), Vector{Sym}) - @test free_symbols(ex) == [x, y, z] - - ## number conversions - @test Sym(2) == 2 - @test Sym(2.0) == 2.0 - @test Sym(2//1) == 2 - @test Sym(im) == 1im - @test Sym(2im) == 2im - @test Sym(1 + 2im) == 1 + 2im - - pi, e, catalan = Base.MathConstants.pi, Base.MathConstants.e, Base.MathConstants.catalan - @test N(Sym(pi)) == pi - @test N(Sym(ℯ)) == ℯ - @test N(Sym(catalan)) == catalan - - - ## function conversion - f1 = convert(Function, x^2) - @test f1(2) == 4 - - ### Subs - ## subs, |> (x == number) - f(x) = x^2 - 2 - y = f(x) - @test float(y.subs( x, 1)) == f(1) - ### @test float( y |> subs(x,1) ) == f(1) no subs method - - ## interfaces - ex = (x-1)*(y-2) - @test ex.subs(x, 1) == 0 - @test ex.subs(((x,1),)) == 0 - @test ex.subs(((x,2),(y,2))) == 0 - - @test subs(ex, x=>1) == 0 # removed - @test subs(ex, x=>2, y=>2) == 0 - @test subs(ex, Dict(x=>1)) == 0 - @test ex(x=>1) == 0 - @test ex(x=>2, y=>2) == 0 - @test ex.subs(Dict(x=>1)) == 0 - - ### doit - @syms x f() g() - D = Differential(x) - df = D(f(x)) - dfx = subs(df, f(x), x^2) - @test dfx.doit() == 2*x - @test doit(dfx) == 2*x - @test dfx |> doit == 2*x - # use deep=true to force nested evaluations - dgfx = g(dfx) - @test dgfx.doit(deep=true) == g(2*x) - @test doit(dgfx, deep=true) == g(2*x) - @test dgfx |> doit(deep=true) == g(2*x) - - ## match, replace, xreplace, rewrite - x,y,z = symbols("x, y, z") - a,b,c = map(Wild, (:a,:b,:c)) - ## match: we have pattern, expression to follow `match` - d = match(a^a, (x+y)^(x+y)) - @test d[a] == x+y - d = match(a^b, (x+y)^(x+y)) - @test d[b] == x + y - ex = (2x)^2 - pat = a*b^c - d = match(pat, ex) - @test d[a] == 4 && d[b] == x && d[c] == 2 - @test pat.xreplace(d) == 4x^2 - - ## replace - a = Wild("a") - ex = log(sin(x)) + tan(sin(x^2)) - ##XXX @test replace(ex, func(sin(x)), u -> sin(2u)) == log(sin(2x)) + tan(sin(2x^2)) - @test replace(ex, func(sin(x)), func(tan(x))) == log(tan(x)) + tan(tan(x^2)) - @test replace(ex, sin(a), tan(a)) == log(tan(x)) + tan(tan(x^2)) - @test replace(ex, sin(a), a) == log(x) + tan(x^2) - @test replace(x*y, a*x, a) == y - - ## xreplace - @test (1 + x*y).xreplace(Dict(x => PI)) == 1 + PI*y - @test (x*y + z).xreplace(Dict(x*y => PI)) == z + PI - @test (x*y * z).xreplace(Dict(x*y => PI)) == x* y * z - @test (x + 2 + exp(x + 2)).xreplace(Dict(x+2=>y)) == x + exp(y) + 2 - - - # Test subs on simple numbers - @vars x y - @test Sym(2)(x=>300, y=>1.2) == 2 - - #Test subs for pars and dicts - ex = 1 - dict1 = Dict{String,Any}() - dict2 = Dict{Any,Any}() - #test subs - for i=1:4 - x = Sym("x$i") - ex=ex*x - dict2[x] = i - dict1[string(x)] = i - end - for d in [dict1, dict2] - @test ex |> subs(d) == factorial(4) - @test subs(ex, d) == factorial(4) - @test subs(ex, d...) == factorial(4) - @test ex |> subs(d...) == factorial(4) - @test ex(d) == factorial(4) - @test ex(d...) == factorial(4) - end - - a = Sym("a") - b = Sym("b") - line = x -> a + b * x - sol = solve([line(0)-1, line(1)-2],[a,b]) - ex = line(10) - @test ex(sol) == ex(sol...) == 11 - - ## Simplify (issue 343) - @vars x - @test simplify(sin(x)^2 + cos(x)^2) == 1 - @test simplify(sympy.gamma(x)/sympy.gamma(x-2)) == (x-1)*(x-2) - _ones = (1, 1.0, big"1", "1", one) - @test simplify.(_ones) == _ones - - ## Conversion - x = Sym("x") - p = x.subs(x,pi) - q = x.subs(x,Sym(1)/2) - r = x.subs(x,1.2) - z = x.subs(x,1) - @test isa(N(p), Float64) -##XXX @test isa(N(p, 60), BigFloat) - @test isa(p.evalf(), Sym) - @test isa(N(x), Sym) - @test isa(N(q), Rational) - @test isa(N(r), Float64) - @test isa(N(z), Integer) - - ## method calls via getproperty - p = (x-1)*(x-2) - @test sympy.roots(p) == Dict{Any,Any}(Sym(1) => 1, Sym(2)=> 1) # sympy.roots - p = sympy.Poly(p, x) - @test p.coeffs() == Any[1,-3,2] # p.coeffs - - ## algebra - @test expand((x + 1)*(x + 2)) == x^2 + 3x + 2 # v0.7 deprecates expand, in v1.0 this is fine w/o qualifacation - x1 = (x + 1)*(x + 2) - @test expand(x1) == x^2 + 3x + 2 - @test sympy.expand_trig(sin(2x)) == 2sin(x)*cos(x) - - ## math functions - u = abs(x^2 - 2) - @test u(x=>0) == 2 - u = min(x, x^2, x^3, x^4) - @test u(x=>2) == 2 - @test u(x=>1//2) == 1//2^4 - - ## solve - x,y,a = symbols("x,y,a", real=true) - solve(x^2 - 2x) - solve(x^2 - 2a, x) - solve(x^2 - 2a, a) - solve(Lt(x-2, 0)) - solve( x-2 ≪ 0) - exs = [x-y-1, x+y-2] - di = solve(exs) - @test di[x] == 3//2 - @test map(ex -> subs(ex, di), exs) == [0,0] - solve([x-y-a, x+y], [x,y]) - - ## linsolve - M=Sym[1 2 3; 2 3 4] - as = linsolve(M, x, y) - @test length(elements(as)) == 1 - @vars a b; eqs = (a*x+2y-3, 2b*x + 3y - 4) - as = linsolve(eqs, x, y) - @test length(elements(as)) == 1 - - ## nsolve -- not method for arrays, issue 268 - ## Add interface for nsolve - @vars z1 z2z1 positive=true - #@test_throws MethodError nsolve([z1^2-1, z1+z2z1-2], [z1,z2z1], [1,1]) # non symbolic first argument - @test all(N.(nsolve([z1^2-1, z1+z2z1-2], [z1,z2z1], (1,1))) .≈ [1.0, 1.0]) - - - - - ## issue 355: direct definition of LinearAlgebra.:\ - @test_throws SingularException Sym[1 1; 1 1] \ [1, 2] - @vars a b c d e f - A, b= [a b; c d], [e, f] - x = A \ b - @test simplify.(A*x-b) == [0,0] - out = Sym[1 1; 1 1] \ [1,1] - u = free_symbols(out)[1] - @test out == [1-u,u] - - - # Just a made-up example to test if manageable - @vars a1 a2 - n = 7 - A = diagm(0 => ones(Int, n), 1 => fill(a1, n-1), 2 => fill(1, n-2), -1 => fill(a2, n-1)) - b = Vector{Sym}(1:n) - x = A \ b - @test length(free_symbols(x)) == 2 - - - ## limits - @vars x - @test limit(x -> sin(x)/x, 0) == 1 - @test limit(sin(x)/x, x, 0) |> float == 1 - @test limit(sin(x)/x, x => 0) == 1 - @vars x h - out = limit((sin(x+h) - sin(x))/h, h, 0) - @test (out.replace(x, pi) |> float) == -1.0 - - - ## diff - diff(sin(x), x) - out = diff(sin(x), x, 2) - @test abs((out.replace(x, pi/4) |> float) - - sin(pi/4)) < sqrt(eps()) - - # partial derivatives - @vars x y - @test diff(x^2 + x*y^2, x, 1) == 2x + y^2 - - t = symbols("t", real=true) # vector-valued functions - r1(t) = [sin(t), cos(t), t] - u = r1(t) - kappa = norm(diff.(u) × diff.(u,t,2)) / norm(diff.(u))^3 |> simplify - @test convert(Rational,kappa) == 1//2 - - u = SymFunction("u") - eqn = Eq(x^2 + u(x)^2, x^3 - u(x)) - # diff(eqn) doesn't evaluate over Eq: - @test func(eqn)(diff.(args(eqn))...) == Eq(2x + 2u(x) * diff(u(x),x), 3x^2 - diff(u(x),x)) - - ## integrate - @test integrate(sin(x)) == -cos(x) - @test integrate(sin(x), (x, 0, pi)) == 2.0 - a, b, t = symbols("a, b, t") - @test integrate(sin(x), (x, a, b)) == cos(a) - cos(b) - @test integrate(sin(x), (x, a, b)).replace(a, 0).replace(b, pi) == 2.0 - @test integrate(sin(x) * DiracDelta(x)) == sin(Sym(0)) - @test integrate(Heaviside(x), (x, -1, 1)) == 1 - curv = sympy.Curve([exp(t)-1, exp(t)+1], (t, 0, log(Sym(2)))) - @test line_integrate(x + y, curv, [x,y]) == 3 * sqrt(Sym(2)) - - - ## summation - summation(1/x^2, (x, 1, 10)) - out = summation(1/x^2, (x, 1, 10)) - out1 = sum([1//x^2 for x in 1:10]) - @test round(Integer, out.p) == out1.num - @test round(Integer, out.q) == out1.den - - - ## Ops - s = 3 - x = Sym("x") - v = [x, 1] - rv = [x 1] - a = [x 1; 1 x] - b = [x 1 2; 1 2 x] - - DIMERROR = DimensionMismatch - DimensionOrMethodError = Union{MethodError, DimensionMismatch} - ## scalar, [vector, matrix] - @test s .+ v == [x+3, 4] - @test v .+ s == [x+3, 4] - @test s .+ rv == [x+3 4] - @test rv .+ s == [x+3 4] - @test s .+ a == [x+3 4; 4 x+3] - @test a .+ s == [x+3 4; 4 x+3] - - @test s .- v == [3-x, 2] - @test v .- s == [x-3, -2] - @test s .- rv == [3-x 2] - @test rv .- s == [x-3 -2] - @test s .- a == [3-x 2; 2 3-x] - @test a .- s == [x-3 -2; -2 x-3] - - 2v - 2rv - 2a - s .* v - v .* s - s .* rv - rv .* s - s .* a - a .* s - - ## s / v ## broadcasts s Depreacated - @test s ./ v == [3/x, 3] - @test v / s == [x/3, Sym(1)/3] - @test v .\ s == s ./ v - @test s \ v == v / s - ## s / rv ## broadcasts s Deprecated - @test s ./ rv == [3/x 3] - @test rv / s == [x/3 Sym(1)/3] - @test rv .\ s == s ./ rv - @test s \ rv == rv / s - ## s / a ## broadcasts s Deprecated - @test s ./ a == [3/x 3; 3 3/x] - @test a / s == [x/3 Sym(1)/3; Sym(1)/3 x/3] - @test a .\ s == s ./ a - @test s \ a == a / s - - @test_throws MethodError s ^ v ## error - @test s .^ v == [3^x, 3] - @test_throws DimensionOrMethodError v ^ s ## error - v .^ s - @test_throws MethodError s ^ rv ## error - s .^ rv - @test_throws DimensionMismatch rv ^ s ## error - rv .^ s - @test_throws MethodError s ^ a ## error - s .^ a - a ^ s - a .^ s - - - ## vector vector - @test v .+ v == 2v - ##@test_throws MethodError v .+ rv ## no longer an error, broadcase - @test v .- v == [0, 0] - @test_throws DIMERROR v - rv - @test_throws DimensionOrMethodError v * v ## error - @test v .* v == [x^2,1] - @test dot(v, v) == 1 + conj(x)*x - v * rv - rv * v ## 1x2 2 x 1 == 1x1 - v .* rv ## XXX ?? should be what? -- not 2 x 2 - rv .* v ## XXX ditto - ## @test_throws MethodError v / v ## error - v ./ v ## ones() - v .\ v - ## @test_throws MethodError v / rv ## error - v ./ rv ## ?? - rv .\ v - @test_throws MethodError v ^ v ## error - v .^ v - @test_throws MethodError v ^ rv ## error - v .^ rv ## ?? - - - ## vector matrix - @test_throws DIMERROR v + a ## error (Broadcast?) - @test_throws DIMERROR a + v ## error - v .+ a ## broadcasts - a .+ v - @test_throws DIMERROR v - a ## error - v .- a - @test_throws DimensionMismatch v * a ## error - v .* a - #@test_throws MethodError v / a ## error - v ./ a - a .\ v - @test_throws MethodError v ^ a ## error - v .^ a - v - ## matrix matrix - a + a - @test_throws DIMERROR a + b ## error - a + 2a - a - a - @test_throws DIMERROR a - b ## error - a * a - a .* a - a * b ## 2x2 * 2*3 -- 2x3 - @test_throws DIMERROR a .* b ## error -- wrong size - #@test_throws MethodError a / a - a ./ a ## ones - a .\ a - ##@test_throws MethodError a / b ## error - @test_throws DIMERROR a ./ b ## error - @test_throws DIMERROR b .\ a ## error - @test_throws MethodError a ^ a ## error - a .^ a - @test_throws MethodError a ^ b ## error - @test_throws DIMERROR a .^ b ## error - - - ## Number theory - #@test isprime(100) == isprime(Sym(100)) - #@test factorint(Sym(100)) == factor(100) - @test prime(Sym(100)) == 541 - @test multiplicity(Sym(10), 100) == 2 - - - ## polynomials - @vars x y - f1 = 5x^2 + 10x + 3 - g1 = 2x + 2 - q,r = sympy.div(f1,g1, domain="QQ") # use sympy.div to dispatch; o/w we can't disambiguate div(Sym(7), 5)) to do integer division - @test r == Sym(-2) - @test simplify(q*g1 + r - f1) == Sym(0) - ## sympy.interpolate as first arg is not symbolic - @test sympy.interpolate([1,2,4], x) == sympy.interpolate(collect(zip([1,2,3], [1,2,4])), x) - @test sympy.interpolate(collect(zip([-1,0,1], [0,1,0])), x) == 1 - x^2 - - ## piecewise - x = Sym("x") - # sympy.Piecewise is a FunctionClass, we qualify, as args not Symbolic - p = sympy.Piecewise((x, Ge(x,0)), (0, Lt(x,0)), (1, Eq(x,0))) - ## using infix \ll, \gt, \Equal - p = sympy.Piecewise((x, (x ≫ 0)), (0, x ≪ 0), (1, x ⩵ 0)) - @test p.subs(x,2) == 2 - @test p.subs(x,-1) == 0 - @test p.subs(x,0) == 1 - - ## if VERSION < v"0.7.0-" # ifelse changed - ## u = ifelse(Lt(x, 0), "neg", ifelse(Gt(x, 0), "pos", "zero")) - ## @test u.subs(u,x,-1) == Sym("neg") - ## @test subs(u,x, 0) == Sym("zero") - ## @test subs(u,x, 1) == Sym("pos") - ## end - p = sympy.Piecewise((-x, x ≪ 0), (x, x ≧ 0)) - - - ## relations - x,y=symbols("x, y") - ex = Eq(x^2, x) - @test ex.lhs() == x^2 - @test ex.rhs() == x - @test args(ex) == (x^2, x) - - # alternative operators - for (ex1,ex2) ∈ ((Eq(x^2, x), x^2 ⩵ x), - (Eq(x^2, x), x^2 ~ x), - (Lt(x^2, x), x^2 ≪ x), - (Le(x^2, x), x^2 ≦ x), - (Ge(x^2, x), x^2 ≧ x), - (Gt(x^2, x), x^2 ≫ x)) - @test lhs(ex1) == lhs(ex2) - @test rhs(ex1) == rhs(ex2) - end - - ## mpmath functions -# if @isdefined mpmath - if isdefined(SymPy, :mpmath) - x = Sym("x") - Sym(big(2)) - Sym(big(2.0)) # may need mpmath (e.g., conda install mpmath) - -# VersionNumber(sympy.__version__) != v"1.9" && (@test limit(besselj(Sym(1),1/x), x, 0) == Sym(0)) - complex(N(SymPy.mpmath.hankel2(2, pi))) - SymPy.mpmath.bei(2, 3.5) - SymPy.mpmath.bei(1+im, 2+3im) - end - - ## Assumptions - @test ask(𝑄.even(Sym(2))) == true - @test ask(𝑄.even(Sym(3))) == false - @test ask(𝑄.nonzero(Sym(3))) == true - @vars x_real real=true - @vars x_real_positive real=true positive=true - @test ask(𝑄.positive(x_real)) == nothing - @test ask(𝑄.positive(x_real_positive)) == true - @test ask(𝑄.nonnegative(x_real^2)) == true - @test ask(𝑄.upper_triangular([x_real 1; 0 x_real])) == true - @test ask(𝑄.positive_definite([x_real 1; 1 x_real])) == nothing - - - ## sets - s = sympy.FiniteSet("H","T") - s1 = s.powerset() - VERSION >= v"0.4.0" && @test length(collect(convert(Set, s1))) == length(collect(s1.__pyobject__)) - a, b = sympy.Interval(0,1), sympy.Interval(2,3) - @test a.is_disjoint(b) == true - @test a.union(b).measure() == 2 - - - - ## test cse output - @test cse(x) == (Any[], Sym[x]) - @test sympy.cse([x]) == (Any[], [reshape([x],1,1)]) - @test sympy.cse([x, x]) == (Any[], [reshape([x,x], 2, 1)] ) - @test sympy.cse([x x; x x]) == (Any[], [[x x; x x]]) - - ## sympy"..."(...) - ## removed - #@vars x - #@test sympy"sin"(1) == sin(Sym(1)) -end - -@testset "Syms macro" begin - @syms u - @test isa(u, Sym) - - ret = @syms a, b, c - @test isa(ret, Tuple{Sym, Sym, Sym}) - - @syms x::(real,positive)=>"x₀", y, z::complex, n::integer - @test isa(x, Sym) - @test ask(And(𝑄.real(x), 𝑄.positive(x))) - @test string(x) == "x₀" - - @test isa(y, Sym) - - @test isa(z, Sym) - @test ask(𝑄.complex(z)) - - @test isa(n, Sym) - @test ask(𝑄.integer(n)) - - @syms f()::(real, positive), g(), h()::complex=>"h̄" - @test isa(f, SymFunction) - @test ask(And(𝑄.real(f(x)), 𝑄.positive(f(x)))) - - @test isa(g, SymFunction) - - @test isa(h, SymFunction) - @test ask(𝑄.complex(h(x))) - @test string(h) == "h̄" - - @syms X[1:20] - @test isa(X, Vector{Sym}) - @test size(X) == (20,) - @test string(X[11]) == "X₁₁" - - @syms bigy[1:5]=>"Y" - @test string(bigy[3]) == "Y₃" - - @syms Z[1:5, 1:6] - @test isa(Z, Matrix{Sym}) - @test size(Z) == (5, 6) - @test string(Z[2,4]) == "Z₂_₄" - - @syms F[1:2](), G()[1:2] - @test isa(F, Vector{SymFunction}) - @test isa(G, Vector{SymFunction}) - - @syms WOW[1:3, 1:2:4]()::(real, positive)=>"f" - @test isa(WOW, Matrix{SymFunction}) - @test size(WOW) == (3, 2) - @test ask(And(𝑄.real(WOW[1,2](x)), 𝑄.positive(WOW[1,2](x)))) - @test string(WOW[1,2]) == "f₁_₃" -end - -@testset "SymFunctions" begin - @syms x::real y::real - @symfuns f g real=true - - @test isreal(f(x)) - @test isreal(g(y)) - - @symfuns h real=true positive=true - @test h(x)>0 -end - -@testset "Fix past issues" begin - @vars x y z - ## Issue # 56 - @test Sym(1+2im) == 1+2IM - @test convert(Sym, 1 + 2im) == 1 + 2IM - - - ## Issue #59 - cse(sin(x)+sin(x)*cos(x)) - sympy.cse([sin(x), sin(x)*cos(x)]) - sympy.cse( [sin(x), sin(x)*cos(x), cos(x), sin(x)*cos(x)]) - - ## Issue #60, lambidfy - x, y = symbols("x, y") - lambdify(sin(x)*cos(2x) * exp(x^2/2)) - fn = lambdify(sin(x)*asin(x)*sinh(x)); fn(0.25) - lambdify(real(x)*imag(x)) - @test lambdify(Min(x,y))(3,2) == 2 - - ex = 2 * x^2/(3-x)*exp(x)*sin(x)*sind(x) - fn = lambdify(ex); map(fn, rand(10)) - ex = x - y - @test lambdify(ex, (x,y))(3,2) == 1 - - Indicator(x, a, b) = sympy.Piecewise((1, Lt(x, b) & Gt(x,a)), (0, Le(x,a)), (0, Ge(x,b))) - i = Indicator(x, 0, 1) - u = lambdify(i) - @test u(.5) == 1 - @test u(1.5) == 0 - -# i2 = SymPy.lambdify_expr(x^2,name=:square) -# @test i2.head == :function -# @test i2.args[1].args[1] == :square - ## @test i2.args[2] == :(x.^2) # too fussy - - - ## issue #67 - @test N(Sym(4)/3) == 4//3 - @test N(convert(Sym, 4//3)) == 4//3 - - ## issue #71 - @test log(Sym(3), Sym(4)) == log(Sym(4)) / log(Sym(3)) - - ## issue #103 # this does not work for `x` (which has `classname(x) == "Symbol"`), but should work for other expressions - for ex in (sin(x), x*y^2*x, sqrt(x^2 - 2y)) - @test SymPy.Introspection.func(ex)(SymPy.Introspection.args(ex)...) == ex - end - - ## properties (Issue #119) - @test (sympify(3).is_odd) == true - @test sympy.Poly(x^2 -2, x).is_monic == true - - ## test round (Issue #153) - y = Sym(eps()) - @test round(N(y), digits=5) == 0 - @test round(N(y), digits=16) != 0 - - ## lambdify over a matrix #218 - @vars x y - s = [1 0; 0 1] - @test lambdify(x*s)(2) == 2 * s - U = [x-y x+y; x+y x-y] - @test lambdify(U, [x,y])(2,4) == [-2 6;6 -2] - @test lambdify(U, [y,x])(2,4) == [ 2 6;6 2] - - @test eltype(lambdify([x 0; 1 x])(0)) <: Integer - @test eltype(lambdify([x 0; 1 x], T=Float64)(0)) == Float64 - - # issue 222 type of eigvals - A = [Sym("a") 1; 0 1] - @test typeof(eigvals(A)) <: Vector{Sym} - - # issue 231 Q.complex - @vars x_maybecomplex - @vars x_imag imaginary=true - @test ask(𝑄.complex(x_maybecomplex)) == nothing - @test ask(𝑄.complex(x_imag)) == true - - # issue 242 lambdify and conjugate - @vars x - expr = conjugate(x) - fn = lambdify(expr) - @test fn(1.0im) == 0.0 - 1.0im - fn = lambdify(expr, use_julia_code=true) - @test fn(1.0im) == 0.0 - 1.0im - - # issue 245 missing sincos - @test applicable(sincos, x) - @test sincos(x)[1] == sin(x) - - # issue 256 det - @vars rho phi theta real=true - xs = [rho*cos(theta)*sin(phi), rho*sin(theta)*sin(phi), rho*cos(phi)] - J = [diff(x, u) for x in xs, u in (rho, phi, theta)] - J.det() - - # issue #273 x[i] - x = sympy.IndexedBase("x") - i,j = sympy.symbols("i j", integer=True) - @test x[i] == PyCall.py"$x[$i]" - - - # issue 298 lambdify for results of dsolve - @vars t - F = SymFunction("F") - diffeq = diff(F(t),t) - 3*F(t) - res = dsolve(diffeq, F(t), ics=Dict(F(0) => 2)) # 2exp(3t) - @test lambdify(res)(1) ≈ 2*exp(3*1) - - # issue 304 wrong values for sind, ... - a = Sym(45) - @test sind(a) == sin(PI/4) - - # issue #319 with use of Dummy, but - # really a lambdify issue - dummy = sympy.Dummy - # Symbolic differentiation of functions - function D(f) - x = dummy("x") - lambdify(diff.(f(x), x), (x,)) - end - @test D(t -> t^2)(1) == 2 - - # issue #320 with integrate(f) when - # f is consant - @test integrate(x -> 1, 0, 1) == 1 - @test limit(x->1, x, 0) == 1 - @test diff(x->1) == 0 - - ## Issue 324 with inference of matrix operations - A = fill(Sym("a"), 2, 2) - @test eltype(A*A) == Sym - @test eltype(A*ones(2,2)) == Sym - @test eltype(A*Diagonal([1,1])) == Sym - VERSION >= v"1.2.0" && @test eltype(A * I(2)) == Sym - - ## Issue 328 with E -> e - @vars x - ex = 3 * sympy.E * x - fn = lambdify(ex) - @test fn(1) ≈ 3*exp(1) * 1 - - ## Issue 332 with `abs2` - @vars x real=true - @test abs2(x) == x*x - @vars x - @test abs2(x) == x*conj(x) - - ## Issue 376 promote to Sym Before pycall - f(x) = x^2 + 1 +log(abs( 11*x-15 ))/99 - @test limit(f, 15//11) == limit(f(x), x, 15//11) == limit(f(x), x=>15//11) == -oo - - ## Issue #390 on div (__div__ was depracated, use __truediv__) - @test Sym(2):-Sym(2):-Sym(2) |> collect == [2, 0, -2] - - ## Lambda function to create a lambda - @vars x - ex = x^2 - 2 - fn1 = Lambda(x, ex) - fn2 = lambdify(ex) - @test fn1(3) == fn2(3) - - ## issue 402 with lamdify and Order - @vars x - t = series(exp(x), x, 0, 2) - @test lambdify(t)(1/2) == 1 + 1/2 - - ## issue #411 with Heaviside - @vars t - u = Heaviside(t) - λ = lambdify(u) - @test all((iszero(λ(-1)), isone(λ(1)))) - if VersionNumber(sympy.__version__) >= v"1.9" - @test λ(0) == 1//2 - else - @test isnan(λ(0)) - end - u = Heaviside(t, 1) - λ = lambdify(u) - @test all((iszero(λ(-1)), isone(λ(0)), isone(λ(1)))) - - ## issue #295 with piecewise function - @syms x - p = sympy.Piecewise((x,Gt(x,0)), (x^2, Le(x,0))) - @test lambdify(p)(2) == 2 - @test lambdify(p)(-2) == (-2)^2 - - ## Issue #474 with rational powers - @syms x - ex = 1/(2*x*sympy.pi^(1//4)) - @test N(ex(2)) ≈ lambdify(ex)(2) - - ## Issue catch all for N; floats only - @syms x - ex = integrate(sqrt(1 + (1/x)^2), (x, 1/sympy.E, sympy.E)) - @test N(ex) ≈ 3.196198513599507 - - ## Issue #477 non typestable ops - @test Base.Broadcast.combine_eltypes(+, (zero(Sym), zero(Sym))) == Sym - @test Base.Broadcast.combine_eltypes(-, (zero(Sym), zero(Sym))) == Sym - @test Base.Broadcast.combine_eltypes(*, (zero(Sym), zero(Sym))) == Sym - @test Base.Broadcast.combine_eltypes(/, (zero(Sym), zero(Sym))) == Sym - @test Base.Broadcast.combine_eltypes(^, (zero(Sym), zero(Sym))) == Sym - -end - -@testset "generic programming, issue 223" begin - # arose in issue 223 - @vars xreal real=true - @vars xcomplex - zreal = sympify(1) - zcomplex = sympify(1) + sympify(2)*IM - - @test isreal(xreal) # is_real(xreal) is also true, but xreal is Sym, not a Julia object - @test !isreal(xcomplex) # is_real(xcomplex) is nothing - @test isreal(zreal) - @test !isreal(zcomplex) - - # conversions - @test complex(xreal) == xreal - @test complex(xreal, xreal) == xreal + IM*xreal - @test complex(xcomplex) != xcomplex - @test complex(zreal) == zreal - @test complex(zreal) !== zreal # Complex{Int} !== Sym - @test complex(zcomplex) == zcomplex - @test complex(zcomplex) !== zcomplex - - ## issue 284 N(PI,50) - @test N(PI, 50) ≈ pi - @test length(string(N(PI,50))) == 2 + 50 - ## issue 284 lambdify of Pi - if VersionNumber(sympy.__version__) < v"1.11" - mpi = SymPy.PyCall.pyimport("sympy.parsing.mathematica")."mathematica"("Pi") - else - mpi = SymPy.PyCall.pyimport("sympy.parsing.mathematica")."parse_mathematica"("Pi") - end - @test SymPy.walk_expression(mpi) == :pi - @test lambdify(PI^4*xreal)(256) == 256 * pi^4 - - - ## Issue 351 booleans and arithmetic operations - @test Sym(1) + true == Sym(2) == true + Sym(1) - @test Sym(1) - true == Sym(0) == true - Sym(1) - @test Sym(1) * true == Sym(1) == true * Sym(1) - @test Sym(1) / true == Sym(1) == true / Sym(1) - @test true^Sym(1) == Sym(1) == Sym(1)^true - - ## issue with `pycall_hasproperty` and nothing values. - @test !SymPy.is_rational(Sym(2.5)) - - ## Issue #405 with ambigous methods - @vars α - M = SymMatrix([1 2; 3 4]) - @test α * M == M * α - @test 2 * M == M * 2 - @test isa(M/α, SymMatrix) - @test isa(α * inv(M), SymMatrix) - - ## issue #408 with inv - @vars n integer=true positive=true - A = sympy.MatrixSymbol("A", n, n) - @test inv(A) == A.I - - # ceil broken - @syms x - @test limit(ceil(x), x=>0, dir="+") != limit(ceil(x), x=>0, dir="-") - @test limit(floor(x), x=>0, dir="+") != limit(floor(x), x=>0, dir="-") - - ## Issue #433 add sympy docstrings, clean up docstring - sprint(io -> show(io, SymPy.Doc(:sin))) - - ## Issue 456 use free_symbols() not .free_symbols in call - @syms x y - df = sympy.diff(sin(y-x),x) - @test df(x, PI/2) == -sin(x) - -end - -@test SymPy.convert_expr(sympy.Indexed(sympy.IndexedBase(:x), 1, -2)) == :(x[1, -2])