From 8141bb63ac908014fe5b6bc7ebed4d2889c014d4 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Fri, 18 Nov 2022 03:04:57 +0000 Subject: CTAN sync 202211180304 --- .../biblatex-contrib/biblatex-chicago/README | 4 +- .../biblatex-contrib/biblatex-chicago/RELEASE | 9 + .../biblatex-chicago/doc/biblatex-chicago.pdf | Bin 1268516 -> 1245018 bytes .../biblatex-chicago/doc/biblatex-chicago.tex | 126 +- .../biblatex-chicago/doc/cms-dates-intro.pdf | Bin 256022 -> 233089 bytes .../biblatex-chicago/doc/cms-dates-intro.tex | 10 +- .../biblatex-chicago/doc/cms-dates-sample.pdf | Bin 205707 -> 182889 bytes .../biblatex-chicago/doc/cms-dates-sample.tex | 8 +- .../biblatex-chicago/doc/cms-legal-sample.pdf | Bin 86325 -> 64860 bytes .../biblatex-chicago/doc/cms-legal-sample.tex | 8 +- .../biblatex-chicago/doc/cms-noteref-demo.pdf | Bin 109286 -> 87995 bytes .../biblatex-chicago/doc/cms-noteref-demo.tex | 8 +- .../biblatex-chicago/doc/cms-notes-intro.pdf | Bin 286590 -> 280404 bytes .../biblatex-chicago/doc/cms-notes-intro.tex | 8 +- .../biblatex-chicago/doc/cms-notes-sample.pdf | Bin 180012 -> 158653 bytes .../biblatex-chicago/doc/cms-notes-sample.tex | 8 +- .../biblatex-chicago/doc/cms-trad-appendix.pdf | Bin 87269 -> 68316 bytes .../biblatex-chicago/doc/cms-trad-appendix.tex | 8 +- .../biblatex-chicago/doc/cms-trad-sample.pdf | Bin 180128 -> 157473 bytes .../biblatex-chicago/doc/cms-trad-sample.tex | 8 +- .../biblatex-chicago/latex/chicago-authordate.bbx | 40 +- .../latex/chicago-dates-common.cbx | 78 +- .../latex/chicago-dates-common16.cbx | 17 +- .../biblatex-chicago/latex/chicago-notes.bbx | 40 +- .../biblatex-chicago/latex/chicago-notes.cbx | 75 +- .../biblatex-chicago/latex/chicago-notes16.bbx | 6 +- .../biblatex-chicago/latex/chicago-notes16.cbx | 17 +- .../biblatex-chicago/latex/cmsdocs.sty | 5 +- macros/latex/contrib/lgrmath/README | 48 + macros/latex/contrib/lgrmath/lgrmath.dtx | 1263 ++++++++++++++++++++ macros/latex/contrib/lgrmath/lgrmath.pdf | Bin 0 -> 125218 bytes macros/latex/contrib/memoir/README | 26 + macros/latex/contrib/memoir/memman.pdf | Bin 2837551 -> 2837566 bytes macros/latex/contrib/memoir/memoir.dtx | 41 +- macros/latex/contrib/memoir/mempatch.dtx | 68 +- macros/latex/contrib/udes-genie-these/README.md | 35 +- macros/latex/contrib/udes-genie-these/document.tex | 50 +- .../contrib/udes-genie-these/udes-genie-these.dtx | 76 +- .../contrib/udes-genie-these/udes-genie-these.ins | 2 +- .../contrib/udes-genie-these/udes-genie-these.pdf | Bin 124734 -> 129528 bytes macros/luatex/generic/luakeys/README.md | 18 + macros/luatex/generic/luakeys/luakeys-debug.sty | 2 +- macros/luatex/generic/luakeys/luakeys-doc.pdf | Bin 276812 -> 283942 bytes macros/luatex/generic/luakeys/luakeys-doc.tex | 130 +- macros/luatex/generic/luakeys/luakeys.lua | 118 +- macros/luatex/generic/luakeys/luakeys.sty | 2 +- macros/luatex/latex/luacas/README.md | 11 + macros/luatex/latex/luacas/doc/appendix/luacas.dat | 2 +- .../latex/luacas/doc/appendix/versionhistory.tex | 22 + macros/luatex/latex/luacas/doc/luacas.pdf | Bin 758211 -> 760471 bytes macros/luatex/latex/luacas/doc/luacas.tex | 10 +- macros/luatex/latex/luacas/tex/_lib/inspect.lua | 335 ------ .../latex/luacas/tex/_lib/luacas-inspect.lua | 335 ++++++ .../latex/luacas/tex/_lib/luacas-pepperfish.lua | 629 ++++++++++ .../luatex/latex/luacas/tex/_lib/luacas-table.lua | 185 +++ macros/luatex/latex/luacas/tex/_lib/pepperfish.lua | 629 ---------- macros/luatex/latex/luacas/tex/_lib/table.lua | 185 --- macros/luatex/latex/luacas/tex/algebra/_init.lua | 24 - .../latex/luacas/tex/algebra/absexpression.lua | 80 -- .../luatex/latex/luacas/tex/algebra/equation.lua | 183 --- .../latex/luacas/tex/algebra/euclideandomain.lua | 63 - .../luacas/tex/algebra/factorialexpression.lua | 102 -- macros/luatex/latex/luacas/tex/algebra/field.lua | 69 -- macros/luatex/latex/luacas/tex/algebra/integer.lua | 952 --------------- .../luacas/tex/algebra/integerquotientring.lua | 197 --- .../luatex/latex/luacas/tex/algebra/logarithm.lua | 186 --- .../luacas/tex/algebra/luacas-absexpression.lua | 80 ++ .../luacas/tex/algebra/luacas-algebra_init.lua | 24 + .../latex/luacas/tex/algebra/luacas-equation.lua | 183 +++ .../luacas/tex/algebra/luacas-euclideandomain.lua | 63 + .../tex/algebra/luacas-factorialexpression.lua | 102 ++ .../latex/luacas/tex/algebra/luacas-field.lua | 69 ++ .../latex/luacas/tex/algebra/luacas-integer.lua | 952 +++++++++++++++ .../tex/algebra/luacas-integerquotientring.lua | 197 +++ .../latex/luacas/tex/algebra/luacas-logarithm.lua | 186 +++ .../luacas/tex/algebra/luacas-polynomialring.lua | 860 +++++++++++++ .../latex/luacas/tex/algebra/luacas-rational.lua | 241 ++++ .../latex/luacas/tex/algebra/luacas-ring.lua | 326 +++++ .../luacas/tex/algebra/luacas-rootexpression.lua | 135 +++ .../luacas/tex/algebra/luacas-sqrtexpression.lua | 196 +++ .../luacas/tex/algebra/luacas-trigexpression.lua | 355 ++++++ .../latex/luacas/tex/algebra/polynomialring.lua | 860 ------------- .../algebra/polynomialring/berlekampfactoring.lua | 187 --- .../tex/algebra/polynomialring/decomposition.lua | 93 -- .../polynomialring/luacas-berlekampfactoring.lua | 187 +++ .../polynomialring/luacas-decomposition.lua | 93 ++ .../polynomialring/luacas-zassenhausfactoring.lua | 220 ++++ .../algebra/polynomialring/zassenhausfactoring.lua | 220 ---- .../luatex/latex/luacas/tex/algebra/rational.lua | 241 ---- macros/luatex/latex/luacas/tex/algebra/ring.lua | 326 ----- .../latex/luacas/tex/algebra/rootexpression.lua | 135 --- .../latex/luacas/tex/algebra/sqrtexpression.lua | 196 --- .../latex/luacas/tex/algebra/trigexpression.lua | 355 ------ macros/luatex/latex/luacas/tex/calculus/_init.lua | 6 - .../luacas/tex/calculus/derivativeexpression.lua | 265 ---- .../latex/luacas/tex/calculus/diffexpression.lua | 190 --- .../luacas/tex/calculus/integralexpression.lua | 934 --------------- .../luacas/tex/calculus/luacas-calculus_init.lua | 6 + .../tex/calculus/luacas-derivativeexpression.lua | 265 ++++ .../luacas/tex/calculus/luacas-diffexpression.lua | 190 +++ .../tex/calculus/luacas-integralexpression.lua | 934 +++++++++++++++ macros/luatex/latex/luacas/tex/core/_init.lua | 16 - .../latex/luacas/tex/core/atomicexpression.lua | 70 -- .../latex/luacas/tex/core/binaryoperation.lua | 800 ------------- .../luacas/tex/core/binaryoperation/difference.lua | 14 - .../tex/core/binaryoperation/luacas-difference.lua | 14 + .../tex/core/binaryoperation/luacas-power.lua | 169 +++ .../tex/core/binaryoperation/luacas-product.lua | 231 ++++ .../tex/core/binaryoperation/luacas-quotient.lua | 14 + .../luacas/tex/core/binaryoperation/luacas-sum.lua | 226 ++++ .../luacas/tex/core/binaryoperation/power.lua | 169 --- .../luacas/tex/core/binaryoperation/product.lua | 231 ---- .../luacas/tex/core/binaryoperation/quotient.lua | 14 - .../latex/luacas/tex/core/binaryoperation/sum.lua | 226 ---- .../latex/luacas/tex/core/compoundexpression.lua | 52 - .../latex/luacas/tex/core/constantexpression.lua | 62 - macros/luatex/latex/luacas/tex/core/expression.lua | 280 ----- .../latex/luacas/tex/core/functionexpression.lua | 295 ----- .../luacas/tex/core/luacas-atomicexpression.lua | 70 ++ .../luacas/tex/core/luacas-binaryoperation.lua | 800 +++++++++++++ .../luacas/tex/core/luacas-compoundexpression.lua | 52 + .../luacas/tex/core/luacas-constantexpression.lua | 62 + .../latex/luacas/tex/core/luacas-core_init.lua | 16 + .../latex/luacas/tex/core/luacas-expression.lua | 280 +++++ .../luacas/tex/core/luacas-functionexpression.lua | 295 +++++ .../luacas/tex/core/luacas-symbolexpression.lua | 132 ++ .../latex/luacas/tex/core/symbolexpression.lua | 132 -- macros/luatex/latex/luacas/tex/luacas.sty | 6 +- .../latex/luacas/tex/test/calculus/derivatives.lua | 30 - .../latex/luacas/tex/test/calculus/integrals.lua | 54 - .../tex/test/calculus/luacas-derivatives.lua | 30 + .../luacas/tex/test/calculus/luacas-integrals.lua | 54 + .../luacas/tex/test/expressions/autosimplify.lua | 199 --- .../latex/luacas/tex/test/expressions/collect.lua | 26 - .../luacas/tex/test/expressions/equations.lua | 17 - .../luacas/tex/test/expressions/functions.lua | 17 - .../luacas/tex/test/expressions/logarithms.lua | 26 - .../tex/test/expressions/luacas-autosimplify.lua | 199 +++ .../luacas/tex/test/expressions/luacas-collect.lua | 26 + .../tex/test/expressions/luacas-equations.lua | 17 + .../tex/test/expressions/luacas-functions.lua | 17 + .../tex/test/expressions/luacas-logarithms.lua | 26 + .../test/expressions/luacas-rationalexponent.lua | 15 + .../tex/test/expressions/luacas-simplify.lua | 22 + .../tex/test/expressions/luacas-substitute.lua | 10 + .../tex/test/expressions/rationalexponent.lua | 15 - .../latex/luacas/tex/test/expressions/simplify.lua | 22 - .../luacas/tex/test/expressions/substitute.lua | 10 - macros/luatex/latex/luacas/tex/test/helper.lua | 322 ----- .../luatex/latex/luacas/tex/test/luacas-helper.lua | 322 +++++ .../luatex/latex/luacas/tex/test/luacas-main.lua | 154 +++ .../luatex/latex/luacas/tex/test/luacas-parser.lua | 323 +++++ macros/luatex/latex/luacas/tex/test/main.lua | 154 --- macros/luatex/latex/luacas/tex/test/parser.lua | 323 ----- .../test/polynomials/luacas-partialfractions.lua | 12 + .../tex/test/polynomials/luacas-polynomial.lua | 153 +++ .../tex/test/polynomials/luacas-polynomialmod.lua | 76 ++ .../luacas/tex/test/polynomials/luacas-roots.lua | 43 + .../tex/test/polynomials/partialfractions.lua | 12 - .../luacas/tex/test/polynomials/polynomial.lua | 153 --- .../luacas/tex/test/polynomials/polynomialmod.lua | 76 -- .../latex/luacas/tex/test/polynomials/roots.lua | 43 - .../latex/luacas/tex/test/rings/conversion.lua | 273 ----- .../luacas/tex/test/rings/luacas-conversion.lua | 273 +++++ .../tex/test/rings/luacas-modulararithmetic.lua | 20 + .../latex/luacas/tex/test/rings/luacas-number.lua | 118 ++ .../luacas/tex/test/rings/modulararithmetic.lua | 20 - .../luatex/latex/luacas/tex/test/rings/number.lua | 118 -- 168 files changed, 13282 insertions(+), 11699 deletions(-) create mode 100644 macros/latex/contrib/lgrmath/README create mode 100644 macros/latex/contrib/lgrmath/lgrmath.dtx create mode 100644 macros/latex/contrib/lgrmath/lgrmath.pdf create mode 100644 macros/luatex/latex/luacas/doc/appendix/versionhistory.tex delete mode 100644 macros/luatex/latex/luacas/tex/_lib/inspect.lua create mode 100644 macros/luatex/latex/luacas/tex/_lib/luacas-inspect.lua create mode 100644 macros/luatex/latex/luacas/tex/_lib/luacas-pepperfish.lua create mode 100644 macros/luatex/latex/luacas/tex/_lib/luacas-table.lua delete mode 100644 macros/luatex/latex/luacas/tex/_lib/pepperfish.lua delete mode 100644 macros/luatex/latex/luacas/tex/_lib/table.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/_init.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/absexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/equation.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/euclideandomain.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/factorialexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/field.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/integer.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/integerquotientring.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/logarithm.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-absexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-algebra_init.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-equation.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-euclideandomain.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-factorialexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-field.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-integer.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-integerquotientring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-logarithm.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-polynomialring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-rational.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-ring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-rootexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-sqrtexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/berlekampfactoring.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/decomposition.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-berlekampfactoring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-decomposition.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-zassenhausfactoring.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/zassenhausfactoring.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/rational.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/ring.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/rootexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/sqrtexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/algebra/trigexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/calculus/_init.lua delete mode 100644 macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/calculus/diffexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/calculus/integralexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/luacas-calculus_init.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/luacas-derivativeexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/luacas-diffexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/luacas-integralexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/_init.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/atomicexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-difference.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-power.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-product.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-quotient.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-sum.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/compoundexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/constantexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/expression.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/functionexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-atomicexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-binaryoperation.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-compoundexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-constantexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-core_init.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-expression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-functionexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/luacas-symbolexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/core/symbolexpression.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/calculus/integrals.lua create mode 100644 macros/luatex/latex/luacas/tex/test/calculus/luacas-derivatives.lua create mode 100644 macros/luatex/latex/luacas/tex/test/calculus/luacas-integrals.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/autosimplify.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/collect.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/equations.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/functions.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/logarithms.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-autosimplify.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-collect.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-equations.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-functions.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-logarithms.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-rationalexponent.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-simplify.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/luacas-substitute.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/rationalexponent.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/simplify.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/expressions/substitute.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/helper.lua create mode 100644 macros/luatex/latex/luacas/tex/test/luacas-helper.lua create mode 100644 macros/luatex/latex/luacas/tex/test/luacas-main.lua create mode 100644 macros/luatex/latex/luacas/tex/test/luacas-parser.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/main.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/parser.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/luacas-partialfractions.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomial.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomialmod.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/luacas-roots.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/partialfractions.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/polynomial.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/polynomialmod.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/roots.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/rings/conversion.lua create mode 100644 macros/luatex/latex/luacas/tex/test/rings/luacas-conversion.lua create mode 100644 macros/luatex/latex/luacas/tex/test/rings/luacas-modulararithmetic.lua create mode 100644 macros/luatex/latex/luacas/tex/test/rings/luacas-number.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/rings/modulararithmetic.lua delete mode 100644 macros/luatex/latex/luacas/tex/test/rings/number.lua (limited to 'macros') diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/README b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/README index eac45ee706..1c8ed3d79a 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/README +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/README @@ -1,7 +1,7 @@ IMPORTANT NOTE: This is the package formerly known as biblatex-chicago-notes-df. It -is designed for use with the latest version (3.18) of biblatex. The +is designed for use with the latest version (3.18b) of biblatex. The package contains the 17th-edition Chicago style files, and I am also maintaining the 16th-edition files for those for whom they remain a necessity, though I have deprecated these older files and will remove @@ -15,7 +15,7 @@ date. Most particularly please note that biber is now the required backend for all the included styles (version 2.18 is designed for use with the latest biblatex). -README (version 2.3, 2022-07-02): +README (version 2.3a, 2022-11-17): Biblatex-chicago contains three biblatex styles implementing the specifications of the Chicago Manual of Style, 17th edition. The diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/RELEASE b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/RELEASE index 84c8102401..6710cdb5cd 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/RELEASE +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/RELEASE @@ -1,3 +1,12 @@ +Release notes for version 2.3a [2022-11-17]: + +This release backports a bug-fix from the most recent LaTeX3 +programming layer. If you are using biblatex 3.18b and an older L3 +layer then the processing of your documents could fail and fall into +an infinite loop, leaving behind an inscrutable message in your log +file. This update should prevent that, and also fixes a few other +minor glitches. + Release notes for version 2.3 [2022-07-02]: All styles now require the current biblatex (3.18) and biber (2.18). diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.pdf index 4ff5330930..360c89392c 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.tex index 96f26ac5ea..03f2413a32 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/biblatex-chicago.tex @@ -26,10 +26,10 @@ filecolor=Teal]{hyperref} % \usepackage[scaled=0.9]{ClearSans} % \usepackage[p]{zlmtt} % \usepackage{gentium} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, @@ -117,7 +117,7 @@ HyphenChar={-}] Style files for biblatex \vspace{.3\baselineskip} -\sffamily\normalsize\bfseries David Fussner\qquad Version 2.3\\ +\sffamily\normalsize\bfseries David Fussner\qquad Version 2.3a\\ \href{mailto:djf027@googlemail.com}{djf027@googlemail.com}\\ \today \end{center} @@ -150,6 +150,8 @@ HyphenChar={-}] to let me know, and of course any suggestions for solving problems more elegantly or accurately would be most welcome.} +\enlargethispage{\baselineskip} + \mylittlespace\textbf{Important Note:} If you have used \textsf{biblatex-chicago} before, especially if you've been using anything earlier than version 2.0, please make sure you have read the @@ -215,7 +217,7 @@ clickable link to an external document. \begin{itemize}{}{} \item The \textsf{biblatex} package, of course! The current version - --- 3.18 at the time of writing --- has received extensive testing, + --- 3.18b at the time of writing --- has received extensive testing, and contains features and bug fixes upon which my code relies. Please don't use any earlier version. \textsf{Biblatex} requires several packages, and it strongly recommends several more: @@ -223,7 +225,7 @@ clickable link to an external document. \item \textsf{biber} --- the next-generation \textsc{Bib}\TeX\ replacement by Philip Kime and Fran\c{c}ois Charette, available from SourceForge (required). You should use the latest version, - 2.18, to work with \textsf{biblatex} 3.18 and + 2.18, to work with \textsf{biblatex} 3.18b and \textsf{biblatex-chicago}; please note that any other backend will not produce accurate results. \item e-\TeX\ (required) @@ -238,8 +240,11 @@ clickable link to an external document. \item \textsf{babel} --- a standard package (\emph{strongly} recommended) \item \textsf{csquotes} --- available from CTAN (recommended). - Please upgrade to the latest version of \textsf{csquotes} (5.2l). + Please upgrade to the latest version of \textsf{csquotes} (5.2n). \end{itemize} +\item The standard \textsf{expl3} and \textsf{xparse} packages are + loaded automatically for most users, and if they aren't + \textsf{biblatex-chicago} does it for you. \item With recent changes both to \textsf{biblatex} and to \textsf{biblatex-chicago}, \textsf{biblatex-chicago} itself now requires two packages, which are both loaded for you if you load @@ -1028,7 +1033,7 @@ the added fields \textsf{version} and \textsf{type} providing information about the app's version and about the system on which it runs (14.268; angry:birds). -%\enlargethispage{-\baselineskip} +%%\enlargethispage{-\baselineskip} \mybigspace This \mymarginpar{\textbf{bookinbook}} type provides the means of referring to parts of books that are considered, in other @@ -1717,7 +1722,7 @@ wrongly-inherited fields. Other sorts of parent entries aren't affected by this, and of course you must be using \textsf{Biber} for the settings to apply. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace Should you wish to employ the new, \textsf{maintitle}-first syntax, then you'll @@ -1800,6 +1805,8 @@ mechanism's designed to save you some typing in common scenarios; please see all of the (multifarious) details in section~\ref{sec:related}, below. +\enlargethispage{\baselineskip} + \paragraph*{\protect\mymarginpar{\textbf{online}}} \label{sec:online} @@ -1941,7 +1948,7 @@ deductively from the basic principles, I hope that the table might simplify most of your choices. If something remains unclear, please let me know and I'll see if I can improve it. -\enlargethispage{\baselineskip} +%\enlargethispage{\baselineskip} \mylittlespace A few more notes are in order. I designed the \textsf{relatedtype} \texttt{commenton} to facilitate citation of @@ -2000,7 +2007,7 @@ way, as it allows you to provide the initialisms that the \textsf{timezone}, \textsf{urldate}, and \textsf{userd} in section~\ref{sec:entryfields}, below. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace The \mymarginpar{\textbf{patent}} \emph{Manual} is very brief on this subject (14.258), but very clear about which information @@ -2567,7 +2574,7 @@ name given here exactly matches that of an editor and/or a translator, then \textsf{biblatex-chicago-notes} will concatenate these fields in the formatted references. -%\enlargethispage{-\baselineskip} +%%\enlargethispage{-\baselineskip} \mylittlespace As noted above, however, this field has a special meaning in the \textsf{suppbook} entry type, used to make an @@ -2657,7 +2664,7 @@ present in the entry, but please remember to use the new option latter doesn't work as smoothly and completely as \textsf{biblatex's} own name toggles. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace This system of options, then, can turn off \textsf{biblatex-chicago-notes}'s mechanism for finding a name to @@ -2760,6 +2767,8 @@ entry type for introductions, forewords and afterwords (\textsf{suppbook}) uses \textsf{bookauthor} as the author of \textsf{title} (polakow:afterw, prose:intro). +\enlargethispage{2\baselineskip} + \mybigspace This, \mymarginpar{\vspace{-8pt}\textbf{bookpagination}} a standard \textsf{biblatex} field, allows you automatically to prefix the appropriate string to information you provide in a \textsf{pages} @@ -3038,7 +3047,7 @@ Please note that you can set both of these options either in the preamble or in the \textsf{options} field of individual entries, allowing you to change the settings on an entry-by-entry basis. -\enlargethispage{2\baselineskip} +%\enlargethispage{2\baselineskip} \mylittlespace Please further note that in earlier releases of \textsf{biblatex-chicago} I recommended against using @@ -3422,7 +3431,7 @@ so these three fields now work more or less as they do in standard abbreviated references to online content than conventional URLs, though I can find no specific reference to them in the \emph{Manual}. -% \enlargethispage{-\baselineskip} +% %\enlargethispage{-\baselineskip} \mybigspace This \mymarginpar{\textbf{eventdate}} is a standard \textsf{biblatex} field which has gradually accumulated functions in @@ -3530,7 +3539,7 @@ usually identify the university for which the thesis was written, while in a \textsf{report} entry it may identify any sort of institution issuing the report. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace As \mymarginpar{\textbf{introduction}} with the \textsf{afterword} and \textsf{foreword} fields above, @@ -3722,7 +3731,7 @@ this field to \textsf{article}, \textsf{periodical}, and of those entry types in section~\ref{sec:entrytypes}, above, and also table~\ref{tab:online:types} (14.208; amlen:hoot). -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace An \mymarginpar{\textbf{maintitleaddon}} annex to the \textsf{maintitle}, for which see previous entry. Such an annex would @@ -4157,7 +4166,7 @@ or enclose the whole name in additional braces. (See \emph{Manual} 14.133--41; aristotle:metaphy:gr, cohen:schiff, creasey:ashe:blast, dunn:revolutions.) -%\enlargethispage{.5\baselineskip} +%%\enlargethispage{.5\baselineskip} \mylittlespace There are, as one might expect, a few further subtleties involved here. If you give two publishers in the field @@ -4615,7 +4624,7 @@ assuming that such a \textsf{title} begins with a lowercase letter in your .bib database. See\,\textbf{\textbackslash autocap} in section~\ref{sec:formatcommands} below for more details. -% \enlargethispage{-\baselineskip} +% %\enlargethispage{-\baselineskip} \mybigspace Standard \colmarginpar{\textbf{titleaddon}} \textsf{biblatex} intends this field for use with additions to titles @@ -4822,7 +4831,7 @@ entry in the bibliography to which the cross-reference will point. (See 14.81--82; creasey:ashe:blast, creasey:morton:hide, creasey:york:death, lecarre:quest.) -%\enlargethispage{2\baselineskip} +%%\enlargethispage{2\baselineskip} \mybigspace The \mymarginpar{\textbf{userd}} \textsf{userd} field acts as a sort of \enquote{\textsf{datetype}} field, allowing you in most @@ -5098,7 +5107,7 @@ contrib:contrib, schweitzer:bach). \textbf{NB:} The \textsf{pubstate} functionality currently has no equivalent using the \textsf{related} field. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace This \mymarginpar{\textbf{related}} field is required to use \textsf{biblatex's} \textsf{related} functionality, and it should @@ -5737,6 +5746,8 @@ all entry types. There may be other places where \textsf{biblatex's} standard styles support this feature, and I shall add them when they come to my attention. +\enlargethispage{\baselineskip} + \mybigspace These \mymarginpar{\textbf{\textbackslash foottextcite\\\textbackslash foottextcites}} two commands look like citation commands, but are in fact wrappers for customizing the @@ -5758,8 +5769,6 @@ change the first to: \texttt{\{\textbackslash newcunit\textbackslash \cmd{headlessparencite(s)} command if you want to retain the long citations inside the parentheses.) -\enlargethispage{\baselineskip} - \mybigspace I \mymarginpar{\textbf{\textbackslash letterdatelong}} have provided this macro mainly for use in the optional postnote field of the various citation commands. When citing a letter (published or @@ -5992,7 +6001,7 @@ that \textsf{biblatex-chicago-notes} sets this command to use the most common citation command you will use, and also works fine in its multicite form, \textbf{\textbackslash autocites}. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace While \mymarginpar{\textbf{\textbackslash cite*}} the \cmd{cite} command works just as you would expect it to, I have also @@ -6492,7 +6501,7 @@ Please see the documentation of \textsf{shorthand} and further options available to you for presenting and formatting these two types of \texttt{biblist}. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace Formerly \mymarginpar{\cmd{Declare-}\\\texttt{Labelname}} available only to @@ -7435,7 +7444,7 @@ list. Please see the documentation of the \textbf{shorthand} field in section~\ref{sec:entryfields} above for information on further options available to you for presenting and formatting the list of shorthands. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace Chris Sparks \mymarginpar{\texttt{shorthandibid}} pointed out that \textsf{biblatex-chicago-notes} would never use @@ -7741,7 +7750,7 @@ Both will work, particularly because you don't need to worry too much about capitalization because the word always appears after the \verb+\bibstring{see}+. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace Now, it's perfectly possible for an introduction to have numbered sections of its own, so a citation there may produce a @@ -7990,7 +7999,7 @@ than the first, but in any case I'll start by explaining the automatic provisions, then move on to the two handcrafted options, leaving you to judge which seems best suited to your needs. -%\enlargethispage{\baselineskip} +%%\enlargethispage{\baselineskip} \mylittlespace For \mymarginpar{\texttt{\textbf{split}}} the automatic subdivision of an endnotes section I have borrowed a concept, if not @@ -9057,7 +9066,7 @@ field helps \textsf{biblatex-chicago} know that the \textsf{audio} entry is a podcast episode, and helps it construct the entry appropriately (danforth:podcast). -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace This \mymarginpar{\textbf{book}} is the standard \textsf{biblatex} and \textsc{Bib}\TeX\ entry type, but the package @@ -9208,7 +9217,7 @@ entries by setting the \texttt{authortitle} option to \texttt{false} either in individual entries or in the preamble for all examples of the entry type. -%\enlargethispage{\baselineskip} +%%\enlargethispage{\baselineskip} \mybigspace This \mymarginpar{\textbf{image}} entry type is now a clone of the \textsf{artwork} type, which see. I retain it here for @@ -9469,7 +9478,7 @@ date at all will be required for entries referring to entire archival collections. In such entries, you may wish to use the word \enquote{\texttt{classical}} as your \textsf{entrysubtype}, which will have no effect on the list of references but will change the look of -the in-text citations (house:papers). Instead of any date, the +the in-text citations (house:\break papers). Instead of any date, the citation will include the \textsf{title}, separated from the \textsf{author's} name by a space, e.g., (House Papers). This same arrangement, happily, allows you easily to cite individual books of @@ -9875,7 +9884,7 @@ deductively from the basic principles, I hope that the table might simplify most of your choices. If something remains unclear, please let me know and I'll see if I can improve it. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace A few more notes are in order. I designed the new \textsf{relatedtype} \texttt{commenton} to facilitate citation of @@ -10166,7 +10175,7 @@ but if it's a fuller entry also containing an \textsf{author} then you'll also need \texttt{useauthor=false} in the \textsf{options} field. Other surplus fields will be ignored. (See osborne:poison.) -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace \textsf{Biblatex-chicago} also, at the behest of Bertold Schweitzer, supports the \textsf{relatedtype} @@ -10407,7 +10416,7 @@ entry, that is, in the \textsf{foreword}, \textsf{afterword}, or \textsf{introduction} field. (See \emph{Manual} 14.110; friedman:intro, polakow:afterw, prose:intro). -%\enlargethispage{\baselineskip} +%%\enlargethispage{\baselineskip} \mybigspace This \mymarginpar{\textbf{suppcollection}} fulfills a function analogous to \textsf{suppbook}. Indeed, I believe the @@ -10481,7 +10490,7 @@ main guidelines: provided here, and will be printed as-is, contextually capitalized. (Cf.\ hitchcock:nbynw.) \item[title, titleaddon, booktitle, booktitleaddon, maintitle:] As - with the other two audiovisual types, \textsf{video} serves as an + with the other 2 audiovisual types, \textsf{video} serves as an analogue both to books and to collections, so the \textsf{title} may be of a whole film DVD or of a TV series, or it may identify one episode in a series or one scene in a film. In the latter cases, @@ -10730,7 +10739,7 @@ pseudonym, and also the package options \texttt{nameaddonformat} and \texttt{nameaddonsep} in sections~\ref{sec:authuseropts} and \ref{sec:authpreset}, below. -%\enlargethispage{2\baselineskip} +%%\enlargethispage{2\baselineskip} \mylittlespace As its name suggests, the author-date style very much wants to have a name of some sort present both for the entries in the @@ -11657,7 +11666,7 @@ should ensure presents whichever of the two editors' names (\textsf{namea} or \textsf{editor}) appears at the head of the reference-list entry. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace In \textsf{biblatex} 0.9 Lehman reworked the string concatenation mechanism, for reasons he outlines in his RELEASE file, @@ -11928,7 +11937,7 @@ undefined) to make an introduction the focus of a citation. field, for providing the International Standard Book Number of a publication. Not typically required by the \emph{Manual}. -%\enlargethispage{-2\baselineskip} +%%\enlargethispage{-2\baselineskip} \mybigspace Standard \mymarginpar{\textbf{isrn}} \textsf{biblatex} field, for providing the International Standard Technical Report @@ -12388,7 +12397,7 @@ organization sponsoring a conference in a \textsf{proceedings} or \textsf{inproceedings} entry, and I have retained this as a possibility, though the \emph{Manual} is silent on the matter. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace This \mymarginpar{\textbf{origdate}} is a standard \textsf{biblatex} field which allows more than one full date @@ -12555,7 +12564,7 @@ appropriate identifying string to information you provide in the all the details on this functionality, as aside from the difference just mentioned the two fields are equivalent. -\enlargethispage{\baselineskip} +%\enlargethispage{\baselineskip} \mybigspace Standard \mymarginpar{\textbf{part}} \textsf{biblatex} field, which identifies physical parts of a single logical volume in @@ -12664,7 +12673,7 @@ purposes. Cf.\ \textsf{annotator} and \textsf{commentator}. \mybigspace See \mymarginpar{\textbf{reprinttitle}} section~\ref{sec:authrelated}, below. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mybigspace A \mymarginpar{\textbf{series}} standard \textsf{biblatex} field, usually just a number in an \textsf{article}, @@ -12770,7 +12779,7 @@ parseable by them, while with the latter you can either rely on the conventions of your field or, alternately, provide a list of journal abbreviations using \cmd{printbiblist\{shortjournal\}}. -% \enlargethispage{-\baselineskip} +% %\enlargethispage{-\baselineskip} \mylittlespace For long institutional names the \emph{Manual's} recommendation (15.37) involves using an abbreviation which will @@ -13100,7 +13109,7 @@ serve to clarify them, and help you to understand when \textsf{biblatex-chicago-authordate-trad} might need your help in order to comply with them. -%\enlargethispage{2\baselineskip} +%%\enlargethispage{2\baselineskip} \mylittlespace With regard to sentence-style capitalization, the rules of the Chicago \textsf{authordate-trad} style are fairly simple: @@ -13415,7 +13424,7 @@ part of an \textsf{urldate}. The \emph{Manual} prefers initialisms like \enquote{EST} for this purpose, and you can provide parentheses around it at your discretion (cp.\ 10.41 and 14.191). -%\enlargethispage{\baselineskip} +%%\enlargethispage{\baselineskip} \mybigspace A \mymarginpar{\textbf{usera}} supplemental \textsf{biblatex} field which in certain contexts in @@ -13973,7 +13982,7 @@ Now, the Chicago-specific types: and \textsf{review} entries (along with the latter's clone, \textsf{suppperiodical}). -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} In \textsf{article} entries it replaces the \textsf{titleaddon} with the \textsf{relatedstring} followed by the \textsf{title} of the @@ -14547,7 +14556,7 @@ when presenting date ranges. The new style option now compressed automatically according to the \emph{Manual's} instructions (9.64; section~\ref{sec:authpreset}). -\enlargethispage{\baselineskip} +\enlargethispage{-\baselineskip} \mylittlespace In \mymarginpar{\texttt{alltimes=12h}} entries which print time stamps, they will, when the stamp is part of a @@ -14617,7 +14626,7 @@ or whole spreads in twoside mode. minor problem with punctuation in titles, ensuring that the colon between a title and a subtitle appears in the correct, matching font. -\enlargethispage{-\baselineskip} +%\enlargethispage{-\baselineskip} \mylittlespace This \mymarginpar{\texttt{related=true}} is the standard \textsf{biblatex} bibliography option, and it enables the use @@ -14701,6 +14710,8 @@ class, which I believe has its own commands for defining these parameters. You can also disable it by using the \texttt{footmarkoff} package option, on which see below. +\enlargethispage{\baselineskip} + \mylittlespace Gildas Hamel pointed out that my default definition, in \textsf{biblatex-chicago.sty}, of \textsf{biblatex's} \cmd{bibnamedash} didn't work well with many fonts, leaving a line of @@ -14817,7 +14828,7 @@ per-entry basis in the \textsf{options} field (though rather than use this latter method it would make sense to eliminate the \textsf{pages} field from the affected entries). -\enlargethispage{\baselineskip} +%\enlargethispage{\baselineskip} \mylittlespace This \colmarginpar{\texttt{doi=true}} option controls whether any \textsf{doi} fields present in the .bib file will be @@ -15670,7 +15681,7 @@ which I haven't extensively tested, so I'm labeling this option \textsf{postnote} field of citation commands, not the \textsf{pages} field in your .bib file. -% \enlargethispage{\baselineskip} +% %\enlargethispage{\baselineskip} \mylittlespace This \mymarginpar{\texttt{seriesabbrev}} option controls the printing, in the reference list, of the @@ -16164,7 +16175,7 @@ public and legal material. commercial electronic database, of a legislative publication. It will be associated with the \textsf{date} in long notes but won't appear in short ones. (Cf.\ congress:debate:new and - state:statute:ky.)%\enlargethispage{\baselineskip} + state:statute:ky.)%%\enlargethispage{\baselineskip} \item[addendum] You can use this field to specify the speaker at hearings or in debates, the Canadian or British jurisdiction of some laws if not otherwise clear from the citation, or possibly simply @@ -16759,7 +16770,16 @@ do my best to fix them. \section{Revision History} \label{sec:history} -\textbf{2.3: Released \today} +\textbf{2.3a: Released \today} + +\mylittlespace This release backports a bug-fix from the most recent +\LaTeX 3 programming layer. If you are using \textsf{biblatex} 3.18b +and an older L3 layer then the processing of your documents could fail +and fall into an infinite loop, leaving behind an inscrutable message +in your log file. This update should prevent that, and also fixes a +few other minor glitches. + +\mybigspace\textbf{2.3: Released July 2, 2022} \mylittlespace All styles require the current \textsf{biblatex} (3.18) and \textsf{biber} (2.18). @@ -18246,7 +18266,7 @@ Edition: \item In \textbf{suppbook} entries, the \emph{Manual} now requires you to provide the page range (in the \textsf{pages} field) for the specific part you are citing, e.g., an introduction, foreword, or - afterword.%%\enlargethispage{-2\baselineskip} + afterword.%%%\enlargethispage{-2\baselineskip} \item In \textbf{patent} entries, the \emph{Manual} now prefers sentence-style capitalization for titles, which you'll need to provide yourself by hand. diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.pdf index b6d5d15613..9bec0f838a 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.tex index fc8c8effdd..ce73187927 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-intro.tex @@ -40,10 +40,10 @@ formatbib=minwo]{biblatex-chicago} \usepackage[colorlinks,urlcolor=DarkSlateGrey,citecolor=MidnightBlue, plainpages=false,breaklinks=true,linkcolor=DarkSlateGrey,filecolor=Teal, baseurl=biblatex-chicago.pdf\#]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, @@ -102,7 +102,7 @@ author-date specification you will need to use \textsf{biber} to process your .bib files, as \textsc{Bib}\TeX\ (and its more recent variants) will no longer provide all the features the style requires. For this release, you really need the current versions of -\textsf{biber} (2.18) and \textsf{biblatex} (3.18), which contain +\textsf{biber} (2.18) and \textsf{biblatex} (3.18b), which contain features and bug-fixes on which my own code relies. The advice that follows in this document assumes that you are using \textsf{biber}; if you wish to continue using \textsc{Bib}\TeX\ then you need diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.pdf index c281daaa47..320fd9992d 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.tex index 26037b7238..77e079d821 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-dates-sample.tex @@ -33,10 +33,10 @@ postnotepunct,compresspages,strict,cmsbreakurl,cmsnameparts]{biblatex-chicago} \newcommand{\cmd}[1]{\texttt{\textbackslash #1}} \usepackage[colorlinks,urlcolor=blue,citecolor=black, plainpages=false,breaklinks=true]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.pdf index b6416af4fd..74207ffb42 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.tex index 1cce627b47..a7a4cae57a 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-legal-sample.tex @@ -21,10 +21,10 @@ booklongxref=false,compresspages,related=true]{biblatex-chicago} \usepackage[colorlinks,urlcolor=DarkSlateGrey,citecolor=MidnightBlue, plainpages=false,breaklinks=true,linkcolor=DarkSlateGrey, filecolor=Teal]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.pdf index 75064411c9..e4afcb084a 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.tex index cf005981a4..71a321649d 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-noteref-demo.tex @@ -31,10 +31,10 @@ noteref=section,noterefintro=introduction]{biblatex-chicago} \usepackage[colorlinks,filecolor=Teal,citecolor=black, plainpages=false,breaklinks=true,urlcolor=DarkSlateBlue, linkcolor=DarkSlateBlue,baseurl=biblatex-chicago.pdf\#]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.pdf index 33bf8e3a86..4d3ee06024 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.tex index 8565537229..a33dd347e2 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-intro.tex @@ -35,10 +35,10 @@ booklongxref=false,annotation,compresspages,formatbib=min]{biblatex-chicago} \usepackage[colorlinks,filecolor=Teal,citecolor=black, plainpages=false,breaklinks=true,urlcolor=DarkSlateBlue, linkcolor=DarkSlateBlue,baseurl=biblatex-chicago.pdf\#]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.pdf index 48a58a104d..9d07d47fee 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.tex index fa88ea1eda..b62ad5b5ad 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-notes-sample.tex @@ -22,10 +22,10 @@ booklongxref=false,compresspages,cmsnameparts]{biblatex-chicago} \hyphenation{A-p-ril} %\renewcommand{\sfdefault}{phv} %\renewcommand{\ttdefault}{pcr} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[Scale = MatchLowercase] \setmonofont{lmmono9-regular.otf} % \usepackage[fallback,CJKspace=true]{xeCJK} diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.pdf index 1310be1c08..26f5668cba 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.tex index 023a20d0da..9a19f2a010 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-appendix.tex @@ -32,10 +32,10 @@ postnotepunct,compresspages,strict,annotation]{biblatex-chicago} \usepackage[hyperref,svgnames]{xcolor} \usepackage[colorlinks,urlcolor=DarkSlateGrey,citecolor=black, plainpages=false,breaklinks=true,linkcolor=DarkSlateGrey]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.pdf b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.pdf index db23521788..f82bc3416a 100644 Binary files a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.pdf and b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.pdf differ diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.tex b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.tex index bdbf89399d..42c5d4ec5f 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.tex +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/doc/cms-trad-sample.tex @@ -31,10 +31,10 @@ postnotepunct,compresspages,strict]{biblatex-chicago} \newcommand{\cmd}[1]{\texttt{\textbackslash #1}} \usepackage[colorlinks,urlcolor=blue,citecolor=black, plainpages=false,breaklinks=true]{hyperref} -\setmainfont{GentiumPlus-R.ttf}[ -ItalicFont = GentiumPlus-I.ttf, -BoldFont = GenBasB.ttf, -BoldItalicFont = GenBasBI.ttf] +\setmainfont{GentiumPlus-Regular.ttf}[ +ItalicFont = GentiumPlus-Italic.ttf, +BoldFont = GentiumPlus-Bold.ttf, +BoldItalicFont = GentiumPlus-BoldItalic.ttf] \setsansfont{ClearSans-Regular.ttf}[ BoldFont = ClearSans-Bold.ttf, ItalicFont = ClearSans-Italic.ttf, diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-authordate.bbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-authordate.bbx index 30fecc564f..dad0dc2c46 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-authordate.bbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-authordate.bbx @@ -2,7 +2,7 @@ % Lehman's standard.bbx and from chicago-notes.bbx. It provides the % reference list formatting for the Chicago author-date style. -\ProvidesFile{chicago-authordate.bbx}[2022/07/02 v 3.18 biblatex +\ProvidesFile{chicago-authordate.bbx}[2022/11/17 v 3.18b biblatex bibliography style] %%%% Initialize and format bibliography and los %%%% @@ -849,43 +849,11 @@ bibliography style] match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ replace=\regexp{\\citeincitefs$1$6\}}] } - \map[overwrite]{ - \step[fieldsource=titleaddon, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=titleaddon, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=annotation, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=annotation, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=annote, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=annote, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=addendum, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=addendum, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=note, + \map[overwrite, foreach={titleaddon,annotation,annote,addendum,note}]{ + \step[fieldsource=$MAPLOOP, match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=note, + \step[fieldsource=$MAPLOOP, match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ replace=\regexp{\\citeincitefs$1$6\}}] } diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common.cbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common.cbx index 7c1f797561..7cfe99f699 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common.cbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common.cbx @@ -4,7 +4,7 @@ % list) for the two author-date styles of the Chicago Manual of Style, % 17th edition. -\ProvidesFile{chicago-dates-common.cbx}[2022/07/02 v 3.18 biblatex +\ProvidesFile{chicago-dates-common.cbx}[2022/11/17 v 3.18b biblatex citation style] %%%% Biblatex initialization + Chicago options + Toggles %%%% @@ -17,6 +17,34 @@ citation style] \newbool{cms:tcit} \newbool{cms:postsh} +% Here we provide a slightly improved \if@cms@capital replacement +% using expl3 facilities, and also backport a patch for a bug in +% (older versions of) expl3's case-changing code. + +\ifdef{\ExplSyntaxOff}{}{\RequirePackage{xparse}\RequirePackage{expl3}}% + +\ExplSyntaxOn% + +\NewDocumentCommand \IfCMSFieldInitCS {m} +{ + \regex_match:nnTF + {\A(?:\c{citeincite(?:f|s){0,2}}|\cM.)} + {#1} + {\use_i:nn} + {\use_ii:nn} +} + +\cs_if_exist:NTF \__text_change_case_switch_titleonly:nnNnnnn +{} +{\cs_new:Npn \__text_change_case_switch_titleonly:nnNnnnn #1#2#3#4#5#6#7 + { + \__text_change_case_store:n {#7} + \__text_change_case_break:w + } +} + +\ExplSyntaxOff + \providecommand*{\mkibid}[1]{#1} \providetoggle{cms@inlineibid} @@ -597,9 +625,9 @@ citation style] {\ExecuteBibliographyOptions[jurisdiction,legal,legislation]{skipbib}}% {}% -\AtEndPreamble{% Automatic sorting by shorthand when it appears - \iftoggle{cms@los}% at the head of the entry. - {\DeclareSourcemap{ +\AtEndPreamble{% + \iftoggle{cms@los}% Automatic sorting by shorthand when it appears + {\DeclareSourcemap{% at the head of the entry. \maps[datatype=bibtex]{ \map{ \step[fieldsource=shorthand, final] @@ -867,10 +895,8 @@ citation style] \ifx\cms@ldt@cmsnameparts\cms@tpl #1{#3}% #1[#2]{#3} - \blx@warning@noline{#2 is the So Global Template} \else #1[#2]{#3} - \blx@warning@noline{#2 is the Local Template} \fi} %% Name format declarations for bibliography and notes. The @@ -2729,7 +2755,9 @@ citation style] \DeclareFieldFormat{shortvol}{#1} \DeclareFieldFormat[jurisdiction,legal,legislation]{addendum}{% - \ifcapital{\mkbibparens{\MakeCapital{#1\isdot}}}{\mkbibparens{#1\isdot}}} + \ifcapital{\IfCMSFieldInitCS{#1}{\mkbibparens{#1\isdot}}% + {\mkbibparens{\MakeCapital{#1\isdot}}}}% + {\mkbibparens{#1\isdot}}} \DeclareFieldFormat[legal,legislation]{part}{% \ifnumerals{#1}% @@ -3121,7 +3149,7 @@ citation style] \DeclareFieldFormat{letterday}{\mkbibcurdinal{#1}} \DeclareFieldFormat{note}{% - \if@cms@capital{#1}{\MakeCapital{#1}}{#1}}% + \ifcapital{\IfCMSFieldInitCS{#1}{#1}{\MakeCapital{#1}}}{#1}}% \DeclareFieldFormat{capital}{% \ifcapital{\MakeCapital{#1}}{#1}} @@ -3279,17 +3307,19 @@ citation style] \DeclareFieldFormat{edition}{% New in 0.8 \ifinteger{#1}% {\mkbibordedition{#1}~\bibstring{edition}}% - {\ifcapital + {\ifcapital% {\MakeCapital{#1\isdot}}% {#1\isdot}}} \DeclareFieldFormat{usere}{[#1]} % Better than mkbibbrackets? \DeclareFieldFormat{titleaddon}{% - \if@cms@capital{#1}{\MakeCapital{#1\isdot}}{#1\isdot}}%\custpunctc? + \ifcapital{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}{#1\isdot}}%\custpunctc? \DeclareFieldFormat[periodical]{titleaddon}{% - \ifcapital{\MakeCapital{#1\isdot}}{#1\isdot}} + \ifcapital{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}{#1\isdot}} \DeclareFieldAlias{booktitleaddon}{titleaddon} @@ -3327,30 +3357,8 @@ citation style] \DeclareFieldFormat{shortseries}{#1\isdot} \DeclareFieldFormat{addendum}{% - \if@cms@capital{#1}{\MakeCapital{#1\isdot}}{#1\isdot}} - -% There was unfortunate interaction between the case-changing code and -% cite commands when the latter were at the beginning of the -% field. This modified \ifcapital will be used in the fields in which -% the \citeincite(f|s) commands can appear. - -\newrobustcmd*{\if@cms@capital}[1]{% - \noexpandarg - \StrChar{#1}{1}[\cms@char]% - \ifboolexpr{% - test {\ifcapital}% - and - not test {\ifdefstring{\cms@char}{\citeincite}}% - and - not test {\ifdefstring{\cms@char}{\citeincites}}% - and - not test {\ifdefstring{\cms@char}{\citeincitef}}% - and - not test {\ifdefstring{\cms@char}{\citeincitefs}}% - }% - {\@firstoftwo}% - {\@secondoftwo}% -} + \ifcapital{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}{#1\isdot}} % For annotation fixes move all code for separators to the entrytail % macro, below. diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common16.cbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common16.cbx index 68faa4d853..048f64096b 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common16.cbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-dates-common16.cbx @@ -4,7 +4,7 @@ % for the two author-date styles of the Chicago Manual of Style, 16th % edition. -\ProvidesFile{chicago-dates-common16.cbx}[2022/07/02 v 3.18 biblatex +\ProvidesFile{chicago-dates-common16.cbx}[2022/11/16 v 3.18b biblatex citation style] %%%% Biblatex initialization + Chicago options + Toggles %%%% @@ -14,6 +14,21 @@ citation style] \newbool{cms:extraparens} \newbool{cms:comma} +\ifdef{\ExplSyntaxOff}{}{\RequirePackage{xparse}\RequirePackage{expl3}}% + +\ExplSyntaxOn% + +\cs_if_exist:NTF \__text_change_case_switch_titleonly:nnNnnnn +{} +{\cs_new:Npn \__text_change_case_switch_titleonly:nnNnnnn #1#2#3#4#5#6#7 + { + \__text_change_case_store:n {#7} + \__text_change_case_break:w + } +} + +\ExplSyntaxOff + \providecommand*{\mkibid}[1]{#1} \providetoggle{cms@inlineibid} diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.bbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.bbx index 9222c66e9c..0b16b3b51a 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.bbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.bbx @@ -2,7 +2,7 @@ % Lehman's standard.bbx. It provides the bibliography formatting for % the Chicago notes + bibliography style. -\ProvidesFile{chicago-notes.bbx}[2022/07/02 v 3.18 biblatex bibliography style] +\ProvidesFile{chicago-notes.bbx}[2022/11/17 v 3.18b biblatex bibliography style] %%%% Initialize and format bibliography and los %%%% @@ -859,43 +859,11 @@ final] \step[fieldset=maintitle, fieldvalue=\csgdef{@cmsst}{\@ne}, append] } - \map[overwrite]{ - \step[fieldsource=titleaddon, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=titleaddon, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=annotation, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=annotation, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=annote, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=annote, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=addendum, - match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ - replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=addendum, - match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ - replace=\regexp{\\citeincitefs$1$6\}}] - } - \map[overwrite]{ - \step[fieldsource=note, + \map[overwrite, foreach={titleaddon,annotation,annote,addendum,note}]{ + \step[fieldsource=$MAPLOOP, match=\regexp{\\citeincite(\{|\[[^\]]*\]\[\]\{)([^\}]+)\}$},%$ replace=\regexp{\\citeincitef$1$2\}}] - \step[fieldsource=note, + \step[fieldsource=$MAPLOOP, match=\regexp{\\citeincites(((\{|(\[[^\]]*\])+\{|\([^\)]*\)\(\)\{|\([^\)]*\)\(\)(\[[^\]]*\])+\{)[^\}]+\})+)((\{|\[[^\]]*\]\[\]\{)[^\}]+)\}$},%$ replace=\regexp{\\citeincitefs$1$6\}}] } diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.cbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.cbx index 97b61e12e6..f5b2f942e0 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.cbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes.cbx @@ -4,7 +4,7 @@ % formatted according to the specifications of the Chicago Manual of % Style. -\ProvidesFile{chicago-notes.cbx}[2022/07/02 v 3.18 biblatex citation style] +\ProvidesFile{chicago-notes.cbx}[2022/11/17 v 3.18b biblatex citation style] %%%% Biblatex initialization + Chicago options + Toggles %%%% @@ -13,6 +13,34 @@ \newbool{cms:comma} \newbool{cms:nd} +% Here we provide a slightly improved \if@cms@capital replacement +% using expl3 facilities, and also backport a patch for a bug in +% (older versions of) expl3's case-changing code. + +\ifdef{\ExplSyntaxOff}{}{\RequirePackage{xparse}\RequirePackage{expl3}}% + +\ExplSyntaxOn% + +\NewDocumentCommand \IfCMSFieldInitCS {m} +{ + \regex_match:nnTF + {\A(?:\c{citeincite(?:f|s){0,2}}|\cM.)} + {#1} + {\use_i:nn} + {\use_ii:nn} +} + +\cs_if_exist:NTF \__text_change_case_switch_titleonly:nnNnnnn +{} +{\cs_new:Npn \__text_change_case_switch_titleonly:nnNnnnn #1#2#3#4#5#6#7 + { + \__text_change_case_store:n {#7} + \__text_change_case_break:w + } +} + +\ExplSyntaxOff + \providecommand*{\mkibid}[1]{#1} \providetoggle{cms@oneyear}% Needed for author-date @@ -1456,10 +1484,8 @@ \ifx\cms@ldt@cmsnameparts\cms@tpl #1{#3}% #1[#2]{#3} - \blx@warning@noline{#2 is the So Global Template} \else #1[#2]{#3} - \blx@warning@noline{#2 is the Local Template} \fi} %% Name format declarations for bibliography and notes. The @@ -6971,7 +6997,8 @@ \DeclareFieldFormat[patent]{lostitle}{\MakeSentenceCase*{#1}\isdot} -\DeclareFieldFormat{prenote}{\ifcapital{\MakeCapital{#1}}{#1}\isdot} +\DeclareFieldFormat{prenote}{% + \ifcapital{\MakeCapital{#1}}{#1}\isdot} %% comprange code moved to .sty @@ -7233,7 +7260,7 @@ \DeclareFieldFormat{letterday}{\mkbibcurdinal{#1}} \DeclareFieldFormat{note}{% - \if@cms@capital{#1}{\MakeCapital{#1}}{#1}}% + \ifcapital{\IfCMSFieldInitCS{#1}{#1}{\MakeCapital{#1}}}{#1}}% \DeclareFieldFormat{capital}{% \ifcapital{\MakeCapital{#1}}{#1}} @@ -7388,7 +7415,7 @@ \DeclareFieldFormat{edition}{% New in 0.8 \ifinteger{#1} {\mkbibordedition{#1}~\bibstring{edition}}% - {\ifcapital + {\ifcapital% {\MakeCapital{#1\isdot}}% {#1\isdot}}} @@ -7413,10 +7440,12 @@ \DeclareFieldFormat{usere}{[#1]} % Better than mkbibbrackets? \DeclareFieldFormat{titleaddon}{% - \if@cms@capital{#1}{\MakeCapital{#1\isdot}}{#1\isdot}}% + \ifcapital{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}{#1\isdot}}% \DeclareFieldFormat[periodical]{titleaddon}{% - \ifcapital{\MakeCapital{#1\isdot}}{#1\isdot}} + \ifcapital{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}{#1\isdot}} \DeclareFieldAlias{booktitleaddon}{titleaddon} @@ -7502,33 +7531,13 @@ \DeclareFieldFormat{shortseries}{#1\isdot} \DeclareFieldFormat{addendum}{% - \if@cms@capital{#1}{\MakeCapital{#1\isdot}}{#1\isdot}} + \ifcapital{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}{#1\isdot}} \DeclareFieldFormat[jurisdiction,legal,legislation]{addendum}{% - \ifcapital{\mkbibparens{\MakeCapital{#1\isdot}}}{\mkbibparens{#1\isdot}}} - -% There was unfortunate interaction between the case-changing code and -% cite commands when the latter were at the beginning of the -% field. This modified \ifcapital will be used in the fields in which -% the \citeincite(s) commands can appear. - -\newrobustcmd*{\if@cms@capital}[1]{% - \noexpandarg - \StrChar{#1}{1}[\cms@char]% - \ifboolexpr{% - test {\ifcapital}% - and - not test {\ifdefstring{\cms@char}{\citeincite}}% - and - not test {\ifdefstring{\cms@char}{\citeincitef}}% - and - not test {\ifdefstring{\cms@char}{\citeincites}}% - and - not test {\ifdefstring{\cms@char}{\citeincitefs}}% - }% - {\@firstoftwo}% - {\@secondoftwo}% -} + \ifcapital% + {\mkbibparens{\IfCMSFieldInitCS{#1}{#1\isdot}% + {\MakeCapital{#1\isdot}}}}{\mkbibparens{#1\isdot}}} % For annotation fixes move all code for separators to the entrytail % macro, below. diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.bbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.bbx index b0593b162f..1391f065bd 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.bbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.bbx @@ -2,7 +2,7 @@ % standard.bbx It provides the bibliography formatting for the Chicago % notes + bibliography style. -\ProvidesFile{chicago-notes16.bbx}[2022/07/02 v 3.18 biblatex +\ProvidesFile{chicago-notes16.bbx}[2022/11/16 v 3.18b biblatex bibliography style] %%%% Initialize and format bibliography and los %%%% @@ -678,7 +678,7 @@ bibliography style] \usebibmacro{issuetitle}% \setunit*{\addcomma\addspace}% \usebibmacro{byeditor+others}% - \usebibmacro{editorpunct}%\newunit\newblock + \newunit\newblock \printfield{note}% \setunit*{\addcomma\addspace}\newblock% \usebibmacro{mag+news+date}% @@ -722,7 +722,7 @@ bibliography style] \usebibmacro{issuetitle}% \setunit*{\addcomma\addspace}% \usebibmacro{byeditor+others}% - \usebibmacro{editorpunct}%\newunit\newblock + \newunit\newblock \printfield{note}% \setunit*{\addcomma\addspace}\newblock% (changed for 0.7)?? \usebibmacro{journal+issue+year+pages}% diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.cbx b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.cbx index 4301ea4756..922f264508 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.cbx +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/chicago-notes16.cbx @@ -3,7 +3,7 @@ % of providing footnote citations and a bibliography formatted % according to the specifications of the Chicago Manual of Style. -\ProvidesFile{chicago-notes16.cbx}[2022/07/02 v 3.18 biblatex citation style] +\ProvidesFile{chicago-notes16.cbx}[2022/11/16 v 3.18b biblatex citation style] %%%% Biblatex initialization + Chicago options + Toggles %%%% @@ -12,6 +12,21 @@ \newbool{cms:comma} \newbool{cms:nd} +\ifdef{\ExplSyntaxOff}{}{\RequirePackage{xparse}\RequirePackage{expl3}}% + +\ExplSyntaxOn% + +\cs_if_exist:NTF \__text_change_case_switch_titleonly:nnNnnnn +{} +{\cs_new:Npn \__text_change_case_switch_titleonly:nnNnnnn #1#2#3#4#5#6#7 + { + \__text_change_case_store:n {#7} + \__text_change_case_break:w + } +} + +\ExplSyntaxOff + \providecommand*{\mkibid}[1]{#1} \providetoggle{cms@oneyear}% Needed for author-date diff --git a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/cmsdocs.sty b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/cmsdocs.sty index 5847723412..e584bdf333 100644 --- a/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/cmsdocs.sty +++ b/macros/latex/contrib/biblatex-contrib/biblatex-chicago/latex/cmsdocs.sty @@ -326,7 +326,8 @@ {#1}}}% {}% -\renewbibmacro*{cite:journal}{% +\ifbibmacroundef{cite:journal}{}% + {\renewbibmacro*{cite:journal}{% \ifthenelse{\ifciteibid\AND\NOT\iffirstonpage}% {\iffieldundef{prenote}% {\bibsentence\usebibmacro{cite:ibid}}% @@ -364,7 +365,7 @@ \ifthenelse{\iffieldundef{pagination}\AND% \iffieldundef{bookpagination}}% {\setunit{\postvolpunct}}% - {\setunit{\addcolon\addspace}}}}}} + {\setunit{\addcolon\addspace}}}}}}} \DeclareFieldFormat{cmsbiblink}{% \hyperlink{\getrefbykeydefault{\abx@field@entrykey}{anchor}{}}{#1}} diff --git a/macros/latex/contrib/lgrmath/README b/macros/latex/contrib/lgrmath/README new file mode 100644 index 0000000000..ed0eeadaf9 --- /dev/null +++ b/macros/latex/contrib/lgrmath/README @@ -0,0 +1,48 @@ + +-------------------------------------------------------+ + + lgrmath + + + Greek letters in math mode from + + + any LGR-encoded font + + + https://github.com/jfbu/lgrmath + + +-------------------------------------------------------+ + + This is the README file for the LaTeX2e package + `lgrmath', version 1.0 of 2022/11/16. + + Copyright (C) 2022 Jean-François Burnol + + This Work may be distributed and/or modified + under the conditions of the LaTeX Project Public License, + version 1.3c. The latest version of this license is in + http://www.latex-project.org/lppl.txt + and version 1.3 or later is part of all distributions of + LaTeX version 2003/12/01 or later. + + This Work includes lgrmath.sty, the documentation + lgrmath.pdf, this README and the source lgrmath.dtx. + +With \usepackage[font=]{lgrmath} the Greek letters in math +mode will be from the given font name, assumed to exist on the +LaTeX installation in LGR encoding for traditional LaTeX/pdfLaTeX. + +The shape of the Greek letters are configured via package +options, and they also come in \...up and \...it variants. + +Manual installation: +-------------------- + +Execute This creates + + etex lgrmath.dtx lgrmath.sty + lgrmath.tex + +The first one is the package file. The second one is for +building the PDF documentation. To do the latter: + + latexmk lgrmath.tex + dvipdfmx lgrmath.dvi + +The PDF documentation then includes the commented source code. + +It is possible to run latex + dvipdfmx, pdflatex, lualatex, or +xelatex directly on lgrmath.dtx rather than on lgrmath.tex. +The PDF will then not include the commented source code. diff --git a/macros/latex/contrib/lgrmath/lgrmath.dtx b/macros/latex/contrib/lgrmath/lgrmath.dtx new file mode 100644 index 0000000000..8e442721cf --- /dev/null +++ b/macros/latex/contrib/lgrmath/lgrmath.dtx @@ -0,0 +1,1263 @@ +% -*- coding: utf-8; time-stamp-format: "%02d-%02m-%:y at %02H:%02M:%02S %Z" -*- +% Execute etex on this file to extract package files. +% See in README for more build instructions. +%<*dtx> +\def\dtxtimestamp {Time-stamp: <16-11-2022 at 21:29:30 CET>} +%% +%% Package: lgrmath +%% Greek in math mode via LGR font of one's choice (JFB) +%% Version: 1.0 +%% License: LPPL 1.3c +%% Copyright (C) 2022 Jean-François Burnol +%% Repository: https://github.com/jfbu/lgrmath +%% +% +%<*tex> +\def\lgrmathversion{1.0} +\def\lgrmathdate{2022/11/16} +% +%<*dtx> +\iffalse +% +%<*tex> +%% This is a generated file. Run latexmk on this file lgrmath.tex then +%% run dvipdfmx on lgrmath.dvi to produce the documentation lgrmath.pdf, +%% with the package source code included. +%% +%% Customize as desired the class options and the two toggles below. +%% +%% See lgrmath.dtx for the copyright and the conditions for distribution +%% and/or modification of this Work. +%% +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{lgrmath.tex}% +[\lgrmathdate\space v\lgrmathversion\space + driver file for lgrmath documentation (JFB)]% +\PassOptionsToClass{a4paper,fontsize=11pt}{scrartcl} +\chardef\Withdvipdfmx \ifdefined\pdfoutput\ifnum\pdfoutput>0 0\else1\fi\else0\fi\relax +\chardef\NoSourceCode 0 % replace 0 by 1 for no source code +\input lgrmath.dtx +%%% Local Variables: +%%% mode: latex +%%% End: +% +%<*dtx> +\fi +\chardef\noetex 0 +\ifx\numexpr\undefined\chardef\noetex 1 \fi +\ifnum\noetex=1 \chardef\extractfiles 0 % extract files, then stop +\else + \ifx\ProvidesFile\undefined + \chardef\extractfiles 0 % etex etc.. on lgrmath.dtx + \else % latex/pdflatex on lgrmath.tex or on lgrmath.dtx + \ifx\Withdvipdfmx\undefined + % (lua|xe|pdf)latex run is on lgrmath.dtx, we will extract all files + \chardef\extractfiles 1 % 1 = extract all and typeset doc + \chardef\Withdvipdfmx + \ifdefined\pdfoutput\ifnum\pdfoutput>0 0\else1\fi\else0\fi\relax + \chardef\NoSourceCode 1 % + \NeedsTeXFormat{LaTeX2e}% + \PassOptionsToClass{a4paper,fontsize=11pt}{scrartcl}% + \else % latex run is on lgrmath.tex, + \chardef\extractfiles 2 % no extractions + \fi + \ProvidesFile{lgrmath.dtx}% + [lgrmath source (\lgrmathversion\space of \lgrmathdate) (JFB)]% + \fi +\fi +\ifnum\extractfiles<2 % extract files +\def\MessageDeFin{\newlinechar10 \let\Msg\message +\Msg{^^J}% +\Msg{********************************************************************^^J}% +\Msg{*^^J}% +\Msg{* To finish the installation you have to move the following^^J}% +\Msg{* file into a directory searched by TeX:^^J}% +\Msg{*^^J}% +\Msg{*\space\space\space\space lgrmath.sty^^J}% +\Msg{*^^J}% +\Msg{* To produce the documentation with source code included run latexmk^^J}% +\Msg{* on extracted lgrmath.tex and then dvipdfmx on lgrmath.dvi^^J}% +\Msg{*^^J}% +\Msg{* Happy TeXing!^^J}% +\Msg{*^^J}% +\Msg{********************************************************************^^J}% +}% +\begingroup +\input docstrip.tex +\askforoverwritefalse +\generate{\nopreamble + \usepostamble\defaultpostamble + \file{lgrmath.tex}{\from{lgrmath.dtx}{tex}}% + \usepreamble\defaultpreamble + \file{lgrmath.sty}{\from{lgrmath.dtx}{sty}}% +} +\endgroup +\fi % end of file extractions +\ifnum\extractfiles=0 +% tex/etex/xetex/etc on lgrmath.dtx, files are now extracted, stop + \MessageDeFin\expandafter\end +\fi +% no file extractions if latex compilation was on lgrmath.tex +\ifdefined\MessageDeFin\AtEndDocument{\MessageDeFin}\fi +\ifnum\Withdvipdfmx>0 +\documentclass [dvipdfmx]{scrartcl} +\else +\documentclass {scrartcl} +\fi +\tracinglostchars=2 + +% use doc with v2 because hyperref option clash with hypdoc +% no time to read documentation now +\usepackage{doc}[=v2] +% but the v2 does not define \cs, it got defined in ltxdoc +\def\cmd#1{\cs{\expandafter\cmd@to@cs\string#1}} +\def\cmd@to@cs#1#2{\char\number`#2\relax} +\DeclareRobustCommand\cs[1]{\texttt{\bslash#1}} +\AtBeginDocument{% +\pdfstringdefDisableCommands{\def\cs#1{\textbackslash\detokenize{#1}}}% +}% +% these are not defined by doc package +\providecommand\marg[1]{% + {\ttfamily\char`\{}\meta{#1}{\ttfamily\char`\}}} +\providecommand\oarg[1]{% + {\ttfamily[}\meta{#1}{\ttfamily]}} +\providecommand\parg[1]{% + {\ttfamily(}\meta{#1}{\ttfamily)}} + +\CodelineNumbered +\AtBeginDocument{\MakeShortVerb\|} +\ifnum\NoSourceCode=1 + \OnlyDescription +\fi +\usepackage[LGR,T1]{fontenc} + +\usepackage{hologo} +%% \hologoFontSetup{general=\upshape\rmfamily} +\DeclareRobustCommand*{\pdfLaTeX}{\hologo{pdfLaTeX}}% +% \DeclareRobustCommand*{\eTeX}{\hologo{eTeX}}% +% \DeclareRobustCommand*{\LuaTeX}{\hologo{LuaTeX}}% +% \DeclareRobustCommand*{\LuaLaTeX}{\hologo{LuaLaTeX}}% +% \DeclareRobustCommand*{\XeTeX}{\hologo{XeTeX}}% +% \DeclareRobustCommand*{\XeLaTeX}{\hologo{XeLaTeX}}% + +\usepackage{geometry} +\usepackage[english]{babel} + +\usepackage{color} +\definecolor{joli}{RGB}{225,95,0} +\definecolor{JOLI}{RGB}{225,95,0} +\definecolor{urlcolor}{RGB}{38,128,192} + +\usepackage{colorframed} +\definecolor{shadecolor}{RGB}{223,219,195} + +\usepackage{hyperref} +\hypersetup{% +linktoc=all,% +breaklinks=true,% +colorlinks=true,% +linkcolor={red},% +urlcolor={urlcolor}, +pdfauthor={Jean-François Burnol},% +pdftitle={The lgrmath package},% +pdfstartview=FitH,% +pdfpagemode=UseNone,% +} +\usepackage{hypcap} +\usepackage{bookmark} +\usepackage{enumerate} + +\usepackage{mlmodern} +\DeclareEncodingSubset{TS1}{mlmtt}{0}% fix \textasciigrave related latex2e#905 + +% useless with doc.sty +% \usepackage{upquote} +% as \verb, verbatim and macrocode do not use \@noligs +\begingroup +\catcode`\'\active\catcode`\`\active +\gdef\MakePrivateLetters{\makeatletter\def`{\textasciigrave}\def'{\textquotesingle}} +\endgroup +% https://github.com/latex3/latex2e/issues/953, for the record + +\usepackage{xspace} + +\newcommand\ctanpkg[1]{\href{https://ctan.org/pkg/#1}{#1}} + +% Vendredi 11 novembre 2022 : impossible d'utiliser \hypersetup{urlcolor=joli} +% dans \section (même sans le \texorpdfstring). +% même si la macro \lgrmath est déclarée robuste mais avec \protected c'est bon +% \DeclareRobustCommand\lgrmath{{\hypersetup{urlcolor=joli}lgrmath}}% <-- BAD +% ceci ok (mais il faudra ajouter \texorpdfstring) +% \protected\def\lgrmath{{\hypersetup{urlcolor=joli}lgrmath}}% <-- OK +% L'erreur est +% Argument of \KVS@@CommaComma has an extra } +% mais je n'ai pas cherché à en savoir plus. De plus dans le code ci-dessous +% on avait plutôt un stack overflow dû à boucle infinie. + +% le \texorpdfstring ne sert à rien si \protected dans \section +\protected\def\lgrmath{% + {\hypersetup{urlcolor=joli}\bfseries \ctanpkg{lgrmath}}\xspace +} +\pdfstringdefDisableCommands{\def\lgrmath{lgrmath\xspace}} + +\usepackage{centeredline} +\usepackage{lgrmath} + +\begin{document}\renewcommand\familydefault\sfdefault +\begin{center} + {\Large The \lgrmath package}\\ + \textsc{Jean-François Burnol}\\ + \texttt{jfbu (at) free (dot) fr}\\ +\footnotesize + This is \lgrmathversion\ (\lgrmathdate) from + source file having \dtxtimestamp\\ + Report issues at \url{https://github.com/jfbu/lgrmath/issues} +\end{center} + +{ +\addtocontents{toc}{\protect\hypersetup{hidelinks}} +\addtocontents{lot}{\protect\hypersetup{hidelinks}} + +\tableofcontents + +\listoftables +} + +\section{Description} + +The \lgrmath package sets the Greek letters {in math mode} (\textbf{only}) to use glyphs +from the LGR-encoded font of one's choice. + +Thus \lgrmath is for people who want \emph{only} to adjust Greek letters in +math mode (and easily configure usage of upright or italic/slanted shapes), +perhaps in the context of having changed Latin letters as well, e.g.\@ from +using the \ctanpkg{frenchmath}% +% +\footnote{Antoine \textsc{Missier}, \emph{Typesetting mathematics according to French rules}, \url{https://ctan.org/pkg/frenchmath}.} +% +package which makes uppercase Latin letters in +math mode render upright, among quite a few other adjustments tailored for +French mathematical typesetting, or the \ctanpkg{mathastext}% +% +\footnote{Verfasser, \emph{Use the text font in math mode}, \url{https://ctan.org/pkg/mathastext}.} +% +package. +Actually \lgrmath is in part inspired from this latter package |LGRgreek| +option and \cs{MTgreekfont} command. But \lgrmath currently does not +incorporate a mechanism for defining and using multiple math versions, each one +with its own font for Greek letters, as is already provided by +\ctanpkg{mathastext}. + +The package is also related to \ctanpkg{libgreek}% +% +\footnote{Verfasser, \emph{Greek letters in math mode from Libertinus or Linux + Libertine/Biolinum}, \url{https://ctan.org/pkg/libgreek}.}, +% +also by the author, and shares most of its codebase, after dropping matters +related to |libgreek-legacy|, and the |scale| option which can not be +implemented generically. + +The Greek letters all come with \cs{...up} and \cs{...it} named variants, and +whether ``bare'' control sequences map to the `|up|' or `|it|' ones can be +configured via package options, even midway in the document via +\cs{lgrmathsetup}. Further, the package optionally defines two math +alphabets \cs{lgrmathup} and \cs{lgrmathit}. What `|up|' and `|it|' +actually mean can be configured using the |upshape| and |itshape| keys at +package loading time. + + +\section{Options of the \lgrmath package} + +\begin{table}[htbp] +\capstart +\DeleteShortVerb\|% a effet global ! +\let\s\string +\ttfamily +\lgrmathsetup{style=fReNcH}% testing lowercasing works + +\centering + +\begin{tabular}{|lc|lc|lc|lc|} +\hline +\s\Alpha&$\Alpha$ &\s\Nu&$\Nu$ &\s\alpha&$\alpha$ &\s\nu&$\nu$\\ +\s\Beta&$\Beta$ &\s\Xi&$\Xi$ &\s\beta&$\beta$ &\s\xi&$\xi$\\ +\s\Gamma&$\Gamma$ &\s\Omicron&$\Omicron$&\s\gamma&$\gamma$ &\s\omicron&$\omicron$\\ +\s\Delta&$\Delta$ &\s\Pi&$\Pi$ &\s\delta&$\delta$ &\s\pi&$\pi$\\ +\s\Epsilon&$\Epsilon$&\s\Rho&$\Rho$ &\s\epsilon&$\epsilon$&\s\rho&$\rho$\\ +\s\Zeta&$\Zeta$ &\s\Sigma&$\Sigma$ &\s\zeta&$\zeta$ &\s\sigma&$\sigma$\\ +\s\Eta&$\Eta$ &\s\Tau&$\Tau$ &\s\eta&$\eta$ &\s\tau&$\tau$\\ +\s\Theta&$\Theta$ &\s\Upsilon&$\Upsilon$&\s\theta&$\theta$ &\s\upsilon&$\upsilon$\\ +\s\Iota&$\Iota$ &\s\Phi&$\Phi$ &\s\iota&$\iota$ &\s\phi&$\phi$\\ +\s\Kappa&$\Kappa$ &\s\Chi&$\Chi$ &\s\kappa&$\kappa$ &\s\chi&$\chi$\\ +\s\Lambda&$\Lambda$ &\s\Psi&$\Psi$ &\s\lambda&$\lambda$ &\s\psi&$\psi$\\ +\s\Mu&$\Mu$ &\s\Omega&$\Omega$ &\s\mu&$\mu$ &\s\omega&$\omega$\\ +\hline +\end{tabular} + +\medskip + +\begin{tabular}{lclclc} +\s\varsigma&$\varsigma$ & \s\digamma&$\digamma$ & \s\varSigma&$\varSigma$ \\ +\s\varvarsigma&$\varvarsigma$ & \s\koppa&$\koppa$ & \s\Sampi&$\Sampi$ \\ +\s\sampi&$\sampi$ & & & \s\Digamma&$\Digamma$ +\end{tabular} +\caption{Greek letters, upright shapes, default family} +\label{table:upright} +\MakeShortVerb\| +\end{table} + +\begin{table}[htbp] +\capstart +\DeleteShortVerb\|% a effet global ! +\let\s\string +\ttfamily +\lgrmathsetup{style=ISO} + +\centering +\begin{tabular}{|lc|lc|lc|lc|} +\hline +\s\Alpha&$\Alpha$ &\s\Nu&$\Nu$ &\s\alpha&$\alpha$ &\s\nu&$\nu$\\ +\s\Beta&$\Beta$ &\s\Xi&$\Xi$ &\s\beta&$\beta$ &\s\xi&$\xi$\\ +\s\Gamma&$\Gamma$ &\s\Omicron&$\Omicron$&\s\gamma&$\gamma$ &\s\omicron&$\omicron$\\ +\s\Delta&$\Delta$ &\s\Pi&$\Pi$ &\s\delta&$\delta$ &\s\pi&$\pi$\\ +\s\Epsilon&$\Epsilon$&\s\Rho&$\Rho$ &\s\epsilon&$\epsilon$&\s\rho&$\rho$\\ +\s\Zeta&$\Zeta$ &\s\Sigma&$\Sigma$ &\s\zeta&$\zeta$ &\s\sigma&$\sigma$\\ +\s\Eta&$\Eta$ &\s\Tau&$\Tau$ &\s\eta&$\eta$ &\s\tau&$\tau$\\ +\s\Theta&$\Theta$ &\s\Upsilon&$\Upsilon$&\s\theta&$\theta$ &\s\upsilon&$\upsilon$\\ +\s\Iota&$\Iota$ &\s\Phi&$\Phi$ &\s\iota&$\iota$ &\s\phi&$\phi$\\ +\s\Kappa&$\Kappa$ &\s\Chi&$\Chi$ &\s\kappa&$\kappa$ &\s\chi&$\chi$\\ +\s\Lambda&$\Lambda$ &\s\Psi&$\Psi$ &\s\lambda&$\lambda$ &\s\psi&$\psi$\\ +\s\Mu&$\Mu$ &\s\Omega&$\Omega$ &\s\mu&$\mu$ &\s\omega&$\omega$\\ +\hline +\end{tabular} +\medskip + +\begin{tabular}{lclclc} +\s\varsigma&$\varsigma$ & \s\digamma&$\digamma$ & \s\varSigma&$\varSigma$ \\ +\s\varvarsigma&$\varvarsigma$ & \s\koppa&$\koppa$ & \s\Sampi&$\Sampi$ \\ +\s\sampi&$\sampi$ & & & \s\Digamma&$\Digamma$ +\end{tabular} +\caption{Greek letters, italic shapes, default family} +\label{table:italic} +\MakeShortVerb\| +\end{table} + +Here are the options recognized by the package: +\begin{description} +\item[font=\meta{font\textunderscore name}] This specifies which font (font family in the + sens of the \LaTeX{} font selection scheme) to use. It defaults to |lmr|. + + In \autoref{table:upright} and \autoref{table:italic} we display the + glyphs from this default font |lmr| in LGR encoding, available to \LaTeX{} + thanks to the + support files from the package (in the sense of CTAN or \TeX Live, not of a + \LaTeX{} document) \ctanpkg{cbfonts-fd}.% +% + \footnote{Claudio~\textsc{Beccari}, \emph{\LaTeX{} font description files for the CB Greek fonts}, \url{https://ctan.org/pkg/cbfonts-fd}.} +% + It is recommended to user to have a look at its + documentation\centeredline{|texdoc cbfonts|} + in particular the section on Customizations which mentions alternate shapes + (such as |rs|, |ro|, |li|, |iv|, |uv| --- those last two are actually for + sans-serif |lmss| ---, and there are also comments relative to the series) + and use appropriately the |upshape|, |itshape|, |series| and |boldseries| + \lgrmath keys which are documented next. + + The allowable names \meta{font\textunderscore name}'s are those |foo| for + which a file |LGRfoo.fd| or |lgrfoo.fd| exists on the system. + + The above remarks about customization apply generally to all fonts, try to + see if there is some documentation associated with the font you choose. + Ultimate experts will look into the |.fd| files to see (for example) if there + is some interface to rescale the fonts by some factor. + + Here is now a list of suitable such font definition files from which you can + extract usable font family names. This has been obtained via exercising the + Unix |find| utility in a \TeX Live 2022 installation (possibly only partial). + To test a font the package provides \cs{lgrmathgreektable} and + \cs{lgrmathgreektableextra} which are documented in the next section. + + \begin{snugshade} +\catcode`\'\active\catcode`\`\active +\def\MacroFont{\ttfamily\footnotesize\def'{\textquotesingle}\def`{\textasciigrave}} +\catcode\string`\`12 \catcode`\' 12 +\begin{verbatim} +in directory /usr/local/texlive/2022/texmf-dist/tex/latex we execute + find . -name 'LGR*fd' +and then rearrange somewhat the output to put it in alphabetical order, +and gain some space horizontally so as to obtain a two-column display +Naturally in many instances the various -TLF, -OsF, and so on, refer +to options of digit characters and have no impact on the Greek letters, +nevertheless I kept all filenames, just pick one, drop LGR and .fd parts. + +./alegreya/ ./librefranklin/ + LGRAlegreya-Inf.fd LGRLibreFranklin-Sup.fd + LGRAlegreya-LF.fd LGRLibreFranklin-TLF.fd + LGRAlegreya-OsF.fd ./linguisticspro/ + LGRAlegreya-Sup.fd LGRLinguisticsPro-LF.fd + LGRAlegreya-TLF.fd LGRLinguisticsPro-OsF.fd + LGRAlegreya-TOsF.fd ./nimbus15/ + LGRAlegreyaSans-Inf.fd LGRNimbuSans.fd + LGRAlegreyaSans-LF.fd LGRNimbusMono.fd + LGRAlegreyaSans-OsF.fd LGRNimbusMonoN.fd + LGRAlegreyaSans-Sup.fd LGRNimbusSerif.fd + LGRAlegreyaSans-TLF.fd ./noto/ + LGRAlegreyaSans-TOsF.fd LGRNotoSans-LF.fd +./clara/ LGRNotoSans-OsF.fd + LGRClara-Sup.fd LGRNotoSans-Sup.fd + LGRClara-TLF.fd LGRNotoSans-TLF.fd + LGRClara-TOsF.fd LGRNotoSans-TOsF.fd +./cochineal/ LGRNotoSansMono-Sup.fd + LGRCochineal-LF.fd LGRNotoSansMono-TLF.fd + LGRCochineal-OsF.fd LGRNotoSansMono-TOsF.fd + LGRCochineal-TLF.fd LGRNotoSerif-LF.fd + LGRCochineal-TOsF.fd LGRNotoSerif-OsF.fd +./comfortaa/ LGRNotoSerif-Sup.fd + LGRcomfortaa.fd LGRNotoSerif-TLF.fd +./dejavu/ LGRNotoSerif-TOsF.fd + LGRDejaVuSans-TLF.fd ./oldstandard/ + LGRDejaVuSansCondensed-TLF.fd LGROldStandard-Sup.fd + LGRDejaVuSansMono-TLF.fd LGROldStandard-TLF.fd + LGRDejaVuSerif-TLF.fd ./opensans/ + LGRDejaVuSerifCondensed-TLF.fd LGRopensans-LF.fd +./domitian/ LGRopensans-OsF.fd + LGRDomitian-Inf.fd LGRopensans-TLF.fd + LGRDomitian-Sup.fd LGRopensans-TOsF.fd + LGRDomitian-TLF.fd ./plex/ + LGRDomitian-TOsF.fd LGRIBMPlexSans-Sup.fd +./droid/ LGRIBMPlexSans-TLF.fd + LGRdroidsans.fd ./roboto/ + LGRdroidsansmono.fd LGRRoboto-LF.fd + LGRdroidserif.fd LGRRoboto-OsF.fd +./ebgaramond/ LGRRoboto-TLF.fd + LGREBGaramond-Inf.fd LGRRoboto-TOsF.fd + LGREBGaramond-LF.fd LGRRobotoMono-TLF.fd + LGREBGaramond-OsF.fd LGRRobotoSerif-LF.fd + LGREBGaramond-Sup.fd LGRRobotoSerif-OsF.fd + LGREBGaramond-TLF.fd LGRRobotoSerif-Sup.fd + LGREBGaramond-TOsF.fd LGRRobotoSerif-TLF.fd + LGREBGaramondInitials-TLF.fd LGRRobotoSerif-TOsF.fd +./fira/ LGRRobotoSlab-TLF.fd + LGRFiraMono-Sup.fd ./sourcesanspro/ + LGRFiraMono-TLF.fd LGRSourceSansPro-Dnom.fd + LGRFiraMono-TOsF.fd LGRSourceSansPro-Inf.fd + LGRFiraSans-LF.fd LGRSourceSansPro-LF.fd + LGRFiraSans-OsF.fd LGRSourceSansPro-Numr.fd + LGRFiraSans-Sup.fd LGRSourceSansPro-OsF.fd + LGRFiraSans-TLF.fd LGRSourceSansPro-Sup.fd + LGRFiraSans-TOsF.fd LGRSourceSansPro-TLF.fd +./garamond-libre/ LGRSourceSansPro-TOsF.fd + LGRGaramondLibre-Inf.fd ./step/ + LGRGaramondLibre-LF.fd LGRSTEP-Inf.fd + LGRGaramondLibre-OsF.fd LGRSTEP-Sup.fd + LGRGaramondLibre-Sup.fd LGRSTEP-TLF.fd +./gofonts/ LGRSTEP-TOsF.fd + LGRGo-TLF.fd ./stepgreek/ + LGRGoMono-TLF.fd LGRSTEPGreekTest-Sup.fd +./lato/ LGRSTEPGreekTest-TLF.fd + LGRlato-LF.fd LGRSTEPGreekTest-TOsF.fd + LGRlato-OsF.fd ./theanodidot/ + LGRlato-TLF.fd LGRTheanoDidot-TLF.fd + LGRlato-TOsF.fd LGRTheanoDidot-TOsF.fd +./libertinegc/ ./theanomodern/ + LGRLinuxLibertineT-LF.fd LGRTheanoModern-TLF.fd + LGRLinuxLibertineT-OsF.fd LGRTheanoModern-TOsF.fd + LGRLinuxLibertineT-TLF.fd ./theanooldstyle/ + LGRLinuxLibertineT-TOsF.fd LGRTheanoOldStyle-TLF.fd +./libertinus-type1/ LGRTheanoOldStyle-TOsF.fd + LGRLibertinusSans-LF.fd LGRLibertinusSerif-TLF.fd + LGRLibertinusSans-OsF.fd LGRLibertinusSerif-TOsF.fd + LGRLibertinusSans-Sup.fd LGRLibertinusSerifDisplay-LF.fd + LGRLibertinusSans-TLF.fd LGRLibertinusSerifDisplay-OsF.fd + LGRLibertinusSans-TOsF.fd LGRLibertinusSerifDisplay-Sup.fd + LGRLibertinusSerif-LF.fd LGRLibertinusSerifDisplay-TLF.fd + LGRLibertinusSerif-OsF.fd LGRLibertinusSerifDisplay-TOsF.fd + LGRLibertinusSerif-Sup.fd LGRLibertinusSerifInitials-TLF.fd + +And now for more, with lowercase `lgr' filenames: find . -name 'lgr*fd' + +./txfontsb/lgrtxr.fd ./cm-lgc/lgrfcm.fd +./txfontsb/lgrtxrc.fd ./cm-lgc/lgrfct.fd +./txfontsb/lgrtxry.fd ./cm-lgc/lgrfcs.fd +./txfontsb/lgrtxryc.fd ./epigrafica/lgrepigrafica.fd +./gfsbodoni/lgrbodoni.fd ./gfssolomos/lgrsolomos.fd +./lxfonts/lgrllcmtt.fd ./tempora/lgrtempora-tlf.fd +./lxfonts/lgrllcmss.fd ./tempora/lgrtempora-tosf.fd +./kerkis/lgrkfn.fd ./gfscomplutum/lgrcomplutum.fd +./kerkis/lgrmaksf.fd ./gfsartemisia/lgrartemisiaeuler.fd +./kerkis/lgrmak.fd ./gfsartemisia/lgrartemisia.fd +./cbfonts-fd/lgrcmro.fd ./gentium-tug/lgrgentiumbook.fd +./cbfonts-fd/lgrcmss.fd ./gentium-tug/lgrgentium.fd +./cbfonts-fd/lgrlmr.fd ./gfsbaskerville/lgrgfsbaskerville.fd +./cbfonts-fd/lgrlcmtt.fd ./miama/lgrfmm.fd +./cbfonts-fd/lgrlmtt.fd ./gfsneohellenic/lgrneohellenic.fd +./cbfonts-fd/lgrlmss.fd ./gfsdidot/lgrudidot.fd +./cbfonts-fd/lgrlmro.fd ./gfsporson/lgrporson.fd +./cbfonts-fd/lgrlcmss.fd +./cbfonts-fd/lgrcmtt.fd +./cbfonts-fd/lgrcmr.fd +\end{verbatim} +\end{snugshade} +\item[upshape=\meta{shape}] Declares the shape to be used by the \cs{...up} + Greek letters and the \cs{lgrmathup} math alphabet. Defaults to `|n|' + (without the quotes). +\item[itshape=\meta{shape}] Declares the shape to be used by the \cs{...it} + Greek letters and the \cs{lgrmathit} math alphabet. Defaults to `|it|'. +\DeleteShortVerb{\|} +\item[style=\meta{\upshape\ttfamily ISO|UP|TeX}] specifies the shape style + of the Greek letters. \MakeShortVerb{\|} + + |ISO| means `italic' for lowercase + and uppercase, |UP| means `upright' for lowercase and uppercase, |TeX| + means `italic' for lowercase and `upright' for uppercase. The lowercase + forms |iso|, |up| and |tex| are also accepted (or any mixed case). + + One can also use |French| or |french| as an alias to |UP| or |up|. + + This option will + override any |greek| or |Greek| option. The package defaults to |style=TeX|. + + What `upright' and `italic' mean is configured by the |upshape| and + |itshape| respective settings.\DeleteShortVerb\| +\item[greek=\meta{\ttfamily\upshape up|it|...}] Says\MakeShortVerb{\|} whether + Greek letters will be `upright' or `italic' i.e.\@ whether they obey the + |upshape| or |itshape| setting, i.e.\@ whether \cs{alpha} et al.\@ are + \cs{let} to \cs{alphaup} (et al.) or to \cs{alphait} (et + al.).\MakeShortVerb{\|} + + So |greek=it| is like |style=ISO|, and |greek=up| is like |style=French|. + + Other + shape values, such as `|n|' and `|sl|' or even `|sc|', are accepted. For + more details, see the explanations for |Greek|. For example |greek=n| is + like |style=UP|. + + This option is ignored if |style| is used (order does not matter). + \DeleteShortVerb\| +\item[Greek=\meta{\ttfamily\upshape up|it|...}] Says\MakeShortVerb{\|} + whether uppercase Greek + letters (and only them) will be `upright' or `italic' i.e.\@ whether they + use |upshape| or |itshape|, i.e.\@ whether \cs{Alpha} et al.\@ are \cs{let} + to \cs{Alphaup} (et al.) or to \cs{Alphait} (et al.).\MakeShortVerb{\|} + + So to obtain lowercase to be `upright' and uppercase to be `italic', use + |greek=up| and then |Greek=it| (|Greek| must appear after |greek| else it will be + shadowed by it). + + This option, like the |greek| option, is ignored if the |style| option is used. + + Other + shape values, such as `|n|' and `|sl|', are accepted. They will then + override the |upshape| setting for it to match it. For example |Greek=sc| + will force |upshape| to be |sc|, because the assumed style is the \TeX{} one + of italic lowercase and upright uppercase, so setting the shape of uppercase + must update the |upshape| value. + +\item[series=\meta{series}] This tells which series to use. The default is the + value of \cs{seriesdefault} at the time of loading the package. There is no + interface to configure distinct series for the `upright' and `italic' + shapes. + +\item[boldseries=\meta{series}] This tells which series to use in bold + math. Default is \cs{bfdefault} at the time of loading the package. There + is no interface to configure distinct series for the `upright' and `italic' + shapes. +\item[alphabets] Says whether to define \cs{lgrmathup} and \cs{lgrmathit}. +\end{description} + +\section{Commands of the \lgrmath package} + +Here are the commands defined by the package: +\begin{description} +\item[\cs{lgrmathsetup}\marg{key=value,...}] The only allowed keys are + |style|, |greek| and |Greek|. And for the latter two only the values |up| + or |it| should be used (or values matching the |upshape| or |itshape| + settings), as it is only possible after package loading time to toggle + between `upright' and `italic' depending on whether the letter is uppercase + or lowercase, but one can not switch to an altogether different shape as + this would require re-declaring the symbol fonts. + + If the |style| key is used, then |greek/Greek| are ignored. However, + one can always naturally reuse later \cs{lgrmathsetup} using only the + |greek| and/or |Greek| keys. +\item[\cs{lgrmathup}] This is a math alphabet. It is defined only if the + package received the |alphabets| option. +\item[\cs{lgrmathit}] This is a math alphabet. It is defined only if the + package received the |alphabets| option. + +\item[\cs{lgrmathgreektable}\marg{family}\marg{series}\marg{shape}] Produces a + tabular display of the Greek letters available with this font. Here is for + example using% +% + \centeredline{|\lgrmathgreektable{Alegreya-TLF}{regular}{n}|} +% + \centeredline{\lgrmathgreektable{Alegreya-TLF}{regular}{n}} + + We used |regular| + for the \meta{series} mandatory argument after seeing Font Info messages in + the |.log| file about the |m| series not being available and being + substituted for by |regular|, so we used |regular| to avoid those messages. +\item[\cs{lgrmathgreektableextra}\marg{family}\marg{series}\marg{shape}] + Produces a tabular with eight additional glyphs. Here is an example, using +% + \centeredline{|\lgrmathgreektableextra{LibertinusSans-TLF}{m}{n}|} +% + \centeredline{\lgrmathgreektableextra{LibertinusSans-TLF}{m}{n}} Beware that if + we had forgotten the |-TLF| suffix, the font would have been substituted in + favour of fall-back |lmr| by \LaTeX. Always check log for font substitutions + messages... + + And see also the last remark below. +\end{description} + +Miscellaneous remarks: +\begin{enumerate} +\item Even if not receiving the option |alphabets|, the package will declare + all Greek letters to be of type \cs{mathalpha}. +\item The \lgrmath package ignores global class options. It handles only + options originating from the \cs{usepackage} preamble declaration (or some + options handed over via \cs{PassOptionsToPackage} or options passed to + \cs{lgrmathsetup} in the preamble or body). +\item The \ctanpkg{libgreek} package defines \cs{mathchar}'s mapping to + lowercase Greek letters with diacritics, but for time being it has been + decided that \lgrmath would restrict its definitions to the 24+24 base glyphs + and the 8 ``extra'' ones for which there are slots in the LGR encoding table. +\item These 8 ``extra glyphs'' will not always be available, depending on the + font. Here is for example with |Alegreya-TLF|: + \centeredline{\lgrmathgreektableextra{Alegreya-TLF}{regular}{n}} Adding + |\tracinglostchars=3| will cause \TeX\ to raise an error in case such missing + characters are encountered. +\end{enumerate} + +This is the end of the user documentation. The next section is a code listing +with some comments for the advanced users. + +\StopEventually{\end{document}\endinput} + +\cleardoublepage +%\newgeometry{hmarginratio=3:2} +\small + +\makeatletter + +\section{Implementation of the \lgrmath package} + +% https://github.com/latex3/latex2e/issues/563 +\AddToHook{env/macrocode/after}{\@nobreakfalse} +\AddToHook{env/macrocode/begin}{\partopsep0pt\relax} + +% The catcode hackery next is to avoid to have the guard be listed +% in the commented source code... (here <*legacy>) +% (c) 2012/11/19 Jean-François Burnol ;-) + +\def\gardesactifs {\catcode`\<=0 \catcode`\>=11 \catcode`\*=11 \catcode`\/=11 } +\def\gardesinactifs {\catcode`\<=12 \catcode`\>=12 \catcode`\*=12 \catcode`\/=12 } +\gardesactifs +\let\relax +\let<*sty>\gardesinactifs + +\MakePercentIgnore +% +%<*sty> +% \begin{macrocode} +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{lgrmath} + [2022/11/16 1.0 Greek in math mode via LGR font of one's choice (JFB)] +% \end{macrocode} +% We will use \ctanpkg{kvoptions} to handle options with |key=value| syntax. +% \begin{macrocode} +\RequirePackage{kvoptions} +% \end{macrocode} +% To minimize the author's task, we keep close to |libgreek.sty| code with minimal +% adaptations. In particular I decided to keep the fact that |style| option +% makes the |Greek| and |greek| options ignored. But there are some +% complications originating in the addition of the \cs{lgrmathsetup}, which +% requires to keep a trace of various things, for example if |style| option is +% used at package level and then later on using \cs{lgrmathsetup} the user +% employs the |Greek/greek| options. +% +% This package assigns two symbol fonts, one for upright, the other one for +% italic-like. +% +% The |upshape| and |itshape| keys allow to configure what the \cs{...up} +% and \cs{...it} macros will actually use as shapes. +% \begin{macrocode} +\def\lgrmath@fontfamily{lmr} +\def\lgrmath@scale{1} +\def\lgrmath@upshape{n} +\def\lgrmath@itshape{it} +\newif\iflgrmath@upper@up\lgrmath@upper@uptrue +\newif\iflgrmath@lower@up +\edef\lgrmath@series{\seriesdefault} +\edef\lgrmath@boldseries{\bfdefault} +\def\lgrmath@upper@shape{\lgrmath@upshape} +\def\lgrmath@lower@shape{\lgrmath@itshape} +\def\lgrmath@style{TeX} +\newif\iflgrmath@sty +% \end{macrocode} +% We use the \ctanpkg{keyval} interface mostly to not have to rework +% everything, if at all possible, into the \ctanpkg{kvoptions} declarative +% interface. It is a very good thing that the latter package can be used +% without forcing on the user its own declarative interface... +% \begin{macrocode} +\define@key{lgrmath}{font}[lmr]{\def\lgrmath@fontfamily{#1}} +\define@key{lgrmath}{upshape}{\edef\lgrmath@upshape{#1}} +\define@key{lgrmath}{itshape}{\edef\lgrmath@itshape{#1}} +% \end{macrocode} +% Compared to |libgreek 1.1| I decide to use \cs{lowercase} and allow |UP| as +% alias of |French|. +% \begin{macrocode} +\define@key{lgrmath}{style}{% + \edef\lgrmath@style{#1}% + \lowercase\expandafter{\expandafter\def\expandafter\lgrmath@style + \expandafter{\lgrmath@style}}% + \lgrmath@stytrue +} +\define@key{lgrmath}{Greek}{\edef\lgrmath@upper@shape{#1}} +% \end{macrocode} +% Attention to not introduce a space token, as this may be used via +% \cs{lgrmathsetup} in document body. +% \begin{macrocode} +\define@key{lgrmath}{greek}{\edef\lgrmath@lower@shape{#1}% + \edef\lgrmath@upper@shape{#1}} +\define@key{lgrmath}{series}{\edef\lgrmath@series{#1}} +\define@key{lgrmath}{boldseries}{\edef\lgrmath@boldseries{#1}} +% \end{macrocode} +% The single Boolean option, a true one as it uses \ctanpkg{kvoptions} interface. +% \begin{macrocode} +\DeclareBoolOption[false]{alphabets} +% \end{macrocode} +% We need some auxiliaries to handle the |style| values. As mentioned +% already, some extra stuff is executed for reasons of various scenarii +% with \cs{lgrmathsetup}. +% \begin{macrocode} +\def\lgrmath@style@iso{% + \lgrmath@upper@upfalse + \lgrmath@lower@upfalse + \let\lgrmath@upper@shape\lgrmath@itshape + \let\lgrmath@lower@shape\lgrmath@itshape +} +\def\lgrmath@style@french{% + \lgrmath@upper@uptrue + \lgrmath@lower@uptrue + \let\lgrmath@upper@shape\lgrmath@upshape + \let\lgrmath@lower@shape\lgrmath@upshape +} +\let\lgrmath@style@up\lgrmath@style@french +\def\lgrmathk@style@tex{% + \lgrmath@upper@uptrue + \lgrmath@lower@upfalse + \let\lgrmath@upper@shape\lgrmath@upshape + \let\lgrmath@lower@shape\lgrmath@itshape +} +% \end{macrocode} +% This always resets the \cs{iflgrmath@sty} to false for \cs{lgrmathsetup} +% being usable with |greek| and |Greek| keys. +% \begin{macrocode} +\def\lgrmath@process@style{% + \lgrmath@styfalse + \ifcsname lgrmath@style@\lgrmath@style\endcsname + \csname lgrmath@style@\lgrmath@style\endcsname + \else + \PackageWarning{lgrmath}{Unknown (here, lowercased) style `\lgrmath@style'}% + \fi +} +% \end{macrocode} +% This stuff is a bit involved. +% \begin{macrocode} +\def\lgrmath@process@shapes{% + \edef\lgrmath@upper@shape{\lgrmath@upper@shape}% + \edef\lgrmath@lower@shape{\lgrmath@lower@shape}% + \ifx\lgrmath@upper@shape\lgrmath@upshape + \lgrmath@upper@uptrue + \else + \ifx\lgrmath@upper@shape\lgrmath@itshape + \lgrmath@upper@upfalse + \else + \expandafter\in@\expandafter{\expandafter.\lgrmath@upper@shape,}{.up,}% + \ifin@\lgrmath@upper@uptrue + \else + \expandafter\in@\expandafter{\expandafter.\lgrmath@upper@shape,}{.it,}% + \ifin@\lgrmath@upper@upfalse + \else + \lgrmath@process@upper@lastresort + \fi\fi\fi\fi + \ifx\lgrmath@lower@shape\lgrmath@itshape + \lgrmath@lower@upfalse + \else + \ifx\lgrmath@lower@shape\lgrmath@upshape + \lgrmath@lower@uptrue + \else + \expandafter\in@\expandafter{\expandafter.\lgrmath@lower@shape,}{.it,}% + \ifin@\lgrmath@lower@upfalse + \else + \expandafter\in@\expandafter{\expandafter.\lgrmath@lower@shape,}{.up,}% + \ifin@\lgrmath@lower@uptrue + \else + \lgrmath@process@lower@lastresort + \fi\fi\fi\fi +}% +\def\lgrmath@process@upper@lastresort{% + \lgrmath@upper@uptrue + \let\lgrmath@upshape\lgrmath@upper@shape +} +\def\lgrmath@process@lower@lastresort{% + \lgrmath@lower@upfalse + \let\lgrmath@itshape\lgrmath@lower@shape +} +% \end{macrocode} +% The fact that packages may be handed global options is rather dangerous. +% Fortunately \ctanpkg{kvoptions} has an interface to handle only local options. +% \begin{macrocode} +\ProcessLocalKeyvalOptions* +% \end{macrocode} +% We now do the post-processing regarding the shape configuration after +% option parsing. Once this is done we will reconfigure slightly +% \cs{lgrmath@process@shapes} for usability in the document preamble or body, +% after the symbol fonts have been declared. As is well-known the \LaTeX{} +% interface to math fonts is full of ``only-preamble'' restrictions. +% \begin{macrocode} +\iflgrmath@sty + \lgrmath@process@style +\else + \lgrmath@process@shapes +\fi +\def\lgrmath@process@upper@lastresort{% + \PackageWarning{lgrmath}{% + Too late for the shape `\lgrmath@upper@shape'\MessageBreak + originating in Greek or greek option. Ignored.\MessageBreak + Use `up' or `it'}% +} +\def\lgrmath@process@lower@lastresort{% + \PackageWarning{lgrmath}{% + Too late for the shape `\lgrmath@lower@shape'\MessageBreak + originating in greek option. Ignored.\MessageBreak + Use `up' or `it'}% +} +\def\lgrmathsetup#1{% + \setkeys{lgrmath}{#1}% + \iflgrmath@sty\lgrmath@process@style\else\lgrmath@process@shapes\fi + \lgrmath@setgreekcs +} +% \end{macrocode} +% Almost all options must be restricted to the package loading time only. +% \begin{macrocode} +\DisableKeyvalOption{lgrmath}{font} +\DisableKeyvalOption{lgrmath}{upshape} +\DisableKeyvalOption{lgrmath}{itshape} +\DisableKeyvalOption{lgrmath}{series} +\DisableKeyvalOption{lgrmath}{boldseries} +\DisableKeyvalOption{lgrmath}{alphabets} +% \end{macrocode} +% Declarations of the two symbol fonts, one for `upright' (or whatever is +% configured by the |upshape| key), one for `italic' (or whatever is configured +% by the |itshape| key). One can not specify distinct series, both +% `upright' and `italic' use the same font series. This could be added but I +% doubt anyone will use the package to start with... +% +% The \ctanpkg{libgreek} of |2022/11/11| extracted the |-TLF| postfix from the +% font family name, to reinsert it here explicitly, the options |serif/sans| +% deciding whether to use |LibertinusSerif-TLF| or |LibertinusSans-TLF| for +% reasons now escaping me. I vaguely remember it was useful at some point +% during development. Ah yes, now I remember this separation was for the +% handling of the |scale| option. And we haven't one here. +% \begin{macrocode} +\DeclareFontEncoding{LGR}{}{} +\DeclareSymbolFont{lgrmathup}{LGR}{\lgrmath@fontfamily} + {\lgrmath@series} + {\lgrmath@upshape} +\SetSymbolFont{lgrmathup}{bold}{LGR}{\lgrmath@fontfamily} + {\lgrmath@boldseries} + {\lgrmath@upshape} +\DeclareSymbolFont{lgrmathit}{LGR}{\lgrmath@fontfamily} + {\lgrmath@series} + {\lgrmath@itshape} +\SetSymbolFont{lgrmathit}{bold}{LGR}{\lgrmath@fontfamily} + {\lgrmath@boldseries} + {\lgrmath@itshape} +% \end{macrocode} +% As all Greek letters are already available in \cs{...up} and \cs{...it} +% variants, it is indeed not immediately pressing to have math alphabets, so +% let's not do it by default. +% \begin{macrocode} +\iflgrmath@alphabets + \DeclareSymbolFontAlphabet{\lgrmathup}{lgrmathup} + \DeclareSymbolFontAlphabet{\lgrmathit}{lgrmathit} +\fi +% \end{macrocode} +% Definition of the `|up|' \cs{mathchar}'s. There are 48 `standard' +% ones plus 8 extras.^^A and 11 with diacritics for a total of 67 ones. +% +% Hesitation whether I should declare with \cs{mathalpha} \emph{only} if +% |alphabets| is passed to the package. +% \begin{macrocode} +\DeclareMathSymbol{\Alphaup}{\mathalpha}{lgrmathup}{65} +\DeclareMathSymbol{\Betaup}{\mathalpha}{lgrmathup}{66} +\DeclareMathSymbol{\Gammaup}{\mathalpha}{lgrmathup}{71} +\DeclareMathSymbol{\Deltaup}{\mathalpha}{lgrmathup}{68} +\DeclareMathSymbol{\Epsilonup}{\mathalpha}{lgrmathup}{69} +\DeclareMathSymbol{\Zetaup}{\mathalpha}{lgrmathup}{90} +\DeclareMathSymbol{\Etaup}{\mathalpha}{lgrmathup}{72} +\DeclareMathSymbol{\Thetaup}{\mathalpha}{lgrmathup}{74} +\DeclareMathSymbol{\Iotaup}{\mathalpha}{lgrmathup}{73} +\DeclareMathSymbol{\Kappaup}{\mathalpha}{lgrmathup}{75} +\DeclareMathSymbol{\Lambdaup}{\mathalpha}{lgrmathup}{76} +\DeclareMathSymbol{\Muup}{\mathalpha}{lgrmathup}{77} +\DeclareMathSymbol{\Nuup}{\mathalpha}{lgrmathup}{78} +\DeclareMathSymbol{\Xiup}{\mathalpha}{lgrmathup}{88} +\DeclareMathSymbol{\Omicronup}{\mathalpha}{lgrmathup}{79} +\DeclareMathSymbol{\Piup}{\mathalpha}{lgrmathup}{80} +\DeclareMathSymbol{\Rhoup}{\mathalpha}{lgrmathup}{82} +\DeclareMathSymbol{\Sigmaup}{\mathalpha}{lgrmathup}{83} +\DeclareMathSymbol{\Tauup}{\mathalpha}{lgrmathup}{84} +\DeclareMathSymbol{\Upsilonup}{\mathalpha}{lgrmathup}{85} +\DeclareMathSymbol{\Phiup}{\mathalpha}{lgrmathup}{70} +\DeclareMathSymbol{\Chiup}{\mathalpha}{lgrmathup}{81} +\DeclareMathSymbol{\Psiup}{\mathalpha}{lgrmathup}{89} +\DeclareMathSymbol{\Omegaup}{\mathalpha}{lgrmathup}{87} +\DeclareMathSymbol{\alphaup}{\mathalpha}{lgrmathup}{97} +\DeclareMathSymbol{\betaup}{\mathalpha}{lgrmathup}{98} +\DeclareMathSymbol{\gammaup}{\mathalpha}{lgrmathup}{103} +\DeclareMathSymbol{\deltaup}{\mathalpha}{lgrmathup}{100} +\DeclareMathSymbol{\epsilonup}{\mathalpha}{lgrmathup}{101} +\DeclareMathSymbol{\zetaup}{\mathalpha}{lgrmathup}{122} +\DeclareMathSymbol{\etaup}{\mathalpha}{lgrmathup}{104} +\DeclareMathSymbol{\thetaup}{\mathalpha}{lgrmathup}{106} +\DeclareMathSymbol{\iotaup}{\mathalpha}{lgrmathup}{105} +\DeclareMathSymbol{\kappaup}{\mathalpha}{lgrmathup}{107} +\DeclareMathSymbol{\lambdaup}{\mathalpha}{lgrmathup}{108} +\DeclareMathSymbol{\muup}{\mathalpha}{lgrmathup}{109} +\DeclareMathSymbol{\nuup}{\mathalpha}{lgrmathup}{110} +\DeclareMathSymbol{\xiup}{\mathalpha}{lgrmathup}{120} +\DeclareMathSymbol{\omicronup}{\mathalpha}{lgrmathup}{111} +\DeclareMathSymbol{\piup}{\mathalpha}{lgrmathup}{112} +\DeclareMathSymbol{\rhoup}{\mathalpha}{lgrmathup}{114} +\DeclareMathSymbol{\sigmaup}{\mathalpha}{lgrmathup}{115} +\DeclareMathSymbol{\tauup}{\mathalpha}{lgrmathup}{116} +\DeclareMathSymbol{\upsilonup}{\mathalpha}{lgrmathup}{117} +\DeclareMathSymbol{\phiup}{\mathalpha}{lgrmathup}{102} +\DeclareMathSymbol{\chiup}{\mathalpha}{lgrmathup}{113} +\DeclareMathSymbol{\psiup}{\mathalpha}{lgrmathup}{121} +\DeclareMathSymbol{\omegaup}{\mathalpha}{lgrmathup}{119} +% \end{macrocode} +% Defintion of the `|it|' \cs{mathchar}'s. +% \begin{macrocode} +\DeclareMathSymbol{\Alphait}{\mathalpha}{lgrmathit}{65} +\DeclareMathSymbol{\Betait}{\mathalpha}{lgrmathit}{66} +\DeclareMathSymbol{\Gammait}{\mathalpha}{lgrmathit}{71} +\DeclareMathSymbol{\Deltait}{\mathalpha}{lgrmathit}{68} +\DeclareMathSymbol{\Epsilonit}{\mathalpha}{lgrmathit}{69} +\DeclareMathSymbol{\Zetait}{\mathalpha}{lgrmathit}{90} +\DeclareMathSymbol{\Etait}{\mathalpha}{lgrmathit}{72} +\DeclareMathSymbol{\Thetait}{\mathalpha}{lgrmathit}{74} +\DeclareMathSymbol{\Iotait}{\mathalpha}{lgrmathit}{73} +\DeclareMathSymbol{\Kappait}{\mathalpha}{lgrmathit}{75} +\DeclareMathSymbol{\Lambdait}{\mathalpha}{lgrmathit}{76} +\DeclareMathSymbol{\Muit}{\mathalpha}{lgrmathit}{77} +\DeclareMathSymbol{\Nuit}{\mathalpha}{lgrmathit}{78} +\DeclareMathSymbol{\Xiit}{\mathalpha}{lgrmathit}{88} +\DeclareMathSymbol{\Omicronit}{\mathalpha}{lgrmathit}{79} +\DeclareMathSymbol{\Piit}{\mathalpha}{lgrmathit}{80} +\DeclareMathSymbol{\Rhoit}{\mathalpha}{lgrmathit}{82} +\DeclareMathSymbol{\Sigmait}{\mathalpha}{lgrmathit}{83} +\DeclareMathSymbol{\Tauit}{\mathalpha}{lgrmathit}{84} +\DeclareMathSymbol{\Upsilonit}{\mathalpha}{lgrmathit}{85} +\DeclareMathSymbol{\Phiit}{\mathalpha}{lgrmathit}{70} +\DeclareMathSymbol{\Chiit}{\mathalpha}{lgrmathit}{81} +\DeclareMathSymbol{\Psiit}{\mathalpha}{lgrmathit}{89} +\DeclareMathSymbol{\Omegait}{\mathalpha}{lgrmathit}{87} +\DeclareMathSymbol{\alphait}{\mathalpha}{lgrmathit}{97} +\DeclareMathSymbol{\betait}{\mathalpha}{lgrmathit}{98} +\DeclareMathSymbol{\gammait}{\mathalpha}{lgrmathit}{103} +\DeclareMathSymbol{\deltait}{\mathalpha}{lgrmathit}{100} +\DeclareMathSymbol{\epsilonit}{\mathalpha}{lgrmathit}{101} +\DeclareMathSymbol{\zetait}{\mathalpha}{lgrmathit}{122} +\DeclareMathSymbol{\etait}{\mathalpha}{lgrmathit}{104} +\DeclareMathSymbol{\thetait}{\mathalpha}{lgrmathit}{106} +\DeclareMathSymbol{\iotait}{\mathalpha}{lgrmathit}{105} +\DeclareMathSymbol{\kappait}{\mathalpha}{lgrmathit}{107} +\DeclareMathSymbol{\lambdait}{\mathalpha}{lgrmathit}{108} +\DeclareMathSymbol{\muit}{\mathalpha}{lgrmathit}{109} +\DeclareMathSymbol{\nuit}{\mathalpha}{lgrmathit}{110} +\DeclareMathSymbol{\xiit}{\mathalpha}{lgrmathit}{120} +\DeclareMathSymbol{\omicronit}{\mathalpha}{lgrmathit}{111} +\DeclareMathSymbol{\piit}{\mathalpha}{lgrmathit}{112} +\DeclareMathSymbol{\rhoit}{\mathalpha}{lgrmathit}{114} +\DeclareMathSymbol{\sigmait}{\mathalpha}{lgrmathit}{115} +\DeclareMathSymbol{\tauit}{\mathalpha}{lgrmathit}{116} +\DeclareMathSymbol{\upsilonit}{\mathalpha}{lgrmathit}{117} +\DeclareMathSymbol{\phiit}{\mathalpha}{lgrmathit}{102} +\DeclareMathSymbol{\chiit}{\mathalpha}{lgrmathit}{113} +\DeclareMathSymbol{\psiit}{\mathalpha}{lgrmathit}{121} +\DeclareMathSymbol{\omegait}{\mathalpha}{lgrmathit}{119} +% \end{macrocode} +% Extras: alternate shapes and other glyphs, `upright'. +% \begin{macrocode} +\DeclareMathSymbol{\varsigmaup}{\mathalpha}{lgrmathup}{99} +\DeclareMathSymbol{\varvarsigmaup}{\mathalpha}{lgrmathup}{6} +\DeclareMathSymbol{\varSigmaup}{\mathalpha}{lgrmathup}{22} +\DeclareMathSymbol{\Sampiup}{\mathalpha}{lgrmathup}{23} +\DeclareMathSymbol{\sampiup}{\mathalpha}{lgrmathup}{27} +\DeclareMathSymbol{\digammaup}{\mathalpha}{lgrmathup}{147} +\DeclareMathSymbol{\Digammaup}{\mathalpha}{lgrmathup}{195} +\DeclareMathSymbol{\koppaup}{\mathalpha}{lgrmathup}{18} +% \end{macrocode} +% Extras: alternate shapes and other glyphs, `italic'. +% \begin{macrocode} +\DeclareMathSymbol{\varsigmait}{\mathalpha}{lgrmathit}{99} +\DeclareMathSymbol{\varvarsigmait}{\mathalpha}{lgrmathit}{6} +\DeclareMathSymbol{\varSigmait}{\mathalpha}{lgrmathit}{22} +\DeclareMathSymbol{\Sampiit}{\mathalpha}{lgrmathit}{23} +\DeclareMathSymbol{\sampiit}{\mathalpha}{lgrmathit}{27} +\DeclareMathSymbol{\digammait}{\mathalpha}{lgrmathit}{147} +\DeclareMathSymbol{\Digammait}{\mathalpha}{lgrmathit}{195} +\DeclareMathSymbol{\koppait}{\mathalpha}{lgrmathit}{18} +% \end{macrocode} +% Some glyphs with diacritics. I decided not to keep this +% in \lgrmath. Let's wait for extremely improbable feature request, as I won't +% do the feature request and will probably remain the sole user. Actually I +% don't think I will ever use this package as contexts where +% it could be useful are those where I would use \ctanpkg{mathastext} and its +% |LGRgreek| option and \cs{MTgreekfont} command... +% \makeatletter\codeline@indexfalse +% \begin{macrocode} +% \DeclareMathSymbol{\alphatonosup}{\mathalpha}{lgrmathup}{136} +% \DeclareMathSymbol{\epsilontonosup}{\mathalpha}{lgrmathup}{232} +% \DeclareMathSymbol{\etatonosup}{\mathalpha}{lgrmathup}{160} +% \DeclareMathSymbol{\iotatonosup}{\mathalpha}{lgrmathup}{208} +% \DeclareMathSymbol{\omicrontonosup}{\mathalpha}{lgrmathup}{236} +% \DeclareMathSymbol{\upsilontonosup}{\mathalpha}{lgrmathup}{212} +% \DeclareMathSymbol{\omegatonosup}{\mathalpha}{lgrmathup}{184} +% \DeclareMathSymbol{\upsilondieresistonosup}{\mathalpha}{lgrmathup}{246} +% \DeclareMathSymbol{\iotadieresisup}{\mathalpha}{lgrmathup}{240} +% \DeclareMathSymbol{\iotadieresistonosup}{\mathalpha}{lgrmathup}{242} +% \DeclareMathSymbol{\upsilondieresisup}{\mathalpha}{lgrmathup}{244} +% \DeclareMathSymbol{\alphatonosit}{\mathalpha}{lgrmathit}{136} +% \DeclareMathSymbol{\epsilontonosit}{\mathalpha}{lgrmathit}{232} +% \DeclareMathSymbol{\etatonosit}{\mathalpha}{lgrmathit}{160} +% \DeclareMathSymbol{\iotatonosit}{\mathalpha}{lgrmathit}{208} +% \DeclareMathSymbol{\omicrontonosit}{\mathalpha}{lgrmathit}{236} +% \DeclareMathSymbol{\upsilontonosit}{\mathalpha}{lgrmathit}{212} +% \DeclareMathSymbol{\omegatonosit}{\mathalpha}{lgrmathit}{184} +% \DeclareMathSymbol{\upsilondieresistonosit}{\mathalpha}{lgrmathit}{246} +% \DeclareMathSymbol{\iotadieresisit}{\mathalpha}{lgrmathit}{240} +% \DeclareMathSymbol{\iotadieresistonosit}{\mathalpha}{lgrmathit}{242} +% \DeclareMathSymbol{\upsilondieresisit}{\mathalpha}{lgrmathit}{244} +% \end{macrocode} +% \codeline@indextrue +% Definition of the \cs{mathchar}'s without `|up/it|' postfix. There are 27=24+3 +% uppercase and 29=24+5 lowercase letters, for a total of 56=48+8 glyphs. Actually, +% I had done some work with LGR in September 2011. I kept +% the file around. But +% at no point did I go back to check if I had done exhaustive work in 2011 and +% whether some other glyphs could be accounted for by LGR (not using +% ligatures) (I did re-check an +% old file about the LGR encoding I had from that 2011 work, but did not try +% to check for updates). Anyway, it is very doubtful whether it made any +% sense for \lgrmath to define control sequences for Greek letters with +% diacritics... +% \begin{macrocode} +\def\lgrmath@setgreekcs{% + \iflgrmath@upper@up + \let\Alpha\Alphaup + \let\Beta\Betaup + \let\Gamma\Gammaup + \let\Delta\Deltaup + \let\Epsilon\Epsilonup + \let\Zeta\Zetaup + \let\Eta\Etaup + \let\Theta\Thetaup + \let\Iota\Iotaup + \let\Kappa\Kappaup + \let\Lambda\Lambdaup + \let\Mu\Muup + \let\Nu\Nuup + \let\Xi\Xiup + \let\Omicron\Omicronup + \let\Pi\Piup + \let\Rho\Rhoup + \let\Sigma\Sigmaup + \let\Tau\Tauup + \let\Upsilon\Upsilonup + \let\Phi\Phiup + \let\Chi\Chiup + \let\Psi\Psiup + \let\Omega\Omegaup + \let\Sampi\Sampiup + \let\Digamma\Digammaup + \let\varSigma\varSigmaup + \else + \let\Alpha\Alphait + \let\Beta\Betait + \let\Gamma\Gammait + \let\Delta\Deltait + \let\Epsilon\Epsilonit + \let\Zeta\Zetait + \let\Eta\Etait + \let\Theta\Thetait + \let\Iota\Iotait + \let\Kappa\Kappait + \let\Lambda\Lambdait + \let\Mu\Muit + \let\Nu\Nuit + \let\Xi\Xiit + \let\Omicron\Omicronit + \let\Pi\Piit + \let\Rho\Rhoit + \let\Sigma\Sigmait + \let\Tau\Tauit + \let\Upsilon\Upsilonit + \let\Phi\Phiit + \let\Chi\Chiit + \let\Psi\Psiit + \let\Omega\Omegait + \let\Sampi\Sampiit + \let\Digamma\Digammait + \let\varSigma\varSigmait + \fi + \iflgrmath@lower@up + \let\alpha\alphaup + \let\beta\betaup + \let\gamma\gammaup + \let\delta\deltaup + \let\epsilon\epsilonup + \let\zeta\zetaup + \let\eta\etaup + \let\theta\thetaup + \let\iota\iotaup + \let\kappa\kappaup + \let\lambda\lambdaup + \let\mu\muup + \let\nu\nuup + \let\xi\xiup + \let\omicron\omicronup + \let\pi\piup + \let\rho\rhoup + \let\sigma\sigmaup + \let\tau\tauup + \let\upsilon\upsilonup + \let\phi\phiup + \let\chi\chiup + \let\psi\psiup + \let\omega\omegaup + \let\varsigma\varsigmaup + \let\varvarsigma\varvarsigmaup + \let\sampi\sampiup + \let\digamma\digammaup + \let\koppa\koppaup +% \end{macrocode} +% The \ctanpkg{doc} |macrocode| makes no provision for being interrupted +% invisibly, it is very complicated (but possible) to do this (see +% \centeredline{\url{https://github.com/latex3/latex2e/issues/847}} +% ), but simplest is to babble something here like this paragraph. +% \codeline@indexfalse +% \begin{macrocode} +% \let\alphatonos\alphatonosup +% \let\epsilontonos\epsilontonosup +% \let\etatonos\etatonosup +% \let\iotatonos\iotatonosup +% \let\omicrontonos\omicrontonosup +% \let\upsilontonos\upsilontonosup +% \let\omegatonos\omegatonosup +% \let\upsilondieresistonos\upsilondieresistonosup +% \let\iotadieresis\iotadieresisup +% \let\iotadieresistonos\iotadieresistonosup +% \let\upsilondieresis\upsilondieresisup +% \end{macrocode} +% babble\codeline@indextrue +% \begin{macrocode} + \else + \let\alpha\alphait + \let\beta\betait + \let\gamma\gammait + \let\delta\deltait + \let\epsilon\epsilonit + \let\zeta\zetait + \let\eta\etait + \let\theta\thetait + \let\iota\iotait + \let\kappa\kappait + \let\lambda\lambdait + \let\mu\muit + \let\nu\nuit + \let\xi\xiit + \let\omicron\omicronit + \let\pi\piit + \let\rho\rhoit + \let\sigma\sigmait + \let\tau\tauit + \let\upsilon\upsilonit + \let\phi\phiit + \let\chi\chiit + \let\psi\psiit + \let\omega\omegait + \let\varsigma\varsigmait + \let\varvarsigma\varvarsigmait + \let\sampi\sampiit + \let\digamma\digammait + \let\koppa\koppait +% \end{macrocode} +% babble\codeline@indexfalse +% \begin{macrocode} +% \let\alphatonos\alphatonosit +% \let\epsilontonos\epsilontonosit +% \let\etatonos\etatonosit +% \let\iotatonos\iotatonosit +% \let\omicrontonos\omicrontonosit +% \let\upsilontonos\upsilontonosit +% \let\omegatonos\omegatonosit +% \let\upsilondieresistonos\upsilondieresistonosit +% \let\iotadieresis\iotadieresisit +% \let\iotadieresistonos\iotadieresistonosit +% \let\upsilondieresis\upsilondieresisit +% \end{macrocode} +% babble\codeline@indextrue +% \begin{macrocode} + \fi +}% +\lgrmath@setgreekcs +% \end{macrocode} +% Finally we define \cs{lgrmathgreektable} and \cs{lgrmathgreektableextra}. +% \begin{macrocode} +\def\lgrmathgreektable#1#2#3{% +\begingroup +\def\s##1{{\usefont{T1}{mlmtt}{m}{n}\string##1}}% +\usefont{LGR}{#1}{#2}{#3}% +\begin{tabular}{|lc|lc|lc|lc|} +\hline +\s\Alpha &A &\s\Nu &N &\s\alpha &a &\s\nu &n \\ +\s\Beta &B &\s\Xi &X &\s\beta &b &\s\xi &x \\ +\s\Gamma &G &\s\Omicron&O &\s\gamma &g &\s\omicron&o \\ +\s\Delta &D &\s\Pi &P &\s\delta &d &\s\pi &p \\ +\s\Epsilon&E &\s\Rho &R &\s\epsilon&e &\s\rho &r \\ +\s\Zeta &Z &\s\Sigma &S &\s\zeta &z &\s\sigma &s \\ +\s\Eta &H &\s\Tau &T &\s\eta &h &\s\tau &t \\ +\s\Theta &J &\s\Upsilon&U &\s\theta &j &\s\upsilon&u \\ +\s\Iota &I &\s\Phi &F &\s\iota &i &\s\phi &f \\ +\s\Kappa &K &\s\Chi &Q &\s\kappa &k &\s\chi &q \\ +\s\Lambda &L &\s\Psi &Y &\s\lambda &l &\s\psi &y \\ +\s\Mu &M &\s\Omega &W &\s\mu &m &\s\omega &w \\ +\hline +\end{tabular} +\endgroup +}% +\def\lgrmathgreektableextra#1#2#3{% +\begingroup +\def\s##1{{\usefont{T1}{mlmtt}{m}{n}\string##1}}% +\usefont{LGR}{#1}{#2}{#3}% +\begin{tabular}{lclclc} +\s\varsigma &\char99 &\s\digamma&\char147 &\s\varSigma&\char22 \\ +\s\varvarsigma&\char6 &\s\koppa &\char18 &\s\Sampi &\char23 \\ +\s\sampi &\char27 & & &\s\Digamma &\char195\relax +\end{tabular} +\endgroup +}% +% \end{macrocode} +% And we have now reached the end of the \lgrmath package code. The actual +% |.sty| file will contain an \cs{endinput} added by the DocStrip extraction. +%\MakePercentComment +% +%<*dtx> +\Finale +%% +%% End of file `lgrmath.dtx'. diff --git a/macros/latex/contrib/lgrmath/lgrmath.pdf b/macros/latex/contrib/lgrmath/lgrmath.pdf new file mode 100644 index 0000000000..e5c483014f Binary files /dev/null and b/macros/latex/contrib/lgrmath/lgrmath.pdf differ diff --git a/macros/latex/contrib/memoir/README b/macros/latex/contrib/memoir/README index 81d34511ab..b2581abbb8 100644 --- a/macros/latex/contrib/memoir/README +++ b/macros/latex/contrib/memoir/README @@ -27,6 +27,32 @@ o Backmatter: Command glossary ~ 40 pages Bibliography, indexes ~ 50 pages +Changes (2022/11/17) + +o memoir v3.7.19 + +-- changed version numbering to v3.7. (no plans to switch to 3.8 + anytime soon) +-- changed several \theH... definitions to \def to work better with + coming hyperref/tagging changes +-- similar for \toclevel@... macros +-- only declare \stockwidth and \stockheight if the kernel hadn't done + so already + +o memhfixc v1.20 + +-- removed patch for \@starttoc and thus \Hy@AtBeginDocument is no + longer used in memhfixc + + +WORD OF ADVISE: It is not recommended to manually update memoir into +an old LaTeX installation. Not all changes are compatible with older +un-updated LaTeX systems and we often make no attempt to be backwards +compatible. Instead we assume that users use updated memoir with an +updated LaTeX installation. + + + Changes (2022/07/29) o memoir v3.7r diff --git a/macros/latex/contrib/memoir/memman.pdf b/macros/latex/contrib/memoir/memman.pdf index 92cc612001..4b27d2a696 100644 Binary files a/macros/latex/contrib/memoir/memman.pdf and b/macros/latex/contrib/memoir/memman.pdf differ diff --git a/macros/latex/contrib/memoir/memoir.dtx b/macros/latex/contrib/memoir/memoir.dtx index fc65ffc581..ba5162db9d 100644 --- a/macros/latex/contrib/memoir/memoir.dtx +++ b/macros/latex/contrib/memoir/memoir.dtx @@ -20,7 +20,7 @@ % This work consists of the files listed in the README file. % % \fi -% \CheckSum{31043} +% \CheckSum{31045} % % \changes{v0.1}{2001/05/20}{First public alpha release} % \changes{v0.2}{2001/06/03}{First beta release} @@ -128,6 +128,8 @@ % \changes{v3.7o}{2021/03/23}{Maintenance and a provide for the kernel} % \changes{v3.7p}{2021/06/16}{Small adjustments of memoir vs memhfixc} % \changes{v3.7q}{2022/02/20}{Maintenance} +% \changes{v3.7r}{2022/07/29}{Maintenance} +% \changes{v3.7.19}{2022/11/17}{Maintenance} % % \def\dtxfile{memoir.dtx} % @@ -194,6 +196,7 @@ % \def\fileversion{v3.7p} \def\filedate{2021/06/16} % \def\fileversion{v3.7q} \def\filedate{2022/02/20} % \def\fileversion{v3.7r} \def\filedate{2022/07/29} +% \def\fileversion{v3.7.19} \def\filedate{2022/11/17} % \title{The LaTeX \Lpack{memoir} class for configurable book % typesetting: Source code\thanks{This % file (\texttt{\dtxfile}) has version number \fileversion, last revised @@ -424,11 +427,11 @@ % Announce the name, option files and version for LaTeX2e files: % \begin{macrocode} %\ProvidesClass{memoir}% -% [2022/07/29 v3.7r configurable book, report, article document class] +% [2022/11/17 v3.7.19 configurable book, report, article document class] % \end{macrocode} % In the manual it is useful to know the current version. % \begin{macrocode} -%\newcommand\memversion{v3.7r, 2022/07/29} +%\newcommand\memversion{v3.7.19, 2022/11/17} %<9pt>\ProvidesFile{mem9.clo}% %<9pt> [2022/07/29 v0.5 memoir class 9pt size option] %<10pt>\ProvidesFile{mem10.clo}% @@ -1922,9 +1925,11 @@ % same as the physical sheet. In other words the \cs{paper...} and \cs{stock...} % sizes are the same and the trims are zero. This class assumes that this % is the normal case. +% \changes{v3.7.19}{2022/10/28}{\cs{stockheight} and \cs{stockwidth} +% may now be defined in the kernel, so we test for that} % \begin{macrocode} -\newlength{\stockheight} -\newlength{\stockwidth} +\@ifundefined{stockwidth}{\newdimen{\stockheight}}{} +\@ifundefined{stockwidth}{\newdimen{\stockwidth}}{} \newlength{\trimtop} \newlength{\trimedge} @@ -8916,9 +8921,11 @@ % \begin{macro}{\theHbook} % \begin{macro}{\toclevel@book} % These are needed if the \Lpack{hyperref} is used. +% \changes{v3.7.19}{2022/10/25}{Changing \cs{theH...} definitions to +% \cs{def} for better compability with future hyperref} % \begin{macrocode} -\newcommand*{\theHbook}{\arabic{book}} -\newcommand*{\toclevel@book}{-2} +\def\theHbook{\arabic{book}} +\def\toclevel@book{-2} % \end{macrocode} % \end{macro} @@ -14291,10 +14298,12 @@ % lines of a poem. See the thread \textit{PDFTEX/Hyperef hates memoir verse % environment?} on \ctt{} October 2002. % \changes{v1.3}{2002/11/14}{Added verse counter and \cs{theHpoemline}} +% \changes{v3.7.19}{2022/10/25}{Changing \cs{theH...} definitions to +% \cs{def} for better compability with future hyperref} % \begin{macrocode} \newcounter{verse} \setcounter{verse}{0} -\newcommand{\theHpoemline}{\theverse.\thepoemline} +\def\theHpoemline{\theverse.\thepoemline} % \end{macrocode} % \end{macro} @@ -23683,6 +23692,8 @@ % \begin{macro}{\toclevel@part} % Needed if the \Lpack{hyperref} package is used. % \changes{v1.61803}{2008/01/30}{Added \cs{toclevel@part} (mempatch v4.9)} +% \changes{v3.7.19}{2022/10/25}{Changing \cs{toclevel@part} definition to +% \cs{def} for better compability with future hyperref} % \begin{macrocode} \newcommand*{\toclevel@part}{-1} @@ -23843,8 +23854,10 @@ % \begin{macro}{\toclevel@chapter} % Needed if the \Lpack{hyperref} package is used. % \changes{v1.61803}{2008/01/30}{Added \cs{toclevel@chapter} (mempatch v4.9)} +% \changes{v3.7.19}{2022/10/25}{Changing \cs{toclevel@chapter} definitions to +% \cs{def} for better compability with future hyperref} % \begin{macrocode} -\newcommand*{\toclevel@chapter}{0} +\def\toclevel@chapter{0} % \end{macrocode} % \end{macro} @@ -23865,10 +23878,12 @@ % \begin{macro}{\toclevel@appendix} % Similar to \cs{l@chapter} but for an appendix. % \changes{v1.618}{2005/09/03}{Added \cs{l@apendix} (mempatch v3.12)} +% \changes{v3.7.19}{2022/10/25}{Changing \cs{toclevel@appendix} definitions to +% \cs{def} for better compability with future hyperref} % \begin{macrocode} \newcommand*{\l@appendix}[2]{% \l@chapapp{#1}{#2}{\cftappendixname}} -\newcommand{\toclevel@appendix}{0} +\def\toclevel@appendix{0} % \end{macrocode} % \end{macro} @@ -24441,9 +24456,11 @@ % as `division levels'. % \changes{v1.61803}{2008/01/30}{Added \cs{toclevel@none} and \cs{toclevel@all} % (mempatch v4.4)} +% \changes{v3.7.19}{2022/10/25}{Changing \cs{toclevel@...} definitions to +% \cs{def} for better compability with future hyperref} % \begin{macrocode} -\newcommand*{\toclevel@none}{-10} -\newcommand*{\toclevel@all}{50} +\def\toclevel@none{-10} +\def\toclevel@all{50} % \end{macrocode} % \end{macro} diff --git a/macros/latex/contrib/memoir/mempatch.dtx b/macros/latex/contrib/memoir/mempatch.dtx index 135166c33e..540bc678cb 100644 --- a/macros/latex/contrib/memoir/mempatch.dtx +++ b/macros/latex/contrib/memoir/mempatch.dtx @@ -20,7 +20,7 @@ % % % \fi -% \CheckSum{609} +% \CheckSum{610} % % \def\dtxfile{\texttt{mempatch.dtx}} % \def\fileversion{v1.0} \def\filedate{2003/10/04} @@ -309,6 +309,10 @@ %% With thanks to Heiko Oberdiek, if you use hyperref dated 2006/11/15 %% or later, memhfixc will be automatically loaded after hyperref. %% +%% With the new hooking system from 2020+ memhfixc is automatically +%% added after loading hyperref with the memoir class +%% +%% Version 1.20 2022/11/17 %% Version 1.19 2021/06/16 %% Version 1.18 2019/10/24 %% Version 1.17 2013/05/30 @@ -334,7 +338,7 @@ % % % \begin{macrocode} -\ProvidesPackage{memhfixc}[2021/06/16 v1.19 nameref/hyperref package fixes for memoir class] +\ProvidesPackage{memhfixc}[2022/11/17 v1.20 nameref/hyperref package fixes for memoir class] % \end{macrocode} % % \begin{macro}{\M@hfixcfinish} @@ -821,40 +825,42 @@ % \end{macro} % % \changes{v1.16}{2013/05/14}{Added fix for \cs{@starttoc}} -% \begin{macro}{\@starttoc} -% In memoir we altered \cs{@starttoc} such that \cs{tableofcontents} -% could be used multiple times. \Lpack{hyperref} resets this. So here -% is our reset of that reset. +% \changes{v1.20}{2022/11/17}{Patch removed, by 2022 hyperref no longer change \cs{@starttoc}} +% ^^A \begin{macro}{\@starttoc} +% ^^A In memoir we altered \cs{@starttoc} such that \cs{tableofcontents} +% ^^A could be used multiple times. \Lpack{hyperref} resets this. So here +% ^^A is our reset of that reset. % \begin{macrocode} -\Hy@AtBeginDocument{% - \ifx\hyper@last\@undefined - \def\@starttoc#1{% - \begingroup\makeatletter - \IfFileExists{\jobname.#1}{% - \Hy@WarningNoLine{% - old #1 file detected, not used; run LaTeX again% - }% - }{}% - \if@filesw + % patch removed as per 2022 hyperref does not touch \@starttoc + % \Hy@AtBeginDocument{% + % \ifx\hyper@last\@undefined + % \def\@starttoc#1{% + % \begingroup\makeatletter + % \IfFileExists{\jobname.#1}{% + % \Hy@WarningNoLine{% + % old #1 file detected, not used; run LaTeX again% + % }% + % }{}% + % \if@filesw % \end{macrocode} -% We rewrite this part to match our definition. The rest is a copy -% from \texttt{hyperref.sty}. +% ^^A We rewrite this part to match our definition. The rest is a copy +% ^^A from \texttt{hyperref.sty}. % \begin{macrocode} - \AtEndDocument{% - \expandafter\ifx\csname tf@#1\endcsname\relax - \expandafter\newwrite\csname tf@#1\endcsname - \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax - \fi - } - \fi - \@nobreakfalse - \endgroup - }% - \fi -} + % \AtEndDocument{% + % \expandafter\ifx\csname tf@#1\endcsname\relax + % \expandafter\newwrite\csname tf@#1\endcsname + % \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax + % \fi + % } + % \fi + % \@nobreakfalse + % \endgroup + % }% + % \fi + % } % \end{macrocode} -% \end{macro} +% ^^A \end{macro} % % \changes{v3.6k}{2013/05/16}{Added hyperref support for page notes} % In order to enable hyperlinks for page notes, we need a few extra diff --git a/macros/latex/contrib/udes-genie-these/README.md b/macros/latex/contrib/udes-genie-these/README.md index b0caad3982..82bbd5cff7 100644 --- a/macros/latex/contrib/udes-genie-these/README.md +++ b/macros/latex/contrib/udes-genie-these/README.md @@ -1,12 +1,31 @@ # udes-genie-these : Thesis class for the Faculté de génie at the Université de Sherbrooke ## Overview -The udes-genie-these class can be used for Ph.D. thesis, master's thesis and project definition at the Faculté de génie of the Université de Sherbrooke (Québec, Canada). The class file is coherent with the latest version of the *Protocole de rédaction aux études supérieures* which is available on the faculty's intranet. +The udes-genie-these class can be used for Ph.D. thesis, master's thesis and project definition at the Faculté de génie of the Université de Sherbrooke (Québec, Canada). The class file is coherent with the latest version of the *Protocole de rédaction aux études supérieures* which is available on the faculty's intranet. The class file documentation is in French, the language of the typical user at the Université de Sherbrooke. An example of use is also distributed with the documentation. Copyright (C) 2017-2022 Charles-Antoine Brunet +## Class file (udes-genie-these.cls) +The class file (udes-genie-these.cls) is generated with the installation file (udes-genie-these.ins) with the following command: +``` +pdflatex udes-genie-these.ins +``` + +## File description +#### Documentation +- udes-genie-these.pdf: package documentation +- README.md: this file + +#### Source files +- udes-genie-these.dtx: source file +- udes-genie-these.ins: installation file + +#### Usage example +- document.tex: main file +- \*.tex, \*.bib: files used by main file + ## Authors and maintainers Charles-Antoine Brunet (Charles-Antoine.Brunet@USherbrooke.ca) @@ -21,16 +40,4 @@ CTAN: [udes-genie-these](https://www.ctan.org/pkg/udes-genie-these) TDS archive: [udes-genie-these.tds.zip](https://mirrors.ctan.org/install/macros/latex/contrib/udes-genie-these.tds.zip) -### File description - -#### Documentation -- udes-genie-these.pdf: package documentation -- README.md: this file - -#### Source files -- udes-genie-these.dtx: source file -- udes-genie-these.ins: installation file - -#### Usage example -- document.tex: main file -- \*.tex, \*.bib: files used by main file +Flat archive: [udes-genie-these.zip](https://mirrors.ctan.org/macros/latex/contrib/udes-genie-these.zip) diff --git a/macros/latex/contrib/udes-genie-these/document.tex b/macros/latex/contrib/udes-genie-these/document.tex index 88b1c28de5..66245c37d8 100644 --- a/macros/latex/contrib/udes-genie-these/document.tex +++ b/macros/latex/contrib/udes-genie-these/document.tex @@ -18,6 +18,7 @@ fichier-symboles = {symboles}, fichier-acronymes = {acronymes}, fichiers-references = {references}, + %style-references = plain, } \TitreFrancais{CECI EST LE TITRE FRANÇAIS\\DU DOCUMENT} @@ -46,44 +47,39 @@ Ce document est uniquement un exemple d'utilisation de la classe \LaTeX\ pour la Les références bibliographiques qui suivent ne sont que des exemples. Il faut se référer au protocole pour plus de détails à ce sujet afin de s'assurer que les références sont complètes et qu'elles sont conformes aux exigences. \begin{description} - \item[Article de périodique:] - il y a quatre exemples \cite{burke1995,pattamatta2009,roy2009,vasu2009}. - - \item[Articles d'actes de conférence:] - il y a un exemple court~\cite{prevenslik2009} - et deux exemples longs~\cite{coronado2005,yeung2006}. - - \item[Livre:] - il y a un exemple court~\cite{russell2003} - et trois exemples longs~\cite{kececioglu1991,knuth1998-num,meriam2010}. - - \item[Livre, une partie:] - il y a deux exemples courts~\cite{cormen2001,gaddis2009} - et trois exemples longs~\cite{kececioglu1991-partie,knuth1998-sort,meriam2010-dist}. - - \item[Essai, mémoire et thèse:] - il y a un exemple de chacun \cite{belanger2009,chafei2009,francoeur2009}. - - \item[Rapport technique:] - il y a un exemple court~\cite{belley2008} - et un exemple long~\cite{kernighan1991}. - + \item[Article de périodique:] + il y a quatre exemples \cite{burke1995,pattamatta2009,roy2009,vasu2009}. + + \item[Articles d'actes de conférence:] + il y a un exemple court~\cite{prevenslik2009} + et deux exemples longs~\cite{coronado2005,yeung2006}. + + \item[Livre:] + il y a un exemple court~\cite{russell2003} + et trois exemples longs~\cite{kececioglu1991,knuth1998-num,meriam2010}. + + \item[Livre, une partie:] + il y a deux exemples courts~\cite{cormen2001,gaddis2009} + et trois exemples longs~\cite{kececioglu1991-partie,knuth1998-sort,meriam2010-dist}. + + \item[Essai, mémoire et thèse:] + il y a un exemple de chacun \cite{belanger2009,chafei2009,francoeur2009}. + + \item[Rapport technique:] + il y a un exemple court~\cite{belley2008} + et un exemple long~\cite{kernighan1991}. + \end{description} \chapter{CONCEPTION} -Le texte de la conception. \chapter{RÉALISATION} -Le texte de la réalisation. \chapter{TESTS} -Le texte des tests. \chapter{ANALYSE} -Le texte de l'analyse. \chapter{CONCLUSION} -Le texte de la conclusion. \appendix diff --git a/macros/latex/contrib/udes-genie-these/udes-genie-these.dtx b/macros/latex/contrib/udes-genie-these/udes-genie-these.dtx index 9b8282eb1b..25fa39a50e 100644 --- a/macros/latex/contrib/udes-genie-these/udes-genie-these.dtx +++ b/macros/latex/contrib/udes-genie-these/udes-genie-these.dtx @@ -16,7 +16,7 @@ % can be contacted at Charles-Antoine.Brunet@USherbrooke.ca % % This work consists of the files udes-genie-these.dtx and udes-genie-these.ins -% and the derived files udes-genie-these.cls and udes-genie-protocole.cls. +% and the derived files udes-genie-these.cls. % \fi % % \iffalse @@ -26,9 +26,9 @@ %%%--- IDENTIFICATION ------------------------------------------------------ %%%----------------------------------------------------------------------------- % -%\ProvidesExplClass{udes-genie-these}{2022/09/19}{3.0} +%\ProvidesExplClass{udes-genie-these}{2022/11/17}{3.0.1} % {Université de Sherbrooke, Faculté de génie: classe de document pour thèses, mémoires, DPR et essais} -%\ProvidesExplClass{udes-genie-protocole}{2022/09/19}{3.0} +%\ProvidesExplClass{udes-genie-protocole}{2022/11/17}{3.0.1} % {Université de Sherbrooke, Faculté de génie: classe de document pour le protocole de rédaction} % %<*driver> @@ -37,7 +37,7 @@ \usepackage{kpfonts} \usepackage[scaled=0.7881]{DejaVuSansMono} \usepackage[french]{babel} -\usepackage{hologo,setspace,parskip,microtype,xspace,tabularx,xcolor,pifont,bookmark} +\usepackage{setspace,xspace,hologo,bookmark} \hypersetup{colorlinks=true,linkcolor=blue,citecolor=blue,urlcolor=blue,linktocpage=true} \setlength{\parskip}{1ex plus0.25ex minus0ex} \setlength{\parindent}{0em} @@ -69,11 +69,12 @@ \newcommand{\xelatex}{\hologo{XeLaTeX}\xspace} \newcommand{\bibtex}{\hologo{BibTeX}\xspace} \newcommand{\fac}{Faculté de génie\xspace} +\newcommand{\overleaf}{\textsf{Overleaf}\xspace} \newcommand{\protocole}{protocole\xspace} \newcommand{\protocoleit}{\textit{protocole}\xspace} \newcommand{\protlgu}{\textit{Protocole de rédaction aux études supérieures} de la Faculté de génie de l'Université de Sherbrooke\xspace} -\newcommand{\package}{\textit{package}\xspace} -\newcommand{\packages}{\textit{packages}\xspace} +\newcommand{\package}{package\xspace} +\newcommand{\packages}{packages\xspace} \newcommand{\encodage}{UTF-8\xspace} \newcommand{\enchamp}{\textbf{Nom} & \textbf{Requis} & \textbf{\bibtex}} \newcommand{\non}{} @@ -81,11 +82,12 @@ \newcommand{\bipm}{\textit{Bureau international des poids et mesures} (BIPM)\xspace} \newcommand{\siunitx}{\pkg{siunitx}\xspace} \newcommand{\windows}{\textsf{Windows}\xspace} \newcommand{\macos}{\textsf{MacOS}\xspace} -\newcommand{\linux}{\textsf{Linux} (ou \textsf{UNIX})\xspace} +\newcommand{\linux}{\textsf{Linux}\xspace} \newcommand{\miktex}{\textsf{MiKTeX}\xspace} \newcommand{\texlive}{\textsf{TeX Live}\xspace} \newcommand{\tlmgr}{\textsf{TeX Live Manager}\xspace} \newcommand{\ctan}{\textsf{CTAN}\xspace} +\newcommand{\ctanurl}{\url{https://www.ctan.org/pkg/udes-genie-these}} \addto\captionsfrench { @@ -122,17 +124,18 @@ % Grave accent \` Left brace \{ Vertical bar \| % Right brace \} Tilde \~} % -% \changes{v1.0}{2017/01/18}{Première version} -% \changes{v1.1}{2017/07/18}{Adaptation à la nouvelle version de babel (changements internes).} -% \changes{v1.2}{2017/12/21}{Adaptation à la nouvelle version de \latexiii (changements internes).} -% \changes{v2.0}{2018/09/26}{Adaptations pour respecter la nouvelle version du protocole de rédaction.} -% \changes{v2.0.1}{2020/04/17}{(Aussi connue comme v2.0a) Correction de bugs. Changements internes. Changements à la documentation.} -% \changes{v2.0.2}{2022/09/15}{Maintenance (LaTeX2e 2020-02-02 à 2022-06-01). Distribution sur CTAN. Adaptations à la documentation.} -% \changes{v3.0}{2022/09/19}{Support pour \pkg{biblatex}. Ajustements à la documentation.} +% \changes{v1.0}{2017-01-18}{Première version} +% \changes{v1.1}{2017-07-18}{Adaptation à la nouvelle version de babel (changements internes).} +% \changes{v1.2}{2017-12-21}{Adaptation à la nouvelle version de \latexiii (changements internes).} +% \changes{v2.0}{2018-09-26}{Adaptations pour respecter la nouvelle version du protocole de rédaction.} +% \changes{v2.0.1}{2020-04-17}{(Aussi connue comme v2.0a) Correction de bugs. Changements internes. Changements à la documentation.} +% \changes{v2.0.2}{2022-09-15}{Maintenance (LaTeX2e 2020-02-02 à 2022-06-01). Distribution sur CTAN. Adaptations à la documentation.} +% \changes{v3.0}{2022-09-19}{Support pour \pkg{biblatex}. Ajustements à la documentation.} +% \changes{v3.0.1}{2022-11-17}{Ajustements à la documentation et à l'exemple. Changements internes pour les formats LaTeX antérieurs à 2022-06-01.} % % \GetFileInfo{udes-genie-these.dtx} % -% \title{CLASSE DE DOCUMENT \latex POUR LA RÉDACTION AUX ÉTUDES SUPÉRIEURES À LA FACULTÉ DE GÉNIE DE L'UNIVERSITÉ DE SHERBROOKE\\{\large \laclassetitre, version 3.0}} +% \title{CLASSE DE DOCUMENT \latex POUR LA RÉDACTION AUX ÉTUDES SUPÉRIEURES À LA FACULTÉ DE GÉNIE DE L'UNIVERSITÉ DE SHERBROOKE\\{\large \laclassetitre, version 3.0.1}} % \author{Charles-Antoine Brunet} % \date{\today} % \maketitle @@ -155,11 +158,34 @@ % Le style de références est maintenant spécifié par le nouveau champ de configuration |style-references| et non plus dans le préambule avec la macro |\bibliographystyle|. Il faut donc retirer une macro du style |\bibliographystyle{AAA}| et la remplacer par un champ de configuration du genre |style-references = AAA|. C'est tout! % %\section{Installation et vérification}\label{sec:inst} -% La classe \laclasse peut être installée comme tout autre \package \latex avec le gestionnaire de \packages de votre distribution, comme avec la console de \miktex ou le \tlmgr. Du même coup, il faut s'assurer d'avoir une version à jour de la distribution \latex, car \laclasse utilise des fonctionnalités qui sont relativement récentes. Si vous optez pour une installation manuelle, la classe \laclasse est disponible sur \href{https://www.ctan.org/pkg/udes-genie-these/}{\ctan} et s'installe en accord avec les procédures spécifiées dans la documentation de votre distribution \latex. +% La classe \laclasse utilise des fonctionnalités récentes de \latex. Il faut donc vous assurer d'avoir une version à jour de votre distribution \latex (\texlive ou \miktex). Si vous utilisez \overleaf, il faut vous assurer que votre projet compile avec la version la plus récente de \texlive. % -% Afin de vérifier que l'installation s'est bien passée, il vous est suggéré de compiler l'exemple distribué avec \laclasse. C'est le meilleur moyen de savoir si votre installation \latex a tout ce qu'il faut pour utiliser \laclasse. L'exemple se trouve dans le même répertoire que la documentation. +% Selon votre distribution \latex, choisissez l'option qui vous convient. +% la classe +% \subsection*{\texlive et \miktex} +% La classe \laclasse est disponible sur \ctan et fait donc partie des deux distributions. Il y a deux options d'installation: pour une utilisation à \textit{long terme} et pour une utilisation à \textit{court terme}. % -% L'exemple distribué avec \laclasse a été compilé avec succès sous \windows, \macos et \linux avec les distributions \miktex et \texlive. Si des erreurs surviennent avec l'exemple, c'est que l'installation de \laclasse n'a pas été faite correctement ou que l'installation de \latex qui est utilisée est incomplète ou désuète. +% \textbf{Long terme:}\\ Si vous pensez utiliser \laclasse pour les prochaines années, alors vous pouvez installer \laclasse comme tout autre \package \latex: à partir de la console de \miktex ou du \tlmgr. Si vous utilisez \miktex, n'oubliez pas d'installer aussi la documentation, car elle est installée séparément. Une fois la classe \laclasse installée, les mises à jour de \laclasse seront faites en même temps que les autres \packages à chaque fois que vous faites une mise à jour de votre distribution \latex. +% +% Notez que la documentation de \laclasse est disponible avec la commande |texdoc|, comme tous les autres \packages \latex. Sur la ligne de commande, il suffit d'utiliser la commande suivante: +% +% |texdoc udes-genie-these| +% +% L'exemple d'utilisation qui est distribué avec \laclasse est situé dans le même répertoire que la documentation, donc dans un sous-répertoire de votre distribution \latex. +% +% \textbf{Court terme:}\\ Si vous pensez utiliser \laclasse à court terme, alors procédez comme avec \overleaf, comme ce qui suit. +% +% \subsection*{\overleaf} +% Pour utiliser \laclasse, vous pouvez récupérer une archive sur l'intranet de la Faculté de génie qui contient tout ce qu'il faut pour compiler un document. L'archive contient aussi la documentation et un exemple d'utilisation. Il suffit d'extraire les fichiers de l'archive dans un répertoire et de compiler avec votre variante favorite de \latex. Cette méthode fonctionne pour \overleaf et aussi toutes les autres distributions \latex. +% +% Cette manière de fonctionner fait en sorte que vous êtes responsables de faire les mises à jour, car elles ne se feront pas automatiquement. Cela veut aussi dire qu'au cours du temps, si vous n'êtes pas vigilants, vous pourriez avoir plusieurs projets qui utilisent \laclasse, mais qui utilisent des versions différentes. +% +% Dans \overleaf, la classe \laclasse ne fait pas partie des \packages préinstallés et il n'est pas possible non plus de l'installer de manière standard, comme avec les autres distributions \latex (\miktex et \texlive). Il reste deux options: avoir une copie de \laclasse présente dans chaque projet ou utiliser le mécanisme de partage de fichiers entre vos différents projets afin d'éviter d'en avoir plusieurs copies. +% +% \subsection*{Vérification} +% Afin de vérifier que l'installation s'est bien passée, il vous est suggéré de compiler l'exemple distribué avec \laclasse. C'est le meilleur moyen de savoir si vous avez tout ce qu'il faut. +% +% L'exemple a été compilé avec succès avec \overleaf et les distributions \miktex et \texlive sous \windows, \macos et \linux. Si des erreurs surviennent avec l'exemple, typiquement, c'est que l'installation de \laclasse n'a pas été faite correctement ou que la distribution de \latex qui est utilisée est incomplète ou désuète. % %\section{Contact et support technique}\label{sec:support} % Une personne est responsable du support de \laclasse. Vous pouvez contacter cette personne uniquement dans les situations suivantes: @@ -346,35 +372,32 @@ %%----------------------------------------------------------------------------- %%--- OPTIONS DE CLASSE --------------------------------------------------- %%----------------------------------------------------------------------------- -\DeclareKeys { +\keys_define:nn { udes-genie-these } +{ biblatex .code:n = {\tl_gset_eq:NN \g_@@_references_engin_tl \c_@@_references_biblatex_tl}, - biblatex .usage:n = load, bibtex .code:n = {\tl_gset_eq:NN \g_@@_references_engin_tl \c_@@_references_bibtex_tl}, - bibtex .usage:n = load, livre .code:n = { \PassOptionsToClass{twoside,openright}{book} \PassOptionsToPackage{twoside=true}{geometry} \tl_gset_eq:NN \g_@@_format_tl \c_@@_format_livre_tl }, - livre .usage:n = load, simple .code:n = { \PassOptionsToClass{oneside,openany}{book} \PassOptionsToPackage{twoside=false}{geometry} \tl_gset_eq:NN \g_@@_format_tl \c_@@_format_simple_tl }, - simple .usage:n = load, unknown .code:n = {\PassOptionsToClass{\CurrentOption}{book}}, } -\SetKeys{livre} +\keys_set:nn { udes-genie-these } {livre} \IfFormatAtLeastTF {2022-06-01} { - \ProcessKeyOptions + \ProcessKeyOptions[udes-genie-these] } { \RequirePackage {l3keys2e} - \ProcessKeysOptions + \ProcessKeysOptions{udes-genie-these} } %%----------------------------------------------------------------------------- @@ -411,6 +434,7 @@ % fichier-symboles = {symboles}, % fichier-acronymes = {acronymes}, % fichiers-references = {journaux,conferences}, +% style-references = plain, % } %\end{verbatim} %\end{syntax} diff --git a/macros/latex/contrib/udes-genie-these/udes-genie-these.ins b/macros/latex/contrib/udes-genie-these/udes-genie-these.ins index 985c4e5698..2975ed180c 100644 --- a/macros/latex/contrib/udes-genie-these/udes-genie-these.ins +++ b/macros/latex/contrib/udes-genie-these/udes-genie-these.ins @@ -38,7 +38,7 @@ The Current Maintainer of this work is Charles-Antoine Brunet and can be contacted at Charles-Antoine.Brunet@USherbrooke.ca This work consists of the files udes-genie-these.dtx and udes-genie-these.ins -and the derived files udes-genie-these.cls and udes-genie-protocole.cls. +and the derived files udes-genie-these.cls. \endpreamble diff --git a/macros/latex/contrib/udes-genie-these/udes-genie-these.pdf b/macros/latex/contrib/udes-genie-these/udes-genie-these.pdf index 2e39e9516a..e834a41846 100644 Binary files a/macros/latex/contrib/udes-genie-these/udes-genie-these.pdf and b/macros/latex/contrib/udes-genie-these/udes-genie-these.pdf differ diff --git a/macros/luatex/generic/luakeys/README.md b/macros/luatex/generic/luakeys/README.md index 2bff44088b..bd30edb066 100644 --- a/macros/luatex/generic/luakeys/README.md +++ b/macros/luatex/generic/luakeys/README.md @@ -78,6 +78,9 @@ local defs = { ```lua local opts = { + -- Configure the delimiter that assigns a value to a key. + assignment_operator = '=', + -- Automatically convert dimensions into scaled points (1cm -> 1864679). convert_dimensions = false, @@ -97,6 +100,12 @@ local opts = { -- lower, snake, upper format_keys = { 'snake' }, + -- Configure the delimiter that marks the beginning of a group. + group_begin = '{', + + -- Configure the delimiter that marks the end of a group. + group_end = '}', + -- Listed in the order of execution hooks = { kv_string = function(kv_string) @@ -131,6 +140,9 @@ local opts = { end, }, + -- Configure the delimiter that separates list items from each other. + list_separator = ',', + -- If true, naked keys are converted to values: -- { one = true, two = true, three = true } -> { 'one', 'two', 'three' } naked_as_value = false, @@ -138,6 +150,12 @@ local opts = { -- Throw no error if there are unknown keys. no_error = false, + -- Configure the delimiter that marks the beginning of a string. + quotation_begin = '"', + + -- Configure the delimiter that marks the end of a string. + quotation_end = '"', + -- { key = { 'value' } } -> { key = 'value' } unpack = false, } diff --git a/macros/luatex/generic/luakeys/luakeys-debug.sty b/macros/luatex/generic/luakeys/luakeys-debug.sty index 1a26a66450..5f8cc808f5 100644 --- a/macros/luatex/generic/luakeys/luakeys-debug.sty +++ b/macros/luatex/generic/luakeys/luakeys-debug.sty @@ -17,6 +17,6 @@ % luakeys-debug.sty and luakeys-debug.tex. \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{luakeys-debug}[2022/07/06 0.7.0 Debug package for luakeys.] +\ProvidesPackage{luakeys-debug}[2022/11/17 0.8.0 Debug package for luakeys.] \input luakeys-debug.tex diff --git a/macros/luatex/generic/luakeys/luakeys-doc.pdf b/macros/luatex/generic/luakeys/luakeys-doc.pdf index ee0f3411f6..04e307a84d 100644 Binary files a/macros/luatex/generic/luakeys/luakeys-doc.pdf and b/macros/luatex/generic/luakeys/luakeys-doc.pdf differ diff --git a/macros/luatex/generic/luakeys/luakeys-doc.tex b/macros/luatex/generic/luakeys/luakeys-doc.tex index dd6fcc0973..f7d2940f66 100644 --- a/macros/luatex/generic/luakeys/luakeys-doc.tex +++ b/macros/luatex/generic/luakeys/luakeys-doc.tex @@ -58,7 +58,7 @@ The default value of the option “\texttt{#1}” is: \href{https://github.com/Josef-Friedrich/luakeys} {github.com/Josef-Friedrich/luakeys}% } -\date{0.7.0 from 2022/07/06} +\date{0.8.0 from 2022/11/17} \maketitle @@ -357,7 +357,7 @@ The \lua{parse} function can be called with an options table. This options are supported: \catcode`_=12 \directlua{luakeys.print_names('opts')} -\InputLua[firstline=5,lastline=69]{opts/all-opts.lua} +\InputLua[firstline=5,lastline=86]{opts/all-opts.lua} \noindent The options can also be set globally using the exported table @@ -371,6 +371,32 @@ The options can also be set globally using the exported table % %% +\subsubsection{Option “\texttt{assignment_operator}”} +\label{option:assignment-operator} +\label{options-delimiter} + +The option \lua{assignment_operator} configures the delimiter that +assigns a value to a key. The default value of this option is +\texttt{"="}. + +The code example below demonstrates all six delimiter related options. + +\InputLua[firstline=4,lastline=13]{opts/delimiters.lua} + +\begin{tabular}{ll} +\textbf{Delimiter options} & \textbf{Section} \\ +assignment_operator & \ref{option:assignment-operator}\\ +group_begin & \ref{option:group-begin}\\ +group_end & \ref{option:group-end}\\ +list_separator & \ref{option:list-separator}\\ +quotation_begin & \ref{option:quotation-begin}\\ +quotation_end & \ref{option:quotation-end}\\ +\end{tabular} + +%% +% +%% + \subsubsection{Option “\texttt{convert_dimensions}”} % Wenn Sie die Option \lua{convert_dimensions} auf \lua{true} setzen, @@ -515,26 +541,76 @@ we can write ... \subsubsection{Option “\texttt{format_keys}”} +% Mit Hilfe der Option \lua{format_keys} können die Schlüssel formatiert +% werden. +With the help of the option \lua{format_keys} the keys can be formatted. +% Die Werte dieser Option müssen in einer Tabelle angegeben werden. +The values of this option must be specified in a table. + \begin{description} -\item[lower] \strut +\item[lower] + +% Um alle Schlüssel in \emph{Kleinbuchstaben} umzuwandeln, geben sie in +% der Optionentabelle \lua{lower} an. +To convert all keys to \emph{lowercase}, specify \lua{lower} in the +options table. \InputLua[firstline=4,lastline=5]{opts/format-keys.lua} -\item[snake] \strut +\item[snake] + +% Um alle Schlüssel in \emph{snake case} (Die Wörter sind durch +% Unterstriche getrennt) umzuwandeln, geben sie in der Optionentabelle +% \lua{snake} an. +To make all keys \emph{snake case} (The words are separated by +underscores), specify \lua{snake} in the options +table. \InputLua[firstline=11,lastline=12]{opts/format-keys.lua} -\item[upper] \strut +\item[upper] + +% Um alle Schlüssel in \emph{Grossbuchstaben} umzuwandeln, geben sie in der +% Optionentabelle \lua{upper} an. +To convert all keys to \emph{uppercase}, specify \lua{upper} in the +options table. \InputLua[firstline=18,lastline=19]{opts/format-keys.lua} \end{description} +% Sie können auch mehrere Formatierungsarten kombinieren. +You can also combine several types of formatting. + +\InputLua[firstline=25,lastline=26]{opts/format-keys.lua} + \DefaultOptDescription{format_keys} %% % %% +\subsubsection{Option “\texttt{group_begin}”} +\label{option:group-begin} + +The option \lua{group_begin} configures the delimiter that marks the +beginning of a group. The default value of this option is \texttt{"\{"}. +A code example can be found in section \ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{group_end}”} +\label{option:group-end} + +The option \lua{group_end} configures the delimiter that marks the end +of a group. The default value of this option is \texttt{"\}"}. A code +example can be found in section \ref{options-delimiter}. + +%% +% +%% + \subsubsection{Option “\texttt{hooks}”} % Die folgenden Hooks bzw. Callback-Funktionen ermöglichen es in den @@ -623,6 +699,18 @@ as a parameter. % %% +\subsubsection{Option “\texttt{list_separator}”} +\label{option:list-separator} + +The option \lua{list_separator} configures the delimiter that separates +list items from each other. The default value of this option is +\texttt{","}. A code example can be found in section +\ref{options-delimiter}. + +%% +% +%% + \subsubsection{Option “\texttt{naked_as_value}”} % Mit Hilfe der Option \lua{naked_as_value} werden nackte Schlüssel @@ -665,13 +753,37 @@ If we set the option \lua{no_error} to \lua{true}: % %% +\subsubsection{Option “\texttt{quotation_begin}”} +\label{option:quotation-begin} + +The option \lua{quotation_begin} configures the delimiter that marks the +beginning of a string. The default value of this option is +\texttt{'"'} (double quotes). A code example can be found in section +\ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{quotation_end}”} +\label{option:quotation-end} + +The option \lua{quotation_end} configures the delimiter that marks the +end of a string. The default value of this option is \texttt{'"'} +(double quotes). A code example can be found in section +\ref{options-delimiter}. + +%% +% +%% + \subsubsection{Option “\texttt{unpack}”} % Mit Hilfe der Option \lua{unpack} werden alle Tabellen, die nur aus % einem einzigen nackten Schlüssel bzw. einen einzigen alleinstehenden % Wert bestehen, aufgelöst. With the help of the option \lua{unpack}, all tables that consist of -only one a single naked key or a single standalone value are unpacked. +only a single naked key or a single standalone value are unpacked. \InputLua[firstline=4,lastline=5]{opts/unpack.lua} @@ -1816,6 +1928,12 @@ An example of how to use the command in \LaTeX: that means scan for tokens that are enclosed in square brackets * extend and improve documentation } +\changes{0.8.0}{2022/11/17}{ +* Add 6 new options to change the delimiters: “assignment_operator”, + “group_begin”, “group_end”, “list_separator”, “quotation_begin”, + “quotation_end”. +* Extend the documentation about the option “format_keys”. +} \pagebreak \PrintChanges \pagebreak diff --git a/macros/luatex/generic/luakeys/luakeys.lua b/macros/luatex/generic/luakeys/luakeys.lua index 3221fc3efd..8b065289de 100644 --- a/macros/luatex/generic/luakeys/luakeys.lua +++ b/macros/luatex/generic/luakeys/luakeys.lua @@ -172,15 +172,21 @@ local utils = { local namespace = { opts = { + assignment_operator = '=', convert_dimensions = false, debug = false, default = true, defaults = false, defs = false, format_keys = false, + group_begin = '{', + group_end = '}', hooks = {}, + list_separator = ',', naked_as_value = false, no_error = false, + quotation_begin = '"', + quotation_end = '"', unpack = true, }, @@ -224,6 +230,38 @@ local function throw_error(message) end end +--- Normalize the parse options. +--- +---@param opts? table # Options in a raw format. The table may be empty or some keys are not set. +--- +---@return table +local function normalize_opts(opts) + if type(opts) ~= 'table' then + opts = {} + end + for key, _ in pairs(opts) do + if namespace.opts[key] == nil then + throw_error('Unknown parse option: ' .. tostring(key) .. '!') + end + end + local old_opts = opts + opts = {} + for name, _ in pairs(namespace.opts) do + if old_opts[name] ~= nil then + opts[name] = old_opts[name] + else + opts[name] = default_options[name] + end + end + + for hook in pairs(opts.hooks) do + if namespace.hooks[hook] == nil then + throw_error('Unknown hook: ' .. tostring(hook) .. '!') + end + end + return opts +end + local l3_code_cctab = 10 --- Convert back to strings @@ -375,13 +413,12 @@ end --- * [TUGboat article: Parsing complex data formats in LuaTEX with LPEG](https://tug.or-g/TUGboat/tb40-2/tb125menke-Patterndf) --- ---@param initial_rule string # The name of the first rule of the grammar table passed to the `lpeg.P(attern)` function (e. g. `list`, `number`). ----@param convert_dimensions? boolean # Whether the dimensions should be converted to scaled points (by default `false`). +---@param opts? table # Whether the dimensions should be converted to scaled points (by default `false`). --- ---@return userdata # The parser. -local function generate_parser(initial_rule, - convert_dimensions) - if convert_dimensions == nil then - convert_dimensions = false +local function generate_parser(initial_rule, opts) + if type(opts) ~= 'table' then + opts = normalize_opts(opts) end local Variable = lpeg.V @@ -413,7 +450,7 @@ local function generate_parser(initial_rule, input = input:gsub('%s+', '') -- Convert the unit string into lowercase. input = input:lower() - if convert_dimensions then + if opts.convert_dimensions then return tex.sp(input) else return input @@ -458,7 +495,7 @@ local function generate_parser(initial_rule, -- '{' list '}' list_container = - ws('{') * Variable('list') * ws('}'), + ws(opts.group_begin) * Variable('list') * ws(opts.group_end), -- ( list_container / key_value_pair / value ) ','? list_item = @@ -466,11 +503,11 @@ local function generate_parser(initial_rule, Variable('list_container') + Variable('key_value_pair') + Variable('value') - ) * ws(',')^-1, + ) * ws(opts.list_separator)^-1, -- key '=' (list_container / value) key_value_pair = - (Variable('key') * ws('=')) * (Variable('list_container') + Variable('value')), + (Variable('key') * ws(opts.assignment_operator)) * (Variable('list_container') + Variable('value')), -- number / string_quoted / string_unquoted key = @@ -555,9 +592,9 @@ local function generate_parser(initial_rule, -- '"' ('\"' / !'"')* '"' string_quoted = - white_space^0 * Pattern('"') * - CaptureSimple((Pattern('\\"') + 1 - Pattern('"'))^0) * - Pattern('"') * white_space^0, + white_space^0 * Pattern(opts.quotation_begin) * + CaptureSimple((Pattern('\\' .. opts.quotation_end) + 1 - Pattern(opts.quotation_end))^0) * + Pattern(opts.quotation_end) * white_space^0, string_unquoted = white_space^0 * @@ -566,7 +603,11 @@ local function generate_parser(initial_rule, (Set(' \t')^1 * Variable('word_unquoted')^1)^0) * white_space^0, - word_unquoted = (1 - white_space - Set('{},='))^1 + word_unquoted = (1 - white_space - Set( + opts.group_begin .. + opts.group_end .. + opts.assignment_operator .. + opts.list_separator))^1 }) -- LuaFormatter on end @@ -614,7 +655,7 @@ local is = { if type(value) == 'boolean' then return true end - local parser = generate_parser('boolean_only', false) + local parser = generate_parser('boolean_only') local result = parser:match(tostring(value)) return result ~= nil end, @@ -623,7 +664,7 @@ local is = { if value == nil then return false end - local parser = generate_parser('dimension_only', false) + local parser = generate_parser('dimension_only') local result = parser:match(tostring(value)) return result ~= nil end, @@ -643,7 +684,7 @@ local is = { if type(value) == 'number' then return true end - local parser = generate_parser('number_only', false) + local parser = generate_parser('number_only') local result = parser:match(tostring(value)) return result ~= nil end, @@ -778,7 +819,10 @@ local function apply_definitions(defs, -- integer elseif def.data_type == 'integer' then if is.number(value) then - converted = math.floor(tonumber(value)) + local n = tonumber(value) + if type(n) == 'number' and n ~= nil then + converted = math.floor(n) + end end -- number elseif def.data_type == 'number' then @@ -1038,7 +1082,7 @@ end --- Parse a LaTeX/TeX style key-value string into a Lua table. --- ---@param kv_string string # A string in the TeX/LaTeX style key-value format as described above. ----@param opts table # A table containing the settings: +---@param opts? table # A table containing the settings: --- `convert_dimensions`, `unpack`, `naked_as_value`, `converter`, --- `debug`, `preprocess`, `postprocess`. -- @@ -1047,48 +1091,16 @@ end ---@return table raw # The unprocessed, raw result of the LPeg parser. local function parse(kv_string, opts) if kv_string == nil then - return {} + return {}, {}, {} end - --- Normalize the parse options. - --- - ---@param opts table # Options in a raw format. The table may be empty or some keys are not set. - --- - ---@return table - local function normalize_opts(opts) - if type(opts) ~= 'table' then - opts = {} - end - for key, _ in pairs(opts) do - if namespace.opts[key] == nil then - throw_error('Unknown parse option: ' .. tostring(key) .. '!') - end - end - local old_opts = opts - opts = {} - for name, _ in pairs(namespace.opts) do - if old_opts[name] ~= nil then - opts[name] = old_opts[name] - else - opts[name] = default_options[name] - end - end - - for hook in pairs(opts.hooks) do - if namespace.hooks[hook] == nil then - throw_error('Unknown hook: ' .. tostring(hook) .. '!') - end - end - return opts - end opts = normalize_opts(opts) if type(opts.hooks.kv_string) == 'function' then kv_string = opts.hooks.kv_string(kv_string) end - local result = generate_parser('list', opts.convert_dimensions):match( - kv_string) + local result = generate_parser('list', opts):match(kv_string) local raw = clone_table(result) local function apply_hook(name) @@ -1218,7 +1230,7 @@ local result_store = {} -- @section local export = { - version = { 0, 7, 0 }, + version = { 0, 8, 0 }, namespace = namespace, diff --git a/macros/luatex/generic/luakeys/luakeys.sty b/macros/luatex/generic/luakeys/luakeys.sty index d0917f1d9d..68c3892ee4 100644 --- a/macros/luatex/generic/luakeys/luakeys.sty +++ b/macros/luatex/generic/luakeys/luakeys.sty @@ -17,5 +17,5 @@ % luakeys-debug.sty and luakeys-debug.tex. \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{luakeys}[2022/07/06 0.7.0 Parsing key-value options using Lua.] +\ProvidesPackage{luakeys}[2022/11/17 0.8.0 Parsing key-value options using Lua.] \directlua{luakeys = require('luakeys')} diff --git a/macros/luatex/latex/luacas/README.md b/macros/luatex/latex/luacas/README.md index 8e757dfea9..0d04c2ba04 100644 --- a/macros/luatex/latex/luacas/README.md +++ b/macros/luatex/latex/luacas/README.md @@ -10,10 +10,21 @@ The Lua CAS itself can be downloaded or cloned from [https://github.com/cochraef The package manager for your local TeX distribution ought to work just fine. Alternatively, download luacas.tds.zip and unzip the file in the root of one of your TDS trees. You may need to update some filename database after this (this may vary depending on your TeX distribution). +To run the LuaCAS tests, navigate to the luacas directory in either repository, and run the command +`lua test\main.lua`. You will need Lua version 5.3 or later. + +# Documentation + +Documentation for any version is included with the release. + # Authors This package is authored and maintained by Evan Cochrane and Timothy All. +# Bug Reporting + +Bug reports with the documentation or bugs related to the LaTeX end should go in this repository, while bug reports related to the CAS should go in [https://github.com/cochraef/LuaCAS](https://github.com/cochraef/LuaCAS). There is no required format for bugs, but it should be clear how to replicate the bug and what you think the intended functionality should be. + # License Permission is granted to copy, distribute and/or modify this diff --git a/macros/luatex/latex/luacas/doc/appendix/luacas.dat b/macros/luatex/latex/luacas/doc/appendix/luacas.dat index 60866bae05..7f2efd0a10 100644 --- a/macros/luatex/latex/luacas/doc/appendix/luacas.dat +++ b/macros/luatex/latex/luacas/doc/appendix/luacas.dat @@ -11,7 +11,7 @@ \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{luacas} - [2022/05/07 v1.0 CAS written in Lua for LaTeX] + [2022/11/15 v1.0.1 CAS written in Lua for LaTeX] \RequirePackage{iftex} \ifluatex diff --git a/macros/luatex/latex/luacas/doc/appendix/versionhistory.tex b/macros/luatex/latex/luacas/doc/appendix/versionhistory.tex new file mode 100644 index 0000000000..26754b3c7d --- /dev/null +++ b/macros/luatex/latex/luacas/doc/appendix/versionhistory.tex @@ -0,0 +1,22 @@ +\documentclass{article} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\begin{document} + +\section{Version History} + +\subsection*{v1.0.1} + +\begin{itemize} + \item Update CAS file names for \TeX{}Live +\end{itemize} + +\subsection*{v1.0.0} + +\begin{itemize} + \item Intial release +\end{itemize} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/luacas.pdf b/macros/luatex/latex/luacas/doc/luacas.pdf index d3ea07adf3..667791efb6 100644 Binary files a/macros/luatex/latex/luacas/doc/luacas.pdf and b/macros/luatex/latex/luacas/doc/luacas.pdf differ diff --git a/macros/luatex/latex/luacas/doc/luacas.tex b/macros/luatex/latex/luacas/doc/luacas.tex index b5aad9c9f9..3d0c51a343 100644 --- a/macros/luatex/latex/luacas/doc/luacas.tex +++ b/macros/luatex/latex/luacas/doc/luacas.tex @@ -160,9 +160,9 @@ style=numeric, \begin{document} \title{The {\ttfamily luacas} package} \author{Evan Cochrane\thanks{\href{mailto:cochraef@rose-hulman.edu}{\ttfamily cochraef@rose-hulman.edu}} , Timothy All\thanks{\href{mailto:timothy.all@rose-hulman.edu}{\ttfamily timothy.all@rose-hulman.edu}}} -\date{v1.0.0 \\ \today} +\date{v1.0.1 \\ \today} -\maketitle +\maketitle \begin{abstract} The {\ttfamily luacas} package is a portable Computer Algebra System capable of symbolic computation, written entirely in Lua, designed for use in Lua\LaTeX{}. @@ -186,9 +186,9 @@ style=numeric, \include{reference/ref_core/ref_core.tex} -\include{reference/ref_core/ref_core_classes/ref_core_classes.tex} +\include{reference/ref_core/ref_core_classes/ref_core_classes.tex} -\include{reference/ref_core/ref_core_methods/ref_core_methods.tex} +\include{reference/ref_core/ref_core_methods/ref_core_methods.tex} \include{reference/ref_algebra/ref_algebra.tex} @@ -206,6 +206,8 @@ style=numeric, \include{appendix/latexcode.tex} +\include{appendix/versionhistory.tex} + \newpage \printindex diff --git a/macros/luatex/latex/luacas/tex/_lib/inspect.lua b/macros/luatex/latex/luacas/tex/_lib/inspect.lua deleted file mode 100644 index dedd91f248..0000000000 --- a/macros/luatex/latex/luacas/tex/_lib/inspect.lua +++ /dev/null @@ -1,335 +0,0 @@ -local inspect ={ - _VERSION = 'inspect.lua 3.1.0', - _URL = 'http://github.com/kikito/inspect.lua', - _DESCRIPTION = 'human-readable representations of tables', - _LICENSE = [[ - MIT LICENSE - - Copyright (c) 2013 Enrique García Cota - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ]] -} - -local tostring = tostring - -inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end}) -inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end}) - -local function rawpairs(t) - return next, t, nil -end - --- Apostrophizes the string if it has quotes, but not aphostrophes --- Otherwise, it returns a regular quoted string -local function smartQuote(str) - if str:match('"') and not str:match("'") then - return "'" .. str .. "'" - end - return '"' .. str:gsub('"', '\\"') .. '"' -end - --- \a => '\\a', \0 => nil -local shortControlCharEscapes = { - ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", - ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", -} -local longControlCharEscapes = {["\127"]="\127"} -- \a => nil, \0 => \000, 31 => \031 -for i=0, 31 do - local ch = string.char(i) - if not shortControlCharEscapes[ch] then - shortControlCharEscapes[ch] = "\\"..i - longControlCharEscapes[ch] = string.format("\\%03d", i) - end -end ---longControlCharEscapes["\127"]="\\127" - -local function escape(str) - return (str:gsub("\\", "\\\\") - :gsub("(%c)%f[0-9]", longControlCharEscapes) - :gsub("%c", shortControlCharEscapes)) -end - -local function isIdentifier(str) - return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) -end - -local function isSequenceKey(k, sequenceLength) - return type(k) == 'number' - and 1 <= k - and k <= sequenceLength - and math.floor(k) == k -end - -local defaultTypeOrders = { - ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, - ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 -} - -local function sortKeys(a, b) - local ta, tb = type(a), type(b) - - -- strings and numbers are sorted numerically/alphabetically - if ta == tb and (ta == 'string' or ta == 'number') then return a < b end - - local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] - -- Two default types are compared according to the defaultTypeOrders table - if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] - elseif dta then return true -- default types before custom ones - elseif dtb then return false -- custom types after default ones - end - - -- custom types are sorted out alphabetically - return ta < tb -end - --- For implementation reasons, the behavior of rawlen & # is "undefined" when --- tables aren't pure sequences. So we implement our own # operator. -local function getSequenceLength(t) - local len = 1 - local v = rawget(t,len) - while v ~= nil do - len = len + 1 - v = rawget(t,len) - end - return len - 1 -end - -local function getNonSequentialKeys(t) - local keys, keysLength = {}, 0 - local sequenceLength = getSequenceLength(t) - for k,_ in rawpairs(t) do - if not isSequenceKey(k, sequenceLength) then - keysLength = keysLength + 1 - keys[keysLength] = k - end - end - table.sort(keys, sortKeys) - return keys, keysLength, sequenceLength -end - -local function countTableAppearances(t, tableAppearances) - tableAppearances = tableAppearances or {} - - if type(t) == 'table' then - if not tableAppearances[t] then - tableAppearances[t] = 1 - for k,v in rawpairs(t) do - countTableAppearances(k, tableAppearances) - countTableAppearances(v, tableAppearances) - end - countTableAppearances(getmetatable(t), tableAppearances) - else - tableAppearances[t] = tableAppearances[t] + 1 - end - end - - return tableAppearances -end - -local copySequence = function(s) - local copy, len = {}, #s - for i=1, len do copy[i] = s[i] end - return copy, len -end - -local function makePath(path, ...) - local keys = {...} - local newPath, len = copySequence(path) - for i=1, #keys do - newPath[len + i] = keys[i] - end - return newPath -end - -local function processRecursive(process, item, path, visited) - if item == nil then return nil end - if visited[item] then return visited[item] end - - local processed = process(item, path) - if type(processed) == 'table' then - local processedCopy = {} - visited[item] = processedCopy - local processedKey - - for k,v in rawpairs(processed) do - processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) - if processedKey ~= nil then - processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) - end - end - - local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) - if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field - setmetatable(processedCopy, mt) - processed = processedCopy - end - return processed -end - - - -------------------------------------------------------------------- - -local Inspector = {} -local Inspector_mt = {__index = Inspector} - -function Inspector:puts(...) - local args = {...} - local buffer = self.buffer - local len = #buffer - for i=1, #args do - len = len + 1 - buffer[len] = args[i] - end -end - -function Inspector:down(f) - self.level = self.level + 1 - f() - self.level = self.level - 1 -end - -function Inspector:tabify() - self:puts(self.newline, string.rep(self.indent, self.level)) -end - -function Inspector:alreadyVisited(v) - return self.ids[v] ~= nil -end - -function Inspector:getId(v) - local id = self.ids[v] - if not id then - local tv = type(v) - id = (self.maxIds[tv] or 0) + 1 - self.maxIds[tv] = id - self.ids[v] = id - end - return tostring(id) -end - -function Inspector:putKey(k) - if isIdentifier(k) then return self:puts(k) end - self:puts("[") - self:putValue(k) - self:puts("]") -end - -function Inspector:putTable(t) - if t == inspect.KEY or t == inspect.METATABLE then - self:puts(tostring(t)) - elseif self:alreadyVisited(t) then - self:puts('') - elseif self.level >= self.depth then - self:puts('{...}') - else - if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end - - local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t) - local mt = getmetatable(t) - - self:puts('{') - self:down(function() - local count = 0 - for i=1, sequenceLength do - if count > 0 then self:puts(',') end - self:puts(' ') - self:putValue(t[i]) - count = count + 1 - end - - for i=1, nonSequentialKeysLength do - local k = nonSequentialKeys[i] - if count > 0 then self:puts(',') end - self:tabify() - self:putKey(k) - self:puts(' = ') - self:putValue(t[k]) - count = count + 1 - end - - if type(mt) == 'table' then - if count > 0 then self:puts(',') end - self:tabify() - self:puts(' = ') - self:putValue(mt) - end - end) - - if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing } - self:tabify() - elseif sequenceLength > 0 then -- array tables have one extra space before closing } - self:puts(' ') - end - - self:puts('}') - end -end - -function Inspector:putValue(v) - local tv = type(v) - - if tv == 'string' then - self:puts(smartQuote(escape(v))) - elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or - tv == 'cdata' or tv == 'ctype' then - self:puts(tostring(v)) - elseif tv == 'table' then - self:putTable(v) - else - self:puts('<', tv, ' ', self:getId(v), '>') - end -end - -------------------------------------------------------------------- - -function inspect.inspect(root, options) - options = options or {} - - local depth = options.depth or math.huge - local newline = options.newline or '\n' - local indent = options.indent or ' ' - local process = options.process - - if process then - root = processRecursive(process, root, {}, {}) - end - - local inspector = setmetatable({ - depth = depth, - level = 0, - buffer = {}, - ids = {}, - maxIds = {}, - newline = newline, - indent = indent, - tableAppearances = countTableAppearances(root) - }, Inspector_mt) - - inspector:putValue(root) - - return table.concat(inspector.buffer) -end - -setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) - -return inspect - diff --git a/macros/luatex/latex/luacas/tex/_lib/luacas-inspect.lua b/macros/luatex/latex/luacas/tex/_lib/luacas-inspect.lua new file mode 100644 index 0000000000..dedd91f248 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/_lib/luacas-inspect.lua @@ -0,0 +1,335 @@ +local inspect ={ + _VERSION = 'inspect.lua 3.1.0', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2013 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +local tostring = tostring + +inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end}) +inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end}) + +local function rawpairs(t) + return next, t, nil +end + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' +end + +-- \a => '\\a', \0 => nil +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", +} +local longControlCharEscapes = {["\127"]="\127"} -- \a => nil, \0 => \000, 31 => \031 +for i=0, 31 do + local ch = string.char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\"..i + longControlCharEscapes[ch] = string.format("\\%03d", i) + end +end +--longControlCharEscapes["\127"]="\\127" + +local function escape(str) + return (str:gsub("\\", "\\\\") + :gsub("(%c)%f[0-9]", longControlCharEscapes) + :gsub("%c", shortControlCharEscapes)) +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) +end + +local function isSequenceKey(k, sequenceLength) + return type(k) == 'number' + and 1 <= k + and k <= sequenceLength + and math.floor(k) == k +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then return a < b end + + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then return true -- default types before custom ones + elseif dtb then return false -- custom types after default ones + end + + -- custom types are sorted out alphabetically + return ta < tb +end + +-- For implementation reasons, the behavior of rawlen & # is "undefined" when +-- tables aren't pure sequences. So we implement our own # operator. +local function getSequenceLength(t) + local len = 1 + local v = rawget(t,len) + while v ~= nil do + len = len + 1 + v = rawget(t,len) + end + return len - 1 +end + +local function getNonSequentialKeys(t) + local keys, keysLength = {}, 0 + local sequenceLength = getSequenceLength(t) + for k,_ in rawpairs(t) do + if not isSequenceKey(k, sequenceLength) then + keysLength = keysLength + 1 + keys[keysLength] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLength, sequenceLength +end + +local function countTableAppearances(t, tableAppearances) + tableAppearances = tableAppearances or {} + + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k,v in rawpairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end + end + + return tableAppearances +end + +local copySequence = function(s) + local copy, len = {}, #s + for i=1, len do copy[i] = s[i] end + return copy, len +end + +local function makePath(path, ...) + local keys = {...} + local newPath, len = copySequence(path) + for i=1, #keys do + newPath[len + i] = keys[i] + end + return newPath +end + +local function processRecursive(process, item, path, visited) + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == 'table' then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k,v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + + + +------------------------------------------------------------------- + +local Inspector = {} +local Inspector_mt = {__index = Inspector} + +function Inspector:puts(...) + local args = {...} + local buffer = self.buffer + local len = #buffer + for i=1, #args do + len = len + 1 + buffer[len] = args[i] + end +end + +function Inspector:down(f) + self.level = self.level + 1 + f() + self.level = self.level - 1 +end + +function Inspector:tabify() + self:puts(self.newline, string.rep(self.indent, self.level)) +end + +function Inspector:alreadyVisited(v) + return self.ids[v] ~= nil +end + +function Inspector:getId(v) + local id = self.ids[v] + if not id then + local tv = type(v) + id = (self.maxIds[tv] or 0) + 1 + self.maxIds[tv] = id + self.ids[v] = id + end + return tostring(id) +end + +function Inspector:putKey(k) + if isIdentifier(k) then return self:puts(k) end + self:puts("[") + self:putValue(k) + self:puts("]") +end + +function Inspector:putTable(t) + if t == inspect.KEY or t == inspect.METATABLE then + self:puts(tostring(t)) + elseif self:alreadyVisited(t) then + self:puts('
') + elseif self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end + + local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t) + local mt = getmetatable(t) + + self:puts('{') + self:down(function() + local count = 0 + for i=1, sequenceLength do + if count > 0 then self:puts(',') end + self:puts(' ') + self:putValue(t[i]) + count = count + 1 + end + + for i=1, nonSequentialKeysLength do + local k = nonSequentialKeys[i] + if count > 0 then self:puts(',') end + self:tabify() + self:putKey(k) + self:puts(' = ') + self:putValue(t[k]) + count = count + 1 + end + + if type(mt) == 'table' then + if count > 0 then self:puts(',') end + self:tabify() + self:puts(' = ') + self:putValue(mt) + end + end) + + if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing } + self:tabify() + elseif sequenceLength > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + + self:puts('}') + end +end + +function Inspector:putValue(v) + local tv = type(v) + + if tv == 'string' then + self:puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or + tv == 'cdata' or tv == 'ctype' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<', tv, ' ', self:getId(v), '>') + end +end + +------------------------------------------------------------------- + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local inspector = setmetatable({ + depth = depth, + level = 0, + buffer = {}, + ids = {}, + maxIds = {}, + newline = newline, + indent = indent, + tableAppearances = countTableAppearances(root) + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buffer) +end + +setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) + +return inspect + diff --git a/macros/luatex/latex/luacas/tex/_lib/luacas-pepperfish.lua b/macros/luatex/latex/luacas/tex/_lib/luacas-pepperfish.lua new file mode 100644 index 0000000000..4717543c4b --- /dev/null +++ b/macros/luatex/latex/luacas/tex/_lib/luacas-pepperfish.lua @@ -0,0 +1,629 @@ +---@diagnostic disable: param-type-mismatch, lowercase-global, need-check-nil, assign-type-mismatch +--[[ + +== Introduction == + + Note that this requires os.clock(), debug.sethook(), + and debug.getinfo() or your equivalent replacements to + be available if this is an embedded application. + + Example usage: + + profiler = newProfiler() + profiler:start() + + < call some functions that take time > + + profiler:stop() + + local outfile = io.open( "profile.txt", "w+" ) + profiler:report( outfile ) + outfile:close() + +== Optionally choosing profiling method == + +The rest of this comment can be ignored if you merely want a good profiler. + + newProfiler(method, sampledelay): + +If method is omitted or "time", will profile based on real performance. +optionally, frequency can be provided to control the number of opcodes +per profiling tick. By default this is 100000, which (on my system) provides +one tick approximately every 2ms and reduces system performance by about 10%. +This can be reduced to increase accuracy at the cost of performance, or +increased for the opposite effect. + +If method is "call", will profile based on function calls. Frequency is +ignored. + + +"time" may bias profiling somewhat towards large areas with "simple opcodes", +as the profiling function (which introduces a certain amount of unavoidable +overhead) will be called more often. This can be minimized by using a larger +sample delay - the default should leave any error largely overshadowed by +statistical noise. With a delay of 1000 I was able to achieve inaccuray of +approximately 25%. Increasing the delay to 100000 left inaccuracy below my +testing error. + +"call" may bias profiling heavily towards areas with many function calls. +Testing found a degenerate case giving a figure inaccurate by approximately +20,000%. (Yes, a multiple of 200.) This is, however, more directly comparable +to common profilers (such as gprof) and also gives accurate function call +counts, which cannot be retrieved from "time". + +I strongly recommend "time" mode, and it is now the default. + +== History == + +2021-01-04 - Larry Deaton ( larry.deaton@dynetics.com ) + Modified the profiling by "call" operation to ignore internal LUA functions + since LUA only provides debug hooks for the calling of the function and not + the return of the function. Without this change, the call stack is + continually growing. + +2008-09-16 - Time-based profiling and conversion to Lua 5.1 + by Ben Wilhelm ( zorba-pepperfish@pavlovian.net ). + Added the ability to optionally choose profiling methods, along with a new + profiling method. + +Converted to Lua 5, a few improvements, and +additional documentation by Tom Spilman ( tom@sickheadgames.com ) + +Additional corrections and tidying by original author +Daniel Silverstone ( dsilvers@pepperfish.net ) + +== Status == + +Daniel Silverstone is no longer using this code, and judging by how long it's +been waiting for Lua 5.1 support, I don't think Tom Spilman is either. I'm +perfectly willing to take on maintenance, so if you have problems or +questions, go ahead and email me :) +-- Ben Wilhelm ( zorba-pepperfish@pavlovian.net ) ' + +== Copyright == + +Lua profiler - Copyright Pepperfish 2002,2003,2004 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to +do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +--]] + + +-- +-- All profiler related stuff is stored in the top level table '_profiler' +-- +_profiler = {} + + +-- +-- newProfiler() creates a new profiler object for managing +-- the profiler and storing state. Note that only one profiler +-- object can be executing at one time. +-- +function newProfiler(variant, sampledelay) + if _profiler.running then + print("Profiler already running.") + return + end + + variant = variant or "time" + + if variant ~= "time" and variant ~= "call" then + print("Profiler method must be 'time' or 'call'.") + return + end + + local newprof = {} + for k,v in pairs(_profiler) do + newprof[k] = v + end + newprof.variant = variant + newprof.sampledelay = sampledelay or 100000 + return newprof +end + + +-- +-- This function starts the profiler. It will do nothing +-- if this (or any other) profiler is already running. +-- +function _profiler.start(self) + if _profiler.running then + return + end + -- Start the profiler. This begins by setting up internal profiler state + _profiler.running = self + self.rawstats = {} + self.callstack = {} + if self.variant == "time" then + self.lastclock = os.clock() + debug.sethook( _profiler_hook_wrapper_by_time, "", self.sampledelay ) + elseif self.variant == "call" then + debug.sethook( _profiler_hook_wrapper_by_call, "cr" ) + else + print("Profiler method must be 'time' or 'call'.") + end +end + + +-- +-- This function stops the profiler. It will do nothing +-- if a profiler is not running, and nothing if it isn't +-- the currently running profiler. +-- +function _profiler.stop(self) + if _profiler.running ~= self then + return + end + -- Stop the profiler. + debug.sethook( nil ) + _profiler.running = nil +end + + +-- +-- Simple wrapper to handle the hook. You should not +-- be calling this directly. Duplicated to reduce overhead. +-- +function _profiler_hook_wrapper_by_call(action) + if _profiler.running == nil then + debug.sethook( nil ) + end + _profiler.running:_internal_profile_by_call(action) +end +function _profiler_hook_wrapper_by_time(action) + if _profiler.running == nil then + debug.sethook( nil ) + end + _profiler.running:_internal_profile_by_time(action) +end + + +-- +-- This is the main by-function-call function of the profiler and should not +-- be called except by the hook wrapper +-- +function _profiler._internal_profile_by_call(self,action) + -- Since we can obtain the 'function' for the item we've had call us, we + -- can use that... + local caller_info = debug.getinfo( 3 ) + if caller_info == nil then + print "No caller_info" + return + end + + if caller_info.short_src == "[C]" then + -- LMD -- These are builtin functions and for some reason the + -- debug hook does not catch the return of these functions. + -- So, for now, we are just going to skip them. + return + end + + --SHG_LOG("[_profiler._internal_profile] "..(caller_info.name or "")) + + -- Retrieve the most recent activation record... + local latest_ar = nil + if #self.callstack > 0 then + latest_ar = self.callstack[#self.callstack] + end + + -- Are we allowed to profile this function? + local should_not_profile = 0 + for k,v in pairs(self.prevented_functions) do + if k == caller_info.func then + should_not_profile = v + end + end + -- Also check the top activation record... + if latest_ar then + if latest_ar.should_not_profile == 2 then + should_not_profile = 2 + end + end + + -- Now then, are we in 'call' or 'return' ? + -- print("Profile:", caller_info.name, "SNP:", should_not_profile, + -- "Action:", action ) + if action == "call" then + -- Making a call... + local this_ar = {} + this_ar.should_not_profile = should_not_profile + this_ar.parent_ar = latest_ar + this_ar.anon_child = 0 + this_ar.name_child = 0 + this_ar.children = {} + this_ar.children_time = {} + this_ar.clock_start = os.clock() + -- Last thing to do on a call is to insert this onto the ar stack... + table.insert( self.callstack, this_ar ) + else + local this_ar = latest_ar + if this_ar == nil then + return -- No point in doing anything if no upper activation record + end + + -- Right, calculate the time in this function... + this_ar.clock_end = os.clock() + this_ar.this_time = this_ar.clock_end - this_ar.clock_start + + -- Now, if we have a parent, update its call info... + if this_ar.parent_ar then + this_ar.parent_ar.children[caller_info.func] = + (this_ar.parent_ar.children[caller_info.func] or 0) + 1 + this_ar.parent_ar.children_time[caller_info.func] = + (this_ar.parent_ar.children_time[caller_info.func] or 0 ) + + this_ar.this_time + if caller_info.name == nil then + this_ar.parent_ar.anon_child = + this_ar.parent_ar.anon_child + this_ar.this_time + else + this_ar.parent_ar.name_child = + this_ar.parent_ar.name_child + this_ar.this_time + end + end + -- Now if we're meant to record information about ourselves, do so... + if this_ar.should_not_profile == 0 then + local inforec = self:_get_func_rec(caller_info.func,1) + inforec.count = inforec.count + 1 + inforec.time = inforec.time + this_ar.this_time + inforec.anon_child_time = inforec.anon_child_time + this_ar.anon_child + inforec.name_child_time = inforec.name_child_time + this_ar.name_child + inforec.func_info = caller_info + for k,v in pairs(this_ar.children) do + inforec.children[k] = (inforec.children[k] or 0) + v + inforec.children_time[k] = + (inforec.children_time[k] or 0) + this_ar.children_time[k] + end + end + + -- Last thing to do on return is to drop the last activation record... + table.remove( self.callstack, #self.callstack) + end +end + + +-- +-- This is the main by-time internal function of the profiler and should not +-- be called except by the hook wrapper +-- +function _profiler._internal_profile_by_time(self,action) + -- we do this first so we add the minimum amount of extra time to this call + local timetaken = os.clock() - self.lastclock + + local depth = 3 + local at_top = true + local last_caller + local caller = debug.getinfo(depth) + while caller do + if not caller.func then caller.func = "(tail call)" end + if self.prevented_functions[caller.func] == nil then + local info = self:_get_func_rec(caller.func, 1, caller) + info.count = info.count + 1 + info.time = info.time + timetaken + if last_caller then + -- we're not the head, so update the "children" times also + if last_caller.name then + info.name_child_time = info.name_child_time + timetaken + else + info.anon_child_time = info.anon_child_time + timetaken + end + info.children[last_caller.func] = + (info.children[last_caller.func] or 0) + 1 + info.children_time[last_caller.func] = + (info.children_time[last_caller.func] or 0) + timetaken + end + end + depth = depth + 1 + last_caller = caller + caller = debug.getinfo(depth) + end + + self.lastclock = os.clock() +end + + +-- +-- This returns a (possibly empty) function record for +-- the specified function. It is for internal profiler use. +-- +function _profiler._get_func_rec(self,func,force,info) + -- Find the function ref for 'func' (if force and not present, create one) + local ret = self.rawstats[func] + if ret == nil and force ~= 1 then + return nil + end + if ret == nil then + -- Build a new function statistics table + ret = {} + ret.func = func + ret.count = 0 + ret.time = 0 + ret.anon_child_time = 0 + ret.name_child_time = 0 + ret.children = {} + ret.children_time = {} + ret.func_info = info + self.rawstats[func] = ret + end + return ret +end + + +-- +-- This writes a profile report to the output file object. If +-- sort_by_total_time is nil or false the output is sorted by +-- the function time minus the time in it's children. +-- +function _profiler.report( self, outfile, sort_by_total_time ) + + outfile:write + [[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+ + +]] + + -- This is pretty awful. + local terms = {} + if self.variant == "time" then + terms.capitalized = "Sample" + terms.single = "sample" + terms.pastverb = "sampled" + elseif self.variant == "call" then + terms.capitalized = "Call" + terms.single = "call" + terms.pastverb = "called" + else + assert(false) + end + + local total_time = 0 + local ordering = {} + for func,record in pairs(self.rawstats) do + table.insert(ordering, func) + end + + if sort_by_total_time then + table.sort( ordering, + function(a,b) return self.rawstats[a].time > self.rawstats[b].time end + ) + else + table.sort( ordering, + function(a,b) + local arec = self.rawstats[a] + local brec = self.rawstats[b] + local atime = arec.time - (arec.anon_child_time + arec.name_child_time) + local btime = brec.time - (brec.anon_child_time + brec.name_child_time) + return atime > btime + end + ) + end + + for i=1,#ordering do + local func = ordering[i] + local record = self.rawstats[func] + local thisfuncname = " " .. self:_pretty_name(func) .. " " + if string.len( thisfuncname ) < 42 then + thisfuncname = + string.rep( "-", (42 - string.len(thisfuncname))/2 ) .. thisfuncname + thisfuncname = + thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) ) + end + + total_time = total_time + ( record.time - ( record.anon_child_time + + record.name_child_time ) ) + outfile:write( string.rep( "-", 19 ) .. thisfuncname .. + string.rep( "-", 19 ) .. "\n" ) + outfile:write( terms.capitalized.." count: " .. + string.format( "%4d", record.count ) .. "\n" ) + outfile:write( "Time spend total: " .. + string.format( "%4.3f", record.time ) .. "s\n" ) + outfile:write( "Time spent in children: " .. + string.format("%4.3f",record.anon_child_time+record.name_child_time) .. + "s\n" ) + local timeinself = + record.time - (record.anon_child_time + record.name_child_time) + outfile:write( "Time spent in self: " .. + string.format("%4.3f", timeinself) .. "s\n" ) + outfile:write( "Time spent per " .. terms.single .. ": " .. + string.format("%4.5f", record.time/record.count) .. + "s/" .. terms.single .. "\n" ) + outfile:write( "Time spent in self per "..terms.single..": " .. + string.format( "%4.5f", timeinself/record.count ) .. "s/" .. + terms.single.."\n" ) + + -- Report on each child in the form + -- Child called n times and took a.bs + local added_blank = 0 + for k,v in pairs(record.children) do + if self.prevented_functions[k] == nil or + self.prevented_functions[k] == 0 + then + if added_blank == 0 then + outfile:write( "\n" ) -- extra separation line + added_blank = 1 + end + outfile:write( "Child " .. self:_pretty_name(k) .. + string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " .. + terms.pastverb.." " .. string.format("%6d", v) ) + outfile:write( " times. Took " .. + string.format("%4.2f", record.children_time[k] ) .. "s\n" ) + end + end + + outfile:write( "\n" ) -- extra separation line + outfile:flush() + end + outfile:write( "\n\n" ) + outfile:write( "Total time spent in profiled functions: " .. + string.format("%5.3g",total_time) .. "s\n" ) + outfile:write( [[ + +END +]] ) + outfile:flush() +end + + +-- +-- This writes the profile to the output file object as +-- loadable Lua source. +-- +function _profiler.lua_report(self,outfile) + -- Purpose: Write out the entire raw state in a cross-referenceable form. + local ordering = {} + local functonum = {} + for func,record in pairs(self.rawstats) do + table.insert(ordering, func) + functonum[func] = #ordering + end + + outfile:write( + "-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" ) + outfile:write( "-- Function names\nfuncnames = {}\n" ) + for i=1,#ordering do + local thisfunc = ordering[i] + outfile:write( "funcnames[" .. i .. "] = " .. + string.format("%q", self:_pretty_name(thisfunc)) .. "\n" ) + end + outfile:write( "\n" ) + outfile:write( "-- Function times\nfunctimes = {}\n" ) + for i=1,#ordering do + local thisfunc = ordering[i] + local record = self.rawstats[thisfunc] + outfile:write( "functimes[" .. i .. "] = { " ) + outfile:write( "tot=" .. record.time .. ", " ) + outfile:write( "achild=" .. record.anon_child_time .. ", " ) + outfile:write( "nchild=" .. record.name_child_time .. ", " ) + outfile:write( "count=" .. record.count .. " }\n" ) + end + outfile:write( "\n" ) + outfile:write( "-- Child links\nchildren = {}\n" ) + for i=1,#ordering do + local thisfunc = ordering[i] + local record = self.rawstats[thisfunc] + outfile:write( "children[" .. i .. "] = { " ) + for k,v in pairs(record.children) do + if functonum[k] then -- non-recorded functions will be ignored now + outfile:write( functonum[k] .. ", " ) + end + end + outfile:write( "}\n" ) + end + outfile:write( "\n" ) + outfile:write( "-- Child call counts\nchildcounts = {}\n" ) + for i=1,#ordering do + local thisfunc = ordering[i] + local record = self.rawstats[thisfunc] + outfile:write( "children[" .. i .. "] = { " ) + for k,v in record.children do + if functonum[k] then -- non-recorded functions will be ignored now + outfile:write( v .. ", " ) + end + end + outfile:write( "}\n" ) + end + outfile:write( "\n" ) + outfile:write( "-- Child call time\nchildtimes = {}\n" ) + for i=1,#ordering do + local thisfunc = ordering[i] + local record = self.rawstats[thisfunc]; + outfile:write( "children[" .. i .. "] = { " ) + for k,v in pairs(record.children) do + if functonum[k] then -- non-recorded functions will be ignored now + outfile:write( record.children_time[k] .. ", " ) + end + end + outfile:write( "}\n" ) + end + outfile:write( "\n\n-- That is all.\n\n" ) + outfile:flush() +end + +-- Internal function to calculate a pretty name for the profile output +function _profiler._pretty_name(self,func) + + -- Only the data collected during the actual + -- run seems to be correct.... why? + local info = self.rawstats[ func ].func_info + -- local info = debug.getinfo( func ) + + local name = "" + if info.what == "Lua" then + name = "L:" + end + if info.what == "C" then + name = "C:" + end + if info.what == "main" then + name = " :" + end + + if info.name == nil then + name = name .. "<"..tostring(func) .. ">" + else + name = name .. info.name + end + + if info.source then + name = name .. "@" .. info.source + else + if info.what == "C" then + name = name .. "@?" + else + name = name .. "@" + end + end + name = name .. ":" + if info.what == "C" then + name = name .. "?" + else + name = name .. info.linedefined + end + + return name +end + + +-- +-- This allows you to specify functions which you do +-- not want profiled. Setting level to 1 keeps the +-- function from being profiled. Setting level to 2 +-- keeps both the function and its children from +-- being profiled. +-- +-- BUG: 2 will probably act exactly like 1 in "time" mode. +-- If anyone cares, let me (zorba) know and it can be fixed. +-- +function _profiler.prevent(self, func, level) + self.prevented_functions[func] = (level or 1) +end + + +_profiler.prevented_functions = { + [_profiler.start] = 2, + [_profiler.stop] = 2, + [_profiler._internal_profile_by_time] = 2, + [_profiler._internal_profile_by_call] = 2, + [_profiler_hook_wrapper_by_time] = 2, + [_profiler_hook_wrapper_by_call] = 2, + [_profiler.prevent] = 2, + [_profiler._get_func_rec] = 2, + [_profiler.report] = 2, + [_profiler.lua_report] = 2, + [_profiler._pretty_name] = 2 +} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/_lib/luacas-table.lua b/macros/luatex/latex/luacas/tex/_lib/luacas-table.lua new file mode 100644 index 0000000000..70495d1479 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/_lib/luacas-table.lua @@ -0,0 +1,185 @@ +-- Checks if two arrays are equal, starting at index i +function ArrayEqual(a1, a2, i) + i = i or 1 + while i <= math.max(#a1, #a2) do + if a1[i] ~= a2[i] then + return false + end + i = i + 1 + end + return true +end + +-- Checks if two arrays are equal, the first starting at index i, the second starting at index j +function FancyArrayEqual(a1, a2, i, j) + i = i or 1 + j = j or 1 + while i <= math.max(#a1, #a2) or j <= math.max(#a1, #a2) do + if a1[i] ~= a2[j] then + return false + end + i = i + 1 + j = j + 1 + end + return true +end + +-- Creates a copy of a table +function Copy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in pairs(orig) do + copy[orig_key] = orig_value + end + copy = setmetatable(copy, getmetatable(orig)) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + +-- Joins two arrays +function JoinArrays(a1, a2) + local a = Copy(a1) + for index, value in ipairs(a2) do + a[index + #a1] = value + end + return a +end + +-- Joins two arrays indexed from zero +function JoinZeroArrays(a1, a2) + local a = Copy(a1) + a[#a1 + 1] = a2[0] + for index, value in ipairs(a2) do + a[index + #a1 + 1] = value + end + return a +end + +-- Join two tables, using the second entry if a key appears in both tables +function JoinTables(t1, t2) + local t = Copy(t1) or {} + for key, value in pairs(t2) do + t[key] = value + end + return t +end + +-- Given an array, removes all occurances of that element from the array +function Remove(a, e) + local r = Copy(a) + local found = false + for index, value in ipairs(r) do + if e == value then + found = true + end + if found then + r[index] = r[index + 1] + end + end + return r +end + +-- Given an array, removes all elements in the second array from the first +function RemoveAll(a1, a2) + local r = Copy(a1) + local removed = 0 + for index, value in ipairs(r) do + if a2[value] then + removed = removed + 1 + end + if removed then + r[index] = r[index + removed] + end + end + return r +end + +-- Given an array of arrays, returns only the arrays that have no elements in common with the second array +function RemoveAny(aa, a) + local toremove = {} + for _, v in ipairs(aa) do + for _, v1 in ipairs(v) do + for _, v2 in ipairs(a) do + if v1 == v2 then + toremove[#toremove+1] = v + goto endcheck + end + end + end + ::endcheck:: + end + return RemoveAll(aa, toremove) +end + +-- Converts an array to a string recursively +function ToStringArray(t) + if string.sub(tostring(t), 1, 6) == "table:" then + local out = "{" + for index, value in ipairs(t) do + out = out .. ToStringArray(value) + if t[index + 1] then + out = out .. ", " + end + end + return out .. "}" + end + return tostring(t) +end + +-- Converts a table to a string recursively +function ToStringTable(t) + if string.sub(tostring(t), 1, 6) == "table:" then + local out = "{" + for index, value in pairs(t) do + out = out .. ToStringTable(index) .. " : " .. ToStringTable(value) + if t[index + 1] then + out = out .. ", " + end + end + return out .. "}" + end + return tostring(t) +end + +-- Check if a table contains an element, and returns the index of that element if it does +function Contains(t, e) + for index, value in pairs(t) do + if value == e then + return index + end + end + return false +end + +-- Given an array a of unique elements, returns an array of the n-element subarrays of a +function Subarrays(a, m) + local aout = {} + local l = 1 + local newmax = {} + + if(m <= 0) then + return {{}}, {0} + end + + local rec, max = Subarrays(a, m - 1) + + for recindex, set in pairs(rec) do + for index, element in pairs(a) do + if not set[element] and index > max[recindex] then + local new = Copy(set) + new[#new+1] = element + aout[l] = new + if not newmax[l] or index > newmax[l] then + newmax[l] = index + end + l = l + 1 + end + end + end + + return aout, newmax +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/_lib/pepperfish.lua b/macros/luatex/latex/luacas/tex/_lib/pepperfish.lua deleted file mode 100644 index 4717543c4b..0000000000 --- a/macros/luatex/latex/luacas/tex/_lib/pepperfish.lua +++ /dev/null @@ -1,629 +0,0 @@ ----@diagnostic disable: param-type-mismatch, lowercase-global, need-check-nil, assign-type-mismatch ---[[ - -== Introduction == - - Note that this requires os.clock(), debug.sethook(), - and debug.getinfo() or your equivalent replacements to - be available if this is an embedded application. - - Example usage: - - profiler = newProfiler() - profiler:start() - - < call some functions that take time > - - profiler:stop() - - local outfile = io.open( "profile.txt", "w+" ) - profiler:report( outfile ) - outfile:close() - -== Optionally choosing profiling method == - -The rest of this comment can be ignored if you merely want a good profiler. - - newProfiler(method, sampledelay): - -If method is omitted or "time", will profile based on real performance. -optionally, frequency can be provided to control the number of opcodes -per profiling tick. By default this is 100000, which (on my system) provides -one tick approximately every 2ms and reduces system performance by about 10%. -This can be reduced to increase accuracy at the cost of performance, or -increased for the opposite effect. - -If method is "call", will profile based on function calls. Frequency is -ignored. - - -"time" may bias profiling somewhat towards large areas with "simple opcodes", -as the profiling function (which introduces a certain amount of unavoidable -overhead) will be called more often. This can be minimized by using a larger -sample delay - the default should leave any error largely overshadowed by -statistical noise. With a delay of 1000 I was able to achieve inaccuray of -approximately 25%. Increasing the delay to 100000 left inaccuracy below my -testing error. - -"call" may bias profiling heavily towards areas with many function calls. -Testing found a degenerate case giving a figure inaccurate by approximately -20,000%. (Yes, a multiple of 200.) This is, however, more directly comparable -to common profilers (such as gprof) and also gives accurate function call -counts, which cannot be retrieved from "time". - -I strongly recommend "time" mode, and it is now the default. - -== History == - -2021-01-04 - Larry Deaton ( larry.deaton@dynetics.com ) - Modified the profiling by "call" operation to ignore internal LUA functions - since LUA only provides debug hooks for the calling of the function and not - the return of the function. Without this change, the call stack is - continually growing. - -2008-09-16 - Time-based profiling and conversion to Lua 5.1 - by Ben Wilhelm ( zorba-pepperfish@pavlovian.net ). - Added the ability to optionally choose profiling methods, along with a new - profiling method. - -Converted to Lua 5, a few improvements, and -additional documentation by Tom Spilman ( tom@sickheadgames.com ) - -Additional corrections and tidying by original author -Daniel Silverstone ( dsilvers@pepperfish.net ) - -== Status == - -Daniel Silverstone is no longer using this code, and judging by how long it's -been waiting for Lua 5.1 support, I don't think Tom Spilman is either. I'm -perfectly willing to take on maintenance, so if you have problems or -questions, go ahead and email me :) --- Ben Wilhelm ( zorba-pepperfish@pavlovian.net ) ' - -== Copyright == - -Lua profiler - Copyright Pepperfish 2002,2003,2004 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to -do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. - ---]] - - --- --- All profiler related stuff is stored in the top level table '_profiler' --- -_profiler = {} - - --- --- newProfiler() creates a new profiler object for managing --- the profiler and storing state. Note that only one profiler --- object can be executing at one time. --- -function newProfiler(variant, sampledelay) - if _profiler.running then - print("Profiler already running.") - return - end - - variant = variant or "time" - - if variant ~= "time" and variant ~= "call" then - print("Profiler method must be 'time' or 'call'.") - return - end - - local newprof = {} - for k,v in pairs(_profiler) do - newprof[k] = v - end - newprof.variant = variant - newprof.sampledelay = sampledelay or 100000 - return newprof -end - - --- --- This function starts the profiler. It will do nothing --- if this (or any other) profiler is already running. --- -function _profiler.start(self) - if _profiler.running then - return - end - -- Start the profiler. This begins by setting up internal profiler state - _profiler.running = self - self.rawstats = {} - self.callstack = {} - if self.variant == "time" then - self.lastclock = os.clock() - debug.sethook( _profiler_hook_wrapper_by_time, "", self.sampledelay ) - elseif self.variant == "call" then - debug.sethook( _profiler_hook_wrapper_by_call, "cr" ) - else - print("Profiler method must be 'time' or 'call'.") - end -end - - --- --- This function stops the profiler. It will do nothing --- if a profiler is not running, and nothing if it isn't --- the currently running profiler. --- -function _profiler.stop(self) - if _profiler.running ~= self then - return - end - -- Stop the profiler. - debug.sethook( nil ) - _profiler.running = nil -end - - --- --- Simple wrapper to handle the hook. You should not --- be calling this directly. Duplicated to reduce overhead. --- -function _profiler_hook_wrapper_by_call(action) - if _profiler.running == nil then - debug.sethook( nil ) - end - _profiler.running:_internal_profile_by_call(action) -end -function _profiler_hook_wrapper_by_time(action) - if _profiler.running == nil then - debug.sethook( nil ) - end - _profiler.running:_internal_profile_by_time(action) -end - - --- --- This is the main by-function-call function of the profiler and should not --- be called except by the hook wrapper --- -function _profiler._internal_profile_by_call(self,action) - -- Since we can obtain the 'function' for the item we've had call us, we - -- can use that... - local caller_info = debug.getinfo( 3 ) - if caller_info == nil then - print "No caller_info" - return - end - - if caller_info.short_src == "[C]" then - -- LMD -- These are builtin functions and for some reason the - -- debug hook does not catch the return of these functions. - -- So, for now, we are just going to skip them. - return - end - - --SHG_LOG("[_profiler._internal_profile] "..(caller_info.name or "")) - - -- Retrieve the most recent activation record... - local latest_ar = nil - if #self.callstack > 0 then - latest_ar = self.callstack[#self.callstack] - end - - -- Are we allowed to profile this function? - local should_not_profile = 0 - for k,v in pairs(self.prevented_functions) do - if k == caller_info.func then - should_not_profile = v - end - end - -- Also check the top activation record... - if latest_ar then - if latest_ar.should_not_profile == 2 then - should_not_profile = 2 - end - end - - -- Now then, are we in 'call' or 'return' ? - -- print("Profile:", caller_info.name, "SNP:", should_not_profile, - -- "Action:", action ) - if action == "call" then - -- Making a call... - local this_ar = {} - this_ar.should_not_profile = should_not_profile - this_ar.parent_ar = latest_ar - this_ar.anon_child = 0 - this_ar.name_child = 0 - this_ar.children = {} - this_ar.children_time = {} - this_ar.clock_start = os.clock() - -- Last thing to do on a call is to insert this onto the ar stack... - table.insert( self.callstack, this_ar ) - else - local this_ar = latest_ar - if this_ar == nil then - return -- No point in doing anything if no upper activation record - end - - -- Right, calculate the time in this function... - this_ar.clock_end = os.clock() - this_ar.this_time = this_ar.clock_end - this_ar.clock_start - - -- Now, if we have a parent, update its call info... - if this_ar.parent_ar then - this_ar.parent_ar.children[caller_info.func] = - (this_ar.parent_ar.children[caller_info.func] or 0) + 1 - this_ar.parent_ar.children_time[caller_info.func] = - (this_ar.parent_ar.children_time[caller_info.func] or 0 ) + - this_ar.this_time - if caller_info.name == nil then - this_ar.parent_ar.anon_child = - this_ar.parent_ar.anon_child + this_ar.this_time - else - this_ar.parent_ar.name_child = - this_ar.parent_ar.name_child + this_ar.this_time - end - end - -- Now if we're meant to record information about ourselves, do so... - if this_ar.should_not_profile == 0 then - local inforec = self:_get_func_rec(caller_info.func,1) - inforec.count = inforec.count + 1 - inforec.time = inforec.time + this_ar.this_time - inforec.anon_child_time = inforec.anon_child_time + this_ar.anon_child - inforec.name_child_time = inforec.name_child_time + this_ar.name_child - inforec.func_info = caller_info - for k,v in pairs(this_ar.children) do - inforec.children[k] = (inforec.children[k] or 0) + v - inforec.children_time[k] = - (inforec.children_time[k] or 0) + this_ar.children_time[k] - end - end - - -- Last thing to do on return is to drop the last activation record... - table.remove( self.callstack, #self.callstack) - end -end - - --- --- This is the main by-time internal function of the profiler and should not --- be called except by the hook wrapper --- -function _profiler._internal_profile_by_time(self,action) - -- we do this first so we add the minimum amount of extra time to this call - local timetaken = os.clock() - self.lastclock - - local depth = 3 - local at_top = true - local last_caller - local caller = debug.getinfo(depth) - while caller do - if not caller.func then caller.func = "(tail call)" end - if self.prevented_functions[caller.func] == nil then - local info = self:_get_func_rec(caller.func, 1, caller) - info.count = info.count + 1 - info.time = info.time + timetaken - if last_caller then - -- we're not the head, so update the "children" times also - if last_caller.name then - info.name_child_time = info.name_child_time + timetaken - else - info.anon_child_time = info.anon_child_time + timetaken - end - info.children[last_caller.func] = - (info.children[last_caller.func] or 0) + 1 - info.children_time[last_caller.func] = - (info.children_time[last_caller.func] or 0) + timetaken - end - end - depth = depth + 1 - last_caller = caller - caller = debug.getinfo(depth) - end - - self.lastclock = os.clock() -end - - --- --- This returns a (possibly empty) function record for --- the specified function. It is for internal profiler use. --- -function _profiler._get_func_rec(self,func,force,info) - -- Find the function ref for 'func' (if force and not present, create one) - local ret = self.rawstats[func] - if ret == nil and force ~= 1 then - return nil - end - if ret == nil then - -- Build a new function statistics table - ret = {} - ret.func = func - ret.count = 0 - ret.time = 0 - ret.anon_child_time = 0 - ret.name_child_time = 0 - ret.children = {} - ret.children_time = {} - ret.func_info = info - self.rawstats[func] = ret - end - return ret -end - - --- --- This writes a profile report to the output file object. If --- sort_by_total_time is nil or false the output is sorted by --- the function time minus the time in it's children. --- -function _profiler.report( self, outfile, sort_by_total_time ) - - outfile:write - [[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+ - -]] - - -- This is pretty awful. - local terms = {} - if self.variant == "time" then - terms.capitalized = "Sample" - terms.single = "sample" - terms.pastverb = "sampled" - elseif self.variant == "call" then - terms.capitalized = "Call" - terms.single = "call" - terms.pastverb = "called" - else - assert(false) - end - - local total_time = 0 - local ordering = {} - for func,record in pairs(self.rawstats) do - table.insert(ordering, func) - end - - if sort_by_total_time then - table.sort( ordering, - function(a,b) return self.rawstats[a].time > self.rawstats[b].time end - ) - else - table.sort( ordering, - function(a,b) - local arec = self.rawstats[a] - local brec = self.rawstats[b] - local atime = arec.time - (arec.anon_child_time + arec.name_child_time) - local btime = brec.time - (brec.anon_child_time + brec.name_child_time) - return atime > btime - end - ) - end - - for i=1,#ordering do - local func = ordering[i] - local record = self.rawstats[func] - local thisfuncname = " " .. self:_pretty_name(func) .. " " - if string.len( thisfuncname ) < 42 then - thisfuncname = - string.rep( "-", (42 - string.len(thisfuncname))/2 ) .. thisfuncname - thisfuncname = - thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) ) - end - - total_time = total_time + ( record.time - ( record.anon_child_time + - record.name_child_time ) ) - outfile:write( string.rep( "-", 19 ) .. thisfuncname .. - string.rep( "-", 19 ) .. "\n" ) - outfile:write( terms.capitalized.." count: " .. - string.format( "%4d", record.count ) .. "\n" ) - outfile:write( "Time spend total: " .. - string.format( "%4.3f", record.time ) .. "s\n" ) - outfile:write( "Time spent in children: " .. - string.format("%4.3f",record.anon_child_time+record.name_child_time) .. - "s\n" ) - local timeinself = - record.time - (record.anon_child_time + record.name_child_time) - outfile:write( "Time spent in self: " .. - string.format("%4.3f", timeinself) .. "s\n" ) - outfile:write( "Time spent per " .. terms.single .. ": " .. - string.format("%4.5f", record.time/record.count) .. - "s/" .. terms.single .. "\n" ) - outfile:write( "Time spent in self per "..terms.single..": " .. - string.format( "%4.5f", timeinself/record.count ) .. "s/" .. - terms.single.."\n" ) - - -- Report on each child in the form - -- Child called n times and took a.bs - local added_blank = 0 - for k,v in pairs(record.children) do - if self.prevented_functions[k] == nil or - self.prevented_functions[k] == 0 - then - if added_blank == 0 then - outfile:write( "\n" ) -- extra separation line - added_blank = 1 - end - outfile:write( "Child " .. self:_pretty_name(k) .. - string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " .. - terms.pastverb.." " .. string.format("%6d", v) ) - outfile:write( " times. Took " .. - string.format("%4.2f", record.children_time[k] ) .. "s\n" ) - end - end - - outfile:write( "\n" ) -- extra separation line - outfile:flush() - end - outfile:write( "\n\n" ) - outfile:write( "Total time spent in profiled functions: " .. - string.format("%5.3g",total_time) .. "s\n" ) - outfile:write( [[ - -END -]] ) - outfile:flush() -end - - --- --- This writes the profile to the output file object as --- loadable Lua source. --- -function _profiler.lua_report(self,outfile) - -- Purpose: Write out the entire raw state in a cross-referenceable form. - local ordering = {} - local functonum = {} - for func,record in pairs(self.rawstats) do - table.insert(ordering, func) - functonum[func] = #ordering - end - - outfile:write( - "-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" ) - outfile:write( "-- Function names\nfuncnames = {}\n" ) - for i=1,#ordering do - local thisfunc = ordering[i] - outfile:write( "funcnames[" .. i .. "] = " .. - string.format("%q", self:_pretty_name(thisfunc)) .. "\n" ) - end - outfile:write( "\n" ) - outfile:write( "-- Function times\nfunctimes = {}\n" ) - for i=1,#ordering do - local thisfunc = ordering[i] - local record = self.rawstats[thisfunc] - outfile:write( "functimes[" .. i .. "] = { " ) - outfile:write( "tot=" .. record.time .. ", " ) - outfile:write( "achild=" .. record.anon_child_time .. ", " ) - outfile:write( "nchild=" .. record.name_child_time .. ", " ) - outfile:write( "count=" .. record.count .. " }\n" ) - end - outfile:write( "\n" ) - outfile:write( "-- Child links\nchildren = {}\n" ) - for i=1,#ordering do - local thisfunc = ordering[i] - local record = self.rawstats[thisfunc] - outfile:write( "children[" .. i .. "] = { " ) - for k,v in pairs(record.children) do - if functonum[k] then -- non-recorded functions will be ignored now - outfile:write( functonum[k] .. ", " ) - end - end - outfile:write( "}\n" ) - end - outfile:write( "\n" ) - outfile:write( "-- Child call counts\nchildcounts = {}\n" ) - for i=1,#ordering do - local thisfunc = ordering[i] - local record = self.rawstats[thisfunc] - outfile:write( "children[" .. i .. "] = { " ) - for k,v in record.children do - if functonum[k] then -- non-recorded functions will be ignored now - outfile:write( v .. ", " ) - end - end - outfile:write( "}\n" ) - end - outfile:write( "\n" ) - outfile:write( "-- Child call time\nchildtimes = {}\n" ) - for i=1,#ordering do - local thisfunc = ordering[i] - local record = self.rawstats[thisfunc]; - outfile:write( "children[" .. i .. "] = { " ) - for k,v in pairs(record.children) do - if functonum[k] then -- non-recorded functions will be ignored now - outfile:write( record.children_time[k] .. ", " ) - end - end - outfile:write( "}\n" ) - end - outfile:write( "\n\n-- That is all.\n\n" ) - outfile:flush() -end - --- Internal function to calculate a pretty name for the profile output -function _profiler._pretty_name(self,func) - - -- Only the data collected during the actual - -- run seems to be correct.... why? - local info = self.rawstats[ func ].func_info - -- local info = debug.getinfo( func ) - - local name = "" - if info.what == "Lua" then - name = "L:" - end - if info.what == "C" then - name = "C:" - end - if info.what == "main" then - name = " :" - end - - if info.name == nil then - name = name .. "<"..tostring(func) .. ">" - else - name = name .. info.name - end - - if info.source then - name = name .. "@" .. info.source - else - if info.what == "C" then - name = name .. "@?" - else - name = name .. "@" - end - end - name = name .. ":" - if info.what == "C" then - name = name .. "?" - else - name = name .. info.linedefined - end - - return name -end - - --- --- This allows you to specify functions which you do --- not want profiled. Setting level to 1 keeps the --- function from being profiled. Setting level to 2 --- keeps both the function and its children from --- being profiled. --- --- BUG: 2 will probably act exactly like 1 in "time" mode. --- If anyone cares, let me (zorba) know and it can be fixed. --- -function _profiler.prevent(self, func, level) - self.prevented_functions[func] = (level or 1) -end - - -_profiler.prevented_functions = { - [_profiler.start] = 2, - [_profiler.stop] = 2, - [_profiler._internal_profile_by_time] = 2, - [_profiler._internal_profile_by_call] = 2, - [_profiler_hook_wrapper_by_time] = 2, - [_profiler_hook_wrapper_by_call] = 2, - [_profiler.prevent] = 2, - [_profiler._get_func_rec] = 2, - [_profiler.report] = 2, - [_profiler.lua_report] = 2, - [_profiler._pretty_name] = 2 -} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/_lib/table.lua b/macros/luatex/latex/luacas/tex/_lib/table.lua deleted file mode 100644 index 70495d1479..0000000000 --- a/macros/luatex/latex/luacas/tex/_lib/table.lua +++ /dev/null @@ -1,185 +0,0 @@ --- Checks if two arrays are equal, starting at index i -function ArrayEqual(a1, a2, i) - i = i or 1 - while i <= math.max(#a1, #a2) do - if a1[i] ~= a2[i] then - return false - end - i = i + 1 - end - return true -end - --- Checks if two arrays are equal, the first starting at index i, the second starting at index j -function FancyArrayEqual(a1, a2, i, j) - i = i or 1 - j = j or 1 - while i <= math.max(#a1, #a2) or j <= math.max(#a1, #a2) do - if a1[i] ~= a2[j] then - return false - end - i = i + 1 - j = j + 1 - end - return true -end - --- Creates a copy of a table -function Copy(orig) - local orig_type = type(orig) - local copy - if orig_type == 'table' then - copy = {} - for orig_key, orig_value in pairs(orig) do - copy[orig_key] = orig_value - end - copy = setmetatable(copy, getmetatable(orig)) - else -- number, string, boolean, etc - copy = orig - end - return copy -end - --- Joins two arrays -function JoinArrays(a1, a2) - local a = Copy(a1) - for index, value in ipairs(a2) do - a[index + #a1] = value - end - return a -end - --- Joins two arrays indexed from zero -function JoinZeroArrays(a1, a2) - local a = Copy(a1) - a[#a1 + 1] = a2[0] - for index, value in ipairs(a2) do - a[index + #a1 + 1] = value - end - return a -end - --- Join two tables, using the second entry if a key appears in both tables -function JoinTables(t1, t2) - local t = Copy(t1) or {} - for key, value in pairs(t2) do - t[key] = value - end - return t -end - --- Given an array, removes all occurances of that element from the array -function Remove(a, e) - local r = Copy(a) - local found = false - for index, value in ipairs(r) do - if e == value then - found = true - end - if found then - r[index] = r[index + 1] - end - end - return r -end - --- Given an array, removes all elements in the second array from the first -function RemoveAll(a1, a2) - local r = Copy(a1) - local removed = 0 - for index, value in ipairs(r) do - if a2[value] then - removed = removed + 1 - end - if removed then - r[index] = r[index + removed] - end - end - return r -end - --- Given an array of arrays, returns only the arrays that have no elements in common with the second array -function RemoveAny(aa, a) - local toremove = {} - for _, v in ipairs(aa) do - for _, v1 in ipairs(v) do - for _, v2 in ipairs(a) do - if v1 == v2 then - toremove[#toremove+1] = v - goto endcheck - end - end - end - ::endcheck:: - end - return RemoveAll(aa, toremove) -end - --- Converts an array to a string recursively -function ToStringArray(t) - if string.sub(tostring(t), 1, 6) == "table:" then - local out = "{" - for index, value in ipairs(t) do - out = out .. ToStringArray(value) - if t[index + 1] then - out = out .. ", " - end - end - return out .. "}" - end - return tostring(t) -end - --- Converts a table to a string recursively -function ToStringTable(t) - if string.sub(tostring(t), 1, 6) == "table:" then - local out = "{" - for index, value in pairs(t) do - out = out .. ToStringTable(index) .. " : " .. ToStringTable(value) - if t[index + 1] then - out = out .. ", " - end - end - return out .. "}" - end - return tostring(t) -end - --- Check if a table contains an element, and returns the index of that element if it does -function Contains(t, e) - for index, value in pairs(t) do - if value == e then - return index - end - end - return false -end - --- Given an array a of unique elements, returns an array of the n-element subarrays of a -function Subarrays(a, m) - local aout = {} - local l = 1 - local newmax = {} - - if(m <= 0) then - return {{}}, {0} - end - - local rec, max = Subarrays(a, m - 1) - - for recindex, set in pairs(rec) do - for index, element in pairs(a) do - if not set[element] and index > max[recindex] then - local new = Copy(set) - new[#new+1] = element - aout[l] = new - if not newmax[l] or index > newmax[l] then - newmax[l] = index - end - l = l + 1 - end - end - end - - return aout, newmax -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/_init.lua b/macros/luatex/latex/luacas/tex/algebra/_init.lua deleted file mode 100644 index 0a9b114cb9..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/_init.lua +++ /dev/null @@ -1,24 +0,0 @@ --- Loads algebra files in the correct order. -require("_lib.table") - -require("core._init") - -require("algebra.ring") -require("algebra.euclideandomain") -require("algebra.field") -require("algebra.polynomialring") -require("algebra.integer") -require("algebra.rational") -require("algebra.integerquotientring") -require("algebra.sqrtexpression") - -require("algebra.absexpression") -require("algebra.equation") -require("algebra.factorialexpression") -require("algebra.logarithm") -require("algebra.rootexpression") -require("algebra.trigexpression") - -require("algebra.polynomialring.berlekampfactoring") -require("algebra.polynomialring.zassenhausfactoring") -require("algebra.polynomialring.decomposition") \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/absexpression.lua b/macros/luatex/latex/luacas/tex/algebra/absexpression.lua deleted file mode 100644 index 2bf0b6a7ef..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/absexpression.lua +++ /dev/null @@ -1,80 +0,0 @@ ---- @class AbsExpression ---- The absolute value of an expression. ---- @field expression Expression -AbsExpression = {} -__AbsExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new absolute value expression with the given expression. ---- @param expression Expression ---- @return AbsExpression -function AbsExpression:new(expression) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.expression = expression - - __o.__index = AbsExpression - __o.__tostring = function(a) - return '|' .. tostring(a.expression) .. '|' - end - - o = setmetatable(o, __o) - return o -end - ---- @return Expression -function AbsExpression:evaluate() - if self.expression:isconstant() then - if self.expression >= Integer.zero() then - return self.expression - end - return -self.expression - end - return self -end - ---- @return Expression -function AbsExpression:autosimplify() - return AbsExpression(self.expression:autosimplify()):evaluate() -end - ---- @return table -function AbsExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return AbsExpression -function AbsExpression:setsubexpressions(subexpressions) - return AbsExpression(subexpressions[1]) -end - ---- @param other Expression ---- @return boolean -function AbsExpression:order(other) - return FunctionExpression("abs", self.expression):order(other) -end - ---- @return string -function AbsExpression:tolatex() - return "\\left|" .. self.expression:tolatex() .. "\\right|" -end - ------------------ --- Inheritance -- ------------------ - -__AbsExpression.__index = CompoundExpression -__AbsExpression.__call = AbsExpression.new -AbsExpression = setmetatable(AbsExpression, __AbsExpression) - ----------------------- --- Static constants -- ----------------------- -ABS = function (a) - return AbsExpression(a) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/equation.lua b/macros/luatex/latex/luacas/tex/algebra/equation.lua deleted file mode 100644 index 4e81d5ca95..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/equation.lua +++ /dev/null @@ -1,183 +0,0 @@ ---- @class Equation ---- An expression that represents an equation of the form lhs = rhs. ---- @field lhs Expression ---- @field rhs Expression -Equation = {} -__Equation = {} - --------------------------- --- Static functionality -- --------------------------- - ---- Attempts to isolate the variable var in lhs by moving expressions to rhs. Ony performs a single step. ---- @param lhs Expression ---- @param rhs Expression ---- @param var SymbolExpression ---- @return Expression, Expression -function Equation.isolatelhs(lhs, rhs, var) - if lhs:type() == BinaryOperation then - local stay = Integer.zero() - local switch = Integer.zero() - if lhs.operation == BinaryOperation.ADD then - for _, exp in ipairs(lhs:subexpressions()) do - if exp:freeof(var) then - switch = switch + exp - else - stay = stay + exp - end - end - if switch == Integer.zero() then - lhs = lhs:factor() -- TODO: Replace with collect for efficiency reasons - else - return stay:autosimplify(), (rhs - switch):autosimplify() - end - end - if lhs.operation == BinaryOperation.MUL then - stay = Integer.one() - switch = Integer.one() - for _, exp in ipairs(lhs:subexpressions()) do - if exp:freeof(var) then - switch = switch * exp - else - stay = stay * exp - end - end - return stay:autosimplify(), (rhs / switch):autosimplify() - end - if lhs.operation == BinaryOperation.POW then - if lhs:subexpressions()[1]:freeof(var) then - return lhs:subexpressions()[2]:autosimplify(), Logarithm(lhs:subexpressions()[1], rhs):autosimplify() - elseif lhs:subexpressions()[2]:freeof(var) then - return lhs:subexpressions()[1]:autosimplify(), (rhs ^ (Integer.one()/lhs:subexpressions()[2])):autosimplify() - end - end - elseif lhs:type() == Logarithm then - if lhs.base:freeof(var) then - return lhs.expression:autosimplify(), (lhs.base ^ rhs):autosimplify() - elseif lhs.expression:freeof(var) then - return lhs.base:autosimplify(), (lhs.expression ^ (Integer.one()/rhs)):autosimplify() - end - elseif lhs:type() == TrigExpression then - return lhs.expression:autosimplify(), TrigExpression(TrigExpression.INVERSES[lhs.name], rhs):autosimplify() - end - - return lhs, rhs -end - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new equation with the given expressions. ---- @param lhs Expression ---- @param rhs Expression ---- @return Equation -function Equation:new(lhs, rhs) - - if lhs:type() == Equation or rhs:type() == Equation then - error("Sent parameter of wrong type: cannot nest equations or inequalities") - end - - local o = {} - local __o = Copy(__ExpressionOperations) -- TODO: Ensure only one metatable for each instance of a class - - o.lhs = lhs - o.rhs = rhs - - __o.__index = Equation - __o.__tostring = function(a) - return tostring(a.lhs) .. ' = ' .. tostring(a.rhs) - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway - if not b:type() == Equation then - return false - end - return a.lhs == b.lhs and a.rhs == b.rhs - end - o = setmetatable(o, __o) - - return o -end - ---- Evaluation in this case just checks for structural equality, or guarenteed inequality in the case of constants ---- @return Equation|boolean -function Equation:evaluate() - if self.lhs == self.rhs then - return true -- TODO: Add Boolean Expressions - end - if self.lhs:isconstant() and self.rhs:isconstant() and self.lhs ~= self.rhs then - return false - end - return self -end - ---- @return Equation|boolean -function Equation:autosimplify() - local lhs = self.lhs:autosimplify() - local rhs = self.rhs:autosimplify() - - return Equation(lhs, rhs):evaluate() -end - ---- @return table -function Equation:subexpressions() - return {self.lhs, self.rhs} -end - ---- Attempts to solve the equation for a particular variable. ---- @param var SymbolExpression ---- @return Equation -function Equation:solvefor(var) - local lhs = self.lhs - local rhs = self.rhs - - if lhs:freeof(var) and rhs:freeof(var) then - return self - end - - -- Check for monovariate polynomial expressions - local root = (lhs - rhs):autosimplify() - local poly, status = root:expand():topolynomial() - if status then - -- TODO: Add Set expressions - return Equation(var, poly:roots()[1]) - end - - local newlhs, newrhs = root, Integer(0) - local oldlhs - while newlhs ~= var and oldlhs ~= newlhs do - oldlhs = newlhs - newlhs, newrhs = Equation.isolatelhs(newlhs, newrhs, var) - end - - return Equation(newlhs, newrhs) -end - ---- @param subexpressions table ---- @return Equation -function Equation:setsubexpressions(subexpressions) - return Equation(subexpressions[1], subexpressions[2]) -end - ---- @param other Expression ---- @return boolean -function Equation:order(other) - if other:isatomic() then - return false - end - - return self.lhs:order(other) -end - ---- @return string -function Equation:tolatex() - return self.lhs:tolatex() .. '=' .. self.rhs:tolatex() -end - ------------------ --- Inheritance -- ------------------ -__Equation.__index = CompoundExpression -__Equation.__call = Equation.new -Equation = setmetatable(Equation, __Equation) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/euclideandomain.lua b/macros/luatex/latex/luacas/tex/algebra/euclideandomain.lua deleted file mode 100644 index fab2c5c7c2..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/euclideandomain.lua +++ /dev/null @@ -1,63 +0,0 @@ ---- @class EuclideanDomain ---- Interface for an element of a euclidean domain. -EuclideanDomain = {} -__EuclideanDomain = {} - ----------------------- --- Required methods -- ----------------------- - ---- @param b EuclideanDomain ---- @return EuclideanDomain, EuclideanDomain -function EuclideanDomain:divremainder(b) - error("Called unimplemented method : divremainder()") -end - ----------------------------- --- Instance functionality -- ----------------------------- - ---- @return boolean -function EuclideanDomain:iscommutative() - return true -end - --------------------------- --- Instance metamethods -- --------------------------- - -__EuclideanOperations = Copy(__RingOperations) - --- Division with remainder --- Unfortunately, this can only return 1 result, so it returns the quotient - for the remainder use a % b, or a:divremainder(b) -__EuclideanOperations.__idiv = function(a, b) - if(b == b:zero()) then - error("Cannot divide by zero.") - end - local aring, bring = a:getring(), b:getring() - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to divide two elements of incompatable rings") - end - return a:inring(oring):divremainder(b:inring(oring)) -end - -__EuclideanOperations.__mod = function(a, b) - if(b == b:zero()) then - error("Cannot divide by zero.") - end - local aring, bring = a:getring(), b:getring() - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to divide two elements of incompatable rings") - end - local _,q = a:inring(oring):divremainder(b:inring(oring)) - return q -end - ------------------ --- Inheritance -- ------------------ - -__EuclideanDomain.__index = Ring -EuclideanDomain = setmetatable(EuclideanDomain, __EuclideanDomain) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/factorialexpression.lua b/macros/luatex/latex/luacas/tex/algebra/factorialexpression.lua deleted file mode 100644 index 1c5b824ff7..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/factorialexpression.lua +++ /dev/null @@ -1,102 +0,0 @@ ---- @class FactorialExpression ---- The factorial of an expression. ---- @field expression Expression -FactorialExpression = {} -__FactorialExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new factorial expression with the given expression. ---- @param expression Expression ---- @return FactorialExpression -function FactorialExpression:new(expression) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.expression = expression - - __o.__index = FactorialExpression - __o.__tostring = function(a) - return '(' .. tostring(a.expression) .. ')!' - end - - o = setmetatable(o, __o) - return o -end - ---- @return Expression -function FactorialExpression:evaluate() - if self.expression:type() == Integer then - if self.expression < Integer.zero() then - error("Aritmetic Error: Factorials of negative integers are not defined.") - end - - if not FactorialExpression.LIMIT then - FactorialExpression.LIMIT = Integer(5000) - end - - if self.expression > FactorialExpression.LIMIT then - return self - end - -- TODO: More efficient factorial computations. - local out = Integer.one() - local i = Integer.zero() - while i < self.expression do - i = i + Integer.one() - out = out * i - end - return out - end - return self -end - ---- @return Expression -function FactorialExpression:autosimplify() - return FactorialExpression(self.expression:autosimplify()):evaluate() -end - ---- @return table -function FactorialExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return AbsExpression -function FactorialExpression:setsubexpressions(subexpressions) - return FactorialExpression(subexpressions[1]) -end - ---- @param other Expression ---- @return boolean -function FactorialExpression:order(other) - return FunctionExpression("fact", self.expression):order(other) -end - ---- @return string -function FactorialExpression:tolatex() - if self.expression:isatomic() then - return self.expression:tolatex() .. "!" - end - return "(" .. self.expression:tolatex() .. ")!" -end - ------------------ --- Inheritance -- ------------------ - -__FactorialExpression.__index = CompoundExpression -__FactorialExpression.__call = FactorialExpression.new -FactorialExpression = setmetatable(FactorialExpression, __FactorialExpression) - ----------------------- --- Static constants -- ----------------------- - --- Do not attempt to compute factorials larger than this. -FactorialExpression.LIMIT = Integer(5000) - -FACT = function (a) - return FactorialExpression(a) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/field.lua b/macros/luatex/latex/luacas/tex/algebra/field.lua deleted file mode 100644 index c845e94c55..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/field.lua +++ /dev/null @@ -1,69 +0,0 @@ ---- @class Field ---- Interface for an element of a field. -Field = {} -__Field = {} - ----------------------- --- Required methods -- ----------------------- - ---- @return Field -function Field:div(b) - return self:mul(b:inv()) -end - ---- @return Field -function Field:inv() - error("Called unimplemented method: inv()") -end - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Field exponentiation based on the definition. Specific rings may implement more efficient methods. ---- @return Field -function Field:pow(n) - local base = self - if(n < Integer.zero()) then - n = -n - base = base:inv() - end - local k = Integer.zero() - local b = self.getring().one() - while k < n do - b = b.mul(base) - k = k + Integer.one() - end - return b -end - --------------------------- --- Instance metamethods -- --------------------------- - -__FieldOperations = Copy(__EuclideanOperations) - -__FieldOperations.__div = function(a, b) - if not b.getring and not b:isconstant() then - return BinaryOperation.DIVEXP({a, b}) - end - - if(b == b:zero()) then - error("Arithmetic Error: Cannot divide by zero.") - end - - local aring, bring = a:getring(), b:getring() - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to divide two elements of incompatable rings") - end - return a:inring(oring):div(b:inring(oring)) -end - ------------------ --- Inheritance -- ------------------ - -__Field.__index = EuclideanDomain -Field = setmetatable(Field, __Field) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/integer.lua b/macros/luatex/latex/luacas/tex/algebra/integer.lua deleted file mode 100644 index a3c54f498c..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/integer.lua +++ /dev/null @@ -1,952 +0,0 @@ ---- @class Integer ---- Represents an element of the ring of integers. ---- @field self table ---- @field sign number -Integer = {} -__Integer = {} - --------------------------- --- Static functionality -- --------------------------- - --- The length of each digit in base 10. 10^15 < 2^53 < 10^16, so 15 is the highest value that will work with double-percision numbers. --- For multiplication to work properly, however, this also must be even so we can take the square root of the digit size exactly. --- 10^14 is still larger than 2^26, so it is still efficient to do multiplication this way. -Integer.DIGITLENGTH = 14 --- The maximum size for a digit. While this doesn't need to be a power of 10, it makes implementing converting to and from strings much easier. -Integer.DIGITSIZE = 10 ^ Integer.DIGITLENGTH --- Partition size for multiplying integers so we can get both the upper and lower bits of each digits -Integer.PARTITIONSIZE = math.floor(math.sqrt(Integer.DIGITSIZE)) - ---- Method for computing the gcd of two integers using Euclid's algorithm. ---- @param a Integer ---- @param b Integer ---- @return Integer -function Integer.gcd(a, b) - while b ~= Integer.zero() do - a, b = b, a%b - end - return a -end - ---- Method for computing the gcd of two integers using Euclid's algorithm. ---- Also returns Bezout's coefficients via extended gcd. ---- @param a Integer ---- @param b Integer ---- @return Integer, Integer, Integer -function Integer.extendedgcd(a, b) - local oldr, r = a, b - local olds, s = Integer.one(), Integer.zero() - local oldt, t = Integer.zero(), Integer.one() - while r ~= Integer.zero() do - local q = oldr // r - oldr, r = r, oldr - q*r - olds, s = s, olds - q*s - oldt, t = t, oldt - q*t - end - return oldr, olds, oldt -end - ---- Method for computing the larger of two integers. ---- Also returns the other integer for sorting purposes. ---- @param a Integer ---- @param b Integer ---- @return Integer, Integer -function Integer.max(a, b) - if a > b then - return a, b - end - return b, a -end - ---- Method for computing the smaller of two integers. ---- Also returns the other integer for sorting purposes. ---- @param a Integer ---- @param b Integer ---- @return Integer, Integer -function Integer.min(a, b) - if a < b then - return a, b - end - return b, a -end - ---- Methods for computing the larger magnitude of two integers. ---- Also returns the other integer for sorting purposes, and the number -1 if the two values were swapped, 1 if not. ---- @param a Integer ---- @param b Integer ---- @return Integer, Integer, number -function Integer.absmax(a, b) - if b:ltabs(a) then - return a, b, 1 - end - return b, a, -1 -end - --- Returns the ceiling of the log base (defaults to 10) of a. --- In other words, returns the least n such that base^n > a. ---- @param a Integer ---- @param base Integer ---- @return Integer -function Integer.ceillog(a, base) - base = base or Integer(10) - local k = Integer.zero() - - while (base ^ k) < a do - k = k + Integer.one() - end - - return k -end - ---- Returns a ^ b (mod n). This should be used when a ^ b is potentially large. ---- @param a Integer ---- @param b Integer ---- @param n Integer ---- @return Integer -function Integer.powmod(a, b, n) - if n == Integer.one() then - return Integer.zero() - else - local r = Integer.one() - a = a % n - while b > Integer.zero() do - if b % Integer(2) == Integer.one() then - r = (r * a) % n - end - a = (a ^ Integer(2)) % n - b = b // Integer(2) - end - return r - end -end - ---- @return RingIdentifier -local t = {ring=Integer} -t = setmetatable(t, {__index = Integer, __eq = function(a, b) - return a["ring"] == b["ring"] -end, __tostring = function(a) - return "ZZ" -end}) -function Integer.makering() - return t -end - - ----------------------------- --- Instance functionality -- ----------------------------- - --- So we don't have to copy the Euclidean operations each time we create an integer. -local __o = Copy(__EuclideanOperations) -__o.__index = Integer -__o.__tostring = function(a) -- Only works if the digit size is a power of 10 - local out = "" - for i, digit in ipairs(a) do - local pre = tostring(math.floor(digit)) - if i ~= #a then - while #pre ~= Integer.DIGITLENGTH do - pre = "0" .. pre - end - end - out = pre .. out - end - if a.sign == -1 then - out = "-" .. out - end - return out -end -__o.__div = function(a, b) -- Constructor for a rational number disguised as division - if not b.getring then - return BinaryOperation.DIVEXP({a, b}) - end - if(a:getring() == Integer:getring() and b:getring() == Integer:getring()) then - return Rational(a, b) - end - return __FieldOperations.__div(a, b) -end -__o.__concat = function(a, b) -- Like a decimal, but fancier. Used mainly for the parser with decimal numbers. - return a + b / (Integer(10) ^ Integer.ceillog(b)) -end - ---- Creates a new integer given a string or number representation of the integer. ---- @param n number|string|Integer ---- @return Integer -function Integer:new(n) - local o = {} - o = setmetatable(o, __o) - - if not n then - o[1] = 0 - o.sign = 0 - return o - end - - -- Can convert any floating-point number into an integer, though we generally only want to pass whole numbers into this. - -- This will only approximate very large floating point numbers to a small proportion of the total significant digits - -- After that the result will just be nonsense - strings should probably be used for big numbers - if type(n) == "number" then - n = math.floor(n) - if n == 0 then - o[1] = 0 - o.sign = 0 - else - if n < 0 then - n = -n - o.sign = -1 - else - o.sign = 1 - end - local i = 1 - while n >= Integer.DIGITSIZE do - o[i] = n % Integer.DIGITSIZE - n = n // Integer.DIGITSIZE - i = i + 1 - end - o[i] = n - end - -- Only works on strings that are exact (signed) integers - elseif type(n) == "string" then - if not tonumber(n) then - error("Sent parameter of wrong type: " .. n .. " is not an integer.") - end - if n == "0" then - o[1] = 0 - o.sign = 0 - else - local s = 1 - if string.sub(n, 1, 1) == "-" then - s = s + 1 - o.sign = -1 - else - o.sign = 1 - end - - while string.sub(n, s, s) == "0" do - s = s + 1 - end - - local e = #n - local i = 1 - while e > s + Integer.DIGITLENGTH - 1 do - o[i] = tonumber(string.sub(n, e - Integer.DIGITLENGTH + 1, e)) - e = e - Integer.DIGITLENGTH - i = i + 1 - end - o[i] = tonumber(string.sub(n, s, e)) or 0 - end - -- Copying is expensive in Lua, so this constructor probably should only sparsely be called with an Integer argument. - elseif type(n) == "table" then - o = Copy(n) - else - error("Sent parameter of wrong type: Integer does not accept " .. type(n) .. ".") - end - - return o -end - ---- Returns the ring this object is an element of. ---- @return RingIdentifier -function Integer:getring() - return t -end - ---- @param ring RingIdentifier ---- @return Ring -function Integer:inring(ring) - if ring == self:getring() then - return self - end - - if ring == PolynomialRing:getring() then - return PolynomialRing({self:inring(ring.child)}, ring.symbol) - end - - if ring == Rational:getring() then - if ring.child then - return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) - end - return Rational(self, Integer.one(), true):inring(ring) - end - - if ring == IntegerModN:getring() then - return IntegerModN(self, ring.modulus) - end - - error("Unable to convert element to proper ring.") -end - ---- @param b Integer ---- @return Integer -function Integer:add(b) - if self.sign == 1 and b.sign == -1 then - return self:usub(b, 1) - end - if self.sign == -1 and b.sign == 1 then - return self:usub(b, -1) - end - - local sign = self.sign - if sign == 0 then - sign = b.sign - end - return self:uadd(b, sign) -end - ---- Addition without sign so we don't have to create an entire new integer when switching signs. ---- @param b Integer ---- @param sign number ---- @return Integer -function Integer:uadd(b, sign) - local o = Integer() - o.sign = sign - - local c = 0 - local n = math.max(#self, #b) - for i = 1, n do - local s = (self[i] or 0) + (b[i] or 0) + c - if s >= Integer.DIGITSIZE then - o[i] = s - Integer.DIGITSIZE - c = 1 - else - o[i] = s - c = 0 - end - end - if c == 1 then - o[n + 1] = c - end - return o -end - ---- @param b Integer ---- @return Integer -function Integer:sub(b) - if self.sign == 1 and b.sign == -1 then - return self:uadd(b, 1) - end - if self.sign == -1 and b.sign == 1 then - return self:uadd(b, -1) - end - - local sign = self.sign - if sign == 0 then - sign = b.sign - end - return self:usub(b, sign) -end - --- Subtraction without sign so we don't have to create an entire new integer when switching signs. --- Uses subtraction by compliments. ---- @param b Integer ---- @param sign number ---- @return Integer -function Integer:usub(b, sign) - local a, b, swap = Integer.absmax(self, b) - local o = Integer() - o.sign = sign * swap - - local c = 0 - local n = #a - for i = 1, n do - local s = (a[i] or 0) + Integer.DIGITSIZE - 1 - (b[i] or 0) + c - if i == 1 then - s = s + 1 - end - if s >= Integer.DIGITSIZE then - o[i] = s - Integer.DIGITSIZE - c = 1 - else - o[i] = s - c = 0 - end - end - - -- Remove leading zero digits, since we want integer representations to be unique. - while o[n] == 0 do - o[n] = nil - n = n - 1 - end - - if not o[1] then - o[1] = 0 - o.sign = 0 - end - - return o -end - ---- @return Integer -function Integer:neg() - local o = Integer() - o.sign = -self.sign - for i, digit in ipairs(self) do - o[i] = digit - end - return o -end - ---- @param b Integer ---- @return Integer -function Integer:mul(b) - local o = Integer() - o.sign = self.sign * b.sign - if o.sign == 0 then - o[1] = 0 - return o - end - - -- Fast single-digit multiplication in the most common case - if #self == 1 and #b == 1 then - o[2], o[1] = self:mulone(self[1], b[1]) - - if o[2] == 0 then - o[2] = nil - end - - return o - end - - -- "Grade school" multiplication algorithm for numbers with small numbers of digits works faster than Karatsuba - local n = #self - local m = #b - o[1] = 0 - o[2] = 0 - for i = 2, n+m do - o[i + 1] = 0 - for j = math.max(1, i-m), math.min(n, i-1) do - local u, l = self:mulone(self[j], b[i - j]) - o[i - 1] = o[i - 1] + l - o[i] = o[i] + u - if o[i - 1] >= Integer.DIGITSIZE then - o[i - 1] = o[i - 1] - Integer.DIGITSIZE - o[i] = o[i] + 1 - end - if o[i] >= Integer.DIGITSIZE then - o[i] = o[i] - Integer.DIGITSIZE - o[i + 1] = o[i + 1] + 1 - end - end - end - - -- Remove leading zero digits, since we want integer representations to be unique. - if o[n+m+1] == 0 then - o[n+m+1] = nil - end - - if o[n+m] == 0 then - o[n+m] = nil - end - - return o -end - ---- Multiplies two single-digit numbers and returns two digits. ---- @param a number ---- @param b number ---- @return number, number -function Integer:mulone(a, b) - local P = Integer.PARTITIONSIZE - - local a1 = a // P - local a2 = a % P - local b1 = b // P - local b2 = b % P - - local u = a1 * b1 - local l = a2 * b2 - - local m = ((a1 * b2) + (b1 * a2)) - local mu = m // P - local ml = m % P - - u = u + mu - l = l + ml * P - - if l >= Integer.DIGITSIZE then - l = l - Integer.DIGITSIZE - u = u + 1 - end - - return u, l -end - ---- Naive exponentiation is slow even for small exponents, so this uses binary exponentiation. ---- @param b Integer ---- @return Integer -function Integer:pow(b) - if b < Integer.zero() then - return Integer.one() / (self ^ -b) - end - - if b == Integer.zero() then - return Integer.one() - end - - -- Fast single-digit exponentiation - if #self == 1 and #b == 1 then - local test = (self.sign * self[1]) ^ b[1] - if test < Integer.DIGITSIZE and test > -Integer.DIGITSIZE then - return Integer(test) - end - end - - local x = self - local y = Integer.one() - while b > Integer.one() do - if b[1] % 2 == 0 then - x = x * x - b = b:divbytwo() - else - y = x * y - x = x * x - b = b:divbytwo() - end - end - - return x * y -end - --- Fast integer division by two for binary exponentiation. ---- @return Integer -function Integer:divbytwo() - local o = Integer() - o.sign = self.sign - for i = #self, 1, -1 do - if self[i] % 2 == 0 then - o[i] = self[i] // 2 - else - o[i] = self[i] // 2 - if i ~= 1 then - o[i - 1] = self[i - 1] * 2 - end - end - end - return o -end - ---- Division with remainder over the integers. Uses the standard base 10 long division algorithm. ---- @param b Integer ---- @return Integer, Integer -function Integer:divremainder(b) - if self >= Integer.zero() and b > self or self <= Integer.zero() and b < self then - return Integer.zero(), Integer(self) - end - - if #self == 1 and #b == 1 then - return Integer((self[1]*self.sign) // (b[1]*b.sign)), Integer((self[1]*self.sign) % (b[1]*b.sign)) - end - - local Q = Integer() - local R = Integer() - - Q.sign = self.sign * b.sign - R.sign = 1 - local negativemod = false - if b.sign == -1 then - b.sign = -b.sign - negativemod = true - end - - for i = #self, 1, -1 do - local s = tostring(math.floor(self[i])) - while i ~= #self and #s ~= Integer.DIGITLENGTH do - s = "0" .. s - end - Q[i] = 0 - for j = 1, #s do - R = R:mulbyten() - R[1] = R[1] + tonumber(string.sub(s, j, j)) - if R[1] > 0 then - R.sign = 1 - end - while R >= b do - R = R - b - Q[i] = Q[i] + 10^(#s - j) - end - end - end - - -- Remove leading zero digits, since we want integer representations to be unique. - while Q[#Q] == 0 do - Q[#Q] = nil - end - - if negativemod then - R = -R - b.sign = -b.sign - elseif self.sign == -1 then - R = b - R - end - - return Q, R -end - ---- Fast in-place multiplication by ten for the division algorithm. This means the number IS MODIFIED by this method unlike the rest of the library. ---- @return Integer -function Integer:mulbyten() - local DIGITSIZE = Integer.DIGITSIZE - for i, _ in ipairs(self) do - self[i] = self[i] * 10 - end - for i, _ in ipairs(self) do - if self[i] > DIGITSIZE then - local msd = self[i] // DIGITSIZE - if self[i+1] then - self[i+1] = self[i+1] + msd - else - self[i+1] = msd - end - self[i] = self[i] - DIGITSIZE*msd - end - end - return self -end - ---- @param b Integer ---- @return boolean -function Integer:eq(b) - for i, digit in ipairs(self) do - if not b[i] or not (b[i] == digit) then - return false - end - end - return #self == #b and self.sign == b.sign -end - ---- @param b Integer ---- @return boolean -function Integer:lt(b) - local selfsize = #self - local bsize = #b - if selfsize < bsize then - return b.sign == 1 - end - if selfsize > bsize then - return self.sign == -1 - end - local n = selfsize - while n > 0 do - if self[n]*self.sign < b[n]*b.sign then - return true - end - if self[n]*self.sign > b[n]*b.sign then - return false - end - n = n - 1 - end - return false -end - ---- Same as less than, but ignores signs. ---- @param b Integer ---- @return boolean -function Integer:ltabs(b) - if #self < #b then - return true - end - if #self > #b then - return false - end - local n = #self - while n > 0 do - if self[n] < b[n] then - return true - end - if self[n] > b[n] then - return false - end - n = n - 1 - end - return false -end - ---- @param b Integer ---- @return boolean -function Integer:le(b) - local selfsize = #self - local bsize = #b - if selfsize < bsize then - return b.sign == 1 - end - if selfsize > bsize then - return self.sign == -1 - end - local n = selfsize - while n > 0 do - if self[n]*self.sign < b[n]*b.sign then - return true - end - if self[n]*self.sign > b[n]*b.sign then - return false - end - n = n - 1 - end - return true -end - -local zero = Integer:new(0) ---- @return Integer -function Integer:zero() - return zero -end - -local one = Integer:new(1) ---- @return Integer -function Integer:one() - return one -end - ---- Returns this integer as a floating point number. Can only approximate the value of large integers. ---- @return number -function Integer:asnumber() - local n = 0 - for i, digit in ipairs(self) do - n = n + digit * Integer.DIGITSIZE ^ (i - 1) - end - return self.sign*math.floor(n) -end - ---- Returns all positive divisors of the integer. Not guarenteed to be in any order. ---- @return table -function Integer:divisors() - local primefactors = self:primefactorizationrec() - local divisors = {} - - local terms = {} - for prime in pairs(primefactors) do - if prime == Integer(-1) then - primefactors[prime] = nil - end - terms[prime] = Integer.zero() - end - - local divisor = Integer.one() - - while true do - divisors[#divisors+1] = divisor - for prime, power in pairs(primefactors) do - if terms[prime] < power then - terms[prime] = terms[prime] + Integer.one() - divisor = divisor * prime - break - else - terms[prime] = Integer.zero() - divisor = divisor / (prime ^ power) - end - end - if divisor == Integer.one() then - break - end - end - - return divisors -end - ---- Returns whether this integer is a prime power, of the form p^a for prime p and positive integer a. ---- If it is a prime power, also returns the prime and the power. ---- @return boolean, Expression|nil, Expression|nil -function Integer:isprimepower() - if self <= Integer.one() then - return false - end - local factorization = self:primefactorization() - if factorization:type() == BinaryOperation and #factorization:subexpressions() == 1 then - return true, factorization.expressions[1].expressions[2], factorization.expressions[1].expressions[1] - end - return false -end - ---- Returns whether this integer is a perfect power, of the form a^b for positive integers a and b. ---- If it is a prime power, also returns the prime and the power. ---- @return boolean, Expression|nil, Expression|nil -function Integer:isperfectpower() - if self <= Integer.one() then - return false - end - local factorization = self:primefactorization() - if factorization:type() ~= BinaryOperation then - return false - end - local power = Integer.zero() - for _, term in ipairs(factorization:subexpressions()) do - power = Integer.gcd(power, term.expressions[2]) - if power == Integer.one() then - return false - end - end - local base = Integer.one() - for _, term in ipairs(factorization:subexpressions()) do - base = base * term.expressions[1] ^ (term.expressions[2] / power) - end - return true, base, power -end - ---- Returns the prime factorization of this integer as a expression. ---- @return Expression -function Integer:primefactorization() - if not Integer.FACTORIZATIONLIMIT then - Integer.FACTORIZATIONLIMIT = Integer(Integer.DIGITSIZE) - end - if self > Integer.FACTORIZATIONLIMIT then - return self - end - local result = self:primefactorizationrec() - local mul = {} - local i = 1 - for factor, degree in pairs(result) do - mul[i] = BinaryOperation.POWEXP({factor, degree}) - i = i + 1 - end - return BinaryOperation.MULEXP(mul):lock(Expression.NIL) -end - ---- Recursive part of prime factorization using Pollard Rho. -function Integer:primefactorizationrec() - if self < Integer.zero() then - return Integer.mergefactors({[Integer(-1)]=Integer.one()}, (-self):primefactorizationrec()) - end - if self == Integer.one() then - return {[Integer.one()]=Integer.one()} - end - local result = self:findafactor() - if result == self then - return {[result]=Integer.one()} - end - local remaining = self / result - return Integer.mergefactors(result:primefactorizationrec(), remaining:primefactorizationrec()) -end - - -function Integer.mergefactors(a, b) - local result = Copy(a) - - for factor, degree in pairs(b) do - for ofactor, odegree in pairs(result) do - if factor == ofactor then - result[ofactor] = degree + odegree - goto continue - end - end - result[factor] = degree - ::continue:: - end - return result -end - --- Return a non-trivial factor of n via Pollard Rho, or returns n if n is prime. -function Integer:findafactor() - if self:isprime() then - return self - end - - if self % Integer(2) == Integer.zero() then - return Integer(2) - end - - if self % Integer(3) == Integer.zero() then - return Integer(3) - end - - if self % Integer(5) == Integer.zero() then - return Integer(5) - end - - local g = function(x) - local temp = Integer.powmod(x, Integer(2), self) - return temp - end - - local xstart = Integer(2) - while xstart < self do - local x = xstart - local y = xstart - local d = Integer.one() - while d == Integer.one() do - x = g(x) - y = g(g(y)) - d = Integer.gcd((x - y):abs(), self) - end - - if d < self then - return d - end - - xstart = xstart + Integer.one() - end -end - ---- Uses Miller-Rabin to determine whether a number is prime up to a very large number. -local smallprimes = {Integer:new(2), Integer:new(3), Integer:new(5), Integer:new(7), Integer:new(11), Integer:new(13), Integer:new(17), -Integer:new(19), Integer:new(23), Integer:new(29), Integer:new(31), Integer:new(37), Integer:new(41), Integer:new(43), Integer:new(47)} - -function Integer:isprime() - if self % Integer(2) == Integer.zero() then - if self == Integer(2) then - return true - end - return false - end - - if self == Integer.one() then - return false - end - - for _, value in pairs(smallprimes) do - if value == self then - return true - end - end - - local r = Integer.zero() - local d = self - Integer.one() - while d % Integer(2) == Integer.zero() do - r = r + Integer.one() - d = d / Integer(2) - end - - for _, a in ipairs(smallprimes) do - local s = r - local x = Integer.powmod(a, d, self) - if x == Integer.one() or x == self - Integer.one() then - goto continue - end - - while s > Integer.zero() do - x = Integer.powmod(x, Integer(2), self) - if x == self - Integer.one() then - goto continue - end - s = s - Integer.one() - end - do - return false - end - ::continue:: - end - - return true -end - ---- Returns the absolute value of an integer. ---- @return Integer -function Integer:abs() - if self.sign >= 0 then - return Integer(self) - end - return -self -end - ------------------ --- Inheritance -- ------------------ - -__Integer.__index = EuclideanDomain -__Integer.__call = Integer.new -Integer = setmetatable(Integer, __Integer) - ----------------------- --- Static constants -- ----------------------- - -Integer.FACTORIZATIONLIMIT = Integer(Integer.DIGITSIZE) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/integerquotientring.lua b/macros/luatex/latex/luacas/tex/algebra/integerquotientring.lua deleted file mode 100644 index 4c2188c9e5..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/integerquotientring.lua +++ /dev/null @@ -1,197 +0,0 @@ ---- @class IntegerModN ---- Represents an element of the ring of integers mod n (this is also a field iff n is prime). ---- @field element Integer ---- @field modulus Integer - -IntegerModN = {} -__IntegerModN = {} - --- Metatable for ring objects. -local __obj = {__index = IntegerModN, __eq = function(a, b) - return a["ring"] == b["ring"] and (a["modulus"] == b["modulus"] or a["modulus"] == nil or b["modulus"] == nil) -end, __tostring = function(a) - if a.modulus then return "Z/Z" .. tostring(a.modulus) else return "(Generic Integer Mod Ring)" end -end} - --------------------------- --- Static functionality -- --------------------------- - ---- Creates a new ring with the given modulus. ---- @param modulus Integer ---- @return RingIdentifier -function IntegerModN.makering(modulus) - local t = {ring = IntegerModN} - t.modulus = modulus - t = setmetatable(t, __obj) - return t -end - --- Shorthand constructor for a ring with a particular modulus. -function IntegerModN.R(modulus) - return IntegerModN.makering(modulus) -end - ----------------------------- --- Instance functionality -- ----------------------------- - --- So we don't have to copy the field operations each time -local __o -__o = Copy(__FieldOperations) - -__o.__index = IntegerModN -__o.__tostring = function(a) - return tostring(a.element) -end - ---- Creates a new integer i in Z/nZ. ---- @param i Integer ---- @param n Integer ---- @return IntegerModN -function IntegerModN:new(i, n) - local o = {} - - if n:getring() ~= Integer:getring() or n < Integer.one() then - error("Argument error: modulus must be an integer greater than 0.") - end - - o = setmetatable(o, __o) - - if i < Integer.zero() or i >= n then - i = i % n - end - - o.element = i - o.modulus = n - - return o -end - ---- @return RingIdentifier -function IntegerModN:getring() - local t = {ring = IntegerModN} - if self then - t.modulus = self.modulus - end - t = setmetatable(t, __obj) - return t -end - ---- @param ring RingIdentifier ---- @return Ring -function IntegerModN:inring(ring) - if ring == IntegerModN:getring() then - if ring.modulus then - return IntegerModN(self.element, ring.modulus) - end - return self - end - - if ring == PolynomialRing:getring() then - return PolynomialRing({self:inring(ring.child)}, ring.symbol) - end - - if ring == Rational:getring() and ring.symbol then - return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) - end - - if ring == Integer:getring() then - return self.element:inring(ring) - end - - error("Unable to convert element to proper ring.") -end - ---- @param b IntegerModN ---- @return IntegerModN -function IntegerModN:add(b) - return IntegerModN(self.element + b.element, self.modulus) -end - ---- @return IntegerModN -function IntegerModN:neg() - return IntegerModN(-self.element, self.modulus) -end - ---- @param b IntegerModN ---- @return IntegerModN -function IntegerModN:mul(b) - return IntegerModN(self.element * b.element, self.modulus) -end - --- Overrides the generic power method with powmod. ---- @param b IntegerModN ---- @return IntegerModN -function IntegerModN:pow(b) - return IntegerModN(Integer.powmod(self.element, b.element, self.modulus), self.modulus) -end - --- Returns the multiplicative inverse of this number if it exists. ---- @return IntegerModN -function IntegerModN:inv() - local r, t, _ = Integer.extendedgcd(self.element, self.modulus) - - if r > Integer.one() then - error("Element does not have an inverse in this ring") - end - - return IntegerModN(t, self.modulus) -end - ---- @param b IntegerModN ---- @return IntegerModN -function IntegerModN:div(b) - return self:mul(b:inv()) -end - ---- @param b IntegerModN ---- @return boolean -function IntegerModN:eq(b) - return self.element == b.element -end - ---- @param b IntegerModN ---- @return boolean -function IntegerModN:lt(b) - return self.element < b.element -end - ---- @param b IntegerModN ---- @return boolean -function IntegerModN:le(b) - return self.element <= b.element -end - ---- @return IntegerModN -function IntegerModN:zero() - if not self or not self.modulus then - return Integer.zero() - end - return IntegerModN(Integer.zero(), self.modulus) -end - ---- @return IntegerModN -function IntegerModN:one() - if not self or not self.modulus then - return Integer.one() - end - return IntegerModN(Integer.one(), self.modulus) -end - ---- @return string -function IntegerModN:tolatex(mod) - mod = mod or false - if mod then - return self.element:tolatex() .. "\\bmod{" .. self.modulus:tolatex() .. "}" - else - return self.element:tolatex() - end -end ------------------ --- Inheritance -- ------------------ - -__IntegerModN.__index = Field -__IntegerModN.__call = IntegerModN.new -IntegerModN = setmetatable(IntegerModN, __IntegerModN) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/logarithm.lua b/macros/luatex/latex/luacas/tex/algebra/logarithm.lua deleted file mode 100644 index 07a9db84d1..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/logarithm.lua +++ /dev/null @@ -1,186 +0,0 @@ ---- @class Logarithm ---- An expression for the logarithm of an expression with respect to another. ---- Currently, logarithms are not being evaluated since we are just doing symbolic computation. ---- @field base Expression ---- @field expression Expression -Logarithm = {} -__Logarithm = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new logarithm expression with the given symbol and expression. ---- @param base Expression ---- @param expression Expression ---- @ -function Logarithm:new(base, expression) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.base = Copy(base) - o.expression = Copy(expression) - - __o.__index = Logarithm - __o.__tostring = function(a) - return 'log(' .. tostring(base) .. ', ' .. tostring(expression) .. ')' - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway - if not b:type() == Logarithm then - return false - end - return a.base == b.base and a.expression == b.expression - end - o = setmetatable(o, __o) - - return o -end - ---- @return Expression -function Logarithm:evaluate() - - if (self.base:isconstant() and (self.base <= Integer.zero() or self.base == Integer.one())) or - (self.expression:isconstant() and self.expression <= Integer.zero()) then - error("Arithmetic error: division by zero") - end - - if not self.base:isconstant() or not self.expression:isconstant() then - return self - end - - local power = Integer.one() - local base = self.base - if base:type() == Integer then - local pp, b, p = base:isperfectpower() - if pp then - base = b - power = p - end - end - - if base:type() == Rational then - local ppn, bn, pn = base.numerator:isperfectpower() - local ppd, bd, pd = base.denominator:isperfectpower() - if base.numerator == Integer.one() then - ppn = true - bn = Integer.one() - pn = pd - end - if ppn and ppd and pn == pd then - base = bn / bd - power = pn - end - end - - local result = Integer.one() - local expression = self.expression - local sign = Integer.one() - if base < Integer.one() then - base = Integer.one() / base - sign = -sign - end - - - local current = base - while current < expression do - current = current * base - result = result + Integer.one() - end - if current == expression then - return sign * result / power - else - while current > expression do - current = current / base - result = result - Integer.one() - end - if current == expression then - return sign * result / power - end - end - - return self -end - ---- @return Expression -function Logarithm:autosimplify() - - local base = self.base:autosimplify() - local expression = self.expression:autosimplify() - - local evaluated = Logarithm(base, expression):evaluate() - if evaluated:type() ~= Logarithm then - return evaluated - end - - -- Uses the property that log(b, 1) = 0 - if expression == Integer.one() then - return Integer.zero() - end - - -- Uses the property that log(b, b) = 1 - if expression == base then - return Integer.one() - end - - -- Uses the propery that log(b, x^y) = y * log(b, x) - if expression.operation == BinaryOperation.POW then - return BinaryOperation.MULEXP({expression.expressions[2], Logarithm(base, expression.expressions[1])}):autosimplify() - end - - if expression:type() == Rational and expression.numerator == Integer.one() then - return (-Logarithm(base, expression.denominator)):autosimplify() - end - - -- Our expression cannot be simplified - return Logarithm(base, expression) -end - ---- @return Expression -function Logarithm:expand() - return Logarithm(self.base:expand(), self.expression:expand()):autosimplify() -end - ---- @return table -function Logarithm:subexpressions() - return {self.base, self.expression} -end - ---- @param subexpressions table ---- @return Logarithm -function Logarithm:setsubexpressions(subexpressions) - return Logarithm(subexpressions[1], subexpressions[2]) -end - ---- @param other Expression ---- @return boolean -function Logarithm:order(other) - return FunctionExpression("log", {self.base, self.expression}):order(other) -end - ---- @return string -function Logarithm:tolatex() - if self.base == E then - return '\\ln\\mathopen{}\\left(' .. self.expression:tolatex() .. '\\right)' - end - return '\\log_' .. self.base:tolatex() .. '\\mathopen{}\\left(' .. self.expression:tolatex() .. '\\right)' -end - ------------------ --- Inheritance -- ------------------ -__Logarithm.__index = CompoundExpression -__Logarithm.__call = Logarithm.new -Logarithm = setmetatable(Logarithm, __Logarithm) - ----------------------- --- Static constants -- ----------------------- - -LOG = function(base, expression) - return Logarithm(base, expression) -end - -LN = function(expression) - return Logarithm(E, expression) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-absexpression.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-absexpression.lua new file mode 100644 index 0000000000..2bf0b6a7ef --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-absexpression.lua @@ -0,0 +1,80 @@ +--- @class AbsExpression +--- The absolute value of an expression. +--- @field expression Expression +AbsExpression = {} +__AbsExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new absolute value expression with the given expression. +--- @param expression Expression +--- @return AbsExpression +function AbsExpression:new(expression) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.expression = expression + + __o.__index = AbsExpression + __o.__tostring = function(a) + return '|' .. tostring(a.expression) .. '|' + end + + o = setmetatable(o, __o) + return o +end + +--- @return Expression +function AbsExpression:evaluate() + if self.expression:isconstant() then + if self.expression >= Integer.zero() then + return self.expression + end + return -self.expression + end + return self +end + +--- @return Expression +function AbsExpression:autosimplify() + return AbsExpression(self.expression:autosimplify()):evaluate() +end + +--- @return table +function AbsExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return AbsExpression +function AbsExpression:setsubexpressions(subexpressions) + return AbsExpression(subexpressions[1]) +end + +--- @param other Expression +--- @return boolean +function AbsExpression:order(other) + return FunctionExpression("abs", self.expression):order(other) +end + +--- @return string +function AbsExpression:tolatex() + return "\\left|" .. self.expression:tolatex() .. "\\right|" +end + +----------------- +-- Inheritance -- +----------------- + +__AbsExpression.__index = CompoundExpression +__AbsExpression.__call = AbsExpression.new +AbsExpression = setmetatable(AbsExpression, __AbsExpression) + +---------------------- +-- Static constants -- +---------------------- +ABS = function (a) + return AbsExpression(a) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-algebra_init.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-algebra_init.lua new file mode 100644 index 0000000000..ac37c56a5b --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-algebra_init.lua @@ -0,0 +1,24 @@ +-- Loads algebra files in the correct order. +require("_lib.luacas-table") + +require("core.luacas-core_init") + +require("algebra.luacas-ring") +require("algebra.luacas-euclideandomain") +require("algebra.luacas-field") +require("algebra.luacas-polynomialring") +require("algebra.luacas-integer") +require("algebra.luacas-rational") +require("algebra.luacas-integerquotientring") +require("algebra.luacas-sqrtexpression") + +require("algebra.luacas-absexpression") +require("algebra.luacas-equation") +require("algebra.luacas-factorialexpression") +require("algebra.luacas-logarithm") +require("algebra.luacas-rootexpression") +require("algebra.luacas-trigexpression") + +require("algebra.polynomialring.luacas-berlekampfactoring") +require("algebra.polynomialring.luacas-zassenhausfactoring") +require("algebra.polynomialring.luacas-decomposition") \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-equation.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-equation.lua new file mode 100644 index 0000000000..4e81d5ca95 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-equation.lua @@ -0,0 +1,183 @@ +--- @class Equation +--- An expression that represents an equation of the form lhs = rhs. +--- @field lhs Expression +--- @field rhs Expression +Equation = {} +__Equation = {} + +-------------------------- +-- Static functionality -- +-------------------------- + +--- Attempts to isolate the variable var in lhs by moving expressions to rhs. Ony performs a single step. +--- @param lhs Expression +--- @param rhs Expression +--- @param var SymbolExpression +--- @return Expression, Expression +function Equation.isolatelhs(lhs, rhs, var) + if lhs:type() == BinaryOperation then + local stay = Integer.zero() + local switch = Integer.zero() + if lhs.operation == BinaryOperation.ADD then + for _, exp in ipairs(lhs:subexpressions()) do + if exp:freeof(var) then + switch = switch + exp + else + stay = stay + exp + end + end + if switch == Integer.zero() then + lhs = lhs:factor() -- TODO: Replace with collect for efficiency reasons + else + return stay:autosimplify(), (rhs - switch):autosimplify() + end + end + if lhs.operation == BinaryOperation.MUL then + stay = Integer.one() + switch = Integer.one() + for _, exp in ipairs(lhs:subexpressions()) do + if exp:freeof(var) then + switch = switch * exp + else + stay = stay * exp + end + end + return stay:autosimplify(), (rhs / switch):autosimplify() + end + if lhs.operation == BinaryOperation.POW then + if lhs:subexpressions()[1]:freeof(var) then + return lhs:subexpressions()[2]:autosimplify(), Logarithm(lhs:subexpressions()[1], rhs):autosimplify() + elseif lhs:subexpressions()[2]:freeof(var) then + return lhs:subexpressions()[1]:autosimplify(), (rhs ^ (Integer.one()/lhs:subexpressions()[2])):autosimplify() + end + end + elseif lhs:type() == Logarithm then + if lhs.base:freeof(var) then + return lhs.expression:autosimplify(), (lhs.base ^ rhs):autosimplify() + elseif lhs.expression:freeof(var) then + return lhs.base:autosimplify(), (lhs.expression ^ (Integer.one()/rhs)):autosimplify() + end + elseif lhs:type() == TrigExpression then + return lhs.expression:autosimplify(), TrigExpression(TrigExpression.INVERSES[lhs.name], rhs):autosimplify() + end + + return lhs, rhs +end + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new equation with the given expressions. +--- @param lhs Expression +--- @param rhs Expression +--- @return Equation +function Equation:new(lhs, rhs) + + if lhs:type() == Equation or rhs:type() == Equation then + error("Sent parameter of wrong type: cannot nest equations or inequalities") + end + + local o = {} + local __o = Copy(__ExpressionOperations) -- TODO: Ensure only one metatable for each instance of a class + + o.lhs = lhs + o.rhs = rhs + + __o.__index = Equation + __o.__tostring = function(a) + return tostring(a.lhs) .. ' = ' .. tostring(a.rhs) + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway + if not b:type() == Equation then + return false + end + return a.lhs == b.lhs and a.rhs == b.rhs + end + o = setmetatable(o, __o) + + return o +end + +--- Evaluation in this case just checks for structural equality, or guarenteed inequality in the case of constants +--- @return Equation|boolean +function Equation:evaluate() + if self.lhs == self.rhs then + return true -- TODO: Add Boolean Expressions + end + if self.lhs:isconstant() and self.rhs:isconstant() and self.lhs ~= self.rhs then + return false + end + return self +end + +--- @return Equation|boolean +function Equation:autosimplify() + local lhs = self.lhs:autosimplify() + local rhs = self.rhs:autosimplify() + + return Equation(lhs, rhs):evaluate() +end + +--- @return table +function Equation:subexpressions() + return {self.lhs, self.rhs} +end + +--- Attempts to solve the equation for a particular variable. +--- @param var SymbolExpression +--- @return Equation +function Equation:solvefor(var) + local lhs = self.lhs + local rhs = self.rhs + + if lhs:freeof(var) and rhs:freeof(var) then + return self + end + + -- Check for monovariate polynomial expressions + local root = (lhs - rhs):autosimplify() + local poly, status = root:expand():topolynomial() + if status then + -- TODO: Add Set expressions + return Equation(var, poly:roots()[1]) + end + + local newlhs, newrhs = root, Integer(0) + local oldlhs + while newlhs ~= var and oldlhs ~= newlhs do + oldlhs = newlhs + newlhs, newrhs = Equation.isolatelhs(newlhs, newrhs, var) + end + + return Equation(newlhs, newrhs) +end + +--- @param subexpressions table +--- @return Equation +function Equation:setsubexpressions(subexpressions) + return Equation(subexpressions[1], subexpressions[2]) +end + +--- @param other Expression +--- @return boolean +function Equation:order(other) + if other:isatomic() then + return false + end + + return self.lhs:order(other) +end + +--- @return string +function Equation:tolatex() + return self.lhs:tolatex() .. '=' .. self.rhs:tolatex() +end + +----------------- +-- Inheritance -- +----------------- +__Equation.__index = CompoundExpression +__Equation.__call = Equation.new +Equation = setmetatable(Equation, __Equation) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-euclideandomain.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-euclideandomain.lua new file mode 100644 index 0000000000..fab2c5c7c2 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-euclideandomain.lua @@ -0,0 +1,63 @@ +--- @class EuclideanDomain +--- Interface for an element of a euclidean domain. +EuclideanDomain = {} +__EuclideanDomain = {} + +---------------------- +-- Required methods -- +---------------------- + +--- @param b EuclideanDomain +--- @return EuclideanDomain, EuclideanDomain +function EuclideanDomain:divremainder(b) + error("Called unimplemented method : divremainder()") +end + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- @return boolean +function EuclideanDomain:iscommutative() + return true +end + +-------------------------- +-- Instance metamethods -- +-------------------------- + +__EuclideanOperations = Copy(__RingOperations) + +-- Division with remainder +-- Unfortunately, this can only return 1 result, so it returns the quotient - for the remainder use a % b, or a:divremainder(b) +__EuclideanOperations.__idiv = function(a, b) + if(b == b:zero()) then + error("Cannot divide by zero.") + end + local aring, bring = a:getring(), b:getring() + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to divide two elements of incompatable rings") + end + return a:inring(oring):divremainder(b:inring(oring)) +end + +__EuclideanOperations.__mod = function(a, b) + if(b == b:zero()) then + error("Cannot divide by zero.") + end + local aring, bring = a:getring(), b:getring() + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to divide two elements of incompatable rings") + end + local _,q = a:inring(oring):divremainder(b:inring(oring)) + return q +end + +----------------- +-- Inheritance -- +----------------- + +__EuclideanDomain.__index = Ring +EuclideanDomain = setmetatable(EuclideanDomain, __EuclideanDomain) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-factorialexpression.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-factorialexpression.lua new file mode 100644 index 0000000000..1c5b824ff7 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-factorialexpression.lua @@ -0,0 +1,102 @@ +--- @class FactorialExpression +--- The factorial of an expression. +--- @field expression Expression +FactorialExpression = {} +__FactorialExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new factorial expression with the given expression. +--- @param expression Expression +--- @return FactorialExpression +function FactorialExpression:new(expression) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.expression = expression + + __o.__index = FactorialExpression + __o.__tostring = function(a) + return '(' .. tostring(a.expression) .. ')!' + end + + o = setmetatable(o, __o) + return o +end + +--- @return Expression +function FactorialExpression:evaluate() + if self.expression:type() == Integer then + if self.expression < Integer.zero() then + error("Aritmetic Error: Factorials of negative integers are not defined.") + end + + if not FactorialExpression.LIMIT then + FactorialExpression.LIMIT = Integer(5000) + end + + if self.expression > FactorialExpression.LIMIT then + return self + end + -- TODO: More efficient factorial computations. + local out = Integer.one() + local i = Integer.zero() + while i < self.expression do + i = i + Integer.one() + out = out * i + end + return out + end + return self +end + +--- @return Expression +function FactorialExpression:autosimplify() + return FactorialExpression(self.expression:autosimplify()):evaluate() +end + +--- @return table +function FactorialExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return AbsExpression +function FactorialExpression:setsubexpressions(subexpressions) + return FactorialExpression(subexpressions[1]) +end + +--- @param other Expression +--- @return boolean +function FactorialExpression:order(other) + return FunctionExpression("fact", self.expression):order(other) +end + +--- @return string +function FactorialExpression:tolatex() + if self.expression:isatomic() then + return self.expression:tolatex() .. "!" + end + return "(" .. self.expression:tolatex() .. ")!" +end + +----------------- +-- Inheritance -- +----------------- + +__FactorialExpression.__index = CompoundExpression +__FactorialExpression.__call = FactorialExpression.new +FactorialExpression = setmetatable(FactorialExpression, __FactorialExpression) + +---------------------- +-- Static constants -- +---------------------- + +-- Do not attempt to compute factorials larger than this. +FactorialExpression.LIMIT = Integer(5000) + +FACT = function (a) + return FactorialExpression(a) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-field.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-field.lua new file mode 100644 index 0000000000..c845e94c55 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-field.lua @@ -0,0 +1,69 @@ +--- @class Field +--- Interface for an element of a field. +Field = {} +__Field = {} + +---------------------- +-- Required methods -- +---------------------- + +--- @return Field +function Field:div(b) + return self:mul(b:inv()) +end + +--- @return Field +function Field:inv() + error("Called unimplemented method: inv()") +end + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Field exponentiation based on the definition. Specific rings may implement more efficient methods. +--- @return Field +function Field:pow(n) + local base = self + if(n < Integer.zero()) then + n = -n + base = base:inv() + end + local k = Integer.zero() + local b = self.getring().one() + while k < n do + b = b.mul(base) + k = k + Integer.one() + end + return b +end + +-------------------------- +-- Instance metamethods -- +-------------------------- + +__FieldOperations = Copy(__EuclideanOperations) + +__FieldOperations.__div = function(a, b) + if not b.getring and not b:isconstant() then + return BinaryOperation.DIVEXP({a, b}) + end + + if(b == b:zero()) then + error("Arithmetic Error: Cannot divide by zero.") + end + + local aring, bring = a:getring(), b:getring() + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to divide two elements of incompatable rings") + end + return a:inring(oring):div(b:inring(oring)) +end + +----------------- +-- Inheritance -- +----------------- + +__Field.__index = EuclideanDomain +Field = setmetatable(Field, __Field) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-integer.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-integer.lua new file mode 100644 index 0000000000..a3c54f498c --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-integer.lua @@ -0,0 +1,952 @@ +--- @class Integer +--- Represents an element of the ring of integers. +--- @field self table +--- @field sign number +Integer = {} +__Integer = {} + +-------------------------- +-- Static functionality -- +-------------------------- + +-- The length of each digit in base 10. 10^15 < 2^53 < 10^16, so 15 is the highest value that will work with double-percision numbers. +-- For multiplication to work properly, however, this also must be even so we can take the square root of the digit size exactly. +-- 10^14 is still larger than 2^26, so it is still efficient to do multiplication this way. +Integer.DIGITLENGTH = 14 +-- The maximum size for a digit. While this doesn't need to be a power of 10, it makes implementing converting to and from strings much easier. +Integer.DIGITSIZE = 10 ^ Integer.DIGITLENGTH +-- Partition size for multiplying integers so we can get both the upper and lower bits of each digits +Integer.PARTITIONSIZE = math.floor(math.sqrt(Integer.DIGITSIZE)) + +--- Method for computing the gcd of two integers using Euclid's algorithm. +--- @param a Integer +--- @param b Integer +--- @return Integer +function Integer.gcd(a, b) + while b ~= Integer.zero() do + a, b = b, a%b + end + return a +end + +--- Method for computing the gcd of two integers using Euclid's algorithm. +--- Also returns Bezout's coefficients via extended gcd. +--- @param a Integer +--- @param b Integer +--- @return Integer, Integer, Integer +function Integer.extendedgcd(a, b) + local oldr, r = a, b + local olds, s = Integer.one(), Integer.zero() + local oldt, t = Integer.zero(), Integer.one() + while r ~= Integer.zero() do + local q = oldr // r + oldr, r = r, oldr - q*r + olds, s = s, olds - q*s + oldt, t = t, oldt - q*t + end + return oldr, olds, oldt +end + +--- Method for computing the larger of two integers. +--- Also returns the other integer for sorting purposes. +--- @param a Integer +--- @param b Integer +--- @return Integer, Integer +function Integer.max(a, b) + if a > b then + return a, b + end + return b, a +end + +--- Method for computing the smaller of two integers. +--- Also returns the other integer for sorting purposes. +--- @param a Integer +--- @param b Integer +--- @return Integer, Integer +function Integer.min(a, b) + if a < b then + return a, b + end + return b, a +end + +--- Methods for computing the larger magnitude of two integers. +--- Also returns the other integer for sorting purposes, and the number -1 if the two values were swapped, 1 if not. +--- @param a Integer +--- @param b Integer +--- @return Integer, Integer, number +function Integer.absmax(a, b) + if b:ltabs(a) then + return a, b, 1 + end + return b, a, -1 +end + +-- Returns the ceiling of the log base (defaults to 10) of a. +-- In other words, returns the least n such that base^n > a. +--- @param a Integer +--- @param base Integer +--- @return Integer +function Integer.ceillog(a, base) + base = base or Integer(10) + local k = Integer.zero() + + while (base ^ k) < a do + k = k + Integer.one() + end + + return k +end + +--- Returns a ^ b (mod n). This should be used when a ^ b is potentially large. +--- @param a Integer +--- @param b Integer +--- @param n Integer +--- @return Integer +function Integer.powmod(a, b, n) + if n == Integer.one() then + return Integer.zero() + else + local r = Integer.one() + a = a % n + while b > Integer.zero() do + if b % Integer(2) == Integer.one() then + r = (r * a) % n + end + a = (a ^ Integer(2)) % n + b = b // Integer(2) + end + return r + end +end + +--- @return RingIdentifier +local t = {ring=Integer} +t = setmetatable(t, {__index = Integer, __eq = function(a, b) + return a["ring"] == b["ring"] +end, __tostring = function(a) + return "ZZ" +end}) +function Integer.makering() + return t +end + + +---------------------------- +-- Instance functionality -- +---------------------------- + +-- So we don't have to copy the Euclidean operations each time we create an integer. +local __o = Copy(__EuclideanOperations) +__o.__index = Integer +__o.__tostring = function(a) -- Only works if the digit size is a power of 10 + local out = "" + for i, digit in ipairs(a) do + local pre = tostring(math.floor(digit)) + if i ~= #a then + while #pre ~= Integer.DIGITLENGTH do + pre = "0" .. pre + end + end + out = pre .. out + end + if a.sign == -1 then + out = "-" .. out + end + return out +end +__o.__div = function(a, b) -- Constructor for a rational number disguised as division + if not b.getring then + return BinaryOperation.DIVEXP({a, b}) + end + if(a:getring() == Integer:getring() and b:getring() == Integer:getring()) then + return Rational(a, b) + end + return __FieldOperations.__div(a, b) +end +__o.__concat = function(a, b) -- Like a decimal, but fancier. Used mainly for the parser with decimal numbers. + return a + b / (Integer(10) ^ Integer.ceillog(b)) +end + +--- Creates a new integer given a string or number representation of the integer. +--- @param n number|string|Integer +--- @return Integer +function Integer:new(n) + local o = {} + o = setmetatable(o, __o) + + if not n then + o[1] = 0 + o.sign = 0 + return o + end + + -- Can convert any floating-point number into an integer, though we generally only want to pass whole numbers into this. + -- This will only approximate very large floating point numbers to a small proportion of the total significant digits + -- After that the result will just be nonsense - strings should probably be used for big numbers + if type(n) == "number" then + n = math.floor(n) + if n == 0 then + o[1] = 0 + o.sign = 0 + else + if n < 0 then + n = -n + o.sign = -1 + else + o.sign = 1 + end + local i = 1 + while n >= Integer.DIGITSIZE do + o[i] = n % Integer.DIGITSIZE + n = n // Integer.DIGITSIZE + i = i + 1 + end + o[i] = n + end + -- Only works on strings that are exact (signed) integers + elseif type(n) == "string" then + if not tonumber(n) then + error("Sent parameter of wrong type: " .. n .. " is not an integer.") + end + if n == "0" then + o[1] = 0 + o.sign = 0 + else + local s = 1 + if string.sub(n, 1, 1) == "-" then + s = s + 1 + o.sign = -1 + else + o.sign = 1 + end + + while string.sub(n, s, s) == "0" do + s = s + 1 + end + + local e = #n + local i = 1 + while e > s + Integer.DIGITLENGTH - 1 do + o[i] = tonumber(string.sub(n, e - Integer.DIGITLENGTH + 1, e)) + e = e - Integer.DIGITLENGTH + i = i + 1 + end + o[i] = tonumber(string.sub(n, s, e)) or 0 + end + -- Copying is expensive in Lua, so this constructor probably should only sparsely be called with an Integer argument. + elseif type(n) == "table" then + o = Copy(n) + else + error("Sent parameter of wrong type: Integer does not accept " .. type(n) .. ".") + end + + return o +end + +--- Returns the ring this object is an element of. +--- @return RingIdentifier +function Integer:getring() + return t +end + +--- @param ring RingIdentifier +--- @return Ring +function Integer:inring(ring) + if ring == self:getring() then + return self + end + + if ring == PolynomialRing:getring() then + return PolynomialRing({self:inring(ring.child)}, ring.symbol) + end + + if ring == Rational:getring() then + if ring.child then + return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) + end + return Rational(self, Integer.one(), true):inring(ring) + end + + if ring == IntegerModN:getring() then + return IntegerModN(self, ring.modulus) + end + + error("Unable to convert element to proper ring.") +end + +--- @param b Integer +--- @return Integer +function Integer:add(b) + if self.sign == 1 and b.sign == -1 then + return self:usub(b, 1) + end + if self.sign == -1 and b.sign == 1 then + return self:usub(b, -1) + end + + local sign = self.sign + if sign == 0 then + sign = b.sign + end + return self:uadd(b, sign) +end + +--- Addition without sign so we don't have to create an entire new integer when switching signs. +--- @param b Integer +--- @param sign number +--- @return Integer +function Integer:uadd(b, sign) + local o = Integer() + o.sign = sign + + local c = 0 + local n = math.max(#self, #b) + for i = 1, n do + local s = (self[i] or 0) + (b[i] or 0) + c + if s >= Integer.DIGITSIZE then + o[i] = s - Integer.DIGITSIZE + c = 1 + else + o[i] = s + c = 0 + end + end + if c == 1 then + o[n + 1] = c + end + return o +end + +--- @param b Integer +--- @return Integer +function Integer:sub(b) + if self.sign == 1 and b.sign == -1 then + return self:uadd(b, 1) + end + if self.sign == -1 and b.sign == 1 then + return self:uadd(b, -1) + end + + local sign = self.sign + if sign == 0 then + sign = b.sign + end + return self:usub(b, sign) +end + +-- Subtraction without sign so we don't have to create an entire new integer when switching signs. +-- Uses subtraction by compliments. +--- @param b Integer +--- @param sign number +--- @return Integer +function Integer:usub(b, sign) + local a, b, swap = Integer.absmax(self, b) + local o = Integer() + o.sign = sign * swap + + local c = 0 + local n = #a + for i = 1, n do + local s = (a[i] or 0) + Integer.DIGITSIZE - 1 - (b[i] or 0) + c + if i == 1 then + s = s + 1 + end + if s >= Integer.DIGITSIZE then + o[i] = s - Integer.DIGITSIZE + c = 1 + else + o[i] = s + c = 0 + end + end + + -- Remove leading zero digits, since we want integer representations to be unique. + while o[n] == 0 do + o[n] = nil + n = n - 1 + end + + if not o[1] then + o[1] = 0 + o.sign = 0 + end + + return o +end + +--- @return Integer +function Integer:neg() + local o = Integer() + o.sign = -self.sign + for i, digit in ipairs(self) do + o[i] = digit + end + return o +end + +--- @param b Integer +--- @return Integer +function Integer:mul(b) + local o = Integer() + o.sign = self.sign * b.sign + if o.sign == 0 then + o[1] = 0 + return o + end + + -- Fast single-digit multiplication in the most common case + if #self == 1 and #b == 1 then + o[2], o[1] = self:mulone(self[1], b[1]) + + if o[2] == 0 then + o[2] = nil + end + + return o + end + + -- "Grade school" multiplication algorithm for numbers with small numbers of digits works faster than Karatsuba + local n = #self + local m = #b + o[1] = 0 + o[2] = 0 + for i = 2, n+m do + o[i + 1] = 0 + for j = math.max(1, i-m), math.min(n, i-1) do + local u, l = self:mulone(self[j], b[i - j]) + o[i - 1] = o[i - 1] + l + o[i] = o[i] + u + if o[i - 1] >= Integer.DIGITSIZE then + o[i - 1] = o[i - 1] - Integer.DIGITSIZE + o[i] = o[i] + 1 + end + if o[i] >= Integer.DIGITSIZE then + o[i] = o[i] - Integer.DIGITSIZE + o[i + 1] = o[i + 1] + 1 + end + end + end + + -- Remove leading zero digits, since we want integer representations to be unique. + if o[n+m+1] == 0 then + o[n+m+1] = nil + end + + if o[n+m] == 0 then + o[n+m] = nil + end + + return o +end + +--- Multiplies two single-digit numbers and returns two digits. +--- @param a number +--- @param b number +--- @return number, number +function Integer:mulone(a, b) + local P = Integer.PARTITIONSIZE + + local a1 = a // P + local a2 = a % P + local b1 = b // P + local b2 = b % P + + local u = a1 * b1 + local l = a2 * b2 + + local m = ((a1 * b2) + (b1 * a2)) + local mu = m // P + local ml = m % P + + u = u + mu + l = l + ml * P + + if l >= Integer.DIGITSIZE then + l = l - Integer.DIGITSIZE + u = u + 1 + end + + return u, l +end + +--- Naive exponentiation is slow even for small exponents, so this uses binary exponentiation. +--- @param b Integer +--- @return Integer +function Integer:pow(b) + if b < Integer.zero() then + return Integer.one() / (self ^ -b) + end + + if b == Integer.zero() then + return Integer.one() + end + + -- Fast single-digit exponentiation + if #self == 1 and #b == 1 then + local test = (self.sign * self[1]) ^ b[1] + if test < Integer.DIGITSIZE and test > -Integer.DIGITSIZE then + return Integer(test) + end + end + + local x = self + local y = Integer.one() + while b > Integer.one() do + if b[1] % 2 == 0 then + x = x * x + b = b:divbytwo() + else + y = x * y + x = x * x + b = b:divbytwo() + end + end + + return x * y +end + +-- Fast integer division by two for binary exponentiation. +--- @return Integer +function Integer:divbytwo() + local o = Integer() + o.sign = self.sign + for i = #self, 1, -1 do + if self[i] % 2 == 0 then + o[i] = self[i] // 2 + else + o[i] = self[i] // 2 + if i ~= 1 then + o[i - 1] = self[i - 1] * 2 + end + end + end + return o +end + +--- Division with remainder over the integers. Uses the standard base 10 long division algorithm. +--- @param b Integer +--- @return Integer, Integer +function Integer:divremainder(b) + if self >= Integer.zero() and b > self or self <= Integer.zero() and b < self then + return Integer.zero(), Integer(self) + end + + if #self == 1 and #b == 1 then + return Integer((self[1]*self.sign) // (b[1]*b.sign)), Integer((self[1]*self.sign) % (b[1]*b.sign)) + end + + local Q = Integer() + local R = Integer() + + Q.sign = self.sign * b.sign + R.sign = 1 + local negativemod = false + if b.sign == -1 then + b.sign = -b.sign + negativemod = true + end + + for i = #self, 1, -1 do + local s = tostring(math.floor(self[i])) + while i ~= #self and #s ~= Integer.DIGITLENGTH do + s = "0" .. s + end + Q[i] = 0 + for j = 1, #s do + R = R:mulbyten() + R[1] = R[1] + tonumber(string.sub(s, j, j)) + if R[1] > 0 then + R.sign = 1 + end + while R >= b do + R = R - b + Q[i] = Q[i] + 10^(#s - j) + end + end + end + + -- Remove leading zero digits, since we want integer representations to be unique. + while Q[#Q] == 0 do + Q[#Q] = nil + end + + if negativemod then + R = -R + b.sign = -b.sign + elseif self.sign == -1 then + R = b - R + end + + return Q, R +end + +--- Fast in-place multiplication by ten for the division algorithm. This means the number IS MODIFIED by this method unlike the rest of the library. +--- @return Integer +function Integer:mulbyten() + local DIGITSIZE = Integer.DIGITSIZE + for i, _ in ipairs(self) do + self[i] = self[i] * 10 + end + for i, _ in ipairs(self) do + if self[i] > DIGITSIZE then + local msd = self[i] // DIGITSIZE + if self[i+1] then + self[i+1] = self[i+1] + msd + else + self[i+1] = msd + end + self[i] = self[i] - DIGITSIZE*msd + end + end + return self +end + +--- @param b Integer +--- @return boolean +function Integer:eq(b) + for i, digit in ipairs(self) do + if not b[i] or not (b[i] == digit) then + return false + end + end + return #self == #b and self.sign == b.sign +end + +--- @param b Integer +--- @return boolean +function Integer:lt(b) + local selfsize = #self + local bsize = #b + if selfsize < bsize then + return b.sign == 1 + end + if selfsize > bsize then + return self.sign == -1 + end + local n = selfsize + while n > 0 do + if self[n]*self.sign < b[n]*b.sign then + return true + end + if self[n]*self.sign > b[n]*b.sign then + return false + end + n = n - 1 + end + return false +end + +--- Same as less than, but ignores signs. +--- @param b Integer +--- @return boolean +function Integer:ltabs(b) + if #self < #b then + return true + end + if #self > #b then + return false + end + local n = #self + while n > 0 do + if self[n] < b[n] then + return true + end + if self[n] > b[n] then + return false + end + n = n - 1 + end + return false +end + +--- @param b Integer +--- @return boolean +function Integer:le(b) + local selfsize = #self + local bsize = #b + if selfsize < bsize then + return b.sign == 1 + end + if selfsize > bsize then + return self.sign == -1 + end + local n = selfsize + while n > 0 do + if self[n]*self.sign < b[n]*b.sign then + return true + end + if self[n]*self.sign > b[n]*b.sign then + return false + end + n = n - 1 + end + return true +end + +local zero = Integer:new(0) +--- @return Integer +function Integer:zero() + return zero +end + +local one = Integer:new(1) +--- @return Integer +function Integer:one() + return one +end + +--- Returns this integer as a floating point number. Can only approximate the value of large integers. +--- @return number +function Integer:asnumber() + local n = 0 + for i, digit in ipairs(self) do + n = n + digit * Integer.DIGITSIZE ^ (i - 1) + end + return self.sign*math.floor(n) +end + +--- Returns all positive divisors of the integer. Not guarenteed to be in any order. +--- @return table +function Integer:divisors() + local primefactors = self:primefactorizationrec() + local divisors = {} + + local terms = {} + for prime in pairs(primefactors) do + if prime == Integer(-1) then + primefactors[prime] = nil + end + terms[prime] = Integer.zero() + end + + local divisor = Integer.one() + + while true do + divisors[#divisors+1] = divisor + for prime, power in pairs(primefactors) do + if terms[prime] < power then + terms[prime] = terms[prime] + Integer.one() + divisor = divisor * prime + break + else + terms[prime] = Integer.zero() + divisor = divisor / (prime ^ power) + end + end + if divisor == Integer.one() then + break + end + end + + return divisors +end + +--- Returns whether this integer is a prime power, of the form p^a for prime p and positive integer a. +--- If it is a prime power, also returns the prime and the power. +--- @return boolean, Expression|nil, Expression|nil +function Integer:isprimepower() + if self <= Integer.one() then + return false + end + local factorization = self:primefactorization() + if factorization:type() == BinaryOperation and #factorization:subexpressions() == 1 then + return true, factorization.expressions[1].expressions[2], factorization.expressions[1].expressions[1] + end + return false +end + +--- Returns whether this integer is a perfect power, of the form a^b for positive integers a and b. +--- If it is a prime power, also returns the prime and the power. +--- @return boolean, Expression|nil, Expression|nil +function Integer:isperfectpower() + if self <= Integer.one() then + return false + end + local factorization = self:primefactorization() + if factorization:type() ~= BinaryOperation then + return false + end + local power = Integer.zero() + for _, term in ipairs(factorization:subexpressions()) do + power = Integer.gcd(power, term.expressions[2]) + if power == Integer.one() then + return false + end + end + local base = Integer.one() + for _, term in ipairs(factorization:subexpressions()) do + base = base * term.expressions[1] ^ (term.expressions[2] / power) + end + return true, base, power +end + +--- Returns the prime factorization of this integer as a expression. +--- @return Expression +function Integer:primefactorization() + if not Integer.FACTORIZATIONLIMIT then + Integer.FACTORIZATIONLIMIT = Integer(Integer.DIGITSIZE) + end + if self > Integer.FACTORIZATIONLIMIT then + return self + end + local result = self:primefactorizationrec() + local mul = {} + local i = 1 + for factor, degree in pairs(result) do + mul[i] = BinaryOperation.POWEXP({factor, degree}) + i = i + 1 + end + return BinaryOperation.MULEXP(mul):lock(Expression.NIL) +end + +--- Recursive part of prime factorization using Pollard Rho. +function Integer:primefactorizationrec() + if self < Integer.zero() then + return Integer.mergefactors({[Integer(-1)]=Integer.one()}, (-self):primefactorizationrec()) + end + if self == Integer.one() then + return {[Integer.one()]=Integer.one()} + end + local result = self:findafactor() + if result == self then + return {[result]=Integer.one()} + end + local remaining = self / result + return Integer.mergefactors(result:primefactorizationrec(), remaining:primefactorizationrec()) +end + + +function Integer.mergefactors(a, b) + local result = Copy(a) + + for factor, degree in pairs(b) do + for ofactor, odegree in pairs(result) do + if factor == ofactor then + result[ofactor] = degree + odegree + goto continue + end + end + result[factor] = degree + ::continue:: + end + return result +end + +-- Return a non-trivial factor of n via Pollard Rho, or returns n if n is prime. +function Integer:findafactor() + if self:isprime() then + return self + end + + if self % Integer(2) == Integer.zero() then + return Integer(2) + end + + if self % Integer(3) == Integer.zero() then + return Integer(3) + end + + if self % Integer(5) == Integer.zero() then + return Integer(5) + end + + local g = function(x) + local temp = Integer.powmod(x, Integer(2), self) + return temp + end + + local xstart = Integer(2) + while xstart < self do + local x = xstart + local y = xstart + local d = Integer.one() + while d == Integer.one() do + x = g(x) + y = g(g(y)) + d = Integer.gcd((x - y):abs(), self) + end + + if d < self then + return d + end + + xstart = xstart + Integer.one() + end +end + +--- Uses Miller-Rabin to determine whether a number is prime up to a very large number. +local smallprimes = {Integer:new(2), Integer:new(3), Integer:new(5), Integer:new(7), Integer:new(11), Integer:new(13), Integer:new(17), +Integer:new(19), Integer:new(23), Integer:new(29), Integer:new(31), Integer:new(37), Integer:new(41), Integer:new(43), Integer:new(47)} + +function Integer:isprime() + if self % Integer(2) == Integer.zero() then + if self == Integer(2) then + return true + end + return false + end + + if self == Integer.one() then + return false + end + + for _, value in pairs(smallprimes) do + if value == self then + return true + end + end + + local r = Integer.zero() + local d = self - Integer.one() + while d % Integer(2) == Integer.zero() do + r = r + Integer.one() + d = d / Integer(2) + end + + for _, a in ipairs(smallprimes) do + local s = r + local x = Integer.powmod(a, d, self) + if x == Integer.one() or x == self - Integer.one() then + goto continue + end + + while s > Integer.zero() do + x = Integer.powmod(x, Integer(2), self) + if x == self - Integer.one() then + goto continue + end + s = s - Integer.one() + end + do + return false + end + ::continue:: + end + + return true +end + +--- Returns the absolute value of an integer. +--- @return Integer +function Integer:abs() + if self.sign >= 0 then + return Integer(self) + end + return -self +end + +----------------- +-- Inheritance -- +----------------- + +__Integer.__index = EuclideanDomain +__Integer.__call = Integer.new +Integer = setmetatable(Integer, __Integer) + +---------------------- +-- Static constants -- +---------------------- + +Integer.FACTORIZATIONLIMIT = Integer(Integer.DIGITSIZE) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-integerquotientring.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-integerquotientring.lua new file mode 100644 index 0000000000..4c2188c9e5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-integerquotientring.lua @@ -0,0 +1,197 @@ +--- @class IntegerModN +--- Represents an element of the ring of integers mod n (this is also a field iff n is prime). +--- @field element Integer +--- @field modulus Integer + +IntegerModN = {} +__IntegerModN = {} + +-- Metatable for ring objects. +local __obj = {__index = IntegerModN, __eq = function(a, b) + return a["ring"] == b["ring"] and (a["modulus"] == b["modulus"] or a["modulus"] == nil or b["modulus"] == nil) +end, __tostring = function(a) + if a.modulus then return "Z/Z" .. tostring(a.modulus) else return "(Generic Integer Mod Ring)" end +end} + +-------------------------- +-- Static functionality -- +-------------------------- + +--- Creates a new ring with the given modulus. +--- @param modulus Integer +--- @return RingIdentifier +function IntegerModN.makering(modulus) + local t = {ring = IntegerModN} + t.modulus = modulus + t = setmetatable(t, __obj) + return t +end + +-- Shorthand constructor for a ring with a particular modulus. +function IntegerModN.R(modulus) + return IntegerModN.makering(modulus) +end + +---------------------------- +-- Instance functionality -- +---------------------------- + +-- So we don't have to copy the field operations each time +local __o +__o = Copy(__FieldOperations) + +__o.__index = IntegerModN +__o.__tostring = function(a) + return tostring(a.element) +end + +--- Creates a new integer i in Z/nZ. +--- @param i Integer +--- @param n Integer +--- @return IntegerModN +function IntegerModN:new(i, n) + local o = {} + + if n:getring() ~= Integer:getring() or n < Integer.one() then + error("Argument error: modulus must be an integer greater than 0.") + end + + o = setmetatable(o, __o) + + if i < Integer.zero() or i >= n then + i = i % n + end + + o.element = i + o.modulus = n + + return o +end + +--- @return RingIdentifier +function IntegerModN:getring() + local t = {ring = IntegerModN} + if self then + t.modulus = self.modulus + end + t = setmetatable(t, __obj) + return t +end + +--- @param ring RingIdentifier +--- @return Ring +function IntegerModN:inring(ring) + if ring == IntegerModN:getring() then + if ring.modulus then + return IntegerModN(self.element, ring.modulus) + end + return self + end + + if ring == PolynomialRing:getring() then + return PolynomialRing({self:inring(ring.child)}, ring.symbol) + end + + if ring == Rational:getring() and ring.symbol then + return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) + end + + if ring == Integer:getring() then + return self.element:inring(ring) + end + + error("Unable to convert element to proper ring.") +end + +--- @param b IntegerModN +--- @return IntegerModN +function IntegerModN:add(b) + return IntegerModN(self.element + b.element, self.modulus) +end + +--- @return IntegerModN +function IntegerModN:neg() + return IntegerModN(-self.element, self.modulus) +end + +--- @param b IntegerModN +--- @return IntegerModN +function IntegerModN:mul(b) + return IntegerModN(self.element * b.element, self.modulus) +end + +-- Overrides the generic power method with powmod. +--- @param b IntegerModN +--- @return IntegerModN +function IntegerModN:pow(b) + return IntegerModN(Integer.powmod(self.element, b.element, self.modulus), self.modulus) +end + +-- Returns the multiplicative inverse of this number if it exists. +--- @return IntegerModN +function IntegerModN:inv() + local r, t, _ = Integer.extendedgcd(self.element, self.modulus) + + if r > Integer.one() then + error("Element does not have an inverse in this ring") + end + + return IntegerModN(t, self.modulus) +end + +--- @param b IntegerModN +--- @return IntegerModN +function IntegerModN:div(b) + return self:mul(b:inv()) +end + +--- @param b IntegerModN +--- @return boolean +function IntegerModN:eq(b) + return self.element == b.element +end + +--- @param b IntegerModN +--- @return boolean +function IntegerModN:lt(b) + return self.element < b.element +end + +--- @param b IntegerModN +--- @return boolean +function IntegerModN:le(b) + return self.element <= b.element +end + +--- @return IntegerModN +function IntegerModN:zero() + if not self or not self.modulus then + return Integer.zero() + end + return IntegerModN(Integer.zero(), self.modulus) +end + +--- @return IntegerModN +function IntegerModN:one() + if not self or not self.modulus then + return Integer.one() + end + return IntegerModN(Integer.one(), self.modulus) +end + +--- @return string +function IntegerModN:tolatex(mod) + mod = mod or false + if mod then + return self.element:tolatex() .. "\\bmod{" .. self.modulus:tolatex() .. "}" + else + return self.element:tolatex() + end +end +----------------- +-- Inheritance -- +----------------- + +__IntegerModN.__index = Field +__IntegerModN.__call = IntegerModN.new +IntegerModN = setmetatable(IntegerModN, __IntegerModN) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-logarithm.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-logarithm.lua new file mode 100644 index 0000000000..07a9db84d1 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-logarithm.lua @@ -0,0 +1,186 @@ +--- @class Logarithm +--- An expression for the logarithm of an expression with respect to another. +--- Currently, logarithms are not being evaluated since we are just doing symbolic computation. +--- @field base Expression +--- @field expression Expression +Logarithm = {} +__Logarithm = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new logarithm expression with the given symbol and expression. +--- @param base Expression +--- @param expression Expression +--- @ +function Logarithm:new(base, expression) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.base = Copy(base) + o.expression = Copy(expression) + + __o.__index = Logarithm + __o.__tostring = function(a) + return 'log(' .. tostring(base) .. ', ' .. tostring(expression) .. ')' + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway + if not b:type() == Logarithm then + return false + end + return a.base == b.base and a.expression == b.expression + end + o = setmetatable(o, __o) + + return o +end + +--- @return Expression +function Logarithm:evaluate() + + if (self.base:isconstant() and (self.base <= Integer.zero() or self.base == Integer.one())) or + (self.expression:isconstant() and self.expression <= Integer.zero()) then + error("Arithmetic error: division by zero") + end + + if not self.base:isconstant() or not self.expression:isconstant() then + return self + end + + local power = Integer.one() + local base = self.base + if base:type() == Integer then + local pp, b, p = base:isperfectpower() + if pp then + base = b + power = p + end + end + + if base:type() == Rational then + local ppn, bn, pn = base.numerator:isperfectpower() + local ppd, bd, pd = base.denominator:isperfectpower() + if base.numerator == Integer.one() then + ppn = true + bn = Integer.one() + pn = pd + end + if ppn and ppd and pn == pd then + base = bn / bd + power = pn + end + end + + local result = Integer.one() + local expression = self.expression + local sign = Integer.one() + if base < Integer.one() then + base = Integer.one() / base + sign = -sign + end + + + local current = base + while current < expression do + current = current * base + result = result + Integer.one() + end + if current == expression then + return sign * result / power + else + while current > expression do + current = current / base + result = result - Integer.one() + end + if current == expression then + return sign * result / power + end + end + + return self +end + +--- @return Expression +function Logarithm:autosimplify() + + local base = self.base:autosimplify() + local expression = self.expression:autosimplify() + + local evaluated = Logarithm(base, expression):evaluate() + if evaluated:type() ~= Logarithm then + return evaluated + end + + -- Uses the property that log(b, 1) = 0 + if expression == Integer.one() then + return Integer.zero() + end + + -- Uses the property that log(b, b) = 1 + if expression == base then + return Integer.one() + end + + -- Uses the propery that log(b, x^y) = y * log(b, x) + if expression.operation == BinaryOperation.POW then + return BinaryOperation.MULEXP({expression.expressions[2], Logarithm(base, expression.expressions[1])}):autosimplify() + end + + if expression:type() == Rational and expression.numerator == Integer.one() then + return (-Logarithm(base, expression.denominator)):autosimplify() + end + + -- Our expression cannot be simplified + return Logarithm(base, expression) +end + +--- @return Expression +function Logarithm:expand() + return Logarithm(self.base:expand(), self.expression:expand()):autosimplify() +end + +--- @return table +function Logarithm:subexpressions() + return {self.base, self.expression} +end + +--- @param subexpressions table +--- @return Logarithm +function Logarithm:setsubexpressions(subexpressions) + return Logarithm(subexpressions[1], subexpressions[2]) +end + +--- @param other Expression +--- @return boolean +function Logarithm:order(other) + return FunctionExpression("log", {self.base, self.expression}):order(other) +end + +--- @return string +function Logarithm:tolatex() + if self.base == E then + return '\\ln\\mathopen{}\\left(' .. self.expression:tolatex() .. '\\right)' + end + return '\\log_' .. self.base:tolatex() .. '\\mathopen{}\\left(' .. self.expression:tolatex() .. '\\right)' +end + +----------------- +-- Inheritance -- +----------------- +__Logarithm.__index = CompoundExpression +__Logarithm.__call = Logarithm.new +Logarithm = setmetatable(Logarithm, __Logarithm) + +---------------------- +-- Static constants -- +---------------------- + +LOG = function(base, expression) + return Logarithm(base, expression) +end + +LN = function(expression) + return Logarithm(E, expression) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-polynomialring.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-polynomialring.lua new file mode 100644 index 0000000000..568c21c921 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-polynomialring.lua @@ -0,0 +1,860 @@ +--- @class PolynomialRing +--- Represents an element of a polynomial ring. +--- @field coefficients table +--- @field symbol SymbolExpression +--- @field ring RingIdentifier +PolynomialRing = {} +__PolynomialRing = {} + +-- Metatable for ring objects. +local __obj = {__index = PolynomialRing, __eq = function(a, b) + return a["ring"] == b["ring"] and + (a["child"] == b["child"] or a["child"] == nil or b["child"] == nil) and + (a["symbol"] == b["symbol"] or a["child"] == nil or b["child"] == nil) +end, __tostring = function(a) + if a.child and a.symbol then return tostring(a.child) .. "[" .. a.symbol .. "]" else return "(Generic Polynomial Ring)" end +end} + +-------------------------- +-- Static functionality -- +-------------------------- + +--- Creates a new ring with the given symbol and child ring. +--- @param symbol SymbolExpression +--- @param child RingIdentifier +--- @return RingIdentifier +function PolynomialRing.makering(symbol, child) + local t = {ring = PolynomialRing} + t.symbol = symbol + t.child = child + t = setmetatable(t, __obj) + return t +end + +-- Shorthand constructor for a polynomial ring with integer or integer mod ring coefficients. +function PolynomialRing.R(symbol, modulus) + if modulus then + return PolynomialRing.makering(symbol, IntegerModN.makering(modulus)) + end + return PolynomialRing.makering(symbol, Integer.getring()) +end + +--- Returns the GCD of two polynomials in a ring, assuming both rings are euclidean domains. +--- @param a PolynomialRing +--- @param b PolynomialRing +--- @return PolynomialRing +function PolynomialRing.gcd(a, b) + if a.symbol ~= b.symbol then + error("Cannot take the gcd of two polynomials with different symbols") + end + while b ~= Integer.zero() do + a, b = b, a % b + end + return a // a:lc() +end + +-- Returns the GCD of two polynomials in a ring, assuming both rings are euclidean domains. +-- Also returns bezouts coefficients via extended gcd. +--- @param a PolynomialRing +--- @param b PolynomialRing +--- @return PolynomialRing, PolynomialRing, PolynomialRing +function PolynomialRing.extendedgcd(a, b) + local oldr, r = a, b + local olds, s = Integer.one(), Integer.zero() + local oldt, t = Integer.zero(), Integer.one() + while r ~= Integer.zero() do + local q = oldr // r + oldr, r = r, oldr - q*r + olds, s = s, olds - q*s + oldt, t = t, oldt - q*t + end + return oldr // oldr:lc(), olds // oldr:lc(), oldt // oldr:lc() +end + +-- Returns the resultant of two polynomials in the same ring, whose coefficients are all part of a field. +--- @param a PolynomialRing +--- @param b PolynomialRing +--- @return Field +function PolynomialRing.resultant(a, b) + + if a.ring == PolynomialRing.getring() or b.ring == PolynomialRing.getring() then + return PolynomialRing.resultantmulti(a, b) + end + + local m, n = a.degree, b.degree + if n == Integer.zero() then + return b.coefficients[0]^m + end + + local r = a % b + if r == Integer.zero() then + return r.coefficients[0] + end + + local s = r.degree + local l = b:lc() + + return Integer(-1)^(m*n) * l^(m-s) * PolynomialRing.resultant(b, r) +end + +-- Returns the resultant of two polynomials in the same ring, whose coefficients are not part of a field. +--- @param a PolynomialRing +--- @param b PolynomialRing +--- @return Ring +function PolynomialRing.resultantmulti(a, b) + local m, n = a.degree, b.degree + + if m < n then + return Integer(-1) ^ (m * n) * PolynomialRing.resultantmulti(b, a) + end + if n == Integer.zero() then + return b.coefficients[0]^m + end + + local delta = m - n + Integer(1) + local _ , r = PolynomialRing.pseudodivide(a, b) + if r == Integer.zero() then + return r.coefficients[0] + end + + local s = r.degree + local w = Integer(-1)^(m*n) * PolynomialRing.resultant(b, r) + local l = b:lc() + local k = delta * n - m + s + local f = l ^ k + return w // f +end + +-- Given two polynomials a and b, returns a list of the remainders generated by the monic Euclidean algorithm. +--- @param a PolynomialRing +--- @param b PolynomialRing +--- @return table +function PolynomialRing.monicgcdremainders(a, b) + if a.symbol ~= b.symbol then + error("Cannot take the gcd of two polynomials with different symbols") + end + + local remainders = {a / a:lc(), b / b:lc()} + while true do + local q = remainders[#remainders - 1] // remainders[#remainders] + local c = remainders[#remainders - 1] - q*remainders[#remainders] + if c ~= Integer.zero() then + remainders[#remainders+1] = c/c:lc() + else + break + end + end + + return remainders +end + +-- Returns the partial fraction decomposition of the rational function g/f +-- given g, f, and some (not nessecarily irreducible) factorization of f. +-- If the factorization is omitted, the irreducible factorization is used. +-- The degree of g must be less than the degree of f. +--- @param g PolynomialRing +--- @param f PolynomialRing +--- @param ffactors Expression +--- @return Expression +function PolynomialRing.partialfractions(g, f, ffactors) + + if g.degree >= f.degree then + error("Argument Error: The degree of g must be less than the degree of f.") + end + + -- Converts f to a monic polynomial. + g = g * f:lc() + f = f / f:lc() + + ffactors = ffactors or f:factor() + + local expansionterms = {} + + for _, factor in ipairs(ffactors.expressions) do + local k + local m + if factor.getring and factor:getring() == PolynomialRing:getring() then + m = factor + k = Integer.one() + elseif not factor:isconstant() then + m = factor.expressions[1] + k = factor.expressions[2] + end + + if not factor:isconstant() then + -- Uses Chinese Remainder Theorem for each factor to determine the numerator of the term in the decomposition + local mk = m^k + local v = g % mk + local _, minv, _ = PolynomialRing.extendedgcd(f // mk, mk) + local c = v*minv % mk + + + if k == Integer.one() then + expansionterms[#expansionterms+1] = BinaryOperation.ADDEXP({BinaryOperation.DIVEXP({c, BinaryOperation.POWEXP({m, Integer.one()})})}) + else + -- Uses the p-adic expansion of c to split terms with repeated roots. + local q = c + local r + local innerterms = {} + for i = k:asnumber(), 1, -1 do + q, r = q:divremainder(m) + innerterms[#innerterms+1] = BinaryOperation.DIVEXP({r, BinaryOperation.POWEXP({m, Integer(i)})}) + end + expansionterms[#expansionterms+1] = BinaryOperation.ADDEXP(innerterms) + end + end + end + + return BinaryOperation.ADDEXP(expansionterms) + +end + +---------------------------- +-- Instance functionality -- +---------------------------- + +-- So we don't have to copy the Euclidean operations each time +local __o = Copy(__EuclideanOperations) +__o.__index = PolynomialRing +__o.__tostring = function(a) + local out = "" + local loc = a.degree:asnumber() + while loc >= 0 do + if a.ring == PolynomialRing.getring() or (a.ring == Rational.getring() and a.ring.symbol) then + out = out .. "(" .. tostring(a.coefficients[loc]) .. ")" .. a.symbol .. "^" .. tostring(math.floor(loc)) .. "+" + else + out = out .. tostring(a.coefficients[loc]) .. a.symbol .. "^" .. tostring(math.floor(loc)) .. "+" + end + loc = loc - 1 + end + return string.sub(out, 1, string.len(out) - 1) +end +__o.__div = function(a, b) + if not b.getring then + return BinaryOperation.DIVEXP({a, b}) + end + if Ring.resultantring(a.ring, b:getring()) ~= Ring.resultantring(a:getring(), b:getring()) then + return a:div(b:inring(Ring.resultantring(a:getring(), b:getring()))) + end + if b.ring and b:getring() == Rational:getring() and a.symbol == b.ring.symbol then + return a:inring(Ring.resultantring(a:getring(), b:getring())):div(b) + end + if a:getring() == b:getring() then + return Rational(a, b, true) + end + -- TODO: Fix this for arbitrary depth + if a:getring() == PolynomialRing:getring() and b:getring() == PolynomialRing:getring() and a.symbol == b.symbol then + local oring = Ring.resultantring(a:getring(), b:getring()) + return Rational(a:inring(oring), b:inring(oring), true) + end + return BinaryOperation.DIVEXP({a, b}) +end + +function PolynomialRing:tolatex() + local out = '' + local loc = self.degree:asnumber() + if loc == 0 then + return self.coefficients[loc]:tolatex() + end + if self.ring == Rational.getring() or self.ring == Integer.getring() or self.ring == IntegerModN.getring() then + if self.coefficients[loc] ~= Integer.one() then + out = out .. self.coefficients[loc]:tolatex() .. self.symbol + else + out = out .. self.symbol + end + if loc ~=1 then + out = out .. "^{" .. loc .. "}" + end + loc = loc -1 + while loc >=0 do + local coeff = self.coefficients[loc] + if coeff == Integer.one() then + if loc == 0 then + out = out .. "+" .. coeff:tolatex() + goto skip + else + out = out .. "+" + goto continue + end + end + if coeff == Integer(-1) then + if loc == 0 then + out = out .. "-" .. coeff:neg():tolatex() + goto skip + else + out = out .. "-" + goto continue + end + end + if coeff < Integer.zero() then + out = out .. "-" .. coeff:neg():tolatex() + end + if coeff == Integer.zero() then + goto skip + end + if coeff > Integer.zero() then + out = out .. "+" .. coeff:tolatex() + end + ::continue:: + if loc > 1 then + out = out .. self.symbol .. "^{" .. loc .. "}" + end + if loc == 1 then + out = out .. self.symbol + end + ::skip:: + loc = loc-1 + end + else + while loc >=0 do + if loc >=1 then + out = out .. self.coefficients[loc]:tolatex() .. self.symbol .. "^{" .. loc .. "} + " + else + out = out .. self.coefficients[loc]:tolatex() .. self.symbol .. "^{" .. loc .. "}" + end + loc = loc-1 + end + end + return out +end + +function PolynomialRing:isatomic() + --if self.degree >= Integer.one() then + -- return false + --else + return false + --end +end +--test + +-- Creates a new polynomial ring given an array of coefficients and a symbol +function PolynomialRing:new(coefficients, symbol, degree) + local o = {} + o = setmetatable(o, __o) + + if type(coefficients) ~= "table" then + error("Sent parameter of wrong type: Coefficients must be in an array") + end + o.coefficients = {} + o.degree = degree or Integer(-1) + + if type(symbol) ~= "string" and not symbol.symbol then + error("Symbol must be a string") + end + o.symbol = symbol.symbol or symbol + + -- Determines what ring the polynomial ring should have as its child + for index, coefficient in pairs(coefficients) do + if type(index) ~= "number" then + error("Sent parameter of wrong type: Coefficients must be in an array") + end + if not coefficient.getring then + error("Sent parameter of wrong type: Coefficients must be elements of a ring") + end + if not o.ring then + o.ring = coefficient:getring() + else + local newring = coefficient:getring() + local combinedring = Ring.resultantring(o.ring, newring) + if combinedring == newring then + o.ring = newring + elseif not o.ring == combinedring then + error("Sent parameter of wrong type: Coefficients must all be part of the same ring") + end + end + end + + if not coefficients[0] then + -- Constructs the coefficients when a new polynomial is instantiated as an array + for index, coefficient in ipairs(coefficients) do + o.coefficients[index - 1] = coefficient + o.degree = o.degree + Integer.one() + end + else + -- Constructs the coefficients from an existing polynomial of coefficients + local loc = o.degree:asnumber() + while loc > 0 do + if not coefficients[loc] or coefficients[loc] == coefficients[loc]:zero() then + o.degree = o.degree - Integer.one() + else + break + end + loc = loc - 1 + end + + while loc >= 0 do + o.coefficients[loc] = coefficients[loc] + loc = loc - 1 + end + end + + -- Each value of the polynomial greater than its degree is implicitly zero + o.coefficients = setmetatable(o.coefficients, {__index = function (table, key) + return o:zeroc() + end}) + return o +end + +-- Returns the ring this object is an element of +function PolynomialRing:getring() + local t = {ring = PolynomialRing} + if self then + t.child = self.ring + t.symbol = self.symbol + end + t = setmetatable(t, __obj) + return t +end + +-- Explicitly converts this element to an element of another ring +function PolynomialRing:inring(ring) + + -- Faster equality check + if ring == self:getring() then + return self + end + + if ring == Rational:getring() and ring.symbol then + return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) + end + + if ring.symbol == self.symbol then + local out = {} + for i = 0, self.degree:asnumber() do + out[i + 1] = self.coefficients[i]:inring(ring.child) + end + return PolynomialRing(out, self.symbol) + end + + -- TODO: Allow re-ordering of polynomial rings, so from R[x][y] -> R[y][x] for instance + if ring == PolynomialRing:getring() then + return PolynomialRing({self:inring(ring.child)}, ring.symbol) + end + + error("Unable to convert element to proper ring.") +end + + +-- Returns whether the ring is commutative +function PolynomialRing:iscommutative() + return true +end + +function PolynomialRing:add(b) + local larger + + if self.degree > b.degree then + larger = self + else + larger = b + end + + local new = {} + local loc = 0 + while loc <= larger.degree:asnumber() do + new[loc] = self.coefficients[loc] + b.coefficients[loc] + loc = loc + 1 + end + + return PolynomialRing(new, self.symbol, larger.degree) +end + +function PolynomialRing:neg() + local new = {} + local loc = 0 + while loc <= self.degree:asnumber() do + new[loc] = -self.coefficients[loc] + loc = loc + 1 + end + return PolynomialRing(new, self.symbol, self.degree) +end + +function PolynomialRing:mul(b) + -- Grade-school multiplication is actually faster up to a very large polynomial size due to Lua's overhead. + local new = {} + + local sd = self.degree:asnumber() + local bd = b.degree:asnumber() + + for i = 0, sd+bd do + new[i] = self:zeroc() + for j = math.max(0, i-bd), math.min(sd, i) do + new[i] = new[i] + self.coefficients[j]*b.coefficients[i-j] + end + end + return PolynomialRing(new, self.symbol, self.degree + b.degree) + -- return PolynomialRing(PolynomialRing.mul_rec(self.coefficients, b.coefficients), self.symbol, self.degree + b.degree) +end + +-- Performs Karatsuba multiplication without constructing new polynomials recursively +function PolynomialRing.mul_rec(a, b) + if #a==0 and #b==0 then + return {[0]=a[0] * b[0], [1]=Integer.zero()} + end + + local k = Integer.ceillog(Integer.max(Integer(#a), Integer(#b)) + Integer.one(), Integer(2)) + local n = Integer(2) ^ k + local m = n / Integer(2) + local nn = n:asnumber() + local mn = m:asnumber() + + local a0, a1, b0, b1 = {}, {}, {}, {} + + for e = 0, mn - 1 do + a0[e] = a[e] or Integer.zero() + a1[e] = a[e + mn] or Integer.zero() + b0[e] = b[e] or Integer.zero() + b1[e] = b[e + mn] or Integer.zero() + end + + local p1 = PolynomialRing.mul_rec(a1, b1) + local p2a = Copy(a0) + local p2b = Copy(b0) + for e = 0, mn - 1 do + p2a[e] = p2a[e] + a1[e] + p2b[e] = p2b[e] + b1[e] + end + local p2 = PolynomialRing.mul_rec(p2a, p2b) + local p3 = PolynomialRing.mul_rec(a0, b0) + local r = {} + for e = 0, mn - 1 do + p2[e] = p2[e] - p1[e] - p3[e] + r[e] = p3[e] + r[e + mn] = p2[e] + r[e + nn] = p1[e] + end + for e = mn, nn - 1 do + p2[e] = p2[e] - p1[e] - p3[e] + r[e] = r[e] + p3[e] + r[e + mn] = r[e + mn] + p2[e] + r[e + nn] = p1[e] + end + + return r +end + +-- Uses synthetic division. +function PolynomialRing:divremainder(b) + local n, m = self.degree:asnumber(), b.degree:asnumber() + + if m > n then + return self:zero(), self + end + + local o = Copy(self.coefficients) + local lc = b:lc() + for i = n, m, -1 do + o[i] = o[i] / lc + + if o[i] ~= self:zeroc() then + for j = 1, m do + o[i-j] = o[i-j] - b.coefficients[m - j] * o[i] + end + end + end + + local q = {} + local r = {} + for i = 0, m-1 do + r[i] = o[i] + end + + r[0] = r[0] or self:zeroc() + + for i = m, #o do + q[i - m] = o[i] + end + + return PolynomialRing(q, self.symbol, self.degree), PolynomialRing(r, self.symbol, Integer.max(Integer.zero(), b.degree-Integer.one())) +end + +-- Performs polynomial pseudodivision of this polynomial by another in the same ring, +-- and returns both the pseudoquotient and pseudoremainder. +-- In the case where both coefficients are fields, this is equivalent to division with remainder. +function PolynomialRing:pseudodivide(b) + + local p = self:zero() + local s = self + local m = s.degree + local n = b.degree + local delta = Integer.max(m - n + Integer.one(), Integer.zero()) + + local lcb = b:lc() + local sigma = Integer.zero() + + while m >= n and s ~= Integer.zero() do + local lcs = s:lc() + p = p * lcb + self:one():multiplyDegree((m-n):asnumber()) * lcs + s = s * lcb - b * self:one():multiplyDegree((m-n):asnumber()) * lcs + sigma = sigma + Integer.one() + m = s.degree + end + + if delta - sigma == Integer.zero() then + return p,s + else + return lcb^(delta - sigma) * p, lcb^(delta - sigma) * s + end +end + +-- Polynomial rings are never fields, but when dividing by a polynomial by a constant we may want to use / instead of // +function PolynomialRing:div(b) + return self:divremainder(b) +end + +function PolynomialRing:zero() + return self.coefficients[0]:zero():inring(self:getring()) +end + +function PolynomialRing:zeroc() + return self.coefficients[0]:zero() +end + +function PolynomialRing:one() + return self.coefficients[0]:one():inring(self:getring()) +end + +function PolynomialRing:onec() + return self.coefficients[0]:one() +end + +function PolynomialRing:eq(b) + for i=0,math.max(self.degree:asnumber(), b.degree:asnumber()) do + if self.coefficients[i] ~= b.coefficients[i] then + return false + end + end + return true +end + +-- Returns the leading coefficient of this polynomial +function PolynomialRing:lc() + return self.coefficients[self.degree:asnumber()] +end + +--- @return boolean +function PolynomialRing:isconstant() + return false +end + +-- This expression is free of a symbol if and only if the symbol is not the symbol used to create the ring. +function PolynomialRing:freeof(symbol) + return symbol.symbol ~= self.symbol +end + +-- Replaces each expression in the map with its value. +function PolynomialRing:substitute(map) + return self:tocompoundexpression():substitute(map) +end + +-- Expands a polynomial expression. Polynomials are already in expanded form, so we just need to autosimplify. +function PolynomialRing:expand() + return self:tocompoundexpression():autosimplify() +end + +function PolynomialRing:autosimplify() + return self:tocompoundexpression():autosimplify() +end + +-- Transforms from array format to an expression format. +function PolynomialRing:tocompoundexpression() + local terms = {} + for exponent, coefficient in pairs(self.coefficients) do + terms[exponent + 1] = BinaryOperation(BinaryOperation.MUL, {coefficient:tocompoundexpression(), + BinaryOperation(BinaryOperation.POW, {SymbolExpression(self.symbol), Integer(exponent)})}) + end + return BinaryOperation(BinaryOperation.ADD, terms) +end + +-- Uses Horner's rule to evaluate a polynomial at a point +function PolynomialRing:evaluateat(x) + local out = self:zeroc() + for i = self.degree:asnumber(), 1, -1 do + out = out + self.coefficients[i] + out = out * x + end + return out + self.coefficients[0] +end + +-- Multiplies this polynomial by x^n +function PolynomialRing:multiplyDegree(n) + local new = {} + for e = 0, n-1 do + new[e] = self:zeroc() + end + local loc = n + while loc <= self.degree:asnumber() + n do + new[loc] = self.coefficients[loc - n] + loc = loc + 1 + end + return PolynomialRing(new, self.symbol, self.degree + Integer(n)) +end + +-- Returns the formal derivative of this polynomial +function PolynomialRing:derivative() + if self.degree == Integer.zero() then + return PolynomialRing({self:zeroc()}, self.symbol, Integer(-1)) + end + local new = {} + for e = 1, self.degree:asnumber() do + new[e - 1] = Integer(e) * self.coefficients[e] + end + return PolynomialRing(new, self.symbol, self.degree - Integer.one()) +end + +-- Returns the square-free factorization of a polynomial +function PolynomialRing:squarefreefactorization() + local terms + if self.ring == Rational.getring() or self.ring == Integer.getring() then + terms = self:rationalsquarefreefactorization() + elseif self.ring == IntegerModN.getring() then + if not self.ring.modulus:isprime() then + error("Cannot compute a square-free factorization of a polynomial ring contructed from a ring that is not a field.") + end + terms = self:modularsquarefreefactorization() + end + + local expressions = {self:lc()} + local j = 1 + for index, term in ipairs(terms) do + if term.degree ~= Integer.zero() or term.coefficients[0] ~= Integer.one() then + j = j + 1 + expressions[j] = BinaryOperation.POWEXP({term, Integer(index)}) + end + end + + return BinaryOperation.MULEXP(expressions) +end + +-- Factors a polynomial into irreducible terms +function PolynomialRing:factor() + -- Square-free factorization over an integral domain (so a polynomial ring constructed from a field) + local squarefree = self:squarefreefactorization() + local squarefreeterms = {} + local result = {squarefree.expressions[1]} + for i, expression in ipairs(squarefree.expressions) do + if i > 1 then + -- Converts square-free polynomials with rational coefficients to integer coefficients so Rational Roots / Zassenhaus can factor them + if expression.expressions[1].ring == Rational.getring() then + local factor, integerpoly = expression.expressions[1]:rationaltointeger() + result[1] = result[1] * factor ^ expression.expressions[2] + squarefreeterms[i - 1] = integerpoly + else + squarefreeterms[i - 1] = expression.expressions[1] + end + end + end + + for i, expression in ipairs(squarefreeterms) do + local terms + if expression.ring == Integer.getring() then + -- Factoring over the integers first uses the rational roots test to factor out monomials (for efficiency purposes) + local remaining, factors = expression:rationalroots() + terms = factors + -- Then applies the Zassenhaus algorithm if there entire polynomial has not been factored into monomials + if remaining ~= Integer.one() then + remaining = remaining:zassenhausfactor() + for _, exp in ipairs(remaining) do + terms[#terms+1] = exp + end + end + end + if expression.ring == IntegerModN.getring() then + -- Berlekamp factorization is used for rings with integers mod a prime as coefficients + terms = expression:berlekampfactor() + end + for _, factor in ipairs(terms) do + result[#result+1] = BinaryOperation.POWEXP({factor, squarefree.expressions[i + 1].expressions[2]}) + end + end + return BinaryOperation.MULEXP(result) +end + +-- Uses the Rational Root test to factor out monomials of a square-free polynomial. +function PolynomialRing:rationalroots() + local remaining = self + local roots = {} + if self.coefficients[0] == Integer.zero() then + roots[1] = PolynomialRing({Integer.zero(), Integer.one()}, self.symbol) + remaining = remaining // roots[1] + end + -- This can be slower than Zassenhaus if the digits are large enough, since factoring integers is slow + -- if self.coefficients[0] > Integer(Integer.DIGITSIZE - 1) or self:lc() > Integer(Integer.DIGITSIZE - 1) then + -- return remaining, roots + -- end + while remaining ~= Integer.one() do + :: nextfactor :: + local a = remaining.coefficients[0] + local b = remaining:lc() + local afactors = a:divisors() + local bfactors = b:divisors() + for _, af in ipairs(afactors) do + for _, bf in ipairs(bfactors) do + local testroot = Rational(af, bf, true) + if remaining:evaluateat(testroot) == Integer.zero() then + roots[#roots+1] = PolynomialRing({-testroot.numerator, testroot.denominator}, self.symbol) + remaining = remaining // roots[#roots] + goto nextfactor + end + if remaining:evaluateat(-testroot) == Integer.zero() then + roots[#roots+1] = PolynomialRing({testroot.numerator, testroot.denominator}, self.symbol) + remaining = remaining // roots[#roots] + goto nextfactor + end + end + end + break + end + + return remaining, roots +end + +-- Returns a list of roots of the polynomial, simplified up to cubics. +function PolynomialRing:roots() + local roots = {} + local factorization = self:factor() + + for i, factor in ipairs(factorization.expressions) do + if i > 1 then + local decomp = factor.expressions[1]:decompose() + for _, poly in ipairs(decomp) do + if poly.degree > Integer(3) then + table.insert(roots,RootExpression(factor.expressions[1])) + goto nextfactor + end + end + local factorroots = RootExpression(decomp[#decomp]):autosimplify() + if factorroots == true then + return true + end + if factorroots == false then + goto nextfactor + end + local replaceroots = {} + for j = #decomp - 1,1,-1 do + for _, root in ipairs(factorroots) do + local temp = RootExpression(decomp[j]):autosimplify(root) + if temp == true then + return true + end + if factorroots == false then + goto nextfactor + end + replaceroots = JoinArrays(replaceroots, temp) + end + factorroots = replaceroots + end + roots = JoinArrays(roots, factorroots) + end + end + ::nextfactor:: + return roots +end + +----------------- +-- Inheritance -- +----------------- + +__PolynomialRing.__index = Ring +__PolynomialRing.__call = PolynomialRing.new +PolynomialRing = setmetatable(PolynomialRing, __PolynomialRing) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-rational.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-rational.lua new file mode 100644 index 0000000000..811909a948 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-rational.lua @@ -0,0 +1,241 @@ +--- @class Rational +--- Represents an element of the field of rational numbers or rational functions. +--- @field numerator Ring +--- @field denominator Ring +--- @field ring RingIdentifier +Rational = {} +__Rational = {} + + +-------------------------- +-- Static functionality -- +-------------------------- + +-- Metatable for ring objects. +local __obj = {__index = Rational, __eq = function(a, b) + return a["ring"] == b["ring"] and + (a["child"] == b["child"] or a["child"] == nil or b["child"] == nil) and + (a["symbol"] == b["symbol"] or a["child"] == nil or b["child"] == nil) +end, __tostring = function(a) + if a.symbol then + return tostring(a.child.child) .. "(" .. a.symbol .. ")" + end + if a.child then + return "QQ" + end + return "(Generic Fraction Field)" + end} + +--- @param symbol SymbolExpression +--- @param child RingIdentifier +--- @return RingIdentifier +function Rational.makering(symbol, child) + local t = {ring = Rational} + t.symbol = symbol + t.child = child + t = setmetatable(t, __obj) + return t +end + +--- Converts a string of the form -?[0-9]+ or -?[0-9]+\/[0-9]+ to a rational number. +--- @param str string +--- @return Rational|Integer +function Rational.fromstring(str) + local divloc = string.find(str, "/"); + if not divloc then + return Integer(str) + end + return Rational(Integer(string.sub(str, 1, divloc - 1)), Integer(string.sub(str, divloc + 1, #str))) +end + + +---------------------------- +-- Instance functionality -- +---------------------------- + +-- So we don't have to copy the field operations each time. +local __o = Copy(__FieldOperations) +__o.__index = Rational +__o.__tostring = function(a) + if a.ring.symbol then + return "(" .. tostring(a.numerator)..")/("..tostring(a.denominator) .. ")" + end + return tostring(a.numerator).."/"..tostring(a.denominator) +end + +--- Creates a new rational given a numerator and denominator that are part of the same ring. +--- Rational numbers are represented uniquely. +--- @param n Ring +--- @param d Ring +--- @param keep boolean +function Rational:new(n, d, keep) + local o = {} + o = setmetatable(o, __o) + + if n:getring() == PolynomialRing.getring() then + o.symbol = n.symbol + end + + if d:getring() == PolynomialRing.getring() then + o.symbol = d.symbol + end + + if d == Integer(0) then + error("Arithmetic error: division by zero") + end + + n = n or Integer.zero() + d = d or Integer.one() + o.numerator = n + o.denominator = d + o:reduce() + + if (not keep) and o.denominator == Integer.one() or (not keep) and o.numerator == Integer.zero() then + return o.numerator + end + + return o +end + +--- Reduces a rational expression to standard form. This method mutates its object. +function Rational:reduce() + if self.numerator:getring() == Integer.getring() then + if self.denominator < Integer.zero() then + self.denominator = -self.denominator + self.numerator = -self.numerator + end + local gcd = Integer.gcd(self.numerator, self.denominator) + self.numerator = self.numerator//gcd + self.denominator = self.denominator//gcd + self.ring = Integer.getring() + elseif self.numerator:getring() == PolynomialRing.getring() then + local lc = self.denominator:lc() + self.denominator = self.denominator/lc + self.numerator = self.numerator/lc + local gcd = PolynomialRing.gcd(self.numerator, self.denominator) + self.numerator = self.numerator//gcd + self.denominator = self.denominator//gcd + self.ring = Ring.resultantring(self.numerator:getring(), self.denominator:getring()) + end +end + + +--- @return RingIdentifier +function Rational:getring() + local t = {ring=Rational} + if self then + t.child = self.ring + t.symbol = self.symbol + end + t = setmetatable(t, __obj) + return t +end + +--- @param ring RingIdentifier +--- @return Ring +function Rational:inring(ring) + if ring == self:getring() then + return self + end + + if ring == Rational:getring() and ring.symbol then + if not self:getring().symbol then + return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) + end + return Rational(self.numerator:inring(ring.child), self.denominator:inring(ring.child), true) + end + + if ring == PolynomialRing:getring() then + return PolynomialRing({self:inring(ring.child)}, ring.symbol) + end + + error("Unable to convert element to proper ring.") +end + +--- @return boolean +function Rational:isconstant() + if self.symbol then + return false + end + return true +end + +--- @return Expression +function Rational:tocompoundexpression() + return BinaryOperation(BinaryOperation.DIV, {self.numerator:tocompoundexpression(), self.denominator:tocompoundexpression()}) +end + +--- Returns this rational as a floating point number. Can only approximate the value of most rationals. +--- @return number +function Rational:asnumber() + return self.numerator:asnumber() / self.denominator:asnumber() +end + +function Rational:add(b) + return Rational(self.numerator * b.denominator + self.denominator * b.numerator, self.denominator * b.denominator) +end + +function Rational:neg() + return Rational(-self.numerator, self.denominator, true) +end + +function Rational:mul(b) + return Rational(self.numerator * b.numerator, self.denominator * b.denominator) +end + +-- function Rational:inv(b) +-- return Rational(self.numerator * b.numerator, self.denominator * b.denominator) +-- end + +function Rational:pow(b) + return (self.numerator ^ b) / (self.denominator ^ b) +end + +function Rational:div(b) + return Rational(self.numerator * b.denominator, self.denominator * b.numerator) +end + +function Rational:eq(b) + return self.numerator == b.numerator and self.denominator == b.denominator +end + +function Rational:lt(b) + if self.numerator < Integer.zero() and b.numerator > Integer.zero() then + return true + end + if self.numerator > Integer.zero() and b.numerator < Integer.zero() then + return false + end + + if (self.numerator >= Integer.zero() and b.numerator >= Integer.zero()) or (self.numerator <= Integer.zero() and b.numerator <= Integer.zero()) then + return self.numerator * b.denominator < self.denominator * b.numerator + end + return self.numerator * b.denominator > self.denominator * b.numerator +end + +function Rational:le(b) + return self:eq(b) or self:lt(b) +end + +function Rational:zero() + return Integer.zero() +end + +function Rational:one() + return Integer.one() +end + +function Rational:tolatex() + if string.sub(self.numerator:tolatex(),1,1) == '-' then + return "- \\frac{" .. string.sub(self.numerator:tolatex(),2,-1) .. "}{" .. self.denominator:tolatex() .. "}" + end + return "\\frac{" .. self.numerator:tolatex() .."}{".. self.denominator:tolatex().. "}" +end + +----------------- +-- Inheritance -- +----------------- + +__Rational.__index = Field +__Rational.__call = Rational.new +Rational = setmetatable(Rational, __Rational) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-ring.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-ring.lua new file mode 100644 index 0000000000..4903b9c912 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-ring.lua @@ -0,0 +1,326 @@ +--- @class Ring +--- Interface for an element of a ring with unity. +Ring = {} +__Ring = {} + +-------------------------- +-- Static functionality -- +-------------------------- + +--- Determines which ring the output of a binary operation with inputs in ring1 and ring2 should be, if such a ring exists. +--- If one of the rings is a subring of another ring, the result should be one of the two rings. +--- @param ring1 RingIdentifier +--- @param ring2 RingIdentifier +--- @return RingIdentifier +function Ring.resultantring(ring1, ring2) + if ring1 == ring2 then + return ring1 + end + + if ((ring1 == PolynomialRing.getring() and ring2 == Rational.getring()) or + (ring2 == PolynomialRing.getring() and ring1 == Rational.getring())) + and ring1.symbol == ring2.symbol then + return Rational.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2.child)) + end + + if ring1 == PolynomialRing.getring() or ring2 == PolynomialRing.getring() then + if ring1 == ring2.child then + return ring2 + end + if ring2 == ring1.child then + return ring1 + end + + if ring1 == PolynomialRing.getring() and ring2 == PolynomialRing.getring() and ring1.symbol == ring2.symbol then + return PolynomialRing.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2.child)) + end + + -- If none of the above conditions are satisfied, recusion is a pain, so we just strip all of the variables off of both rings. + -- TODO: Make this properly recursive, or just use a multivariable polynomial ring class + local symbols = {} + while ring1 == PolynomialRing.getring() do + symbols[#symbols+1] = ring1.symbol + ring1 = ring1.child + end + while ring2 == PolynomialRing.getring() do + if not Contains(symbols, ring2.symbol) then + symbols[#symbols+1] = ring2.symbol + end + ring2 = ring2.child + end + local ring = Ring.resultantring(ring1, ring2) + + if ring == Rational.getring() and Contains(symbols, ring.symbol) then + symbols = Remove(symbols, ring.symbol) + end + for i = #symbols, 1, -1 do + ring = PolynomialRing.makering(symbols[i], ring) + end + return ring + end + + if ring1 == Integer.getring() then + if ring2 == Integer.getring() then + return ring2 + end + + if ring2 == Rational.getring() then + return ring2 + end + + if ring2 == IntegerModN.getring() then + return ring2 + end + end + + if ring1 == Rational.getring() then + if ring2 == Integer.getring() then + return ring1 + end + + if ring2 == Rational.getring() then + if not ring1.symbol then + return Rational.makering(ring2.symbol, Ring.resultantring(ring1, ring2.child)) + end + if not ring2.symbol then + return Rational.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2)) + end + if ring1.symbol and ring2.symbol and ring1.symbol == ring2.symbol then + return Rational.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2.child)) + end + return ring2 + end + + if ring2 == IntegerModN.getring() then + return nil + end + end + + if ring1 == IntegerModN.getring() then + if ring2 == Integer.getring() then + return ring1 + end + + if ring2 == Rational.getring() then + return nil + end + + if ring2 == IntegerModN.getring() then + return IntegerModN.makering(Integer.gcd(ring1.modulus, ring2.modulus)) + end + end + + return nil +end + +--- Returns a particular instantiation of a ring. +--- Does the same thing as getring() if there is only one possible ring for a class, i.e., the integers and rationals. +--- @return RingIdentifier +function Ring.makering() + error("Called unimplemented method : makering()") +end + +---------------------- +-- Required methods -- +---------------------- + +--- Returns the ring this element is part of. +--- @return RingIdentifier +function Ring:getring() + error("Called unimplemented method : getring()") +end + +--- Explicitly converts this element to an element of another ring. +--- @param ring RingIdentifier +--- @return Ring +function Ring:inring(ring) + error("Called unimplemented method : in()") +end + +--- Returns whether the ring is commutative. +--- @return boolean +function Ring:iscommutative() + error("Called unimplemented method : iscommutative()") +end + +--- @return Ring +function Ring:add(b) + error("Called unimplemented method : add()") +end + +--- @return Ring +function Ring:sub(b) + return(self:add(b:neg())) +end + +--- @return Ring +function Ring:neg() + error("Called unimplemented method : neg()") +end + +--- @return Ring +function Ring:mul(b) + error("Called unimplemented method : mul()") +end + +--- Ring exponentiation by definition. Specific rings may implement more efficient methods. +--- @return Ring +function Ring:pow(n) + if(n < Integer.zero()) then + error("Execution error: Negative exponentiation is undefined over general rings") + end + local k = Integer.zero() + local b = self:one() + while k < n do + b = b * self + k = k + Integer.one() + end + return b +end + +--- @return boolean +function Ring:eq(b) + error("Execution error: Ring does not have a total order") +end + +--- @return boolean +function Ring:lt(b) + error("Execution error: Ring does not have a total order") +end + +--- @return boolean +function Ring:le(b) + error("Execution error: Ring does not have a total order") +end + +--- The additive identitity of the ring. +--- @return Ring +function Ring:zero() + error("Called unimplemented method : zero()") +end + +--- The multiplicative identitity of the ring. +--- @return Ring +function Ring:one() + error("Called unimplemented method : one()") +end + +-------------------------- +-- Instance metamethods -- +-------------------------- +__RingOperations = {} + +-- Each of these methods just handles coverting each element in the ring to an instance of the proper ring, if possible, +-- then passing the arguments to the function in a specific ring. + +__RingOperations.__unm = function(a) + return a:neg() +end + +__RingOperations.__add = function(a, b) + if not b.getring then + return BinaryOperation.ADDEXP({a, b}) + end + + local aring, bring = a:getring(), b:getring() + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to add two elements of incompatable rings") + end + return a:inring(oring):add(b:inring(oring)) +end + +__RingOperations.__sub = function(a, b) + if not b.getring then + return BinaryOperation.SUBEXP({a, b}) + end + + local aring, bring = a:getring(), b:getring() + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to subtract two elements of incompatable rings") + end + return a:inring(oring):sub(b:inring(oring)) +end + +-- Allows for multiplication by writing two expressions next to each other. +__RingOperations.__call = function (a, b) + return a * b +end + +__RingOperations.__mul = function(a, b) + if not b.getring then + return BinaryOperation.MULEXP({a, b}) + end + + local aring, bring = a:getring(), b:getring() + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to muliply two elements of incompatable rings") + end + return a:inring(oring):mul(b:inring(oring)) +end + +__RingOperations.__pow = function(a, n) + if (not n.getring) or (n.getring and n:getring().ring ~= Integer) then + return BinaryOperation.POWEXP({a, n}) + end + + -- if a == a:zero() and n == Integer.zero() then + -- error("Cannot raise 0 to the power of 0") + -- end + + return a:pow(n) +end + +-- Comparison operations assume, of course, that the ring operation is equipped with a total order +-- All elements of all rings need these metamethods, since in Lua comparisons on tables only fire if both objects have the table +__RingOperations.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway + if not a.getring or not b.getring then + return false + end + local aring, bring = a:getring(), b:getring() + if aring == bring then + return a:eq(b) + end + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to compare two elements of incompatable rings") + end + return a:inring(oring):eq(b:inring(oring)) +end + +__RingOperations.__lt = function(a, b) + local aring, bring = a:getring(), b:getring() + if aring == bring then + return a:lt(b) + end + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to compare two elements of incompatable rings") + end + return a:inring(oring):lt(b:inring(oring)) +end + +__RingOperations.__le = function(a, b) + local aring, bring = a:getring(), b:getring() + if aring == bring then + return a:le(b) + end + local oring = Ring.resultantring(aring, bring) + if not oring then + error("Attempted to compare two elements of incompatable rings") + end + return a:inring(oring):le(b:inring(oring)) +end + +----------------- +-- Inheritance -- +----------------- + +__Ring.__index = ConstantExpression +Ring = setmetatable(Ring, __Ring) + +--- Used for comparing and converting between rings. +--- @class RingIdentifier diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-rootexpression.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-rootexpression.lua new file mode 100644 index 0000000000..a66182579a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-rootexpression.lua @@ -0,0 +1,135 @@ +--- @class RootExpression +--- An expression that represents the solutions to expression = 0. +--- @field expression Expression +RootExpression = {} +__RootExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new root expression with the given expression. +--- @param expression Expression +--- @return RootExpression +function RootExpression:new(expression) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.expression = Copy(expression) + + __o.__index = RootExpression + __o.__tostring = function(a) + return 'Root Of: (' .. tostring(a.expression) .. ')' + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway + if not b:type() == RootExpression then + return false + end + return a.expression == b.expression + end + o = setmetatable(o, __o) + + return o +end + +--- @return Expression +function RootExpression:autosimplify(subpart) + local simplified = self.expression:autosimplify() + local simplified, ispoly = simplified:topolynomial() + + if simplified:isconstant() then + -- 0 = 0 is always true (obviously). + return simplified == simplified:zero() + end + + if ispoly then + if simplified.degree == Integer.zero() then + return simplified == simplified:zero() + end + if simplified.degree == Integer.one() then + return {-simplified.coefficients[0] / simplified.coefficients[1]} + end + if simplified.degree == Integer(2) then + local a = simplified.coefficients[2] + local b = simplified.coefficients[1] + local c = simplified.coefficients[0] + -- This is a hack until we can get more expression manipulation working, but that's okay. + if subpart then + c = (c - subpart):autosimplify() + end + return {((-b + sqrt(b^Integer(2) - Integer(4) * a * c)) / (Integer(2) * a)):autosimplify(), + ((-b - sqrt(b^Integer(2) - Integer(4) * a * c)) / (Integer(2) * a)):autosimplify()} + end + if simplified.degree == Integer(3) then + local a = simplified.coefficients[3] + local b = simplified.coefficients[2] + local c = simplified.coefficients[1] + local d = simplified.coefficients[0] + -- This is a hack until we can get more expression manipulation working, but that's okay. + if subpart then + d = (d - subpart):autosimplify() + end + + local delta0 = (b^Integer(2) - Integer(3)*a*c):autosimplify() + local delta1 = (Integer(2) * b^Integer(3) - Integer(9)*a*b*c+Integer(27)*a^Integer(2)*d):autosimplify() + + local C = sqrt((delta1 + sqrt(delta1 ^ Integer(2) - Integer(4) * delta0 ^ Integer(3))) / Integer(2), Integer(3)):autosimplify() + + if C == Integer.zero() then + C = sqrt((delta1 - sqrt(delta1 ^ Integer(2) - Integer(4) * delta0 ^ Integer(3))) / Integer(2), Integer(3)):autosimplify() + end + + if C == Integer.zero() then + C = (-b/(Integer(3)*a)):autosimplify() + end + + local eta = ((Integer(-1) + sqrt(Integer(-3))) / Integer(2)):autosimplify() + + return {((-Integer.one() / (Integer(3) * a)) * (b + C + delta0 / C)):autosimplify(), + ((-Integer.one() / (Integer(3) * a)) * (b + C*eta + delta0 / (C*eta))):autosimplify(), + ((-Integer.one() / (Integer(3) * a)) * (b + C*eta^Integer(2) + delta0 / (C*eta^Integer(2)))):autosimplify()} + end + end + if ispoly then + simplified = simplified:autosimplify() + end + if subpart then + simplified = (simplified - subpart):autosimplify() + end + return {RootExpression(simplified)} +end + +--- @return table +function RootExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return RootExpression +function RootExpression:setsubexpressions(subexpressions) + return RootExpression(subexpressions[1]) +end + +--- @param other Expression +--- @return boolean +function RootExpression:order(other) + --- TODO: Fix ordering on new expression types + if other:type() ~= RootExpression then + return false + end + + return self.expression:order(other.expression) +end + +--- @return string +function RootExpression:tolatex() + return '\\operatorname{RootOf}\\left(' .. self.expression:tolatex() .. '\\right)' +end + +----------------- +-- Inheritance -- +----------------- +__RootExpression.__index = CompoundExpression +__RootExpression.__call = RootExpression.new +RootExpression = setmetatable(RootExpression, __RootExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-sqrtexpression.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-sqrtexpression.lua new file mode 100644 index 0000000000..bc96f4e1d8 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-sqrtexpression.lua @@ -0,0 +1,196 @@ +--- @class SqrtExpression +--- An expression that represents the positive real solution to x^n = a where n is a positive integer and a is constant. +--- @field expression Expression +SqrtExpression = {} +__SqrtExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new sqrt expression with the given expression. +--- @param expression Expression +--- @param root Integer +--- @return SqrtExpression +function SqrtExpression:new(expression, root) + root = root or Integer(2) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.expression = Copy(expression) + o.root = root + + __o.__index = SqrtExpression + __o.__tostring = function(a) + return tostring(a.expression) .. ' ^ (1/' .. tostring(a.root) .. ')' + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway + if not b:type() == SqrtExpression then + return false + end + return a.expression == b.expression and a.root == b.root + end + o = setmetatable(o, __o) + + return o +end + + +--- @return table +function SqrtExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return SqrtExpression +function SqrtExpression:setsubexpressions(subexpressions) + return SqrtExpression(subexpressions[1], self.root) +end + +--- @param other Expression +--- @return boolean +function SqrtExpression:order(other) + return self:topower():order(other) +end + +function SqrtExpression:topower() + local exponent = BinaryOperation(BinaryOperation.DIV,{Integer.one(),self.root}):autosimplify() + local base = self.expression + return BinaryOperation(BinaryOperation.POW,{base,exponent}):autosimplify() +end + +function SqrtExpression:autosimplify() + local expression = self.expression:autosimplify() + local root = self.root:autosimplify() + + if root == Integer.one() then + return expression + end + + if root:type() == Rational then + return SqrtExpression(BinaryOperation(BinaryOperation.POW,{expression,root.denominator}):autosimplify(), root.numerator):autosimplify() + end + + if not root:isconstant() then + return BinaryOperation(BinaryOperation.POW,{expression,Integer.one() / root}):autosimplify() + end + + if not expression:isconstant() then + if expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() then + local coeff = SqrtExpression(expression.expressions[1],root):autosimplify() + expression.expressions[1] = BinaryOperation(BinaryOperation.MUL,{Integer.one()}) + expression = expression:autosimplify() + local sqrtpart = SqrtExpression(expression,root):autosimplify() + local result = coeff*sqrtpart + return result:autosimplify() + end + return BinaryOperation(BinaryOperation.POW,{expression,Integer.one() / root}):autosimplify() + end + + if expression:type() == Rational then + local result = BinaryOperation(BinaryOperation.MUL, {SqrtExpression(expression.numerator,root):autosimplify(),BinaryOperation(BinaryOperation.POW,{SqrtExpression(expression.denominator,root):autosimplify(),Integer(-1)})}) + return result:autosimplify() + end + + if expression:type() == Integer then + if expression == Integer.zero() then + return Integer.zero() + end + if expression == Integer.one() then + return Integer.one() + end + if expression < Integer.zero() then + if root == Integer(2) then + local result = SqrtExpression(expression:neg(),root):autosimplify() + result = I*result + return result:autosimplify() + end + if root % Integer(2) == Integer.one() then + local result = SqrtExpression(expression:neg(),root):autosimplify() + result = -result + return result:autosimplify() + end + end + local primes = expression:primefactorization() + local coeffresult = {} + local exprresult = {} + local reduction = root + for _, term in ipairs(primes.expressions) do + local primepower = term.expressions[2] + reduction = Integer.gcd(primepower,reduction) + if reduction == Integer.one() then + goto skip + end + end + ::skip:: + local newroot = root / reduction + for index, term in ipairs(primes.expressions) do + local prime = term.expressions[1] + local primepower = term.expressions[2] / reduction + local coeffpower = primepower // newroot + coeffresult[index] = prime ^ coeffpower + local exprpower = primepower - coeffpower*newroot + exprresult[index] = prime ^ exprpower + end + local newexpression = BinaryOperation(BinaryOperation.MUL,exprresult):autosimplify() + local coeff = BinaryOperation(BinaryOperation.MUL,coeffresult):autosimplify() + if coeff == Integer.one() then + if reduction == Integer.one() then + goto stop + end + return SqrtExpression(newexpression,newroot) + end + if newroot == Integer.one() then + return coeff + end + return BinaryOperation(BinaryOperation.MUL,{coeff,SqrtExpression(newexpression,newroot)}):autosimplify() + end + ::stop:: + + if expression.operation == BinaryOperation.POW and expression.expressions[2]:type() == Integer then + local exponent = expression.expressions[2] + local power = exponent // root + local newexponent = (exponent / root) - power + local coeff = expression.expressions[1] ^ power + coeff = coeff:evaluate() + if newexponent == Integer.zero() then + return coeff + else + local num = newexponent.numerator + local den = newexponent.denominator + local newexpression = expression ^ num + newexpression = newexpression:autosimplify() + local result = coeff * SqrtExpression(newexpression,den) + return result + end + end + + return SqrtExpression(expression,root) +end + +function SqrtExpression:tolatex() + local printout = '\\sqrt' + if self.root == Integer(2) then + printout = printout .. '{' .. self.expression:tolatex() .. '}' + else + printout = printout .. '[' .. self.root:tolatex() .. ']' .. '{' .. self.expression:tolatex() .. '}' + end + return printout +end + + +----------------- +-- Inheritance -- +----------------- +__SqrtExpression.__index = CompoundExpression +__SqrtExpression.__call = SqrtExpression.new +SqrtExpression = setmetatable(SqrtExpression, __SqrtExpression) + +---------------------- +-- Static constants -- +---------------------- + +sqrt = function(expression, root) + return SqrtExpression(expression, root) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua b/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua new file mode 100644 index 0000000000..307bfe1737 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/luacas-trigexpression.lua @@ -0,0 +1,355 @@ +--- @class TrigExpression +--- Represents a trigonometric function from one expression to another. +--- @field name string +--- @field expression Expression +TrigExpression = {} +__TrigExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new trig expression with the given name and expression. +--- @param name string|SymbolExpression +--- @param expression Expression +--- @return TrigExpression +function TrigExpression:new(name, expression) + local o = {} + local __o = Copy(__ExpressionOperations) + + if not TrigExpression.NAMES[name] then + error("Argument error: " .. name .. " is not the name of a trigonometric function.") + end + + o.name = name + o.expression = expression + o.expressions = {expression} + if expression:isatomic() then + o.variables = {expression} + else + o.variables = {SymbolExpression('x')} + end + o.derivatives = {Integer.zero()} + + __o.__index = TrigExpression + __o.__tostring = function(a) + return tostring(a.name) .. '(' .. tostring(a.expression) .. ')' + end + __o.__eq = function(a, b) + -- if b:type() == FunctionExpression then + -- return a:tofunction() == b + -- end + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway + if not b:type() == TrigExpression then + return false + end + return a.name == b.name and a.expression == b.expression + end + + o = setmetatable(o, __o) + return o +end + +--- @return TrigExpression +function TrigExpression:evaluate() + local expression = self.expression:autosimplify() + + if expression == Integer.zero() then + if self.name == "cos" or self.name == "sec" then + return Integer.one() + end + if self.name == "sin" or self.name == "tan" then + return Integer.zero() + end + if self.name == "arctan" or self.name == "arcsin" then + return Integer.zero() + end + if self.name == "arccos" or self.name == "arccot" then + return PI / Integer(2) + end + end + + if expression == PI then + if self.name == "cos" or self.name == "sec" then + return Integer(-1) + end + if self.name == "sin" or self.name == "tan" then + return Integer.zero() + end + end + + if expression:ismulratlPI() then + local coeff = expression.expressions[1] + if TrigExpression.COSVALUES[tostring(coeff)] ~= nil then + if self.name == "cos" then + return TrigExpression.COSVALUES[tostring(coeff)]:autosimplify() + end + if self.name == "sin" then + local sign = Integer.one() + if coeff > Integer.one() then + sign = Integer(-1) + end + return (sign*sqrt(Integer.one()-cos(expression)^Integer(2))):autosimplify() + end + if self.name == "tan" then + return (sin(expression) / cos(expression)):autosimplify() + end + if self.name == "sec" then + return (Integer.one() / cos(expression)):autosimplify() + end + if self.name == "csc" then + return (Integer.one() / sin(expression)):autosimplify() + end + if self.name == "cot" then + return (cos(expression) / sin(expression)):autosimplify() + end + end + end + + if TrigExpression.ACOSVALUES[tostring(expression)] ~= nil then + if self.name == "arccos" then + return TrigExpression.ACOSVALUES[tostring(expression)]:autosimplify() + end + if self.name == "arcsin" then + if expression == Integer(-1) then + return TrigExpression.ACOSVALUES["-1"]:autosimplify() + elseif expression.expressions and expression.expressions[1] == Integer(-1) then + local expr = (Integer(-1)*sqrt(Integer.one() - expression ^ Integer(2))):autosimplify() + return TrigExpression.ACOSVALUES[tostring(expr)]:autosimplify() + else + local expr = (sqrt(Integer.one() - expression ^ Integer(2))):autosimplify() + return TrigExpression.ACOSVALUES[tostring(expr)]:autosimplify() + end + end + end + + if self.name == "arctan" and TrigExpression.ATANVALUES[tostring(expression)] ~= nil then + return TrigExpression.ATANVALUES[tostring(expression)]:autosimplify() + end + + return self +end + +--- checks if expression is a rational multiple of pi +--- @return boolean +function Expression:ismulratlPI() + if self.operation == BinaryOperation.MUL and #self.expressions == 2 and (self.expressions[1]:type() == Integer or self.expressions[1]:type() == Rational) and self.expressions[2] == PI then + return true + end + + return false +end + +--- @return TrigExpression +function TrigExpression:autosimplify() + local expression = self.expression:autosimplify() + + -- even and odd properties of trig functions + if (self.name == "sin" or self.name == "tan" or self.name == "csc" or self.name == "cot") and + expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() and expression.expressions[1] < Integer(0) then + return (-Integer.one() * TrigExpression(self.name, -expression)):autosimplify() + end + + if (self.name == "cos" or self.name == "sec") and + expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() and expression.expressions[1] < Integer(0) then + expression = (-expression):autosimplify() + end + + -- uses periodicity of sin and cos and friends + if self.name == "sin" or self.name == "cos" or self.name == "csc" or self.name == "sec" then + if expression == Integer.zero() or expression == PI then + goto skip + end + if expression.operation ~= BinaryOperation.ADD then + expression = BinaryOperation(BinaryOperation.ADD,{expression}) + end + for index,component in ipairs(expression.expressions) do + if component:ismulratlPI() then + local coeff = component.expressions[1] + if coeff:type() == Integer then + coeff = coeff % Integer(2) + coeff = coeff:autosimplify() + end + if coeff:type() == Rational then + local n = coeff.numerator + local d = coeff.denominator + local m = {n:divremainder(d)} + coeff = (m[1] % Integer(2)) + m[2]/d + coeff = coeff:autosimplify() + end + expression.expressions[index].expressions[1] = coeff + end + expression = expression:autosimplify() + end + ::skip:: + end + + -- uses periodicity of tan and cot + if self.name == "tan" or self.name == "cot" then + if expression == Integer.zero() or expression == PI then + goto skip + end + if expression.operation ~= BinaryOperation.ADD then + expression = BinaryOperation(BinaryOperation.ADD,{expression}) + end + for index,component in ipairs(expression.expressions) do + if component:ismulratlPI() then + local coeff = component.expressions[1] + if coeff:type() == Integer then + coeff = Integer.zero() + end + if coeff:type() == Rational then + local n = coeff.numerator + local d = coeff.denominator + local m = {n:divremainder(d)} + coeff = m[2]/d + coeff = coeff:autosimplify() + end + expression.expressions[index].expressions[1] = coeff + end + if component == PI then + expression.expressions[index] = Integer.zero() + end + end + expression = expression:autosimplify() + ::skip:: + end + + return TrigExpression(self.name, expression):evaluate() +end + +--- @return table +function TrigExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return TrigExpression +function TrigExpression:setsubexpressions(subexpressions) + return TrigExpression(self.name, subexpressions[1]) +end + +-- function TrigExpression:freeof(symbol) +-- return self.expression:freeof(symbol) +-- end + +-- function TrigExpression:substitute(map) +-- for expression, replacement in pairs(map) do +-- if self == expression then +-- return replacement +-- end +-- end +-- return TrigExpression(self.name, self.expression:substitute(map)) +-- end + +-- function TrigExpression:order(other) +-- return self:tofunction():order(other) +-- end + +-- function TrigExpression:tofunction() +-- return FunctionExpression(self.name, {self.expression}, true) +-- end + +----------------- +-- Inheritance -- +----------------- + +__TrigExpression.__index = FunctionExpression +__TrigExpression.__call = TrigExpression.new +TrigExpression = setmetatable(TrigExpression, __TrigExpression) + +---------------------- +-- Static constants -- +---------------------- +TrigExpression.NAMES = {sin=1, cos=2, tan=3, csc=4, sec=5, cot=6, + arcsin=7, arccos=8, arctan=9, arccsc=10, arcsec=11, arccot=12} + +TrigExpression.INVERSES = {sin="arcsin", cos="arccos", tan="arctan", csc="arccsc", sec="arcsec", cot="arccot", + arcsin="sin", arccos="cos", arctan="tan", arccsc="csc", arcsec="sec", arccot="cot"} + +TrigExpression.COSVALUES = { + ["0"] = Integer.one(), + ["1/6"] = sqrt(Integer(3))/Integer(2), + ["1/4"] = sqrt(Integer(2))/Integer(2), + ["1/3"] = Integer.one()/Integer(2), + ["1/2"] = Integer.zero(), + ["2/3"] = -Integer.one()/Integer(2), + ["3/4"] = -sqrt(Integer(2))/Integer(2), + ["5/6"] = -sqrt(Integer(3))/Integer(2), + ["1"] = -Integer.one(), + ["7/6"] = -sqrt(Integer(3))/Integer(2), + ["5/4"] = -sqrt(Integer(2))/Integer(2), + ["4/3"] = -Integer.one()/Integer(2), + ["3/2"] = Integer.zero(), + ["5/3"] = Integer.one()/Integer(2), + ["7/4"] = sqrt(Integer(2))/Integer(2), + ["11/6"] = sqrt(Integer(3))/Integer(2), +} +TrigExpression.ACOSVALUES = { + ["1"] = Integer.zero(), + ["(1/2 * sqrt(3,2))"] = PI * Integer(6) ^ Integer(-1), + ["(1/2 * sqrt(2,2))"] = PI * Integer(4) ^ Integer(-1), + ["1/2"] = PI * Integer(3) ^ Integer(-1), + ["0"] = PI * Integer(2) ^ Integer(-1), + ["-1/2"] = PI * Integer(2) * Integer(3) ^ Integer(-1), + ["(-1/2 * sqrt(2,2))"]= PI * Integer(3) * Integer(4) ^ Integer(-1), + ["(-1/2 * sqrt(3,2))"]= PI * Integer(5) * Integer(6) ^ Integer(-1), + ["-1"] = Integer(-1)*PI, +} +TrigExpression.ATANVALUES = { + ["(-1 * sqrt(3,2))"] = Integer(-1) * PI * Integer(3) ^ Integer(-1), + ["-1"] = Integer(-1) * PI * Integer(4) ^ Integer(-1), + ["(-1/3 * sqrt(3,2))"] = Integer(-1) * Integer(6) ^ Integer(-1), + ["0"] = Integer.zero(), + ["(1/3 * sqrt(3,2))"] = PI * Integer(6) ^ Integer(-1), + ["1"] = PI * Integer(4) ^ Integer(-1), + ["sqrt(3,2)"] = PI * Integer(3) ^ Integer(-1) +} + +SIN = function (a) + return TrigExpression("sin", a) +end + +COS = function (a) + return TrigExpression("cos", a) +end + +TAN = function (a) + return TrigExpression("tan", a) +end + +CSC = function (a) + return TrigExpression("csc", a) +end + +SEC = function (a) + return TrigExpression("sec", a) +end + +COT = function (a) + return TrigExpression("cot", a) +end + +ARCSIN = function (a) + return TrigExpression("arcsin", a) +end + +ARCCOS = function (a) + return TrigExpression("arccos", a) +end + +ARCTAN = function (a) + return TrigExpression("arctan", a) +end + +ARCCSC = function (a) + return TrigExpression("arccsc", a) +end + +ARCSEC = function (a) + return TrigExpression("arcsec", a) +end + +ARCCOT = function (a) + return TrigExpression("arccot", a) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring.lua deleted file mode 100644 index 568c21c921..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/polynomialring.lua +++ /dev/null @@ -1,860 +0,0 @@ ---- @class PolynomialRing ---- Represents an element of a polynomial ring. ---- @field coefficients table ---- @field symbol SymbolExpression ---- @field ring RingIdentifier -PolynomialRing = {} -__PolynomialRing = {} - --- Metatable for ring objects. -local __obj = {__index = PolynomialRing, __eq = function(a, b) - return a["ring"] == b["ring"] and - (a["child"] == b["child"] or a["child"] == nil or b["child"] == nil) and - (a["symbol"] == b["symbol"] or a["child"] == nil or b["child"] == nil) -end, __tostring = function(a) - if a.child and a.symbol then return tostring(a.child) .. "[" .. a.symbol .. "]" else return "(Generic Polynomial Ring)" end -end} - --------------------------- --- Static functionality -- --------------------------- - ---- Creates a new ring with the given symbol and child ring. ---- @param symbol SymbolExpression ---- @param child RingIdentifier ---- @return RingIdentifier -function PolynomialRing.makering(symbol, child) - local t = {ring = PolynomialRing} - t.symbol = symbol - t.child = child - t = setmetatable(t, __obj) - return t -end - --- Shorthand constructor for a polynomial ring with integer or integer mod ring coefficients. -function PolynomialRing.R(symbol, modulus) - if modulus then - return PolynomialRing.makering(symbol, IntegerModN.makering(modulus)) - end - return PolynomialRing.makering(symbol, Integer.getring()) -end - ---- Returns the GCD of two polynomials in a ring, assuming both rings are euclidean domains. ---- @param a PolynomialRing ---- @param b PolynomialRing ---- @return PolynomialRing -function PolynomialRing.gcd(a, b) - if a.symbol ~= b.symbol then - error("Cannot take the gcd of two polynomials with different symbols") - end - while b ~= Integer.zero() do - a, b = b, a % b - end - return a // a:lc() -end - --- Returns the GCD of two polynomials in a ring, assuming both rings are euclidean domains. --- Also returns bezouts coefficients via extended gcd. ---- @param a PolynomialRing ---- @param b PolynomialRing ---- @return PolynomialRing, PolynomialRing, PolynomialRing -function PolynomialRing.extendedgcd(a, b) - local oldr, r = a, b - local olds, s = Integer.one(), Integer.zero() - local oldt, t = Integer.zero(), Integer.one() - while r ~= Integer.zero() do - local q = oldr // r - oldr, r = r, oldr - q*r - olds, s = s, olds - q*s - oldt, t = t, oldt - q*t - end - return oldr // oldr:lc(), olds // oldr:lc(), oldt // oldr:lc() -end - --- Returns the resultant of two polynomials in the same ring, whose coefficients are all part of a field. ---- @param a PolynomialRing ---- @param b PolynomialRing ---- @return Field -function PolynomialRing.resultant(a, b) - - if a.ring == PolynomialRing.getring() or b.ring == PolynomialRing.getring() then - return PolynomialRing.resultantmulti(a, b) - end - - local m, n = a.degree, b.degree - if n == Integer.zero() then - return b.coefficients[0]^m - end - - local r = a % b - if r == Integer.zero() then - return r.coefficients[0] - end - - local s = r.degree - local l = b:lc() - - return Integer(-1)^(m*n) * l^(m-s) * PolynomialRing.resultant(b, r) -end - --- Returns the resultant of two polynomials in the same ring, whose coefficients are not part of a field. ---- @param a PolynomialRing ---- @param b PolynomialRing ---- @return Ring -function PolynomialRing.resultantmulti(a, b) - local m, n = a.degree, b.degree - - if m < n then - return Integer(-1) ^ (m * n) * PolynomialRing.resultantmulti(b, a) - end - if n == Integer.zero() then - return b.coefficients[0]^m - end - - local delta = m - n + Integer(1) - local _ , r = PolynomialRing.pseudodivide(a, b) - if r == Integer.zero() then - return r.coefficients[0] - end - - local s = r.degree - local w = Integer(-1)^(m*n) * PolynomialRing.resultant(b, r) - local l = b:lc() - local k = delta * n - m + s - local f = l ^ k - return w // f -end - --- Given two polynomials a and b, returns a list of the remainders generated by the monic Euclidean algorithm. ---- @param a PolynomialRing ---- @param b PolynomialRing ---- @return table -function PolynomialRing.monicgcdremainders(a, b) - if a.symbol ~= b.symbol then - error("Cannot take the gcd of two polynomials with different symbols") - end - - local remainders = {a / a:lc(), b / b:lc()} - while true do - local q = remainders[#remainders - 1] // remainders[#remainders] - local c = remainders[#remainders - 1] - q*remainders[#remainders] - if c ~= Integer.zero() then - remainders[#remainders+1] = c/c:lc() - else - break - end - end - - return remainders -end - --- Returns the partial fraction decomposition of the rational function g/f --- given g, f, and some (not nessecarily irreducible) factorization of f. --- If the factorization is omitted, the irreducible factorization is used. --- The degree of g must be less than the degree of f. ---- @param g PolynomialRing ---- @param f PolynomialRing ---- @param ffactors Expression ---- @return Expression -function PolynomialRing.partialfractions(g, f, ffactors) - - if g.degree >= f.degree then - error("Argument Error: The degree of g must be less than the degree of f.") - end - - -- Converts f to a monic polynomial. - g = g * f:lc() - f = f / f:lc() - - ffactors = ffactors or f:factor() - - local expansionterms = {} - - for _, factor in ipairs(ffactors.expressions) do - local k - local m - if factor.getring and factor:getring() == PolynomialRing:getring() then - m = factor - k = Integer.one() - elseif not factor:isconstant() then - m = factor.expressions[1] - k = factor.expressions[2] - end - - if not factor:isconstant() then - -- Uses Chinese Remainder Theorem for each factor to determine the numerator of the term in the decomposition - local mk = m^k - local v = g % mk - local _, minv, _ = PolynomialRing.extendedgcd(f // mk, mk) - local c = v*minv % mk - - - if k == Integer.one() then - expansionterms[#expansionterms+1] = BinaryOperation.ADDEXP({BinaryOperation.DIVEXP({c, BinaryOperation.POWEXP({m, Integer.one()})})}) - else - -- Uses the p-adic expansion of c to split terms with repeated roots. - local q = c - local r - local innerterms = {} - for i = k:asnumber(), 1, -1 do - q, r = q:divremainder(m) - innerterms[#innerterms+1] = BinaryOperation.DIVEXP({r, BinaryOperation.POWEXP({m, Integer(i)})}) - end - expansionterms[#expansionterms+1] = BinaryOperation.ADDEXP(innerterms) - end - end - end - - return BinaryOperation.ADDEXP(expansionterms) - -end - ----------------------------- --- Instance functionality -- ----------------------------- - --- So we don't have to copy the Euclidean operations each time -local __o = Copy(__EuclideanOperations) -__o.__index = PolynomialRing -__o.__tostring = function(a) - local out = "" - local loc = a.degree:asnumber() - while loc >= 0 do - if a.ring == PolynomialRing.getring() or (a.ring == Rational.getring() and a.ring.symbol) then - out = out .. "(" .. tostring(a.coefficients[loc]) .. ")" .. a.symbol .. "^" .. tostring(math.floor(loc)) .. "+" - else - out = out .. tostring(a.coefficients[loc]) .. a.symbol .. "^" .. tostring(math.floor(loc)) .. "+" - end - loc = loc - 1 - end - return string.sub(out, 1, string.len(out) - 1) -end -__o.__div = function(a, b) - if not b.getring then - return BinaryOperation.DIVEXP({a, b}) - end - if Ring.resultantring(a.ring, b:getring()) ~= Ring.resultantring(a:getring(), b:getring()) then - return a:div(b:inring(Ring.resultantring(a:getring(), b:getring()))) - end - if b.ring and b:getring() == Rational:getring() and a.symbol == b.ring.symbol then - return a:inring(Ring.resultantring(a:getring(), b:getring())):div(b) - end - if a:getring() == b:getring() then - return Rational(a, b, true) - end - -- TODO: Fix this for arbitrary depth - if a:getring() == PolynomialRing:getring() and b:getring() == PolynomialRing:getring() and a.symbol == b.symbol then - local oring = Ring.resultantring(a:getring(), b:getring()) - return Rational(a:inring(oring), b:inring(oring), true) - end - return BinaryOperation.DIVEXP({a, b}) -end - -function PolynomialRing:tolatex() - local out = '' - local loc = self.degree:asnumber() - if loc == 0 then - return self.coefficients[loc]:tolatex() - end - if self.ring == Rational.getring() or self.ring == Integer.getring() or self.ring == IntegerModN.getring() then - if self.coefficients[loc] ~= Integer.one() then - out = out .. self.coefficients[loc]:tolatex() .. self.symbol - else - out = out .. self.symbol - end - if loc ~=1 then - out = out .. "^{" .. loc .. "}" - end - loc = loc -1 - while loc >=0 do - local coeff = self.coefficients[loc] - if coeff == Integer.one() then - if loc == 0 then - out = out .. "+" .. coeff:tolatex() - goto skip - else - out = out .. "+" - goto continue - end - end - if coeff == Integer(-1) then - if loc == 0 then - out = out .. "-" .. coeff:neg():tolatex() - goto skip - else - out = out .. "-" - goto continue - end - end - if coeff < Integer.zero() then - out = out .. "-" .. coeff:neg():tolatex() - end - if coeff == Integer.zero() then - goto skip - end - if coeff > Integer.zero() then - out = out .. "+" .. coeff:tolatex() - end - ::continue:: - if loc > 1 then - out = out .. self.symbol .. "^{" .. loc .. "}" - end - if loc == 1 then - out = out .. self.symbol - end - ::skip:: - loc = loc-1 - end - else - while loc >=0 do - if loc >=1 then - out = out .. self.coefficients[loc]:tolatex() .. self.symbol .. "^{" .. loc .. "} + " - else - out = out .. self.coefficients[loc]:tolatex() .. self.symbol .. "^{" .. loc .. "}" - end - loc = loc-1 - end - end - return out -end - -function PolynomialRing:isatomic() - --if self.degree >= Integer.one() then - -- return false - --else - return false - --end -end ---test - --- Creates a new polynomial ring given an array of coefficients and a symbol -function PolynomialRing:new(coefficients, symbol, degree) - local o = {} - o = setmetatable(o, __o) - - if type(coefficients) ~= "table" then - error("Sent parameter of wrong type: Coefficients must be in an array") - end - o.coefficients = {} - o.degree = degree or Integer(-1) - - if type(symbol) ~= "string" and not symbol.symbol then - error("Symbol must be a string") - end - o.symbol = symbol.symbol or symbol - - -- Determines what ring the polynomial ring should have as its child - for index, coefficient in pairs(coefficients) do - if type(index) ~= "number" then - error("Sent parameter of wrong type: Coefficients must be in an array") - end - if not coefficient.getring then - error("Sent parameter of wrong type: Coefficients must be elements of a ring") - end - if not o.ring then - o.ring = coefficient:getring() - else - local newring = coefficient:getring() - local combinedring = Ring.resultantring(o.ring, newring) - if combinedring == newring then - o.ring = newring - elseif not o.ring == combinedring then - error("Sent parameter of wrong type: Coefficients must all be part of the same ring") - end - end - end - - if not coefficients[0] then - -- Constructs the coefficients when a new polynomial is instantiated as an array - for index, coefficient in ipairs(coefficients) do - o.coefficients[index - 1] = coefficient - o.degree = o.degree + Integer.one() - end - else - -- Constructs the coefficients from an existing polynomial of coefficients - local loc = o.degree:asnumber() - while loc > 0 do - if not coefficients[loc] or coefficients[loc] == coefficients[loc]:zero() then - o.degree = o.degree - Integer.one() - else - break - end - loc = loc - 1 - end - - while loc >= 0 do - o.coefficients[loc] = coefficients[loc] - loc = loc - 1 - end - end - - -- Each value of the polynomial greater than its degree is implicitly zero - o.coefficients = setmetatable(o.coefficients, {__index = function (table, key) - return o:zeroc() - end}) - return o -end - --- Returns the ring this object is an element of -function PolynomialRing:getring() - local t = {ring = PolynomialRing} - if self then - t.child = self.ring - t.symbol = self.symbol - end - t = setmetatable(t, __obj) - return t -end - --- Explicitly converts this element to an element of another ring -function PolynomialRing:inring(ring) - - -- Faster equality check - if ring == self:getring() then - return self - end - - if ring == Rational:getring() and ring.symbol then - return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) - end - - if ring.symbol == self.symbol then - local out = {} - for i = 0, self.degree:asnumber() do - out[i + 1] = self.coefficients[i]:inring(ring.child) - end - return PolynomialRing(out, self.symbol) - end - - -- TODO: Allow re-ordering of polynomial rings, so from R[x][y] -> R[y][x] for instance - if ring == PolynomialRing:getring() then - return PolynomialRing({self:inring(ring.child)}, ring.symbol) - end - - error("Unable to convert element to proper ring.") -end - - --- Returns whether the ring is commutative -function PolynomialRing:iscommutative() - return true -end - -function PolynomialRing:add(b) - local larger - - if self.degree > b.degree then - larger = self - else - larger = b - end - - local new = {} - local loc = 0 - while loc <= larger.degree:asnumber() do - new[loc] = self.coefficients[loc] + b.coefficients[loc] - loc = loc + 1 - end - - return PolynomialRing(new, self.symbol, larger.degree) -end - -function PolynomialRing:neg() - local new = {} - local loc = 0 - while loc <= self.degree:asnumber() do - new[loc] = -self.coefficients[loc] - loc = loc + 1 - end - return PolynomialRing(new, self.symbol, self.degree) -end - -function PolynomialRing:mul(b) - -- Grade-school multiplication is actually faster up to a very large polynomial size due to Lua's overhead. - local new = {} - - local sd = self.degree:asnumber() - local bd = b.degree:asnumber() - - for i = 0, sd+bd do - new[i] = self:zeroc() - for j = math.max(0, i-bd), math.min(sd, i) do - new[i] = new[i] + self.coefficients[j]*b.coefficients[i-j] - end - end - return PolynomialRing(new, self.symbol, self.degree + b.degree) - -- return PolynomialRing(PolynomialRing.mul_rec(self.coefficients, b.coefficients), self.symbol, self.degree + b.degree) -end - --- Performs Karatsuba multiplication without constructing new polynomials recursively -function PolynomialRing.mul_rec(a, b) - if #a==0 and #b==0 then - return {[0]=a[0] * b[0], [1]=Integer.zero()} - end - - local k = Integer.ceillog(Integer.max(Integer(#a), Integer(#b)) + Integer.one(), Integer(2)) - local n = Integer(2) ^ k - local m = n / Integer(2) - local nn = n:asnumber() - local mn = m:asnumber() - - local a0, a1, b0, b1 = {}, {}, {}, {} - - for e = 0, mn - 1 do - a0[e] = a[e] or Integer.zero() - a1[e] = a[e + mn] or Integer.zero() - b0[e] = b[e] or Integer.zero() - b1[e] = b[e + mn] or Integer.zero() - end - - local p1 = PolynomialRing.mul_rec(a1, b1) - local p2a = Copy(a0) - local p2b = Copy(b0) - for e = 0, mn - 1 do - p2a[e] = p2a[e] + a1[e] - p2b[e] = p2b[e] + b1[e] - end - local p2 = PolynomialRing.mul_rec(p2a, p2b) - local p3 = PolynomialRing.mul_rec(a0, b0) - local r = {} - for e = 0, mn - 1 do - p2[e] = p2[e] - p1[e] - p3[e] - r[e] = p3[e] - r[e + mn] = p2[e] - r[e + nn] = p1[e] - end - for e = mn, nn - 1 do - p2[e] = p2[e] - p1[e] - p3[e] - r[e] = r[e] + p3[e] - r[e + mn] = r[e + mn] + p2[e] - r[e + nn] = p1[e] - end - - return r -end - --- Uses synthetic division. -function PolynomialRing:divremainder(b) - local n, m = self.degree:asnumber(), b.degree:asnumber() - - if m > n then - return self:zero(), self - end - - local o = Copy(self.coefficients) - local lc = b:lc() - for i = n, m, -1 do - o[i] = o[i] / lc - - if o[i] ~= self:zeroc() then - for j = 1, m do - o[i-j] = o[i-j] - b.coefficients[m - j] * o[i] - end - end - end - - local q = {} - local r = {} - for i = 0, m-1 do - r[i] = o[i] - end - - r[0] = r[0] or self:zeroc() - - for i = m, #o do - q[i - m] = o[i] - end - - return PolynomialRing(q, self.symbol, self.degree), PolynomialRing(r, self.symbol, Integer.max(Integer.zero(), b.degree-Integer.one())) -end - --- Performs polynomial pseudodivision of this polynomial by another in the same ring, --- and returns both the pseudoquotient and pseudoremainder. --- In the case where both coefficients are fields, this is equivalent to division with remainder. -function PolynomialRing:pseudodivide(b) - - local p = self:zero() - local s = self - local m = s.degree - local n = b.degree - local delta = Integer.max(m - n + Integer.one(), Integer.zero()) - - local lcb = b:lc() - local sigma = Integer.zero() - - while m >= n and s ~= Integer.zero() do - local lcs = s:lc() - p = p * lcb + self:one():multiplyDegree((m-n):asnumber()) * lcs - s = s * lcb - b * self:one():multiplyDegree((m-n):asnumber()) * lcs - sigma = sigma + Integer.one() - m = s.degree - end - - if delta - sigma == Integer.zero() then - return p,s - else - return lcb^(delta - sigma) * p, lcb^(delta - sigma) * s - end -end - --- Polynomial rings are never fields, but when dividing by a polynomial by a constant we may want to use / instead of // -function PolynomialRing:div(b) - return self:divremainder(b) -end - -function PolynomialRing:zero() - return self.coefficients[0]:zero():inring(self:getring()) -end - -function PolynomialRing:zeroc() - return self.coefficients[0]:zero() -end - -function PolynomialRing:one() - return self.coefficients[0]:one():inring(self:getring()) -end - -function PolynomialRing:onec() - return self.coefficients[0]:one() -end - -function PolynomialRing:eq(b) - for i=0,math.max(self.degree:asnumber(), b.degree:asnumber()) do - if self.coefficients[i] ~= b.coefficients[i] then - return false - end - end - return true -end - --- Returns the leading coefficient of this polynomial -function PolynomialRing:lc() - return self.coefficients[self.degree:asnumber()] -end - ---- @return boolean -function PolynomialRing:isconstant() - return false -end - --- This expression is free of a symbol if and only if the symbol is not the symbol used to create the ring. -function PolynomialRing:freeof(symbol) - return symbol.symbol ~= self.symbol -end - --- Replaces each expression in the map with its value. -function PolynomialRing:substitute(map) - return self:tocompoundexpression():substitute(map) -end - --- Expands a polynomial expression. Polynomials are already in expanded form, so we just need to autosimplify. -function PolynomialRing:expand() - return self:tocompoundexpression():autosimplify() -end - -function PolynomialRing:autosimplify() - return self:tocompoundexpression():autosimplify() -end - --- Transforms from array format to an expression format. -function PolynomialRing:tocompoundexpression() - local terms = {} - for exponent, coefficient in pairs(self.coefficients) do - terms[exponent + 1] = BinaryOperation(BinaryOperation.MUL, {coefficient:tocompoundexpression(), - BinaryOperation(BinaryOperation.POW, {SymbolExpression(self.symbol), Integer(exponent)})}) - end - return BinaryOperation(BinaryOperation.ADD, terms) -end - --- Uses Horner's rule to evaluate a polynomial at a point -function PolynomialRing:evaluateat(x) - local out = self:zeroc() - for i = self.degree:asnumber(), 1, -1 do - out = out + self.coefficients[i] - out = out * x - end - return out + self.coefficients[0] -end - --- Multiplies this polynomial by x^n -function PolynomialRing:multiplyDegree(n) - local new = {} - for e = 0, n-1 do - new[e] = self:zeroc() - end - local loc = n - while loc <= self.degree:asnumber() + n do - new[loc] = self.coefficients[loc - n] - loc = loc + 1 - end - return PolynomialRing(new, self.symbol, self.degree + Integer(n)) -end - --- Returns the formal derivative of this polynomial -function PolynomialRing:derivative() - if self.degree == Integer.zero() then - return PolynomialRing({self:zeroc()}, self.symbol, Integer(-1)) - end - local new = {} - for e = 1, self.degree:asnumber() do - new[e - 1] = Integer(e) * self.coefficients[e] - end - return PolynomialRing(new, self.symbol, self.degree - Integer.one()) -end - --- Returns the square-free factorization of a polynomial -function PolynomialRing:squarefreefactorization() - local terms - if self.ring == Rational.getring() or self.ring == Integer.getring() then - terms = self:rationalsquarefreefactorization() - elseif self.ring == IntegerModN.getring() then - if not self.ring.modulus:isprime() then - error("Cannot compute a square-free factorization of a polynomial ring contructed from a ring that is not a field.") - end - terms = self:modularsquarefreefactorization() - end - - local expressions = {self:lc()} - local j = 1 - for index, term in ipairs(terms) do - if term.degree ~= Integer.zero() or term.coefficients[0] ~= Integer.one() then - j = j + 1 - expressions[j] = BinaryOperation.POWEXP({term, Integer(index)}) - end - end - - return BinaryOperation.MULEXP(expressions) -end - --- Factors a polynomial into irreducible terms -function PolynomialRing:factor() - -- Square-free factorization over an integral domain (so a polynomial ring constructed from a field) - local squarefree = self:squarefreefactorization() - local squarefreeterms = {} - local result = {squarefree.expressions[1]} - for i, expression in ipairs(squarefree.expressions) do - if i > 1 then - -- Converts square-free polynomials with rational coefficients to integer coefficients so Rational Roots / Zassenhaus can factor them - if expression.expressions[1].ring == Rational.getring() then - local factor, integerpoly = expression.expressions[1]:rationaltointeger() - result[1] = result[1] * factor ^ expression.expressions[2] - squarefreeterms[i - 1] = integerpoly - else - squarefreeterms[i - 1] = expression.expressions[1] - end - end - end - - for i, expression in ipairs(squarefreeterms) do - local terms - if expression.ring == Integer.getring() then - -- Factoring over the integers first uses the rational roots test to factor out monomials (for efficiency purposes) - local remaining, factors = expression:rationalroots() - terms = factors - -- Then applies the Zassenhaus algorithm if there entire polynomial has not been factored into monomials - if remaining ~= Integer.one() then - remaining = remaining:zassenhausfactor() - for _, exp in ipairs(remaining) do - terms[#terms+1] = exp - end - end - end - if expression.ring == IntegerModN.getring() then - -- Berlekamp factorization is used for rings with integers mod a prime as coefficients - terms = expression:berlekampfactor() - end - for _, factor in ipairs(terms) do - result[#result+1] = BinaryOperation.POWEXP({factor, squarefree.expressions[i + 1].expressions[2]}) - end - end - return BinaryOperation.MULEXP(result) -end - --- Uses the Rational Root test to factor out monomials of a square-free polynomial. -function PolynomialRing:rationalroots() - local remaining = self - local roots = {} - if self.coefficients[0] == Integer.zero() then - roots[1] = PolynomialRing({Integer.zero(), Integer.one()}, self.symbol) - remaining = remaining // roots[1] - end - -- This can be slower than Zassenhaus if the digits are large enough, since factoring integers is slow - -- if self.coefficients[0] > Integer(Integer.DIGITSIZE - 1) or self:lc() > Integer(Integer.DIGITSIZE - 1) then - -- return remaining, roots - -- end - while remaining ~= Integer.one() do - :: nextfactor :: - local a = remaining.coefficients[0] - local b = remaining:lc() - local afactors = a:divisors() - local bfactors = b:divisors() - for _, af in ipairs(afactors) do - for _, bf in ipairs(bfactors) do - local testroot = Rational(af, bf, true) - if remaining:evaluateat(testroot) == Integer.zero() then - roots[#roots+1] = PolynomialRing({-testroot.numerator, testroot.denominator}, self.symbol) - remaining = remaining // roots[#roots] - goto nextfactor - end - if remaining:evaluateat(-testroot) == Integer.zero() then - roots[#roots+1] = PolynomialRing({testroot.numerator, testroot.denominator}, self.symbol) - remaining = remaining // roots[#roots] - goto nextfactor - end - end - end - break - end - - return remaining, roots -end - --- Returns a list of roots of the polynomial, simplified up to cubics. -function PolynomialRing:roots() - local roots = {} - local factorization = self:factor() - - for i, factor in ipairs(factorization.expressions) do - if i > 1 then - local decomp = factor.expressions[1]:decompose() - for _, poly in ipairs(decomp) do - if poly.degree > Integer(3) then - table.insert(roots,RootExpression(factor.expressions[1])) - goto nextfactor - end - end - local factorroots = RootExpression(decomp[#decomp]):autosimplify() - if factorroots == true then - return true - end - if factorroots == false then - goto nextfactor - end - local replaceroots = {} - for j = #decomp - 1,1,-1 do - for _, root in ipairs(factorroots) do - local temp = RootExpression(decomp[j]):autosimplify(root) - if temp == true then - return true - end - if factorroots == false then - goto nextfactor - end - replaceroots = JoinArrays(replaceroots, temp) - end - factorroots = replaceroots - end - roots = JoinArrays(roots, factorroots) - end - end - ::nextfactor:: - return roots -end - ------------------ --- Inheritance -- ------------------ - -__PolynomialRing.__index = Ring -__PolynomialRing.__call = PolynomialRing.new -PolynomialRing = setmetatable(PolynomialRing, __PolynomialRing) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring/berlekampfactoring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/berlekampfactoring.lua deleted file mode 100644 index feabb61a3f..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/polynomialring/berlekampfactoring.lua +++ /dev/null @@ -1,187 +0,0 @@ --- Methods related to the Berlekamp factoring algorithm. - --- Square-free factorization in the modular field Zp. -function PolynomialRing:modularsquarefreefactorization() - local monic = self / self:lc() - local terms = {} - terms[0] = PolynomialRing.gcd(monic, monic:derivative()) - local b = monic // terms[0] - local c = monic:derivative() // terms[0] - local d = c - b:derivative() - local i = 1 - while b ~= Integer.one() do - terms[i] = PolynomialRing.gcd(b, d) - b, c = b // terms[i], d // terms[i] - i = i + 1 - d = c - b:derivative() - end - - if not (terms[i-1]:derivative().degree == Integer.zero() and terms[i-1]:derivative().coefficients[0] == Integer.zero()) then - return terms - end - - local recursiveterms = terms[i-1]:collapseterms(self.ring.modulus):modularsquarefreefactorization() - for k, poly in ipairs(recursiveterms) do - recursiveterms[k] = poly:expandterms(self.ring.modulus) - end - return JoinArrays(terms, recursiveterms) -end - --- Returns a new polnomial consisting of every nth term of the old one - helper method for square-free factorization -function PolynomialRing:collapseterms(n) - local new = {} - local loc = 0 - local i = 0 - local nn = n:asnumber() - while loc <= self.degree:asnumber() do - new[i] = self.coefficients[loc] - loc = loc + nn - i = i + 1 - end - - return PolynomialRing(new, self.symbol, self.degree // n) -end - --- Returns a new polnomial consisting of every nth term of the old one - helper method for square-free factorization -function PolynomialRing:expandterms(n) - local new = {} - local loc = 0 - local i = 0 - local nn = n:asnumber() - while i <= self.degree:asnumber() do - new[loc] = self.coefficients[i] - for j = 1, nn do - new[loc + j] = IntegerModN(Integer.zero(), n) - end - loc = loc + nn - i = i + 1 - end - - return PolynomialRing(new, self.symbol, self.degree * n) -end - --- Uses Berlekamp's Algorithm to factor polynomials in mod p -function PolynomialRing:berlekampfactor() - if self.degree == 0 or self.degree == 1 then - return {self} - end - - local R = self:RMatrix() - local S = self:auxillarybasis(R) - if #S == 1 then - return {self} - end - return self:findfactors(S) -end - --- Gets the R Matrix for Berlekamp factorization -function PolynomialRing:RMatrix() - local R = {} - for i = 1, self.degree:asnumber() do - R[i] = {} - end - for i = 0, self.degree:asnumber()-1 do - local remainder = PolynomialRing({IntegerModN(Integer.one(), self.ring.modulus)}, self.symbol):multiplyDegree(self.ring.modulus:asnumber()*i) % self - for j = 0, self.degree:asnumber()-1 do - R[j + 1][i + 1] = remainder.coefficients[j] - if j == i then - R[j + 1][i + 1] = R[j + 1][i + 1] - IntegerModN(Integer.one(), self.ring.modulus) - end - end - end - return R -end - --- Creates an auxillary basis using the R matrix -function PolynomialRing:auxillarybasis(R) - local P = {} - local n = self.degree:asnumber() - for i = 1, n do - P[i] = 0 - end - S = {} - local q = 1 - for j = 1, n do - local i = 1 - local pivotfound = false - while not pivotfound and i <= n do - if R[i][j] ~= self:zeroc() and P[i] == 0 then - pivotfound = true - else - i = i + 1 - end - end - if pivotfound then - P[i] = j - local a = R[i][j]:inv() - for l = 1, n do - R[i][l] = a * R[i][l] - end - for k = 1, n do - if k ~= i then - local f = R[k][j] - for l = 1, n do - R[k][l] = R[k][l] - f*R[i][l] - end - end - end - else - local s = {} - s[j] = self:onec() - for l = 1, j - 1 do - local e = 0 - i = 1 - while e == 0 and i <= n do - if l == P[i] then - e = i - else - i = i + 1 - end - end - if e > 0 then - local c = -R[e][j] - s[l] = c - else - s[l] = self:zeroc() - end - end - S[#S+1] = PolynomialRing(s, self.symbol) - end - end - return S -end - --- Uses the auxilary basis to find the irreirrducible factors of the polynomial. -function PolynomialRing:findfactors(S) - local r = #S - local p = self.ring.modulus - local factors = {self} - for k = 2,r do - local b = S[k] - local old_factors = Copy(factors) - for i = 1,#old_factors do - local w = old_factors[i] - local j = 0 - while j <= p:asnumber() - 1 do - local g = PolynomialRing.gcd(b-IntegerModN(Integer(j), p), w) - if g == Integer.one() then - j = j + 1 - elseif g == w then - j = p:asnumber() - else - factors = Remove(factors, w) - local q = w // g - factors[#factors+1] = g - factors[#factors+1] = q - if #factors == r then - return factors - else - j = j + 1 - w = q - end - end - - end - end - end -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring/decomposition.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/decomposition.lua deleted file mode 100644 index 84f2af1efc..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/polynomialring/decomposition.lua +++ /dev/null @@ -1,93 +0,0 @@ --- Methods related to polynomial decomposition. - --- Returns a list of polynomials that form a complete decomposition of a polynomial. -function PolynomialRing:decompose() - local U = self - self.coefficients[0] - local S = U:divisors() - local decomposition = {} - local C = PolynomialRing({Integer.zero(), Integer.one()}, self.symbol) - local finalcomponent - - while S[1] do - local w = S[1] - for _, poly in ipairs(S) do - if poly.degree < w.degree then - w = poly - end - end - S = Remove(S, w) - if C.degree < w.degree and w.degree < self.degree and self.degree % w.degree == Integer.zero() then - local g = w:polyexpand(C, self.symbol) - local R = self:polyexpand(w, self.symbol) - if g.degree == Integer.zero() and R.degree == Integer.zero() then - g.symbol = self.symbol - decomposition[#decomposition+1] = g.coefficients[0] - decomposition[#decomposition].symbol = self.symbol - C = w - finalcomponent = R.coefficients[0] - end - end - end - - if not decomposition[1] then - return {self} - end - - finalcomponent.symbol = self.symbol - decomposition[#decomposition+1] = finalcomponent - return decomposition -end - --- Returns a list of all monic divisors of positive degree of the polynomial, assuming the polynomial ring is a Euclidean Domain. -function PolynomialRing:divisors() - local factors = self:factor() - -- Converts each factor to a monic factor (we don't need to worry updating the constant term) - for i, factor in ipairs(factors.expressions) do - if i > 1 then - factor.expressions[1] = factor.expressions[1] / factor.expressions[1]:lc() - end - end - - local terms = {} - for i, _ in ipairs(factors.expressions) do - if i > 1 then - terms[i] = Integer.zero() - end - end - - local divisors = {} - local divisor = PolynomialRing({self:onec()}, self.symbol) - while true do - for i, factor in ipairs(factors.expressions) do - if i > 1 then - local base = factor.expressions[1] - local power = factor.expressions[2] - if terms[i] < power then - terms[i] = terms[i] + Integer.one() - divisor = divisor * base - break - else - terms[i] = Integer.zero() - divisor = divisor // (base ^ power) - end - end - end - if divisor == Integer.one() then - break - end - divisors[#divisors+1] = divisor - end - - return divisors - -end - --- Polynomial expansion as a subroutine of decomposition. -function PolynomialRing:polyexpand(v, x) - local u = self - if u == Integer.zero() then - return Integer.zero() - end - local q,r = u:divremainder(v) - return PolynomialRing({PolynomialRing({Integer.zero(), Integer.one()}, "_")}, x) * q:polyexpand(v, x) + r -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-berlekampfactoring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-berlekampfactoring.lua new file mode 100644 index 0000000000..feabb61a3f --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-berlekampfactoring.lua @@ -0,0 +1,187 @@ +-- Methods related to the Berlekamp factoring algorithm. + +-- Square-free factorization in the modular field Zp. +function PolynomialRing:modularsquarefreefactorization() + local monic = self / self:lc() + local terms = {} + terms[0] = PolynomialRing.gcd(monic, monic:derivative()) + local b = monic // terms[0] + local c = monic:derivative() // terms[0] + local d = c - b:derivative() + local i = 1 + while b ~= Integer.one() do + terms[i] = PolynomialRing.gcd(b, d) + b, c = b // terms[i], d // terms[i] + i = i + 1 + d = c - b:derivative() + end + + if not (terms[i-1]:derivative().degree == Integer.zero() and terms[i-1]:derivative().coefficients[0] == Integer.zero()) then + return terms + end + + local recursiveterms = terms[i-1]:collapseterms(self.ring.modulus):modularsquarefreefactorization() + for k, poly in ipairs(recursiveterms) do + recursiveterms[k] = poly:expandterms(self.ring.modulus) + end + return JoinArrays(terms, recursiveterms) +end + +-- Returns a new polnomial consisting of every nth term of the old one - helper method for square-free factorization +function PolynomialRing:collapseterms(n) + local new = {} + local loc = 0 + local i = 0 + local nn = n:asnumber() + while loc <= self.degree:asnumber() do + new[i] = self.coefficients[loc] + loc = loc + nn + i = i + 1 + end + + return PolynomialRing(new, self.symbol, self.degree // n) +end + +-- Returns a new polnomial consisting of every nth term of the old one - helper method for square-free factorization +function PolynomialRing:expandterms(n) + local new = {} + local loc = 0 + local i = 0 + local nn = n:asnumber() + while i <= self.degree:asnumber() do + new[loc] = self.coefficients[i] + for j = 1, nn do + new[loc + j] = IntegerModN(Integer.zero(), n) + end + loc = loc + nn + i = i + 1 + end + + return PolynomialRing(new, self.symbol, self.degree * n) +end + +-- Uses Berlekamp's Algorithm to factor polynomials in mod p +function PolynomialRing:berlekampfactor() + if self.degree == 0 or self.degree == 1 then + return {self} + end + + local R = self:RMatrix() + local S = self:auxillarybasis(R) + if #S == 1 then + return {self} + end + return self:findfactors(S) +end + +-- Gets the R Matrix for Berlekamp factorization +function PolynomialRing:RMatrix() + local R = {} + for i = 1, self.degree:asnumber() do + R[i] = {} + end + for i = 0, self.degree:asnumber()-1 do + local remainder = PolynomialRing({IntegerModN(Integer.one(), self.ring.modulus)}, self.symbol):multiplyDegree(self.ring.modulus:asnumber()*i) % self + for j = 0, self.degree:asnumber()-1 do + R[j + 1][i + 1] = remainder.coefficients[j] + if j == i then + R[j + 1][i + 1] = R[j + 1][i + 1] - IntegerModN(Integer.one(), self.ring.modulus) + end + end + end + return R +end + +-- Creates an auxillary basis using the R matrix +function PolynomialRing:auxillarybasis(R) + local P = {} + local n = self.degree:asnumber() + for i = 1, n do + P[i] = 0 + end + S = {} + local q = 1 + for j = 1, n do + local i = 1 + local pivotfound = false + while not pivotfound and i <= n do + if R[i][j] ~= self:zeroc() and P[i] == 0 then + pivotfound = true + else + i = i + 1 + end + end + if pivotfound then + P[i] = j + local a = R[i][j]:inv() + for l = 1, n do + R[i][l] = a * R[i][l] + end + for k = 1, n do + if k ~= i then + local f = R[k][j] + for l = 1, n do + R[k][l] = R[k][l] - f*R[i][l] + end + end + end + else + local s = {} + s[j] = self:onec() + for l = 1, j - 1 do + local e = 0 + i = 1 + while e == 0 and i <= n do + if l == P[i] then + e = i + else + i = i + 1 + end + end + if e > 0 then + local c = -R[e][j] + s[l] = c + else + s[l] = self:zeroc() + end + end + S[#S+1] = PolynomialRing(s, self.symbol) + end + end + return S +end + +-- Uses the auxilary basis to find the irreirrducible factors of the polynomial. +function PolynomialRing:findfactors(S) + local r = #S + local p = self.ring.modulus + local factors = {self} + for k = 2,r do + local b = S[k] + local old_factors = Copy(factors) + for i = 1,#old_factors do + local w = old_factors[i] + local j = 0 + while j <= p:asnumber() - 1 do + local g = PolynomialRing.gcd(b-IntegerModN(Integer(j), p), w) + if g == Integer.one() then + j = j + 1 + elseif g == w then + j = p:asnumber() + else + factors = Remove(factors, w) + local q = w // g + factors[#factors+1] = g + factors[#factors+1] = q + if #factors == r then + return factors + else + j = j + 1 + w = q + end + end + + end + end + end +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-decomposition.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-decomposition.lua new file mode 100644 index 0000000000..84f2af1efc --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-decomposition.lua @@ -0,0 +1,93 @@ +-- Methods related to polynomial decomposition. + +-- Returns a list of polynomials that form a complete decomposition of a polynomial. +function PolynomialRing:decompose() + local U = self - self.coefficients[0] + local S = U:divisors() + local decomposition = {} + local C = PolynomialRing({Integer.zero(), Integer.one()}, self.symbol) + local finalcomponent + + while S[1] do + local w = S[1] + for _, poly in ipairs(S) do + if poly.degree < w.degree then + w = poly + end + end + S = Remove(S, w) + if C.degree < w.degree and w.degree < self.degree and self.degree % w.degree == Integer.zero() then + local g = w:polyexpand(C, self.symbol) + local R = self:polyexpand(w, self.symbol) + if g.degree == Integer.zero() and R.degree == Integer.zero() then + g.symbol = self.symbol + decomposition[#decomposition+1] = g.coefficients[0] + decomposition[#decomposition].symbol = self.symbol + C = w + finalcomponent = R.coefficients[0] + end + end + end + + if not decomposition[1] then + return {self} + end + + finalcomponent.symbol = self.symbol + decomposition[#decomposition+1] = finalcomponent + return decomposition +end + +-- Returns a list of all monic divisors of positive degree of the polynomial, assuming the polynomial ring is a Euclidean Domain. +function PolynomialRing:divisors() + local factors = self:factor() + -- Converts each factor to a monic factor (we don't need to worry updating the constant term) + for i, factor in ipairs(factors.expressions) do + if i > 1 then + factor.expressions[1] = factor.expressions[1] / factor.expressions[1]:lc() + end + end + + local terms = {} + for i, _ in ipairs(factors.expressions) do + if i > 1 then + terms[i] = Integer.zero() + end + end + + local divisors = {} + local divisor = PolynomialRing({self:onec()}, self.symbol) + while true do + for i, factor in ipairs(factors.expressions) do + if i > 1 then + local base = factor.expressions[1] + local power = factor.expressions[2] + if terms[i] < power then + terms[i] = terms[i] + Integer.one() + divisor = divisor * base + break + else + terms[i] = Integer.zero() + divisor = divisor // (base ^ power) + end + end + end + if divisor == Integer.one() then + break + end + divisors[#divisors+1] = divisor + end + + return divisors + +end + +-- Polynomial expansion as a subroutine of decomposition. +function PolynomialRing:polyexpand(v, x) + local u = self + if u == Integer.zero() then + return Integer.zero() + end + local q,r = u:divremainder(v) + return PolynomialRing({PolynomialRing({Integer.zero(), Integer.one()}, "_")}, x) * q:polyexpand(v, x) + r +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-zassenhausfactoring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-zassenhausfactoring.lua new file mode 100644 index 0000000000..be19298e64 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/polynomialring/luacas-zassenhausfactoring.lua @@ -0,0 +1,220 @@ +-- Methods related to the Zassenhaus factorization algorithm. + + +-- Square-free factorization in the rational field. +function PolynomialRing:rationalsquarefreefactorization(keeplc) + local monic = self / self:lc() + local terms = {} + terms[0] = PolynomialRing.gcd(monic, monic:derivative()) + local b = monic // terms[0] + local c = monic:derivative() // terms[0] + local d = c - b:derivative() + local i = 1 + while b.degree ~= Integer.zero() or b.coefficients[0] ~= Integer.one() do + terms[i] = PolynomialRing.gcd(b, d) + b, c = b // terms[i], d // terms[i] + i = i + 1 + d = c - b:derivative() + end + if keeplc and terms[1] then + terms[1] = terms[1] * self:lc() + end + return terms +end + +-- Factors the largest possible constant out of a polynomial whos underlying ring is a Euclidean domain but not a field +function PolynomialRing:factorconstant() + local gcd = Integer.zero() + for i = 0, self.degree:asnumber() do + gcd = self.ring.gcd(gcd, self.coefficients[i]) + end + if gcd == Integer.zero() then + return Integer.one(), self + end + return gcd, self / gcd +end + +-- Converts a polynomial in the rational polynomial ring to the integer polynomial ring +function PolynomialRing:rationaltointeger() + local lcm = Integer.one() + for i = 0, self.degree:asnumber() do + if self.coefficients[i]:getring() == Rational:getring() then + lcm = lcm * self.coefficients[i].denominator / Integer.gcd(lcm, self.coefficients[i].denominator) + end + end + return Integer.one() / lcm, self * lcm +end + +-- Uses Zassenhaus's Algorithm to factor sqaure-free polynomials over the intergers +function PolynomialRing:zassenhausfactor() + + -- Creates a monic polynomial V with related roots + local V = {} + local n = self.degree:asnumber() + local l = self:lc() + for i = 0, n - 1 do + V[i] = l ^ Integer(n - 1 - i) * self.coefficients[i] + end + V[n] = Integer.one() + V = PolynomialRing(V, "y", self.degree) + + -- Performs Berlekamp Factorization in a sutable prime base + local p = V:findprime() + local S = V:inring(PolynomialRing.R("y", p)):berlekampfactor() + + -- If a polynomial is irreducible with coefficients in mod p, it is also irreducible over the integers + if #S == 1 then + return {self} + end + + -- Performs Hensel lifting on the factors mod p + local k = V:findmaxlifts(p) + local W = V:henselift(S, k) + local M = {} + + -- Returns the solutions back to the original from the monic transformation + for i, factor in ipairs(W) do + local w = {} + for j = 0, factor.degree:asnumber() do + w[j] = factor.coefficients[j]:inring(Integer.getring()) * l ^ Integer(j) + end + _, M[i] = PolynomialRing(w, self.symbol, factor.degree):factorconstant() + end + + return M + +end + +-- Finds the smallest prime such that this polynomial with coefficients in mod p is square-free +function PolynomialRing:findprime() + + local smallprimes = {Integer(2), Integer(3), Integer(5), Integer(7), Integer(11), Integer(13), Integer(17), Integer(19), Integer(23), + Integer(29), Integer(31), Integer(37), Integer(41), Integer(43), Integer(47), Integer(53), Integer(59)} + + for _, p in pairs(smallprimes) do + local P = PolynomialRing({IntegerModN(Integer.one(), p)}, self.symbol) + local s = self:inring(P:getring()) + if PolynomialRing.gcd(s, s:derivative()) == P then + return p + end + end + + error("Execution error: No suitable prime found for factoring.") +end + +-- Finds the maximum number of times Hensel Lifting will be applied to raise solutions to the appropriate power +function PolynomialRing:findmaxlifts(p) + local n = self.degree:asnumber() + local h = self.coefficients[0] + for i=0 , n do + if self.coefficients[i] > h then + h = self.coefficients[i] + end + end + + local B = 2^n * math.sqrt(n) * h:asnumber() + return Integer(math.ceil(math.log(2*B, p:asnumber()))) +end + +-- Uses Hensel lifting on the factors of a polynomial S mod p to find them in the integers +function PolynomialRing:henselift(S, k) + local p = S[1].ring.modulus + if k == Integer.one() then + return self:truefactors(S, p, k) + end + G = self:genextendsigma(S) + local V = S + for j = 2, k:asnumber() do + local Vp = V[1]:inring(PolynomialRing.R("y")) + for i = 2, #V do + Vp = Vp * V[i]:inring(PolynomialRing.R("y")) + end + local E = self - Vp:inring(PolynomialRing.R("y")) + if E == Integer.zero() then + return V + end + E = E:inring(PolynomialRing.R("y", p ^ Integer(j))):inring(PolynomialRing.R("y")) + F = E / p ^ (Integer(j) - Integer.one()) + R = self:genextendR(V, G, F) + local Vnew = {} + for i, v in ipairs(V) do + local vnew = v:inring(PolynomialRing.R("y", p ^ Integer(j))) + local rnew = R[i]:inring(PolynomialRing.R("y", p ^ Integer(j))) + Vnew[i] = vnew + (p) ^ (Integer(j) - Integer.one()) * rnew + end + V = Vnew + end + return self:truefactors(V, p, k) +end + +-- Gets a list of sigma polynomials for use in hensel lifting +function PolynomialRing:genextendsigma(S) + local v = S[1] * S[2] + local _, A, B = PolynomialRing.extendedgcd(S[2], S[1]) + local SIGMA = {A, B} + for i, _ in ipairs(S) do + if i >= 3 then + v = v * S[i] + local sum = SIGMA[1] * (v // S[1]) + for j = 2, i-1 do + sum = sum + SIGMA[j] * (v // S[j]) + end + _, A, B = PolynomialRing.extendedgcd(sum, v // S[i]) + for j = 1, i-1 do + SIGMA[j] = SIGMA[j] * A + end + SIGMA[i] = B + end + end + + return SIGMA +end + +-- Gets a list of r polynomials for use in hensel lifting +function PolynomialRing:genextendR(V, G, F) + R = {} + for i, v in ipairs(V) do + local pring = G[1]:getring() + R[i] = F:inring(pring) * G[i] % v:inring(pring) + end + return R +end + +-- Updates factors of the polynomial to the correct ones in the integer ring +function PolynomialRing:truefactors(l, p, k) + local U = self + local L = l + local factors = {} + local m = 1 + while m <= #L / 2 do + local C = Subarrays(L, m) + while #C > 0 do + local t = C[1] + local prod = t[1] + for i = 2, #t do + prod = prod * t[i] + end + local T = prod:inring(PolynomialRing.R("y", p ^ k)):inring(PolynomialRing.R("y")) + -- Convert to symmetric representation - this is the only place it actually matters + for i = 0, T.degree:asnumber() do + if T.coefficients[i] > p ^ k / Integer(2) then + T.coefficients[i] = T.coefficients[i] - p^k + end + end + local Q, R = U:divremainder(T) + if R == Integer.zero() then + factors[#factors+1] = T + U = Q + L = RemoveAll(L, t) + C = RemoveAny(C, t) + else + C = Remove(C, t) + end + end + m = m + 1 + end + if U ~= Integer.one() then + factors[#factors+1] = U + end + return factors +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/polynomialring/zassenhausfactoring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/zassenhausfactoring.lua deleted file mode 100644 index be19298e64..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/polynomialring/zassenhausfactoring.lua +++ /dev/null @@ -1,220 +0,0 @@ --- Methods related to the Zassenhaus factorization algorithm. - - --- Square-free factorization in the rational field. -function PolynomialRing:rationalsquarefreefactorization(keeplc) - local monic = self / self:lc() - local terms = {} - terms[0] = PolynomialRing.gcd(monic, monic:derivative()) - local b = monic // terms[0] - local c = monic:derivative() // terms[0] - local d = c - b:derivative() - local i = 1 - while b.degree ~= Integer.zero() or b.coefficients[0] ~= Integer.one() do - terms[i] = PolynomialRing.gcd(b, d) - b, c = b // terms[i], d // terms[i] - i = i + 1 - d = c - b:derivative() - end - if keeplc and terms[1] then - terms[1] = terms[1] * self:lc() - end - return terms -end - --- Factors the largest possible constant out of a polynomial whos underlying ring is a Euclidean domain but not a field -function PolynomialRing:factorconstant() - local gcd = Integer.zero() - for i = 0, self.degree:asnumber() do - gcd = self.ring.gcd(gcd, self.coefficients[i]) - end - if gcd == Integer.zero() then - return Integer.one(), self - end - return gcd, self / gcd -end - --- Converts a polynomial in the rational polynomial ring to the integer polynomial ring -function PolynomialRing:rationaltointeger() - local lcm = Integer.one() - for i = 0, self.degree:asnumber() do - if self.coefficients[i]:getring() == Rational:getring() then - lcm = lcm * self.coefficients[i].denominator / Integer.gcd(lcm, self.coefficients[i].denominator) - end - end - return Integer.one() / lcm, self * lcm -end - --- Uses Zassenhaus's Algorithm to factor sqaure-free polynomials over the intergers -function PolynomialRing:zassenhausfactor() - - -- Creates a monic polynomial V with related roots - local V = {} - local n = self.degree:asnumber() - local l = self:lc() - for i = 0, n - 1 do - V[i] = l ^ Integer(n - 1 - i) * self.coefficients[i] - end - V[n] = Integer.one() - V = PolynomialRing(V, "y", self.degree) - - -- Performs Berlekamp Factorization in a sutable prime base - local p = V:findprime() - local S = V:inring(PolynomialRing.R("y", p)):berlekampfactor() - - -- If a polynomial is irreducible with coefficients in mod p, it is also irreducible over the integers - if #S == 1 then - return {self} - end - - -- Performs Hensel lifting on the factors mod p - local k = V:findmaxlifts(p) - local W = V:henselift(S, k) - local M = {} - - -- Returns the solutions back to the original from the monic transformation - for i, factor in ipairs(W) do - local w = {} - for j = 0, factor.degree:asnumber() do - w[j] = factor.coefficients[j]:inring(Integer.getring()) * l ^ Integer(j) - end - _, M[i] = PolynomialRing(w, self.symbol, factor.degree):factorconstant() - end - - return M - -end - --- Finds the smallest prime such that this polynomial with coefficients in mod p is square-free -function PolynomialRing:findprime() - - local smallprimes = {Integer(2), Integer(3), Integer(5), Integer(7), Integer(11), Integer(13), Integer(17), Integer(19), Integer(23), - Integer(29), Integer(31), Integer(37), Integer(41), Integer(43), Integer(47), Integer(53), Integer(59)} - - for _, p in pairs(smallprimes) do - local P = PolynomialRing({IntegerModN(Integer.one(), p)}, self.symbol) - local s = self:inring(P:getring()) - if PolynomialRing.gcd(s, s:derivative()) == P then - return p - end - end - - error("Execution error: No suitable prime found for factoring.") -end - --- Finds the maximum number of times Hensel Lifting will be applied to raise solutions to the appropriate power -function PolynomialRing:findmaxlifts(p) - local n = self.degree:asnumber() - local h = self.coefficients[0] - for i=0 , n do - if self.coefficients[i] > h then - h = self.coefficients[i] - end - end - - local B = 2^n * math.sqrt(n) * h:asnumber() - return Integer(math.ceil(math.log(2*B, p:asnumber()))) -end - --- Uses Hensel lifting on the factors of a polynomial S mod p to find them in the integers -function PolynomialRing:henselift(S, k) - local p = S[1].ring.modulus - if k == Integer.one() then - return self:truefactors(S, p, k) - end - G = self:genextendsigma(S) - local V = S - for j = 2, k:asnumber() do - local Vp = V[1]:inring(PolynomialRing.R("y")) - for i = 2, #V do - Vp = Vp * V[i]:inring(PolynomialRing.R("y")) - end - local E = self - Vp:inring(PolynomialRing.R("y")) - if E == Integer.zero() then - return V - end - E = E:inring(PolynomialRing.R("y", p ^ Integer(j))):inring(PolynomialRing.R("y")) - F = E / p ^ (Integer(j) - Integer.one()) - R = self:genextendR(V, G, F) - local Vnew = {} - for i, v in ipairs(V) do - local vnew = v:inring(PolynomialRing.R("y", p ^ Integer(j))) - local rnew = R[i]:inring(PolynomialRing.R("y", p ^ Integer(j))) - Vnew[i] = vnew + (p) ^ (Integer(j) - Integer.one()) * rnew - end - V = Vnew - end - return self:truefactors(V, p, k) -end - --- Gets a list of sigma polynomials for use in hensel lifting -function PolynomialRing:genextendsigma(S) - local v = S[1] * S[2] - local _, A, B = PolynomialRing.extendedgcd(S[2], S[1]) - local SIGMA = {A, B} - for i, _ in ipairs(S) do - if i >= 3 then - v = v * S[i] - local sum = SIGMA[1] * (v // S[1]) - for j = 2, i-1 do - sum = sum + SIGMA[j] * (v // S[j]) - end - _, A, B = PolynomialRing.extendedgcd(sum, v // S[i]) - for j = 1, i-1 do - SIGMA[j] = SIGMA[j] * A - end - SIGMA[i] = B - end - end - - return SIGMA -end - --- Gets a list of r polynomials for use in hensel lifting -function PolynomialRing:genextendR(V, G, F) - R = {} - for i, v in ipairs(V) do - local pring = G[1]:getring() - R[i] = F:inring(pring) * G[i] % v:inring(pring) - end - return R -end - --- Updates factors of the polynomial to the correct ones in the integer ring -function PolynomialRing:truefactors(l, p, k) - local U = self - local L = l - local factors = {} - local m = 1 - while m <= #L / 2 do - local C = Subarrays(L, m) - while #C > 0 do - local t = C[1] - local prod = t[1] - for i = 2, #t do - prod = prod * t[i] - end - local T = prod:inring(PolynomialRing.R("y", p ^ k)):inring(PolynomialRing.R("y")) - -- Convert to symmetric representation - this is the only place it actually matters - for i = 0, T.degree:asnumber() do - if T.coefficients[i] > p ^ k / Integer(2) then - T.coefficients[i] = T.coefficients[i] - p^k - end - end - local Q, R = U:divremainder(T) - if R == Integer.zero() then - factors[#factors+1] = T - U = Q - L = RemoveAll(L, t) - C = RemoveAny(C, t) - else - C = Remove(C, t) - end - end - m = m + 1 - end - if U ~= Integer.one() then - factors[#factors+1] = U - end - return factors -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/rational.lua b/macros/luatex/latex/luacas/tex/algebra/rational.lua deleted file mode 100644 index 811909a948..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/rational.lua +++ /dev/null @@ -1,241 +0,0 @@ ---- @class Rational ---- Represents an element of the field of rational numbers or rational functions. ---- @field numerator Ring ---- @field denominator Ring ---- @field ring RingIdentifier -Rational = {} -__Rational = {} - - --------------------------- --- Static functionality -- --------------------------- - --- Metatable for ring objects. -local __obj = {__index = Rational, __eq = function(a, b) - return a["ring"] == b["ring"] and - (a["child"] == b["child"] or a["child"] == nil or b["child"] == nil) and - (a["symbol"] == b["symbol"] or a["child"] == nil or b["child"] == nil) -end, __tostring = function(a) - if a.symbol then - return tostring(a.child.child) .. "(" .. a.symbol .. ")" - end - if a.child then - return "QQ" - end - return "(Generic Fraction Field)" - end} - ---- @param symbol SymbolExpression ---- @param child RingIdentifier ---- @return RingIdentifier -function Rational.makering(symbol, child) - local t = {ring = Rational} - t.symbol = symbol - t.child = child - t = setmetatable(t, __obj) - return t -end - ---- Converts a string of the form -?[0-9]+ or -?[0-9]+\/[0-9]+ to a rational number. ---- @param str string ---- @return Rational|Integer -function Rational.fromstring(str) - local divloc = string.find(str, "/"); - if not divloc then - return Integer(str) - end - return Rational(Integer(string.sub(str, 1, divloc - 1)), Integer(string.sub(str, divloc + 1, #str))) -end - - ----------------------------- --- Instance functionality -- ----------------------------- - --- So we don't have to copy the field operations each time. -local __o = Copy(__FieldOperations) -__o.__index = Rational -__o.__tostring = function(a) - if a.ring.symbol then - return "(" .. tostring(a.numerator)..")/("..tostring(a.denominator) .. ")" - end - return tostring(a.numerator).."/"..tostring(a.denominator) -end - ---- Creates a new rational given a numerator and denominator that are part of the same ring. ---- Rational numbers are represented uniquely. ---- @param n Ring ---- @param d Ring ---- @param keep boolean -function Rational:new(n, d, keep) - local o = {} - o = setmetatable(o, __o) - - if n:getring() == PolynomialRing.getring() then - o.symbol = n.symbol - end - - if d:getring() == PolynomialRing.getring() then - o.symbol = d.symbol - end - - if d == Integer(0) then - error("Arithmetic error: division by zero") - end - - n = n or Integer.zero() - d = d or Integer.one() - o.numerator = n - o.denominator = d - o:reduce() - - if (not keep) and o.denominator == Integer.one() or (not keep) and o.numerator == Integer.zero() then - return o.numerator - end - - return o -end - ---- Reduces a rational expression to standard form. This method mutates its object. -function Rational:reduce() - if self.numerator:getring() == Integer.getring() then - if self.denominator < Integer.zero() then - self.denominator = -self.denominator - self.numerator = -self.numerator - end - local gcd = Integer.gcd(self.numerator, self.denominator) - self.numerator = self.numerator//gcd - self.denominator = self.denominator//gcd - self.ring = Integer.getring() - elseif self.numerator:getring() == PolynomialRing.getring() then - local lc = self.denominator:lc() - self.denominator = self.denominator/lc - self.numerator = self.numerator/lc - local gcd = PolynomialRing.gcd(self.numerator, self.denominator) - self.numerator = self.numerator//gcd - self.denominator = self.denominator//gcd - self.ring = Ring.resultantring(self.numerator:getring(), self.denominator:getring()) - end -end - - ---- @return RingIdentifier -function Rational:getring() - local t = {ring=Rational} - if self then - t.child = self.ring - t.symbol = self.symbol - end - t = setmetatable(t, __obj) - return t -end - ---- @param ring RingIdentifier ---- @return Ring -function Rational:inring(ring) - if ring == self:getring() then - return self - end - - if ring == Rational:getring() and ring.symbol then - if not self:getring().symbol then - return Rational(self:inring(ring.child), self:inring(ring.child):one(), true) - end - return Rational(self.numerator:inring(ring.child), self.denominator:inring(ring.child), true) - end - - if ring == PolynomialRing:getring() then - return PolynomialRing({self:inring(ring.child)}, ring.symbol) - end - - error("Unable to convert element to proper ring.") -end - ---- @return boolean -function Rational:isconstant() - if self.symbol then - return false - end - return true -end - ---- @return Expression -function Rational:tocompoundexpression() - return BinaryOperation(BinaryOperation.DIV, {self.numerator:tocompoundexpression(), self.denominator:tocompoundexpression()}) -end - ---- Returns this rational as a floating point number. Can only approximate the value of most rationals. ---- @return number -function Rational:asnumber() - return self.numerator:asnumber() / self.denominator:asnumber() -end - -function Rational:add(b) - return Rational(self.numerator * b.denominator + self.denominator * b.numerator, self.denominator * b.denominator) -end - -function Rational:neg() - return Rational(-self.numerator, self.denominator, true) -end - -function Rational:mul(b) - return Rational(self.numerator * b.numerator, self.denominator * b.denominator) -end - --- function Rational:inv(b) --- return Rational(self.numerator * b.numerator, self.denominator * b.denominator) --- end - -function Rational:pow(b) - return (self.numerator ^ b) / (self.denominator ^ b) -end - -function Rational:div(b) - return Rational(self.numerator * b.denominator, self.denominator * b.numerator) -end - -function Rational:eq(b) - return self.numerator == b.numerator and self.denominator == b.denominator -end - -function Rational:lt(b) - if self.numerator < Integer.zero() and b.numerator > Integer.zero() then - return true - end - if self.numerator > Integer.zero() and b.numerator < Integer.zero() then - return false - end - - if (self.numerator >= Integer.zero() and b.numerator >= Integer.zero()) or (self.numerator <= Integer.zero() and b.numerator <= Integer.zero()) then - return self.numerator * b.denominator < self.denominator * b.numerator - end - return self.numerator * b.denominator > self.denominator * b.numerator -end - -function Rational:le(b) - return self:eq(b) or self:lt(b) -end - -function Rational:zero() - return Integer.zero() -end - -function Rational:one() - return Integer.one() -end - -function Rational:tolatex() - if string.sub(self.numerator:tolatex(),1,1) == '-' then - return "- \\frac{" .. string.sub(self.numerator:tolatex(),2,-1) .. "}{" .. self.denominator:tolatex() .. "}" - end - return "\\frac{" .. self.numerator:tolatex() .."}{".. self.denominator:tolatex().. "}" -end - ------------------ --- Inheritance -- ------------------ - -__Rational.__index = Field -__Rational.__call = Rational.new -Rational = setmetatable(Rational, __Rational) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/ring.lua b/macros/luatex/latex/luacas/tex/algebra/ring.lua deleted file mode 100644 index 4903b9c912..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/ring.lua +++ /dev/null @@ -1,326 +0,0 @@ ---- @class Ring ---- Interface for an element of a ring with unity. -Ring = {} -__Ring = {} - --------------------------- --- Static functionality -- --------------------------- - ---- Determines which ring the output of a binary operation with inputs in ring1 and ring2 should be, if such a ring exists. ---- If one of the rings is a subring of another ring, the result should be one of the two rings. ---- @param ring1 RingIdentifier ---- @param ring2 RingIdentifier ---- @return RingIdentifier -function Ring.resultantring(ring1, ring2) - if ring1 == ring2 then - return ring1 - end - - if ((ring1 == PolynomialRing.getring() and ring2 == Rational.getring()) or - (ring2 == PolynomialRing.getring() and ring1 == Rational.getring())) - and ring1.symbol == ring2.symbol then - return Rational.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2.child)) - end - - if ring1 == PolynomialRing.getring() or ring2 == PolynomialRing.getring() then - if ring1 == ring2.child then - return ring2 - end - if ring2 == ring1.child then - return ring1 - end - - if ring1 == PolynomialRing.getring() and ring2 == PolynomialRing.getring() and ring1.symbol == ring2.symbol then - return PolynomialRing.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2.child)) - end - - -- If none of the above conditions are satisfied, recusion is a pain, so we just strip all of the variables off of both rings. - -- TODO: Make this properly recursive, or just use a multivariable polynomial ring class - local symbols = {} - while ring1 == PolynomialRing.getring() do - symbols[#symbols+1] = ring1.symbol - ring1 = ring1.child - end - while ring2 == PolynomialRing.getring() do - if not Contains(symbols, ring2.symbol) then - symbols[#symbols+1] = ring2.symbol - end - ring2 = ring2.child - end - local ring = Ring.resultantring(ring1, ring2) - - if ring == Rational.getring() and Contains(symbols, ring.symbol) then - symbols = Remove(symbols, ring.symbol) - end - for i = #symbols, 1, -1 do - ring = PolynomialRing.makering(symbols[i], ring) - end - return ring - end - - if ring1 == Integer.getring() then - if ring2 == Integer.getring() then - return ring2 - end - - if ring2 == Rational.getring() then - return ring2 - end - - if ring2 == IntegerModN.getring() then - return ring2 - end - end - - if ring1 == Rational.getring() then - if ring2 == Integer.getring() then - return ring1 - end - - if ring2 == Rational.getring() then - if not ring1.symbol then - return Rational.makering(ring2.symbol, Ring.resultantring(ring1, ring2.child)) - end - if not ring2.symbol then - return Rational.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2)) - end - if ring1.symbol and ring2.symbol and ring1.symbol == ring2.symbol then - return Rational.makering(ring1.symbol, Ring.resultantring(ring1.child, ring2.child)) - end - return ring2 - end - - if ring2 == IntegerModN.getring() then - return nil - end - end - - if ring1 == IntegerModN.getring() then - if ring2 == Integer.getring() then - return ring1 - end - - if ring2 == Rational.getring() then - return nil - end - - if ring2 == IntegerModN.getring() then - return IntegerModN.makering(Integer.gcd(ring1.modulus, ring2.modulus)) - end - end - - return nil -end - ---- Returns a particular instantiation of a ring. ---- Does the same thing as getring() if there is only one possible ring for a class, i.e., the integers and rationals. ---- @return RingIdentifier -function Ring.makering() - error("Called unimplemented method : makering()") -end - ----------------------- --- Required methods -- ----------------------- - ---- Returns the ring this element is part of. ---- @return RingIdentifier -function Ring:getring() - error("Called unimplemented method : getring()") -end - ---- Explicitly converts this element to an element of another ring. ---- @param ring RingIdentifier ---- @return Ring -function Ring:inring(ring) - error("Called unimplemented method : in()") -end - ---- Returns whether the ring is commutative. ---- @return boolean -function Ring:iscommutative() - error("Called unimplemented method : iscommutative()") -end - ---- @return Ring -function Ring:add(b) - error("Called unimplemented method : add()") -end - ---- @return Ring -function Ring:sub(b) - return(self:add(b:neg())) -end - ---- @return Ring -function Ring:neg() - error("Called unimplemented method : neg()") -end - ---- @return Ring -function Ring:mul(b) - error("Called unimplemented method : mul()") -end - ---- Ring exponentiation by definition. Specific rings may implement more efficient methods. ---- @return Ring -function Ring:pow(n) - if(n < Integer.zero()) then - error("Execution error: Negative exponentiation is undefined over general rings") - end - local k = Integer.zero() - local b = self:one() - while k < n do - b = b * self - k = k + Integer.one() - end - return b -end - ---- @return boolean -function Ring:eq(b) - error("Execution error: Ring does not have a total order") -end - ---- @return boolean -function Ring:lt(b) - error("Execution error: Ring does not have a total order") -end - ---- @return boolean -function Ring:le(b) - error("Execution error: Ring does not have a total order") -end - ---- The additive identitity of the ring. ---- @return Ring -function Ring:zero() - error("Called unimplemented method : zero()") -end - ---- The multiplicative identitity of the ring. ---- @return Ring -function Ring:one() - error("Called unimplemented method : one()") -end - --------------------------- --- Instance metamethods -- --------------------------- -__RingOperations = {} - --- Each of these methods just handles coverting each element in the ring to an instance of the proper ring, if possible, --- then passing the arguments to the function in a specific ring. - -__RingOperations.__unm = function(a) - return a:neg() -end - -__RingOperations.__add = function(a, b) - if not b.getring then - return BinaryOperation.ADDEXP({a, b}) - end - - local aring, bring = a:getring(), b:getring() - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to add two elements of incompatable rings") - end - return a:inring(oring):add(b:inring(oring)) -end - -__RingOperations.__sub = function(a, b) - if not b.getring then - return BinaryOperation.SUBEXP({a, b}) - end - - local aring, bring = a:getring(), b:getring() - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to subtract two elements of incompatable rings") - end - return a:inring(oring):sub(b:inring(oring)) -end - --- Allows for multiplication by writing two expressions next to each other. -__RingOperations.__call = function (a, b) - return a * b -end - -__RingOperations.__mul = function(a, b) - if not b.getring then - return BinaryOperation.MULEXP({a, b}) - end - - local aring, bring = a:getring(), b:getring() - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to muliply two elements of incompatable rings") - end - return a:inring(oring):mul(b:inring(oring)) -end - -__RingOperations.__pow = function(a, n) - if (not n.getring) or (n.getring and n:getring().ring ~= Integer) then - return BinaryOperation.POWEXP({a, n}) - end - - -- if a == a:zero() and n == Integer.zero() then - -- error("Cannot raise 0 to the power of 0") - -- end - - return a:pow(n) -end - --- Comparison operations assume, of course, that the ring operation is equipped with a total order --- All elements of all rings need these metamethods, since in Lua comparisons on tables only fire if both objects have the table -__RingOperations.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway - if not a.getring or not b.getring then - return false - end - local aring, bring = a:getring(), b:getring() - if aring == bring then - return a:eq(b) - end - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to compare two elements of incompatable rings") - end - return a:inring(oring):eq(b:inring(oring)) -end - -__RingOperations.__lt = function(a, b) - local aring, bring = a:getring(), b:getring() - if aring == bring then - return a:lt(b) - end - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to compare two elements of incompatable rings") - end - return a:inring(oring):lt(b:inring(oring)) -end - -__RingOperations.__le = function(a, b) - local aring, bring = a:getring(), b:getring() - if aring == bring then - return a:le(b) - end - local oring = Ring.resultantring(aring, bring) - if not oring then - error("Attempted to compare two elements of incompatable rings") - end - return a:inring(oring):le(b:inring(oring)) -end - ------------------ --- Inheritance -- ------------------ - -__Ring.__index = ConstantExpression -Ring = setmetatable(Ring, __Ring) - ---- Used for comparing and converting between rings. ---- @class RingIdentifier diff --git a/macros/luatex/latex/luacas/tex/algebra/rootexpression.lua b/macros/luatex/latex/luacas/tex/algebra/rootexpression.lua deleted file mode 100644 index a66182579a..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/rootexpression.lua +++ /dev/null @@ -1,135 +0,0 @@ ---- @class RootExpression ---- An expression that represents the solutions to expression = 0. ---- @field expression Expression -RootExpression = {} -__RootExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new root expression with the given expression. ---- @param expression Expression ---- @return RootExpression -function RootExpression:new(expression) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.expression = Copy(expression) - - __o.__index = RootExpression - __o.__tostring = function(a) - return 'Root Of: (' .. tostring(a.expression) .. ')' - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway - if not b:type() == RootExpression then - return false - end - return a.expression == b.expression - end - o = setmetatable(o, __o) - - return o -end - ---- @return Expression -function RootExpression:autosimplify(subpart) - local simplified = self.expression:autosimplify() - local simplified, ispoly = simplified:topolynomial() - - if simplified:isconstant() then - -- 0 = 0 is always true (obviously). - return simplified == simplified:zero() - end - - if ispoly then - if simplified.degree == Integer.zero() then - return simplified == simplified:zero() - end - if simplified.degree == Integer.one() then - return {-simplified.coefficients[0] / simplified.coefficients[1]} - end - if simplified.degree == Integer(2) then - local a = simplified.coefficients[2] - local b = simplified.coefficients[1] - local c = simplified.coefficients[0] - -- This is a hack until we can get more expression manipulation working, but that's okay. - if subpart then - c = (c - subpart):autosimplify() - end - return {((-b + sqrt(b^Integer(2) - Integer(4) * a * c)) / (Integer(2) * a)):autosimplify(), - ((-b - sqrt(b^Integer(2) - Integer(4) * a * c)) / (Integer(2) * a)):autosimplify()} - end - if simplified.degree == Integer(3) then - local a = simplified.coefficients[3] - local b = simplified.coefficients[2] - local c = simplified.coefficients[1] - local d = simplified.coefficients[0] - -- This is a hack until we can get more expression manipulation working, but that's okay. - if subpart then - d = (d - subpart):autosimplify() - end - - local delta0 = (b^Integer(2) - Integer(3)*a*c):autosimplify() - local delta1 = (Integer(2) * b^Integer(3) - Integer(9)*a*b*c+Integer(27)*a^Integer(2)*d):autosimplify() - - local C = sqrt((delta1 + sqrt(delta1 ^ Integer(2) - Integer(4) * delta0 ^ Integer(3))) / Integer(2), Integer(3)):autosimplify() - - if C == Integer.zero() then - C = sqrt((delta1 - sqrt(delta1 ^ Integer(2) - Integer(4) * delta0 ^ Integer(3))) / Integer(2), Integer(3)):autosimplify() - end - - if C == Integer.zero() then - C = (-b/(Integer(3)*a)):autosimplify() - end - - local eta = ((Integer(-1) + sqrt(Integer(-3))) / Integer(2)):autosimplify() - - return {((-Integer.one() / (Integer(3) * a)) * (b + C + delta0 / C)):autosimplify(), - ((-Integer.one() / (Integer(3) * a)) * (b + C*eta + delta0 / (C*eta))):autosimplify(), - ((-Integer.one() / (Integer(3) * a)) * (b + C*eta^Integer(2) + delta0 / (C*eta^Integer(2)))):autosimplify()} - end - end - if ispoly then - simplified = simplified:autosimplify() - end - if subpart then - simplified = (simplified - subpart):autosimplify() - end - return {RootExpression(simplified)} -end - ---- @return table -function RootExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return RootExpression -function RootExpression:setsubexpressions(subexpressions) - return RootExpression(subexpressions[1]) -end - ---- @param other Expression ---- @return boolean -function RootExpression:order(other) - --- TODO: Fix ordering on new expression types - if other:type() ~= RootExpression then - return false - end - - return self.expression:order(other.expression) -end - ---- @return string -function RootExpression:tolatex() - return '\\operatorname{RootOf}\\left(' .. self.expression:tolatex() .. '\\right)' -end - ------------------ --- Inheritance -- ------------------ -__RootExpression.__index = CompoundExpression -__RootExpression.__call = RootExpression.new -RootExpression = setmetatable(RootExpression, __RootExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/sqrtexpression.lua b/macros/luatex/latex/luacas/tex/algebra/sqrtexpression.lua deleted file mode 100644 index bc96f4e1d8..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/sqrtexpression.lua +++ /dev/null @@ -1,196 +0,0 @@ ---- @class SqrtExpression ---- An expression that represents the positive real solution to x^n = a where n is a positive integer and a is constant. ---- @field expression Expression -SqrtExpression = {} -__SqrtExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new sqrt expression with the given expression. ---- @param expression Expression ---- @param root Integer ---- @return SqrtExpression -function SqrtExpression:new(expression, root) - root = root or Integer(2) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.expression = Copy(expression) - o.root = root - - __o.__index = SqrtExpression - __o.__tostring = function(a) - return tostring(a.expression) .. ' ^ (1/' .. tostring(a.root) .. ')' - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway - if not b:type() == SqrtExpression then - return false - end - return a.expression == b.expression and a.root == b.root - end - o = setmetatable(o, __o) - - return o -end - - ---- @return table -function SqrtExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return SqrtExpression -function SqrtExpression:setsubexpressions(subexpressions) - return SqrtExpression(subexpressions[1], self.root) -end - ---- @param other Expression ---- @return boolean -function SqrtExpression:order(other) - return self:topower():order(other) -end - -function SqrtExpression:topower() - local exponent = BinaryOperation(BinaryOperation.DIV,{Integer.one(),self.root}):autosimplify() - local base = self.expression - return BinaryOperation(BinaryOperation.POW,{base,exponent}):autosimplify() -end - -function SqrtExpression:autosimplify() - local expression = self.expression:autosimplify() - local root = self.root:autosimplify() - - if root == Integer.one() then - return expression - end - - if root:type() == Rational then - return SqrtExpression(BinaryOperation(BinaryOperation.POW,{expression,root.denominator}):autosimplify(), root.numerator):autosimplify() - end - - if not root:isconstant() then - return BinaryOperation(BinaryOperation.POW,{expression,Integer.one() / root}):autosimplify() - end - - if not expression:isconstant() then - if expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() then - local coeff = SqrtExpression(expression.expressions[1],root):autosimplify() - expression.expressions[1] = BinaryOperation(BinaryOperation.MUL,{Integer.one()}) - expression = expression:autosimplify() - local sqrtpart = SqrtExpression(expression,root):autosimplify() - local result = coeff*sqrtpart - return result:autosimplify() - end - return BinaryOperation(BinaryOperation.POW,{expression,Integer.one() / root}):autosimplify() - end - - if expression:type() == Rational then - local result = BinaryOperation(BinaryOperation.MUL, {SqrtExpression(expression.numerator,root):autosimplify(),BinaryOperation(BinaryOperation.POW,{SqrtExpression(expression.denominator,root):autosimplify(),Integer(-1)})}) - return result:autosimplify() - end - - if expression:type() == Integer then - if expression == Integer.zero() then - return Integer.zero() - end - if expression == Integer.one() then - return Integer.one() - end - if expression < Integer.zero() then - if root == Integer(2) then - local result = SqrtExpression(expression:neg(),root):autosimplify() - result = I*result - return result:autosimplify() - end - if root % Integer(2) == Integer.one() then - local result = SqrtExpression(expression:neg(),root):autosimplify() - result = -result - return result:autosimplify() - end - end - local primes = expression:primefactorization() - local coeffresult = {} - local exprresult = {} - local reduction = root - for _, term in ipairs(primes.expressions) do - local primepower = term.expressions[2] - reduction = Integer.gcd(primepower,reduction) - if reduction == Integer.one() then - goto skip - end - end - ::skip:: - local newroot = root / reduction - for index, term in ipairs(primes.expressions) do - local prime = term.expressions[1] - local primepower = term.expressions[2] / reduction - local coeffpower = primepower // newroot - coeffresult[index] = prime ^ coeffpower - local exprpower = primepower - coeffpower*newroot - exprresult[index] = prime ^ exprpower - end - local newexpression = BinaryOperation(BinaryOperation.MUL,exprresult):autosimplify() - local coeff = BinaryOperation(BinaryOperation.MUL,coeffresult):autosimplify() - if coeff == Integer.one() then - if reduction == Integer.one() then - goto stop - end - return SqrtExpression(newexpression,newroot) - end - if newroot == Integer.one() then - return coeff - end - return BinaryOperation(BinaryOperation.MUL,{coeff,SqrtExpression(newexpression,newroot)}):autosimplify() - end - ::stop:: - - if expression.operation == BinaryOperation.POW and expression.expressions[2]:type() == Integer then - local exponent = expression.expressions[2] - local power = exponent // root - local newexponent = (exponent / root) - power - local coeff = expression.expressions[1] ^ power - coeff = coeff:evaluate() - if newexponent == Integer.zero() then - return coeff - else - local num = newexponent.numerator - local den = newexponent.denominator - local newexpression = expression ^ num - newexpression = newexpression:autosimplify() - local result = coeff * SqrtExpression(newexpression,den) - return result - end - end - - return SqrtExpression(expression,root) -end - -function SqrtExpression:tolatex() - local printout = '\\sqrt' - if self.root == Integer(2) then - printout = printout .. '{' .. self.expression:tolatex() .. '}' - else - printout = printout .. '[' .. self.root:tolatex() .. ']' .. '{' .. self.expression:tolatex() .. '}' - end - return printout -end - - ------------------ --- Inheritance -- ------------------ -__SqrtExpression.__index = CompoundExpression -__SqrtExpression.__call = SqrtExpression.new -SqrtExpression = setmetatable(SqrtExpression, __SqrtExpression) - ----------------------- --- Static constants -- ----------------------- - -sqrt = function(expression, root) - return SqrtExpression(expression, root) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/algebra/trigexpression.lua b/macros/luatex/latex/luacas/tex/algebra/trigexpression.lua deleted file mode 100644 index 307bfe1737..0000000000 --- a/macros/luatex/latex/luacas/tex/algebra/trigexpression.lua +++ /dev/null @@ -1,355 +0,0 @@ ---- @class TrigExpression ---- Represents a trigonometric function from one expression to another. ---- @field name string ---- @field expression Expression -TrigExpression = {} -__TrigExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new trig expression with the given name and expression. ---- @param name string|SymbolExpression ---- @param expression Expression ---- @return TrigExpression -function TrigExpression:new(name, expression) - local o = {} - local __o = Copy(__ExpressionOperations) - - if not TrigExpression.NAMES[name] then - error("Argument error: " .. name .. " is not the name of a trigonometric function.") - end - - o.name = name - o.expression = expression - o.expressions = {expression} - if expression:isatomic() then - o.variables = {expression} - else - o.variables = {SymbolExpression('x')} - end - o.derivatives = {Integer.zero()} - - __o.__index = TrigExpression - __o.__tostring = function(a) - return tostring(a.name) .. '(' .. tostring(a.expression) .. ')' - end - __o.__eq = function(a, b) - -- if b:type() == FunctionExpression then - -- return a:tofunction() == b - -- end - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway - if not b:type() == TrigExpression then - return false - end - return a.name == b.name and a.expression == b.expression - end - - o = setmetatable(o, __o) - return o -end - ---- @return TrigExpression -function TrigExpression:evaluate() - local expression = self.expression:autosimplify() - - if expression == Integer.zero() then - if self.name == "cos" or self.name == "sec" then - return Integer.one() - end - if self.name == "sin" or self.name == "tan" then - return Integer.zero() - end - if self.name == "arctan" or self.name == "arcsin" then - return Integer.zero() - end - if self.name == "arccos" or self.name == "arccot" then - return PI / Integer(2) - end - end - - if expression == PI then - if self.name == "cos" or self.name == "sec" then - return Integer(-1) - end - if self.name == "sin" or self.name == "tan" then - return Integer.zero() - end - end - - if expression:ismulratlPI() then - local coeff = expression.expressions[1] - if TrigExpression.COSVALUES[tostring(coeff)] ~= nil then - if self.name == "cos" then - return TrigExpression.COSVALUES[tostring(coeff)]:autosimplify() - end - if self.name == "sin" then - local sign = Integer.one() - if coeff > Integer.one() then - sign = Integer(-1) - end - return (sign*sqrt(Integer.one()-cos(expression)^Integer(2))):autosimplify() - end - if self.name == "tan" then - return (sin(expression) / cos(expression)):autosimplify() - end - if self.name == "sec" then - return (Integer.one() / cos(expression)):autosimplify() - end - if self.name == "csc" then - return (Integer.one() / sin(expression)):autosimplify() - end - if self.name == "cot" then - return (cos(expression) / sin(expression)):autosimplify() - end - end - end - - if TrigExpression.ACOSVALUES[tostring(expression)] ~= nil then - if self.name == "arccos" then - return TrigExpression.ACOSVALUES[tostring(expression)]:autosimplify() - end - if self.name == "arcsin" then - if expression == Integer(-1) then - return TrigExpression.ACOSVALUES["-1"]:autosimplify() - elseif expression.expressions and expression.expressions[1] == Integer(-1) then - local expr = (Integer(-1)*sqrt(Integer.one() - expression ^ Integer(2))):autosimplify() - return TrigExpression.ACOSVALUES[tostring(expr)]:autosimplify() - else - local expr = (sqrt(Integer.one() - expression ^ Integer(2))):autosimplify() - return TrigExpression.ACOSVALUES[tostring(expr)]:autosimplify() - end - end - end - - if self.name == "arctan" and TrigExpression.ATANVALUES[tostring(expression)] ~= nil then - return TrigExpression.ATANVALUES[tostring(expression)]:autosimplify() - end - - return self -end - ---- checks if expression is a rational multiple of pi ---- @return boolean -function Expression:ismulratlPI() - if self.operation == BinaryOperation.MUL and #self.expressions == 2 and (self.expressions[1]:type() == Integer or self.expressions[1]:type() == Rational) and self.expressions[2] == PI then - return true - end - - return false -end - ---- @return TrigExpression -function TrigExpression:autosimplify() - local expression = self.expression:autosimplify() - - -- even and odd properties of trig functions - if (self.name == "sin" or self.name == "tan" or self.name == "csc" or self.name == "cot") and - expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() and expression.expressions[1] < Integer(0) then - return (-Integer.one() * TrigExpression(self.name, -expression)):autosimplify() - end - - if (self.name == "cos" or self.name == "sec") and - expression.operation == BinaryOperation.MUL and expression.expressions[1]:isconstant() and expression.expressions[1] < Integer(0) then - expression = (-expression):autosimplify() - end - - -- uses periodicity of sin and cos and friends - if self.name == "sin" or self.name == "cos" or self.name == "csc" or self.name == "sec" then - if expression == Integer.zero() or expression == PI then - goto skip - end - if expression.operation ~= BinaryOperation.ADD then - expression = BinaryOperation(BinaryOperation.ADD,{expression}) - end - for index,component in ipairs(expression.expressions) do - if component:ismulratlPI() then - local coeff = component.expressions[1] - if coeff:type() == Integer then - coeff = coeff % Integer(2) - coeff = coeff:autosimplify() - end - if coeff:type() == Rational then - local n = coeff.numerator - local d = coeff.denominator - local m = {n:divremainder(d)} - coeff = (m[1] % Integer(2)) + m[2]/d - coeff = coeff:autosimplify() - end - expression.expressions[index].expressions[1] = coeff - end - expression = expression:autosimplify() - end - ::skip:: - end - - -- uses periodicity of tan and cot - if self.name == "tan" or self.name == "cot" then - if expression == Integer.zero() or expression == PI then - goto skip - end - if expression.operation ~= BinaryOperation.ADD then - expression = BinaryOperation(BinaryOperation.ADD,{expression}) - end - for index,component in ipairs(expression.expressions) do - if component:ismulratlPI() then - local coeff = component.expressions[1] - if coeff:type() == Integer then - coeff = Integer.zero() - end - if coeff:type() == Rational then - local n = coeff.numerator - local d = coeff.denominator - local m = {n:divremainder(d)} - coeff = m[2]/d - coeff = coeff:autosimplify() - end - expression.expressions[index].expressions[1] = coeff - end - if component == PI then - expression.expressions[index] = Integer.zero() - end - end - expression = expression:autosimplify() - ::skip:: - end - - return TrigExpression(self.name, expression):evaluate() -end - ---- @return table -function TrigExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return TrigExpression -function TrigExpression:setsubexpressions(subexpressions) - return TrigExpression(self.name, subexpressions[1]) -end - --- function TrigExpression:freeof(symbol) --- return self.expression:freeof(symbol) --- end - --- function TrigExpression:substitute(map) --- for expression, replacement in pairs(map) do --- if self == expression then --- return replacement --- end --- end --- return TrigExpression(self.name, self.expression:substitute(map)) --- end - --- function TrigExpression:order(other) --- return self:tofunction():order(other) --- end - --- function TrigExpression:tofunction() --- return FunctionExpression(self.name, {self.expression}, true) --- end - ------------------ --- Inheritance -- ------------------ - -__TrigExpression.__index = FunctionExpression -__TrigExpression.__call = TrigExpression.new -TrigExpression = setmetatable(TrigExpression, __TrigExpression) - ----------------------- --- Static constants -- ----------------------- -TrigExpression.NAMES = {sin=1, cos=2, tan=3, csc=4, sec=5, cot=6, - arcsin=7, arccos=8, arctan=9, arccsc=10, arcsec=11, arccot=12} - -TrigExpression.INVERSES = {sin="arcsin", cos="arccos", tan="arctan", csc="arccsc", sec="arcsec", cot="arccot", - arcsin="sin", arccos="cos", arctan="tan", arccsc="csc", arcsec="sec", arccot="cot"} - -TrigExpression.COSVALUES = { - ["0"] = Integer.one(), - ["1/6"] = sqrt(Integer(3))/Integer(2), - ["1/4"] = sqrt(Integer(2))/Integer(2), - ["1/3"] = Integer.one()/Integer(2), - ["1/2"] = Integer.zero(), - ["2/3"] = -Integer.one()/Integer(2), - ["3/4"] = -sqrt(Integer(2))/Integer(2), - ["5/6"] = -sqrt(Integer(3))/Integer(2), - ["1"] = -Integer.one(), - ["7/6"] = -sqrt(Integer(3))/Integer(2), - ["5/4"] = -sqrt(Integer(2))/Integer(2), - ["4/3"] = -Integer.one()/Integer(2), - ["3/2"] = Integer.zero(), - ["5/3"] = Integer.one()/Integer(2), - ["7/4"] = sqrt(Integer(2))/Integer(2), - ["11/6"] = sqrt(Integer(3))/Integer(2), -} -TrigExpression.ACOSVALUES = { - ["1"] = Integer.zero(), - ["(1/2 * sqrt(3,2))"] = PI * Integer(6) ^ Integer(-1), - ["(1/2 * sqrt(2,2))"] = PI * Integer(4) ^ Integer(-1), - ["1/2"] = PI * Integer(3) ^ Integer(-1), - ["0"] = PI * Integer(2) ^ Integer(-1), - ["-1/2"] = PI * Integer(2) * Integer(3) ^ Integer(-1), - ["(-1/2 * sqrt(2,2))"]= PI * Integer(3) * Integer(4) ^ Integer(-1), - ["(-1/2 * sqrt(3,2))"]= PI * Integer(5) * Integer(6) ^ Integer(-1), - ["-1"] = Integer(-1)*PI, -} -TrigExpression.ATANVALUES = { - ["(-1 * sqrt(3,2))"] = Integer(-1) * PI * Integer(3) ^ Integer(-1), - ["-1"] = Integer(-1) * PI * Integer(4) ^ Integer(-1), - ["(-1/3 * sqrt(3,2))"] = Integer(-1) * Integer(6) ^ Integer(-1), - ["0"] = Integer.zero(), - ["(1/3 * sqrt(3,2))"] = PI * Integer(6) ^ Integer(-1), - ["1"] = PI * Integer(4) ^ Integer(-1), - ["sqrt(3,2)"] = PI * Integer(3) ^ Integer(-1) -} - -SIN = function (a) - return TrigExpression("sin", a) -end - -COS = function (a) - return TrigExpression("cos", a) -end - -TAN = function (a) - return TrigExpression("tan", a) -end - -CSC = function (a) - return TrigExpression("csc", a) -end - -SEC = function (a) - return TrigExpression("sec", a) -end - -COT = function (a) - return TrigExpression("cot", a) -end - -ARCSIN = function (a) - return TrigExpression("arcsin", a) -end - -ARCCOS = function (a) - return TrigExpression("arccos", a) -end - -ARCTAN = function (a) - return TrigExpression("arctan", a) -end - -ARCCSC = function (a) - return TrigExpression("arccsc", a) -end - -ARCSEC = function (a) - return TrigExpression("arcsec", a) -end - -ARCCOT = function (a) - return TrigExpression("arccot", a) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/calculus/_init.lua b/macros/luatex/latex/luacas/tex/calculus/_init.lua deleted file mode 100644 index 59ba1bbcc8..0000000000 --- a/macros/luatex/latex/luacas/tex/calculus/_init.lua +++ /dev/null @@ -1,6 +0,0 @@ --- Loads calculus files in the correct order. -require("algebra._init") - -require("calculus.derivativeexpression") -require("calculus.integralexpression") -require("calculus.diffexpression") diff --git a/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua b/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua deleted file mode 100644 index b757759c4c..0000000000 --- a/macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua +++ /dev/null @@ -1,265 +0,0 @@ ---- @class DerivativeExpression ---- An expression for a single-variable derivative of an expression. ---- @field symbol SymbolExpression ---- @field expression Expression -DerivativeExpression = {} -__DerivativeExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - --- Creates a new single-variable derivative operation with the given symbol and expression. ---- @param expression Expression ---- @param symbol Symbol ---- @return DerivativeExpression -function DerivativeExpression:new(expression, symbol) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.expression = Copy(expression) - o.symbol = symbol or SymbolExpression("x") - - __o.__index = DerivativeExpression - __o.__tostring = function(a) - return '(d/d' .. tostring(a.symbol) .. " " .. tostring(a.expression) .. ')' - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway - if not b:type() == DerivativeExpression then - return false - end - return a.symbol == b.symbol and a.expression == b.expression - end - o = setmetatable(o, __o) - - return o -end - ---- @return Expression -function DerivativeExpression:evaluate() - local exp = self.expression - - -- The derivative of a constant is 0 - if exp:isconstant() then - return Integer.zero() - end - - -- The derivative of a symbol is either 1 or 0 - if exp:type() == SymbolExpression then - if self.symbol == exp then - return Integer.one() - end - return Integer.zero() - end - - -- Chain rule for arbitrary functions - - if exp:type() == FunctionExpression then - local results = {} - for index,expression in ipairs(exp.expressions) do - local dout = FunctionExpression(exp.name,exp.expressions,exp.derivatives) - dout.variables = exp.variables - dout.derivatives[index] = dout.derivatives[index]+Integer.one() - local dinn = DerivativeExpression(expression,self.symbol):evaluate() - results[index] = dout*dinn - end - return BinaryOperation(BinaryOperation.ADD,results):autosimplify() - end - - --if exp:type() == FunctionExpression then - -- local results = {} - -- for index,expression in ipairs(exp.expressions) do - -- local inn = DerivativeExpression(expression,self.symbol):autosimplify() - -- local out = Copy(exp) - -- out.orders[index] = out.orders[index] + Integer.one() - -- local result = inn*out - -- table.insert(results,result) - -- end - -- return BinaryOperation(BinaryOperation.ADD,results):autosimplify() - --end - - --if exp:type() == FunctionExpression then - -- if exp.expressions[2] then - -- return self - -- end - -- return DerivativeExpression(exp.expressions[1], self.symbol) * FunctionExpression(exp.name, exp.expressions, exp.orders[1] + Integer.one(), exp.variables[1]):autosimplify() - --end - - -- Chain rule for trig functions - if exp:type() == TrigExpression then - local internal = DerivativeExpression(exp.expression, self.symbol) - - if exp.name == "sin" then - return (internal * COS(exp.expression)):autosimplify() - end - if exp.name == "cos" then - return (internal * -SIN(exp.expression)):autosimplify() - end - if exp.name == "tan" then - return (internal * SEC(exp.expression)^Integer(2)):autosimplify() - end - if exp.name == "csc" then - return (internal * -CSC(exp.expression)*COT(exp.expression)):autosimplify() - end - if exp.name == "sec" then - return (internal * -SEC(exp.expression)*TAN(exp.expression)):autosimplify() - end - if exp.name == "cot" then - return (internal * -CSC(exp.expression)^Integer(2)):autosimplify() - end - if exp.name == "arcsin" then - return (internal / (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2))):autosimplify() - end - if exp.name == "arccos" then - return (-internal / (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2))):autosimplify() - end - if exp.name == "arctan" then - return (internal / (Integer(1)+exp.expression^Integer(2))):autosimplify() - end - if exp.name == "arccsc" then - return (-internal / (ABS(exp.expression) * (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2)))):autosimplify() - end - if exp.name == "arcsec" then - return (internal / (ABS(exp.expression) * (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2)))):autosimplify() - end - if exp.name == "arccot" then - return (-internal / (Integer(1)+exp.expression^Integer(2))):autosimplify() - end - end - - -- TODO: Piecewise functions - if self:type() == AbsExpression then - return DerivativeExpression(self.expression, self.symbol):autosimplify() - end - - -- Uses linearity of derivatives to evaluate sum expressions - if exp.operation == BinaryOperation.ADD then - local parts = {} - for i, expression in pairs(exp.expressions) do - parts[i] = DerivativeExpression(expression, self.symbol) - end - return BinaryOperation(BinaryOperation.ADD, parts):autosimplify() - end - - -- Uses product rule to evaluate product expressions - if exp.operation == BinaryOperation.MUL then - local sums = {} - for i, expression in pairs(exp.expressions) do - local products = {} - for j, innerexpression in pairs(exp.expressions) do - if i ~= j then - products[j] = innerexpression - else - products[j] = DerivativeExpression(innerexpression, self.symbol) - end - end - sums[i] = BinaryOperation(BinaryOperation.MUL, products) - end - return BinaryOperation(BinaryOperation.ADD, sums):autosimplify() - end - - -- Uses the generalized power rule to evaluate power expressions - if exp.operation == BinaryOperation.POW then - local base = exp.expressions[1] - local exponent = exp.expressions[2] - - return BinaryOperation.MULEXP({ - BinaryOperation.POWEXP({base, exponent}), - BinaryOperation.ADDEXP({ - BinaryOperation.MULEXP({ - DD(base, self.symbol), - BinaryOperation.DIVEXP({exponent, base})}), - BinaryOperation.MULEXP({ - DD(exponent, self.symbol), - LN(base)})}) - }):autosimplify() - end - - if exp:type() == Logarithm then - local base = exp.base - local expression = exp.expression - - return BinaryOperation.SUBEXP({ - BinaryOperation.DIVEXP({DD(expression, self.symbol), - BinaryOperation.MULEXP({expression, LN(base)})}), - - BinaryOperation.DIVEXP({ - BinaryOperation.MULEXP({LN(expression), DD(base, self.symbol)}), - BinaryOperation.MULEXP({BinaryOperation.POWEXP({LN(base), Integer(2)}), base}) - }) - - }):autosimplify() - end - - return self -end - ---- @return Expression -function DerivativeExpression:autosimplify() - return DerivativeExpression(self.expression:autosimplify(), self.symbol):evaluate() -end - ---- @return table -function DerivativeExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return DerivativeExpression -function DerivativeExpression:setsubexpressions(subexpressions) - return DerivativeExpression(subexpressions[1], self.symbol) -end - --- function DerivativeExpression:freeof(symbol) --- return self.symbol.freeof(symbol) and self.expression:freeof(symbol) --- end - --- Substitutes each expression for a new one. --- function DerivativeExpression:substitute(map) --- for expression, replacement in pairs(map) do --- if self == expression then --- return replacement --- end --- end --- -- Typically, we only perform substitution on autosimplified expressions, so this won't get called. May give strange results, i.e., --- -- substituting and then evaluating the derivative may not return the same thing as evaluating the derivative and then substituting. --- return DerivativeExpression(self.expression:substitute(map), self.symbol) --- end - ---- @param other Expression ---- @return boolean -function DerivativeExpression:order(other) - if other:type() == IntegralExpression then - return true - end - - if other:type() ~= DerivativeExpression then - return false - end - - if self.symbol ~= other.symbol then - return self.symbol:order(other.symbol) - end - - return self.expression:order(other.expression) -end - ---- @return string -function DerivativeExpression:tolatex() - return '\\frac{d}{d' .. self.symbol:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)' -end - ------------------ --- Inheritance -- ------------------ -__DerivativeExpression.__index = CompoundExpression -__DerivativeExpression.__call = DerivativeExpression.new -DerivativeExpression = setmetatable(DerivativeExpression, __DerivativeExpression) - ----------------------- --- Static constants -- ----------------------- -DD = function(expression, symbol) - return DerivativeExpression(expression, symbol) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/calculus/diffexpression.lua b/macros/luatex/latex/luacas/tex/calculus/diffexpression.lua deleted file mode 100644 index df1dc90f33..0000000000 --- a/macros/luatex/latex/luacas/tex/calculus/diffexpression.lua +++ /dev/null @@ -1,190 +0,0 @@ ---- @class DiffExpression ---- An expression for a multi-variable higher-order derivatives of an expression. ---- @field symbols SymbolExpression ---- @field expression Expression - -DiffExpression = {} -__DiffExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - --- Creates a new derivative operation with the given symbols and expression. ---- @param expression Expression ---- @param symbols table ---- @return DiffExpression -function DiffExpression:new(expression,symbols) - local o = {} - local __o = Copy(__ExpressionOperations) - - o.symbols = Copy(symbols) - o.degree = #o.symbols - o.expression = Copy(expression) - - __o.__tostring = function(a) - local varlist = '(d' - if a.degree == 1 then - varlist = varlist .. '/d' .. tostring(a.symbols[1]) .. " " .. tostring(a.expression) .. ')' - end - if a.degree > 1 then - varlist = varlist .. '^' .. tostring(a.degree) .. '/' - local varnum = 1 - for index = 1, #a.symbols do - local var = a.symbols[#a.symbols-index+1] - if a.symbols[#a.symbols-index] == var then - varnum = varnum + 1 - goto nextvar - end - if a.symbols[#a.symbols-index] ~=var then - if varnum == 1 then - varlist = varlist .. 'd' .. tostring(var) - else - varlist = varlist .. 'd' .. tostring(var) .. '^' .. tostring(varnum) - end - varnum = 1 - end - ::nextvar:: - end - varlist = varlist .. " " .. tostring(a.expression) .. ')' - end - return varlist - end - - __o.__index = DiffExpression - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway - if not b:type() == DiffExpression then - return false - end - if a.expression ~= b.expression then - return false - end - local loc = 1 - while a.symbols[loc] or b.symbols[loc] do - if not a.symbols[loc] or not b.symbols[loc] or - (a.symbols[loc] ~= b.symbols[loc]) then - return false - end - loc = loc + 1 - end - return true - end - o = setmetatable(o, __o) - - return o -end - ---- @return Expression -function DiffExpression:evaluate() - local exp = self.expression - - for _,var in ipairs(self.symbols) do - exp = DerivativeExpression(exp,var):evaluate() - end - return exp -end - ---- @return Expression -function DiffExpression:autosimplify() - return DiffExpression(self.expression:autosimplify(), self.symbols):evaluate() -end - - ---- @return table -function DiffExpression:subexpressions() - return {self.expression} -end - ---- @param subexpressions table ---- @return DiffExpression -function DiffExpression:setsubexpressions(subexpressions) - return DiffExpression(subexpressions[1], self.symbols) -end - ---- @param other Expression ---- @return boolean -function DiffExpression:order(other) - if other:type() == IntegralExpression then - return true - end - - if other:type() ~= DiffExpression then - return false - end - - if self.degree > other.degree then - return false - end - - if self.degree < other.degree then - return true - end - - return self.expression:order(other.expression) -end - ---- @return string -function DiffExpression:tolatex() - local varlist = '\\frac{' - if self.degree == 1 then - varlist = varlist .. 'd}{d' .. self.symbols[1]:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)' - end - if self.degree > 1 then - local cvarlist = {} - local count = 1 - for index, var in ipairs(self.symbols) do - if var == self.symbols[index+1] then - count = count + 1 - else - table.insert(cvarlist,{var,count}) - count = 1 - end - end - if #cvarlist == 1 then - varlist = varlist .. 'd^{' .. self.degree .. '}}{' .. 'd' .. cvarlist[1][1]:tolatex() .. '^{' .. self.degree .. '}' - end - if #cvarlist > 1 then - varlist = varlist .. '\\partial^{' .. self.degree .. '}}{' - for index, varnum in ipairs(cvarlist) do - var = cvarlist[#cvarlist - index+1][1] - num = cvarlist[#cvarlist - index+1][2] - if num == 1 then - varlist = varlist .. '\\partial ' .. var:tolatex() - else - varlist = varlist .. '\\partial ' .. var:tolatex() .. '^{' .. num .. '}' - end - end - end - varlist = varlist .. '} \\left(' .. self.expression:tolatex() .. '\\right)' - end - return varlist -end - ------------------ --- Inheritance -- ------------------ - -__DiffExpression.__index = CompoundExpression -__DiffExpression.__call = DiffExpression.new -DiffExpression = setmetatable(DiffExpression, __DiffExpression) - ----------------------- --- Static constants -- ----------------------- - -diff = function(expression,...) - local symbols = {} - for i = 1, select("#",...) do - local var = select(i,...) - if #var == 0 then - table.insert(symbols,var) - end - if #var > 0 then - for index=1, RR(var[2]) do - table.insert(symbols,var[1]) - end - end - end - return DiffExpression(expression,symbols) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/calculus/integralexpression.lua b/macros/luatex/latex/luacas/tex/calculus/integralexpression.lua deleted file mode 100644 index 5267127264..0000000000 --- a/macros/luatex/latex/luacas/tex/calculus/integralexpression.lua +++ /dev/null @@ -1,934 +0,0 @@ ---- @class IntegralExpression ---- An expression for the integral of an expression. ---- @field symbol SymbolExpression ---- @field expression Expression ---- @field upper Expression ---- @field lower Expression ---- @field attempts table ---- @field results table ---- @field enhancedsubstitution Integer ---- @field recursive boolean - -IntegralExpression = {} -__IntegralExpression = {} - - --------------------------- --- Static functionality -- --------------------------- - ---- Recursive part of the indefinite integral operator. Returns nil if the expression could not be integrated. ---- We switch to prodcedural programming here because it is more natural. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.integrate(integral) - integral.expression = integral.expression:autosimplify() - - if not integral.recursive and #integral.attempts > 0 then - return Copy(integral:lock(Expression.NIL, true)) - end - - -- print(integral.expression) - - local F = IntegralExpression.table(integral) - if F then return F end - - -- If we see the same integrand again, and hasn't been solved already, then the integral can't be solved - local resultindex = Contains(integral.attempts, integral.expression) - if resultindex then - return integral.results[resultindex] - end - local newindex = #integral.attempts+1 - integral.attempts[newindex] = integral.expression - - -- print("Evalutaing: " .. tostring(integral.expression)) - - F = IntegralExpression.linearproperties(integral) - if F then - -- print("Linear Properties") - integral.results[newindex] = F - return F - end - - -- local exp = integral.expression - -- local sym = integral.symbol - -- local es = integral.enhancedsubstitution - -- integral.enhancedsubstitution = Integer.zero() - F = IntegralExpression.substitutionmethod(integral) - if F then - -- print("u-Substitution") - integral.results[newindex] = F - return F - end - -- integral.expression = exp - -- integral.symbol = sym - -- integral.enhancedsubstitution = es - - - F = IntegralExpression.rationalfunction(integral) - if F then - -- print("Rational Function") - integral.results[newindex] = F - return F - end - - F = IntegralExpression.partsmethod(integral) - if F then - -- print("Parts") - integral.results[newindex] = F - return F - end - - F = IntegralExpression.eulersformula(integral) - if F then - -- print("Euler's formula") - integral.results[newindex] = F - return F - end - - local expanded = integral.expression:expand() - if integral.expression ~= expanded then - integral.expression = expanded - F = IntegralExpression.integrate(integral) - if F then - -- print("Expanded") - integral.results[newindex] = F - return F - end - end - - expanded = (Integer.one()/((Integer.one()/integral.expression):autosimplify():expand())):autosimplify() - if integral.expression ~= expanded then - integral.expression = expanded - F = IntegralExpression.integrate(integral) - if F then - -- print("Inverse Expanded") - integral.results[newindex] = F - return F - end - end - - F = IntegralExpression.enhancedsubstitutionmethod(integral) - if F then - -- print("Enhanced u-Substitution") - integral.results[newindex] = F - return F - end - - return nil -end - ---- A table of basic integrals, returns nil if the integrand isn't in the table. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.table(integral) - local integrand = integral.expression - local symbol = integral.symbol - - -- Constant integrand rule - int(c, x) = c*x - if integrand:freeof(symbol) then - return integrand*symbol - end - - if integrand:type() == SymbolExpression then - - -- int(x, x) = x^2/2 - if integrand == symbol then - return integrand ^ Integer(2) / Integer(2) - end - - -- Constant integrand rule again - return integrand*symbol - end - - if integrand:type() == BinaryOperation then - - if integrand.operation == BinaryOperation.POW then - -- int(1/x, x) = ln(x) - if integrand.expressions[1] == symbol and integrand.expressions[2] == Integer(-1) then - return LN(symbol) - end - - -- Cavalieri's formula - int(x^n, x) = x^(n+1)/(n+1) - if integrand.expressions[1] == symbol and integrand.expressions[2]:freeof(symbol) then - return symbol ^ (integrand.expressions[2] + Integer.one()) / (integrand.expressions[2] + Integer.one()) - end - - -- int(n^x, x) = n^x/ln(n) - if integrand.expressions[1]:freeof(symbol) and integrand.expressions[2] == symbol then - return integrand / LN(integrand.expressions[1]) - end - - -- int(csc(x)^2, x) = -cot(x) - if integrand.expressions[1] == CSC(symbol) and integrand.expressions[2] == Integer(2) then - return -COT(symbol) - end - - -- int(sec(x)^2, x) = tan(x) - if integrand.expressions[1] == SEC(symbol) and integrand.expressions[2] == Integer(2) then - return TAN(symbol) - end - end - - if integrand.operation == BinaryOperation.MUL and #integrand.expressions == 2 then - -- int(tan(x)sec(x), x) = sec(x) - if integrand.expressions[1] == TAN(symbol) and integrand.expressions[2] == SEC(symbol) then - return SEC(symbol) - end - - -- int(csc(x)cot(x), x) = -csc(x) - if integrand.expressions[1] == CSC(symbol) and integrand.expressions[2] == COT(symbol) then - return -CSC(symbol) - end - end - - return nil - end - - if integrand:type() == Logarithm then - -- int(log_n(x), x) = (x*ln(x)-x)/ln(n) - if integrand.base:freeof(symbol) and integrand.expression == symbol then - return (symbol * LN(symbol) - symbol) / LN(integrand.base) - end - - return nil - end - - if integrand:type() == TrigExpression then - if integrand == SIN(symbol) then - return -COS(symbol) - end - - if integrand == COS(symbol) then - return SIN(symbol) - end - - if integrand == TAN(symbol) then - return -LN(COS(symbol)) - end - - if integrand == CSC(symbol) then - return -LN(CSC(symbol)+COT(symbol)) - end - - if integrand == SEC(symbol) then - return LN(SEC(symbol) + TAN(symbol)) - end - - if integrand == COT(symbol) then - return LN(SIN(symbol)) - end - - if integrand == ARCSIN(symbol) then - return symbol*ARCSIN(symbol) + (Integer.one()-symbol^(Integer(2)))^(Integer.one()/Integer(2)) - end - - if integrand == ARCCOS(symbol) then - return symbol*ARCCOS(symbol) - (Integer.one()-symbol^(Integer(2)))^(Integer.one()/Integer(2)) - end - - if integrand == ARCTAN(symbol) then - return symbol*ARCTAN(symbol) - (Integer.one()/Integer(2))*LN(Integer.one()+symbol^Integer(2)) - end - - if integrand == ARCCSC(symbol) then - return symbol*ARCCSC(symbol) + LN(symbol*(Integer.one()+(Integer.one()-symbol^(Integer(-2)))^(Integer.one()/Integer(2)))) - end - - if integrand == ARCSEC(symbol) then - return symbol*ARCSEC(symbol) - LN(symbol*(Integer.one()+(Integer.one()-symbol^(Integer(-2)))^(Integer.one()/Integer(2)))) - end - - if integrand == ARCCOT(symbol) then - return symbol*ARCCOT(symbol) + (Integer.one()/Integer(2))*LN(Integer.one()+symbol^Integer(2)) - end - end - - return nil -end - ---- Uses linearity to break up the integral and integrate each piece. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.linearproperties(integral) - local expression = integral.expression - local symbol = integral.symbol - local es = integral.enhancedsubstitution - - if expression:type() == BinaryOperation then - if expression.operation == BinaryOperation.MUL then - local freepart = Integer.one() - local variablepart = Integer.one() - for _, term in ipairs(expression.expressions) do - if term:freeof(symbol) then - freepart = freepart*term - else - variablepart = variablepart*term - end - end - if freepart == Integer.one() then - return nil - end - integral.expression = variablepart - integral.symbol = symbol - integral.enhancedsubstitution = es - local F = IntegralExpression.integrate(integral) - if F then - return freepart*F - end - return nil - end - - if expression.operation == BinaryOperation.ADD then - local sum = Integer.zero() - for _, term in ipairs(expression.expressions) do - integral.expression = term - integral.symbol = symbol - integral.enhancedsubstitution = es - local F = IntegralExpression.integrate(integral) - if F then - sum = sum + F - else - return nil - end - - end - return sum - end - end - - return nil -end - ---- Attempts u-substitutions to evaluate the integral. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.substitutionmethod(integral) - local expression = integral.expression - local symbol = integral.symbol - local es = integral.enhancedsubstitution - - local P = IntegralExpression.trialsubstitutions(expression) - local F = nil - local i = 1 - - while not F and i <= #P do - local g = P[i] - if g ~= symbol and not g:freeof(symbol) then - local subsymbol = SymbolExpression("u") - if symbol == SymbolExpression("u") then - subsymbol = SymbolExpression("v") - end - local u = (expression / (DerivativeExpression(g, symbol))):autosimplify() - u = u:substitute({[g]=subsymbol}):autosimplify() - - --factor u and cancel like non-constant terms - u = u:factor():autosimplify() - - if u:freeof(symbol) then - integral.expression = u - integral.symbol = subsymbol - integral.enhancedsubstitution = es - F = IntegralExpression.integrate(integral) - if F then - if integral.recursive then - F = F:substitute({[subsymbol]=g}) - end - return F - end - end - end - i = i + 1 - end - - return F -end - ---- Attempts u-substitutions to evaluate the integral, including solving for the original variable and substituting the result into the expression. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.enhancedsubstitutionmethod(integral) - local expression = integral.expression - local symbol = integral.symbol - local es = integral.enhancedsubstitution - - local P = IntegralExpression.trialsubstitutions(expression) - local F = nil - local i = 1 - - while not F and i <= #P do - local g = P[i] - if g ~= symbol and not g:freeof(symbol) then - local subsymbol = SymbolExpression("u") - if symbol == SymbolExpression("u") then - subsymbol = SymbolExpression("v") - end - local u = (expression / (DerivativeExpression(g, symbol))):autosimplify() - u = u:substitute({[g]=subsymbol}):autosimplify() - - --factor u and cancel like non-constant terms - u = u:factor():autosimplify() - - if integral.enhancedsubstitution > Integer.zero() then - local f = Equation(subsymbol, g):solvefor(symbol) - if f.lhs == symbol then - u = u:substitute({[symbol]=f.rhs}):autosimplify() - integral.expression = u - integral.symbol = subsymbol - integral.enhancedsubstitution = integral.enhancedsubstitution - Integer.one() - F = IntegralExpression.integrate(integral) - if F then - if integral.recursive then - F = F:substitute({[subsymbol]=g}) - end - return F - end - integral.enhancedsubstitution = integral.enhancedsubstitution + Integer.one() - end - end - end - i = i + 1 - end - - return F -end - ---- Generates a list of possible u-substitutions to attempt ---- @param expression Expression ---- @return table -function IntegralExpression.trialsubstitutions(expression) - local substitutions = {} - - -- Recursive part - evaluates each term in a product. - if expression:type() == BinaryOperation and expression.operation == BinaryOperation.MUL then - substitutions[#substitutions+1] = expression - for _, term in ipairs(expression.expressions) do - substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(term)) - end - end - - --Recursive part - evaluates each term in a sum. - if expression:type() == BinaryOperation and expression.operation == BinaryOperation.ADD then - substitutions[#substitutions+1] = expression - for _,term in ipairs(expression.expressions) do - substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(term)) - end - end - - -- Function forms and arguments of function forms (includes a recursive part) - if expression:type() == TrigExpression or expression:type() == Logarithm then - substitutions[#substitutions+1] = expression - if not expression.expression:isatomic() then - substitutions[#substitutions+1] = expression.expression - end - substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(expression.expression)) - end - - -- Bases and exponents of powers - if expression:type() == BinaryOperation and expression.operation == BinaryOperation.POW then - substitutions[#substitutions+1] = expression - -- Atomic expressions are technically valid substitutions, but they won't be useful - if not expression.expressions[1]:isatomic() then - --substitutions[#substitutions+1] = expression.expressions[1] - substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(expression.expressions[1])) - end - if not expression.expressions[2]:isatomic() then - --substitutions[#substitutions+1] = expression.expressions[2] - substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(expression.expressions[2])) - end - end - - return substitutions -end - - ---- Uses Lazard, Rioboo, Rothstein, and Trager's method to integrate rational functions. ---- This is mostly to try to avoid factoring and finding the roots of the full denominator whenever possible. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.rationalfunction(integral) - local expression = integral.expression - local symbol = integral.symbol - local es = integral.enhancedsubstitution - - -- Type checking and conversion to polynomial type. - local f, g, fstat, gstat - if expression:type() == BinaryOperation and expression.operation == BinaryOperation.POW and expression.expressions[2] == Integer(-1) then - g, gstat = expression.expressions[1]:topolynomial() - if not gstat then - return nil - end - f = PolynomialRing({Integer.one()}, g.symbol) - else - if expression:type() ~= BinaryOperation or expression.operation ~= BinaryOperation.MUL or expression.expressions[3] then - return nil - end - if expression.expressions[2]:type() == BinaryOperation and expression.expressions[2].operation == BinaryOperation.POW and expression.expressions[2].expressions[2] == Integer(-1) then - if expression.expressions[1].topolynomial ~=nil and expression.expressions[2].expressions[1].topolynomial ~=nil then - f, fstat = expression.expressions[1]:topolynomial() - g, gstat = expression.expressions[2].expressions[1]:topolynomial() - end - elseif expression.expressions[1]:type() == BinaryOperation and expression.expressions[1].operation == BinaryOperation.POW and expression.expressions[1].expressions[2] == Integer(-1) then - if expression.expressions[2].topolynomial ~= nil and expression.expressions[1].expressions[1].topolynomial ~= nil then - f, fstat = expression.expressions[2]:topolynomial() - g, gstat = expression.expressions[1].expressions[1]:topolynomial() - end - else - return nil - end - - if not fstat or not gstat or f.symbol ~= symbol.symbol or g.symbol ~= symbol.symbol then - return nil - end - end - - -- Explicit handling of degree 1 or less over a binomial. - do - local disc = g.coefficients[1]*g.coefficients[1]-Integer(4)*g.coefficients[2]*g.coefficients[0] - if f.degree <= Integer.one() and g.degree == Integer(2) and disc < Integer.zero() then - return (f.coefficients[1] * LN(g.coefficients[0] + g.coefficients[1] * symbol + g.coefficients[2] * symbol ^ Integer(2))/(Integer(2) * g.coefficients[2]) + (Integer(2)*f.coefficients[0]*g.coefficients[2] - f.coefficients[1]*g.coefficients[1]) / (g.coefficients[2] * sqrt(Integer(4)*g.coefficients[0]*g.coefficients[2] - g.coefficients[1] ^ Integer(2))) * ARCTAN((Integer(2)*g.coefficients[2]*symbol+g.coefficients[1]) / sqrt(Integer(4)*g.coefficients[0]*g.coefficients[2]-g.coefficients[1] ^ Integer(2)))):autosimplify() - end - end - - -- If the polynomials are not relatively prime, divides out the common factors. - local gcd = PolynomialRing.gcd(f, g) - if gcd ~= Integer.one() then - f, g = f // gcd, g // gcd - end - - -- Seperates out the polynomial part and rational part and integrates the polynomial part. - local q, h = f:divremainder(g) - integral.expression = q - integral.symbol = symbol - integral.enhancedsubstitution = es - U = IntegralExpression.integrate(integral) - - if h == Integer.zero() then - return U - end - - -- Performs partial fraction decomposition into square-free denominators on the rational part. - local gg = g:squarefreefactorization() - local pfd = PolynomialRing.partialfractions(h, g, gg) - - -- Hermite reduction. - local V = Integer.zero() - for _, term in ipairs(pfd.expressions) do - local i = #term.expressions - if i > 1 then - for j = 1, i-1 do - local n = term.expressions[j].expressions[1] - local d = term.expressions[j].expressions[2].expressions[1] - local p = term.expressions[j].expressions[2].expressions[2] - - local _, s, t = PolynomialRing.extendedgcd(d, d:derivative()) - s = s * n - t = t * n - V = V - t / ((p-Integer.one()) * BinaryOperation.POWEXP({d, p-Integer.one()})) - term.expressions[j+1].expressions[1] = term.expressions[j+1].expressions[1] + s + t:derivative() / (p-Integer.one()) - end - end - end - - --Lazard-Rioboo-Trager method. - local W = Integer.zero() - for _, term in ipairs(pfd.expressions) do - local a = term.expressions[#term.expressions].expressions[1] - local b = term.expressions[1].expressions[2].expressions[1] - local y = a - b:derivative() * PolynomialRing({Integer.zero(), Integer.one()}, "_") - local r = PolynomialRing.resultant(b, y) - - - local rr = r:squarefreefactorization() - local remainders = PolynomialRing.monicgcdremainders(b, y) - for pos, factor in ipairs(rr.expressions) do - if pos > 1 then - local re = factor.expressions[1] - local e = factor.expressions[2] - local roots = re:roots() - for _, root in ipairs(roots) do - local w - for _, remainder in ipairs(remainders) do - if remainder.degree == e then - w = remainder - break - end - end - W = W + root*LN(w:substitute({[SymbolExpression("_")] = root})) - end - end - end - end - - return U + V + W -end - - ---- Attempts integration by parts for expressions with a polynomial factor in them. Other product expressions use Euler's formula. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.partsmethod(integral) - local expression = integral.expression - local symbol = integral.symbol - local es = integral.enhancedsubstitution - - if expression:type() ~= BinaryOperation or expression.operation ~= BinaryOperation.MUL then - return - end - - local u - local vp = Integer.one() - --looking for ILATE - for _, exp in ipairs(expression:subexpressions()) do - if exp:type() == TrigExpression and (exp.name == "arctan" or exp.name == "arccos" or exp.name == "arcsin" or exp.name == "arccot" or exp.name == "arcsec" or exp.name == "arccsc") then - u = exp - else - vp = vp * exp - end - end - - if not u or u:freeof(symbol) then - goto skipI - else - vp = vp:autosimplify() - end - - --if vp:type() == Logarithm or vp.topolynomial or (vp:type() == TrigExpression and (vp.name == "cos" or vp.name == "sin")) or (vp.operation == BinaryOperation.POW and vp.expressions[1]:freeof(symbol)) then - if select(2,vp:topolynomial()) then - integral.expression = vp - integral.symbol = symbol - integral.enhancedsubstitution = es - local v = IntegralExpression.integrate(integral) - if not v then - goto skipI - end - - local up = DerivativeExpression(u, symbol):autosimplify() - - integral.expression = v*up - integral.symbol = symbol - integral.enhancedsubstitution = es - local vup = IntegralExpression.integrate(integral) - if not vup then - goto skipI - end - - local result = u*v - vup - - return result:autosimplify() - end - ::skipI:: - - local u - local vp = Integer.one() - --looking for LATE - for _, exp in ipairs(expression:subexpressions()) do - if exp:type() == Logarithm then - u = exp - else - vp = vp * exp - end - end - - if not u or u:freeof(symbol) then - goto skipL - else - vp = vp:autosimplify() - end - - --if vp.topolynomial or (vp:type() == TrigExpression and (vp.name == "cos" or vp.name == "sin")) or (vp.operation == BinaryOperation.POW and vp.expressions[1]:freeof(symbol)) then - if select(2,vp:topolynomial()) then - integral.expression = vp - integral.symbol = symbol - integral.enhancedsubstitution = es - local v = IntegralExpression.integrate(integral) - if not v then - goto skipL - end - - local up = DerivativeExpression(u, symbol):autosimplify() - - integral.expression = v*up - integral.symbol = symbol - integral.enhancedsubstitution = es - local vup = IntegralExpression.integrate(integral) - if not vup then - goto skipL - end - - local result = u*v - vup - - return result:autosimplify() - end - ::skipL:: - - local u - local vp = Integer.one() - --looking for ATE - for _, exp in ipairs(expression:subexpressions()) do - local _, bool = exp:topolynomial() - if bool then - u = exp - else - vp = vp * exp - end - end - - if not u or u:freeof(symbol) then - return - else - vp = vp:autosimplify() - end - - if (vp:type() == TrigExpression and (vp.name == "cos" or vp.name == "sin")) or (vp.operation == BinaryOperation.POW and vp.expressions[1]:freeof(symbol)) then - local results = {} - while u ~= Integer.zero() do - integral.expression = vp - integral.symbol = symbol - integral.enhancedsubstitution = es - local v = IntegralExpression.integrate(integral):unlock():autosimplify() - if not v then - return - end - local up = DerivativeExpression(u, symbol):autosimplify() - - if not integral.recursive then - return (u*v - IntegralExpression(v*up, symbol):nonrecursive():lock(Expression.NIL, true)):autosimplify() - end - - results[#results+1] = u*v - u = up - vp = v - end - - local result = results[#results] - for i=#results-1,1,-1 do - result = results[i] - result - end - - return result:autosimplify() - end -end - ---- Attempts integration using Euler's formula and kind. Alternative for integration by parts for many expressions. ---- @param integral IntegralExpression ---- @return Expression|nil -function IntegralExpression.eulersformula(integral) - local expression = integral.expression - local symbol = integral.symbol - local es = integral.enhancedsubstitution - - local new = expression:substitute({[COS(symbol)] = (E^(I*symbol) + E^(-I*symbol))/Integer(2), - [SIN(symbol)] = (E^(I*symbol) - E^(-I*symbol))/(Integer(2)*I)}) - - if new == expression then - return - end - - integral.expression = new:autosimplify():expand() - integral.symbol = symbol - integral.enhancedsubstitution = es - local complexresult = IntegralExpression.integrate(integral) - if not complexresult then - return - end - - -- TODO: Proper complex number conversion methods - local function converttorectangular(exp) - exp = exp:expand() - local results = {} - for index, sub in ipairs(exp:subexpressions()) do - results[index] = converttorectangular(sub) - end - local converted = exp:setsubexpressions(results) - - if converted.operation == BinaryOperation.POW and converted.expressions[1] == E and converted.expressions[2].operation == BinaryOperation.MUL then - local ipart - local rest = Integer.one() - for _, factor in ipairs(converted.expressions[2]:subexpressions()) do - if factor == I then - ipart = true - else - rest = rest * factor - end - end - if ipart then - return (COS(rest) + I*SIN(rest)):autosimplify() - end - end - - return converted - end - - return converttorectangular(complexresult:autosimplify()):expand():autosimplify() - -end - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new integral operation with the given symbol and expression. ---- @param expression Expression ---- @param symbol SymbolExpression ---- @param lower Expression ---- @param upper Expression -function IntegralExpression:new(expression, symbol, lower, upper) - local o = {} - local __o = Copy(__ExpressionOperations) - - if not symbol or not expression then - error("Send wrong number of parameters: integrals must have a variable to integrate with respect to and an expression to integrate.") - end - - if lower and not upper then - error("Send wrong number of parameters: definite integrals must have an upper and a lower bound.") - end - - o.symbol = symbol - o.expression = Copy(expression) - o.upper = Copy(upper) - o.lower = Copy(lower) - o.recursive = true - - o.attempts = {} - o.results = {} - o.enhancedsubstitution = IntegralExpression.ENHANCEDSUBSTITUTIONRECURSIONLIMIT - - __o.__index = IntegralExpression - __o.__tostring = function(a) - if a:isdefinite() then - return 'int(' .. tostring(a.expression) .. ", " .. tostring(a.symbol) .. ", ".. tostring(a.lower) .. ', ' .. tostring(a.upper) .. ')' - end - return 'int(' .. tostring(a.expression) .. ", " .. tostring(a.symbol) .. ')' - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway - if not b:type() == IntegralExpression then - return false - end - return a.symbol == b.symbol and a.expression == b.expression and a.upper == b.upper and a.lower == b.lower - end - o = setmetatable(o, __o) - - return o -end - ---- Returns true if the integral is definite, i.e., has an upper and lower bound. ---- @return boolean -function IntegralExpression:isdefinite() - return self.upper ~= nil -end - ---- Sets the integral to not autosimplify other integral expressions that are produced by the integration process. ---- THIS METHOD MUTATES THE OBJECT IT IS CALLED ON. -function IntegralExpression:nonrecursive() - self.recursive = false - return self -end - ---- @return Expression -function IntegralExpression:autosimplify() - local arg = IntegralExpression(self.expression, self.symbol) - local integrated = IntegralExpression.integrate(arg) - - -- Our expression could not be integrated. - if not integrated then - return self - end - - if not self.recursive then - return integrated:autosimplify():unlock(true) - end - - if self:isdefinite() then - return (integrated:substitute({[self.symbol]=self.upper}) - integrated:substitute({[self.symbol]=self.lower})):autosimplify() - end - - return integrated:autosimplify() -end - - ---- @return table -function IntegralExpression:subexpressions() - if self:isdefinite() then - return {self.expression, self.symbol, self.lower, self.upper} - end - - return {self.expression, self.symbol} -end - ---- @param subexpressions table ---- @return IntegralExpression -function IntegralExpression:setsubexpressions(subexpressions) - local out = IntegralExpression(subexpressions[1], subexpressions[2], subexpressions[3], subexpressions[4]) - - return out; -end - --- function IntegralExpression:freeof(symbol) --- if self:isdefinite() then --- return self.expression:freeof(symbol) and self.upper:freeof(symbol) and self.lower:freeof(symbol) --- end --- return self.expression:freeof(symbol) --- end - --- -- Substitutes each expression for a new one. --- function IntegralExpression:substitute(map) --- for expression, replacement in pairs(map) do --- if self == expression then --- return replacement --- end --- end --- -- Typically, we only perform substitution on autosimplified expressions, so this won't get called. May give strange results, i.e., --- -- substituting and then evaluating the integral may not return the same thing as evaluating the integral and then substituting. --- if self:isdefinite() then --- return IntegralExpression(self.symbol, self.expression:substitute(map), self.upper:substitute(map), self.lower:substitute(map)) --- end --- return IntegralExpression(self.symbol, self.expression:substitute(map)) --- end - ---- @param other Expression ---- @return boolean -function IntegralExpression:order(other) - if other:type() ~= IntegralExpression then - return false - end - - if self.symbol ~= other.symbol then - return self.symbol:order(other.symbol) - end - - return self.expression:order(other.expression) -end - ---- @return string -function IntegralExpression:tolatex() - if self:isdefinite() then - return '\\int_{' .. self.lower:tolatex() .. '}^{' .. self.upper:tolatex() .. '}{' .. self.expression:tolatex() .. '\\mathop{d' .. self.symbol:tolatex() .. '}}' - end - return '\\int{' .. self.expression:tolatex() .. '\\mathop{d' .. self.symbol:tolatex() .. '}}' -end - - ------------------ --- Inheritance -- ------------------ -__IntegralExpression.__index = CompoundExpression -__IntegralExpression.__call = IntegralExpression.new -IntegralExpression = setmetatable(IntegralExpression, __IntegralExpression) - ----------------------- --- Static constants -- ----------------------- -INT = function(symbol, expression, lower, upper) - return IntegralExpression(symbol, expression, lower, upper) -end - ----------------------- --- Static constants -- ----------------------- - --- Limit for the maximum number of full u-subs to attempts for any integral. --- This should be low, since integrals are highly unlikely to need more than 1 or 2 u-subs, and gives exponentially worse performance the higher the number is. -IntegralExpression.ENHANCEDSUBSTITUTIONRECURSIONLIMIT = Integer(2) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/calculus/luacas-calculus_init.lua b/macros/luatex/latex/luacas/tex/calculus/luacas-calculus_init.lua new file mode 100644 index 0000000000..89b38b650e --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/luacas-calculus_init.lua @@ -0,0 +1,6 @@ +-- Loads calculus files in the correct order. +require("algebra.luacas-algebra_init") + +require("calculus.luacas-derivativeexpression") +require("calculus.luacas-integralexpression") +require("calculus.luacas-diffexpression") diff --git a/macros/luatex/latex/luacas/tex/calculus/luacas-derivativeexpression.lua b/macros/luatex/latex/luacas/tex/calculus/luacas-derivativeexpression.lua new file mode 100644 index 0000000000..b757759c4c --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/luacas-derivativeexpression.lua @@ -0,0 +1,265 @@ +--- @class DerivativeExpression +--- An expression for a single-variable derivative of an expression. +--- @field symbol SymbolExpression +--- @field expression Expression +DerivativeExpression = {} +__DerivativeExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +-- Creates a new single-variable derivative operation with the given symbol and expression. +--- @param expression Expression +--- @param symbol Symbol +--- @return DerivativeExpression +function DerivativeExpression:new(expression, symbol) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.expression = Copy(expression) + o.symbol = symbol or SymbolExpression("x") + + __o.__index = DerivativeExpression + __o.__tostring = function(a) + return '(d/d' .. tostring(a.symbol) .. " " .. tostring(a.expression) .. ')' + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway + if not b:type() == DerivativeExpression then + return false + end + return a.symbol == b.symbol and a.expression == b.expression + end + o = setmetatable(o, __o) + + return o +end + +--- @return Expression +function DerivativeExpression:evaluate() + local exp = self.expression + + -- The derivative of a constant is 0 + if exp:isconstant() then + return Integer.zero() + end + + -- The derivative of a symbol is either 1 or 0 + if exp:type() == SymbolExpression then + if self.symbol == exp then + return Integer.one() + end + return Integer.zero() + end + + -- Chain rule for arbitrary functions + + if exp:type() == FunctionExpression then + local results = {} + for index,expression in ipairs(exp.expressions) do + local dout = FunctionExpression(exp.name,exp.expressions,exp.derivatives) + dout.variables = exp.variables + dout.derivatives[index] = dout.derivatives[index]+Integer.one() + local dinn = DerivativeExpression(expression,self.symbol):evaluate() + results[index] = dout*dinn + end + return BinaryOperation(BinaryOperation.ADD,results):autosimplify() + end + + --if exp:type() == FunctionExpression then + -- local results = {} + -- for index,expression in ipairs(exp.expressions) do + -- local inn = DerivativeExpression(expression,self.symbol):autosimplify() + -- local out = Copy(exp) + -- out.orders[index] = out.orders[index] + Integer.one() + -- local result = inn*out + -- table.insert(results,result) + -- end + -- return BinaryOperation(BinaryOperation.ADD,results):autosimplify() + --end + + --if exp:type() == FunctionExpression then + -- if exp.expressions[2] then + -- return self + -- end + -- return DerivativeExpression(exp.expressions[1], self.symbol) * FunctionExpression(exp.name, exp.expressions, exp.orders[1] + Integer.one(), exp.variables[1]):autosimplify() + --end + + -- Chain rule for trig functions + if exp:type() == TrigExpression then + local internal = DerivativeExpression(exp.expression, self.symbol) + + if exp.name == "sin" then + return (internal * COS(exp.expression)):autosimplify() + end + if exp.name == "cos" then + return (internal * -SIN(exp.expression)):autosimplify() + end + if exp.name == "tan" then + return (internal * SEC(exp.expression)^Integer(2)):autosimplify() + end + if exp.name == "csc" then + return (internal * -CSC(exp.expression)*COT(exp.expression)):autosimplify() + end + if exp.name == "sec" then + return (internal * -SEC(exp.expression)*TAN(exp.expression)):autosimplify() + end + if exp.name == "cot" then + return (internal * -CSC(exp.expression)^Integer(2)):autosimplify() + end + if exp.name == "arcsin" then + return (internal / (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2))):autosimplify() + end + if exp.name == "arccos" then + return (-internal / (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2))):autosimplify() + end + if exp.name == "arctan" then + return (internal / (Integer(1)+exp.expression^Integer(2))):autosimplify() + end + if exp.name == "arccsc" then + return (-internal / (ABS(exp.expression) * (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2)))):autosimplify() + end + if exp.name == "arcsec" then + return (internal / (ABS(exp.expression) * (Integer(1)-exp.expression^Integer(2))^(Integer(1)/Integer(2)))):autosimplify() + end + if exp.name == "arccot" then + return (-internal / (Integer(1)+exp.expression^Integer(2))):autosimplify() + end + end + + -- TODO: Piecewise functions + if self:type() == AbsExpression then + return DerivativeExpression(self.expression, self.symbol):autosimplify() + end + + -- Uses linearity of derivatives to evaluate sum expressions + if exp.operation == BinaryOperation.ADD then + local parts = {} + for i, expression in pairs(exp.expressions) do + parts[i] = DerivativeExpression(expression, self.symbol) + end + return BinaryOperation(BinaryOperation.ADD, parts):autosimplify() + end + + -- Uses product rule to evaluate product expressions + if exp.operation == BinaryOperation.MUL then + local sums = {} + for i, expression in pairs(exp.expressions) do + local products = {} + for j, innerexpression in pairs(exp.expressions) do + if i ~= j then + products[j] = innerexpression + else + products[j] = DerivativeExpression(innerexpression, self.symbol) + end + end + sums[i] = BinaryOperation(BinaryOperation.MUL, products) + end + return BinaryOperation(BinaryOperation.ADD, sums):autosimplify() + end + + -- Uses the generalized power rule to evaluate power expressions + if exp.operation == BinaryOperation.POW then + local base = exp.expressions[1] + local exponent = exp.expressions[2] + + return BinaryOperation.MULEXP({ + BinaryOperation.POWEXP({base, exponent}), + BinaryOperation.ADDEXP({ + BinaryOperation.MULEXP({ + DD(base, self.symbol), + BinaryOperation.DIVEXP({exponent, base})}), + BinaryOperation.MULEXP({ + DD(exponent, self.symbol), + LN(base)})}) + }):autosimplify() + end + + if exp:type() == Logarithm then + local base = exp.base + local expression = exp.expression + + return BinaryOperation.SUBEXP({ + BinaryOperation.DIVEXP({DD(expression, self.symbol), + BinaryOperation.MULEXP({expression, LN(base)})}), + + BinaryOperation.DIVEXP({ + BinaryOperation.MULEXP({LN(expression), DD(base, self.symbol)}), + BinaryOperation.MULEXP({BinaryOperation.POWEXP({LN(base), Integer(2)}), base}) + }) + + }):autosimplify() + end + + return self +end + +--- @return Expression +function DerivativeExpression:autosimplify() + return DerivativeExpression(self.expression:autosimplify(), self.symbol):evaluate() +end + +--- @return table +function DerivativeExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return DerivativeExpression +function DerivativeExpression:setsubexpressions(subexpressions) + return DerivativeExpression(subexpressions[1], self.symbol) +end + +-- function DerivativeExpression:freeof(symbol) +-- return self.symbol.freeof(symbol) and self.expression:freeof(symbol) +-- end + +-- Substitutes each expression for a new one. +-- function DerivativeExpression:substitute(map) +-- for expression, replacement in pairs(map) do +-- if self == expression then +-- return replacement +-- end +-- end +-- -- Typically, we only perform substitution on autosimplified expressions, so this won't get called. May give strange results, i.e., +-- -- substituting and then evaluating the derivative may not return the same thing as evaluating the derivative and then substituting. +-- return DerivativeExpression(self.expression:substitute(map), self.symbol) +-- end + +--- @param other Expression +--- @return boolean +function DerivativeExpression:order(other) + if other:type() == IntegralExpression then + return true + end + + if other:type() ~= DerivativeExpression then + return false + end + + if self.symbol ~= other.symbol then + return self.symbol:order(other.symbol) + end + + return self.expression:order(other.expression) +end + +--- @return string +function DerivativeExpression:tolatex() + return '\\frac{d}{d' .. self.symbol:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)' +end + +----------------- +-- Inheritance -- +----------------- +__DerivativeExpression.__index = CompoundExpression +__DerivativeExpression.__call = DerivativeExpression.new +DerivativeExpression = setmetatable(DerivativeExpression, __DerivativeExpression) + +---------------------- +-- Static constants -- +---------------------- +DD = function(expression, symbol) + return DerivativeExpression(expression, symbol) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/calculus/luacas-diffexpression.lua b/macros/luatex/latex/luacas/tex/calculus/luacas-diffexpression.lua new file mode 100644 index 0000000000..df1dc90f33 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/luacas-diffexpression.lua @@ -0,0 +1,190 @@ +--- @class DiffExpression +--- An expression for a multi-variable higher-order derivatives of an expression. +--- @field symbols SymbolExpression +--- @field expression Expression + +DiffExpression = {} +__DiffExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +-- Creates a new derivative operation with the given symbols and expression. +--- @param expression Expression +--- @param symbols table +--- @return DiffExpression +function DiffExpression:new(expression,symbols) + local o = {} + local __o = Copy(__ExpressionOperations) + + o.symbols = Copy(symbols) + o.degree = #o.symbols + o.expression = Copy(expression) + + __o.__tostring = function(a) + local varlist = '(d' + if a.degree == 1 then + varlist = varlist .. '/d' .. tostring(a.symbols[1]) .. " " .. tostring(a.expression) .. ')' + end + if a.degree > 1 then + varlist = varlist .. '^' .. tostring(a.degree) .. '/' + local varnum = 1 + for index = 1, #a.symbols do + local var = a.symbols[#a.symbols-index+1] + if a.symbols[#a.symbols-index] == var then + varnum = varnum + 1 + goto nextvar + end + if a.symbols[#a.symbols-index] ~=var then + if varnum == 1 then + varlist = varlist .. 'd' .. tostring(var) + else + varlist = varlist .. 'd' .. tostring(var) .. '^' .. tostring(varnum) + end + varnum = 1 + end + ::nextvar:: + end + varlist = varlist .. " " .. tostring(a.expression) .. ')' + end + return varlist + end + + __o.__index = DiffExpression + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway + if not b:type() == DiffExpression then + return false + end + if a.expression ~= b.expression then + return false + end + local loc = 1 + while a.symbols[loc] or b.symbols[loc] do + if not a.symbols[loc] or not b.symbols[loc] or + (a.symbols[loc] ~= b.symbols[loc]) then + return false + end + loc = loc + 1 + end + return true + end + o = setmetatable(o, __o) + + return o +end + +--- @return Expression +function DiffExpression:evaluate() + local exp = self.expression + + for _,var in ipairs(self.symbols) do + exp = DerivativeExpression(exp,var):evaluate() + end + return exp +end + +--- @return Expression +function DiffExpression:autosimplify() + return DiffExpression(self.expression:autosimplify(), self.symbols):evaluate() +end + + +--- @return table +function DiffExpression:subexpressions() + return {self.expression} +end + +--- @param subexpressions table +--- @return DiffExpression +function DiffExpression:setsubexpressions(subexpressions) + return DiffExpression(subexpressions[1], self.symbols) +end + +--- @param other Expression +--- @return boolean +function DiffExpression:order(other) + if other:type() == IntegralExpression then + return true + end + + if other:type() ~= DiffExpression then + return false + end + + if self.degree > other.degree then + return false + end + + if self.degree < other.degree then + return true + end + + return self.expression:order(other.expression) +end + +--- @return string +function DiffExpression:tolatex() + local varlist = '\\frac{' + if self.degree == 1 then + varlist = varlist .. 'd}{d' .. self.symbols[1]:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)' + end + if self.degree > 1 then + local cvarlist = {} + local count = 1 + for index, var in ipairs(self.symbols) do + if var == self.symbols[index+1] then + count = count + 1 + else + table.insert(cvarlist,{var,count}) + count = 1 + end + end + if #cvarlist == 1 then + varlist = varlist .. 'd^{' .. self.degree .. '}}{' .. 'd' .. cvarlist[1][1]:tolatex() .. '^{' .. self.degree .. '}' + end + if #cvarlist > 1 then + varlist = varlist .. '\\partial^{' .. self.degree .. '}}{' + for index, varnum in ipairs(cvarlist) do + var = cvarlist[#cvarlist - index+1][1] + num = cvarlist[#cvarlist - index+1][2] + if num == 1 then + varlist = varlist .. '\\partial ' .. var:tolatex() + else + varlist = varlist .. '\\partial ' .. var:tolatex() .. '^{' .. num .. '}' + end + end + end + varlist = varlist .. '} \\left(' .. self.expression:tolatex() .. '\\right)' + end + return varlist +end + +----------------- +-- Inheritance -- +----------------- + +__DiffExpression.__index = CompoundExpression +__DiffExpression.__call = DiffExpression.new +DiffExpression = setmetatable(DiffExpression, __DiffExpression) + +---------------------- +-- Static constants -- +---------------------- + +diff = function(expression,...) + local symbols = {} + for i = 1, select("#",...) do + local var = select(i,...) + if #var == 0 then + table.insert(symbols,var) + end + if #var > 0 then + for index=1, RR(var[2]) do + table.insert(symbols,var[1]) + end + end + end + return DiffExpression(expression,symbols) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/calculus/luacas-integralexpression.lua b/macros/luatex/latex/luacas/tex/calculus/luacas-integralexpression.lua new file mode 100644 index 0000000000..5267127264 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/luacas-integralexpression.lua @@ -0,0 +1,934 @@ +--- @class IntegralExpression +--- An expression for the integral of an expression. +--- @field symbol SymbolExpression +--- @field expression Expression +--- @field upper Expression +--- @field lower Expression +--- @field attempts table +--- @field results table +--- @field enhancedsubstitution Integer +--- @field recursive boolean + +IntegralExpression = {} +__IntegralExpression = {} + + +-------------------------- +-- Static functionality -- +-------------------------- + +--- Recursive part of the indefinite integral operator. Returns nil if the expression could not be integrated. +--- We switch to prodcedural programming here because it is more natural. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.integrate(integral) + integral.expression = integral.expression:autosimplify() + + if not integral.recursive and #integral.attempts > 0 then + return Copy(integral:lock(Expression.NIL, true)) + end + + -- print(integral.expression) + + local F = IntegralExpression.table(integral) + if F then return F end + + -- If we see the same integrand again, and hasn't been solved already, then the integral can't be solved + local resultindex = Contains(integral.attempts, integral.expression) + if resultindex then + return integral.results[resultindex] + end + local newindex = #integral.attempts+1 + integral.attempts[newindex] = integral.expression + + -- print("Evalutaing: " .. tostring(integral.expression)) + + F = IntegralExpression.linearproperties(integral) + if F then + -- print("Linear Properties") + integral.results[newindex] = F + return F + end + + -- local exp = integral.expression + -- local sym = integral.symbol + -- local es = integral.enhancedsubstitution + -- integral.enhancedsubstitution = Integer.zero() + F = IntegralExpression.substitutionmethod(integral) + if F then + -- print("u-Substitution") + integral.results[newindex] = F + return F + end + -- integral.expression = exp + -- integral.symbol = sym + -- integral.enhancedsubstitution = es + + + F = IntegralExpression.rationalfunction(integral) + if F then + -- print("Rational Function") + integral.results[newindex] = F + return F + end + + F = IntegralExpression.partsmethod(integral) + if F then + -- print("Parts") + integral.results[newindex] = F + return F + end + + F = IntegralExpression.eulersformula(integral) + if F then + -- print("Euler's formula") + integral.results[newindex] = F + return F + end + + local expanded = integral.expression:expand() + if integral.expression ~= expanded then + integral.expression = expanded + F = IntegralExpression.integrate(integral) + if F then + -- print("Expanded") + integral.results[newindex] = F + return F + end + end + + expanded = (Integer.one()/((Integer.one()/integral.expression):autosimplify():expand())):autosimplify() + if integral.expression ~= expanded then + integral.expression = expanded + F = IntegralExpression.integrate(integral) + if F then + -- print("Inverse Expanded") + integral.results[newindex] = F + return F + end + end + + F = IntegralExpression.enhancedsubstitutionmethod(integral) + if F then + -- print("Enhanced u-Substitution") + integral.results[newindex] = F + return F + end + + return nil +end + +--- A table of basic integrals, returns nil if the integrand isn't in the table. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.table(integral) + local integrand = integral.expression + local symbol = integral.symbol + + -- Constant integrand rule - int(c, x) = c*x + if integrand:freeof(symbol) then + return integrand*symbol + end + + if integrand:type() == SymbolExpression then + + -- int(x, x) = x^2/2 + if integrand == symbol then + return integrand ^ Integer(2) / Integer(2) + end + + -- Constant integrand rule again + return integrand*symbol + end + + if integrand:type() == BinaryOperation then + + if integrand.operation == BinaryOperation.POW then + -- int(1/x, x) = ln(x) + if integrand.expressions[1] == symbol and integrand.expressions[2] == Integer(-1) then + return LN(symbol) + end + + -- Cavalieri's formula - int(x^n, x) = x^(n+1)/(n+1) + if integrand.expressions[1] == symbol and integrand.expressions[2]:freeof(symbol) then + return symbol ^ (integrand.expressions[2] + Integer.one()) / (integrand.expressions[2] + Integer.one()) + end + + -- int(n^x, x) = n^x/ln(n) + if integrand.expressions[1]:freeof(symbol) and integrand.expressions[2] == symbol then + return integrand / LN(integrand.expressions[1]) + end + + -- int(csc(x)^2, x) = -cot(x) + if integrand.expressions[1] == CSC(symbol) and integrand.expressions[2] == Integer(2) then + return -COT(symbol) + end + + -- int(sec(x)^2, x) = tan(x) + if integrand.expressions[1] == SEC(symbol) and integrand.expressions[2] == Integer(2) then + return TAN(symbol) + end + end + + if integrand.operation == BinaryOperation.MUL and #integrand.expressions == 2 then + -- int(tan(x)sec(x), x) = sec(x) + if integrand.expressions[1] == TAN(symbol) and integrand.expressions[2] == SEC(symbol) then + return SEC(symbol) + end + + -- int(csc(x)cot(x), x) = -csc(x) + if integrand.expressions[1] == CSC(symbol) and integrand.expressions[2] == COT(symbol) then + return -CSC(symbol) + end + end + + return nil + end + + if integrand:type() == Logarithm then + -- int(log_n(x), x) = (x*ln(x)-x)/ln(n) + if integrand.base:freeof(symbol) and integrand.expression == symbol then + return (symbol * LN(symbol) - symbol) / LN(integrand.base) + end + + return nil + end + + if integrand:type() == TrigExpression then + if integrand == SIN(symbol) then + return -COS(symbol) + end + + if integrand == COS(symbol) then + return SIN(symbol) + end + + if integrand == TAN(symbol) then + return -LN(COS(symbol)) + end + + if integrand == CSC(symbol) then + return -LN(CSC(symbol)+COT(symbol)) + end + + if integrand == SEC(symbol) then + return LN(SEC(symbol) + TAN(symbol)) + end + + if integrand == COT(symbol) then + return LN(SIN(symbol)) + end + + if integrand == ARCSIN(symbol) then + return symbol*ARCSIN(symbol) + (Integer.one()-symbol^(Integer(2)))^(Integer.one()/Integer(2)) + end + + if integrand == ARCCOS(symbol) then + return symbol*ARCCOS(symbol) - (Integer.one()-symbol^(Integer(2)))^(Integer.one()/Integer(2)) + end + + if integrand == ARCTAN(symbol) then + return symbol*ARCTAN(symbol) - (Integer.one()/Integer(2))*LN(Integer.one()+symbol^Integer(2)) + end + + if integrand == ARCCSC(symbol) then + return symbol*ARCCSC(symbol) + LN(symbol*(Integer.one()+(Integer.one()-symbol^(Integer(-2)))^(Integer.one()/Integer(2)))) + end + + if integrand == ARCSEC(symbol) then + return symbol*ARCSEC(symbol) - LN(symbol*(Integer.one()+(Integer.one()-symbol^(Integer(-2)))^(Integer.one()/Integer(2)))) + end + + if integrand == ARCCOT(symbol) then + return symbol*ARCCOT(symbol) + (Integer.one()/Integer(2))*LN(Integer.one()+symbol^Integer(2)) + end + end + + return nil +end + +--- Uses linearity to break up the integral and integrate each piece. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.linearproperties(integral) + local expression = integral.expression + local symbol = integral.symbol + local es = integral.enhancedsubstitution + + if expression:type() == BinaryOperation then + if expression.operation == BinaryOperation.MUL then + local freepart = Integer.one() + local variablepart = Integer.one() + for _, term in ipairs(expression.expressions) do + if term:freeof(symbol) then + freepart = freepart*term + else + variablepart = variablepart*term + end + end + if freepart == Integer.one() then + return nil + end + integral.expression = variablepart + integral.symbol = symbol + integral.enhancedsubstitution = es + local F = IntegralExpression.integrate(integral) + if F then + return freepart*F + end + return nil + end + + if expression.operation == BinaryOperation.ADD then + local sum = Integer.zero() + for _, term in ipairs(expression.expressions) do + integral.expression = term + integral.symbol = symbol + integral.enhancedsubstitution = es + local F = IntegralExpression.integrate(integral) + if F then + sum = sum + F + else + return nil + end + + end + return sum + end + end + + return nil +end + +--- Attempts u-substitutions to evaluate the integral. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.substitutionmethod(integral) + local expression = integral.expression + local symbol = integral.symbol + local es = integral.enhancedsubstitution + + local P = IntegralExpression.trialsubstitutions(expression) + local F = nil + local i = 1 + + while not F and i <= #P do + local g = P[i] + if g ~= symbol and not g:freeof(symbol) then + local subsymbol = SymbolExpression("u") + if symbol == SymbolExpression("u") then + subsymbol = SymbolExpression("v") + end + local u = (expression / (DerivativeExpression(g, symbol))):autosimplify() + u = u:substitute({[g]=subsymbol}):autosimplify() + + --factor u and cancel like non-constant terms + u = u:factor():autosimplify() + + if u:freeof(symbol) then + integral.expression = u + integral.symbol = subsymbol + integral.enhancedsubstitution = es + F = IntegralExpression.integrate(integral) + if F then + if integral.recursive then + F = F:substitute({[subsymbol]=g}) + end + return F + end + end + end + i = i + 1 + end + + return F +end + +--- Attempts u-substitutions to evaluate the integral, including solving for the original variable and substituting the result into the expression. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.enhancedsubstitutionmethod(integral) + local expression = integral.expression + local symbol = integral.symbol + local es = integral.enhancedsubstitution + + local P = IntegralExpression.trialsubstitutions(expression) + local F = nil + local i = 1 + + while not F and i <= #P do + local g = P[i] + if g ~= symbol and not g:freeof(symbol) then + local subsymbol = SymbolExpression("u") + if symbol == SymbolExpression("u") then + subsymbol = SymbolExpression("v") + end + local u = (expression / (DerivativeExpression(g, symbol))):autosimplify() + u = u:substitute({[g]=subsymbol}):autosimplify() + + --factor u and cancel like non-constant terms + u = u:factor():autosimplify() + + if integral.enhancedsubstitution > Integer.zero() then + local f = Equation(subsymbol, g):solvefor(symbol) + if f.lhs == symbol then + u = u:substitute({[symbol]=f.rhs}):autosimplify() + integral.expression = u + integral.symbol = subsymbol + integral.enhancedsubstitution = integral.enhancedsubstitution - Integer.one() + F = IntegralExpression.integrate(integral) + if F then + if integral.recursive then + F = F:substitute({[subsymbol]=g}) + end + return F + end + integral.enhancedsubstitution = integral.enhancedsubstitution + Integer.one() + end + end + end + i = i + 1 + end + + return F +end + +--- Generates a list of possible u-substitutions to attempt +--- @param expression Expression +--- @return table +function IntegralExpression.trialsubstitutions(expression) + local substitutions = {} + + -- Recursive part - evaluates each term in a product. + if expression:type() == BinaryOperation and expression.operation == BinaryOperation.MUL then + substitutions[#substitutions+1] = expression + for _, term in ipairs(expression.expressions) do + substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(term)) + end + end + + --Recursive part - evaluates each term in a sum. + if expression:type() == BinaryOperation and expression.operation == BinaryOperation.ADD then + substitutions[#substitutions+1] = expression + for _,term in ipairs(expression.expressions) do + substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(term)) + end + end + + -- Function forms and arguments of function forms (includes a recursive part) + if expression:type() == TrigExpression or expression:type() == Logarithm then + substitutions[#substitutions+1] = expression + if not expression.expression:isatomic() then + substitutions[#substitutions+1] = expression.expression + end + substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(expression.expression)) + end + + -- Bases and exponents of powers + if expression:type() == BinaryOperation and expression.operation == BinaryOperation.POW then + substitutions[#substitutions+1] = expression + -- Atomic expressions are technically valid substitutions, but they won't be useful + if not expression.expressions[1]:isatomic() then + --substitutions[#substitutions+1] = expression.expressions[1] + substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(expression.expressions[1])) + end + if not expression.expressions[2]:isatomic() then + --substitutions[#substitutions+1] = expression.expressions[2] + substitutions = JoinArrays(substitutions, IntegralExpression.trialsubstitutions(expression.expressions[2])) + end + end + + return substitutions +end + + +--- Uses Lazard, Rioboo, Rothstein, and Trager's method to integrate rational functions. +--- This is mostly to try to avoid factoring and finding the roots of the full denominator whenever possible. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.rationalfunction(integral) + local expression = integral.expression + local symbol = integral.symbol + local es = integral.enhancedsubstitution + + -- Type checking and conversion to polynomial type. + local f, g, fstat, gstat + if expression:type() == BinaryOperation and expression.operation == BinaryOperation.POW and expression.expressions[2] == Integer(-1) then + g, gstat = expression.expressions[1]:topolynomial() + if not gstat then + return nil + end + f = PolynomialRing({Integer.one()}, g.symbol) + else + if expression:type() ~= BinaryOperation or expression.operation ~= BinaryOperation.MUL or expression.expressions[3] then + return nil + end + if expression.expressions[2]:type() == BinaryOperation and expression.expressions[2].operation == BinaryOperation.POW and expression.expressions[2].expressions[2] == Integer(-1) then + if expression.expressions[1].topolynomial ~=nil and expression.expressions[2].expressions[1].topolynomial ~=nil then + f, fstat = expression.expressions[1]:topolynomial() + g, gstat = expression.expressions[2].expressions[1]:topolynomial() + end + elseif expression.expressions[1]:type() == BinaryOperation and expression.expressions[1].operation == BinaryOperation.POW and expression.expressions[1].expressions[2] == Integer(-1) then + if expression.expressions[2].topolynomial ~= nil and expression.expressions[1].expressions[1].topolynomial ~= nil then + f, fstat = expression.expressions[2]:topolynomial() + g, gstat = expression.expressions[1].expressions[1]:topolynomial() + end + else + return nil + end + + if not fstat or not gstat or f.symbol ~= symbol.symbol or g.symbol ~= symbol.symbol then + return nil + end + end + + -- Explicit handling of degree 1 or less over a binomial. + do + local disc = g.coefficients[1]*g.coefficients[1]-Integer(4)*g.coefficients[2]*g.coefficients[0] + if f.degree <= Integer.one() and g.degree == Integer(2) and disc < Integer.zero() then + return (f.coefficients[1] * LN(g.coefficients[0] + g.coefficients[1] * symbol + g.coefficients[2] * symbol ^ Integer(2))/(Integer(2) * g.coefficients[2]) + (Integer(2)*f.coefficients[0]*g.coefficients[2] - f.coefficients[1]*g.coefficients[1]) / (g.coefficients[2] * sqrt(Integer(4)*g.coefficients[0]*g.coefficients[2] - g.coefficients[1] ^ Integer(2))) * ARCTAN((Integer(2)*g.coefficients[2]*symbol+g.coefficients[1]) / sqrt(Integer(4)*g.coefficients[0]*g.coefficients[2]-g.coefficients[1] ^ Integer(2)))):autosimplify() + end + end + + -- If the polynomials are not relatively prime, divides out the common factors. + local gcd = PolynomialRing.gcd(f, g) + if gcd ~= Integer.one() then + f, g = f // gcd, g // gcd + end + + -- Seperates out the polynomial part and rational part and integrates the polynomial part. + local q, h = f:divremainder(g) + integral.expression = q + integral.symbol = symbol + integral.enhancedsubstitution = es + U = IntegralExpression.integrate(integral) + + if h == Integer.zero() then + return U + end + + -- Performs partial fraction decomposition into square-free denominators on the rational part. + local gg = g:squarefreefactorization() + local pfd = PolynomialRing.partialfractions(h, g, gg) + + -- Hermite reduction. + local V = Integer.zero() + for _, term in ipairs(pfd.expressions) do + local i = #term.expressions + if i > 1 then + for j = 1, i-1 do + local n = term.expressions[j].expressions[1] + local d = term.expressions[j].expressions[2].expressions[1] + local p = term.expressions[j].expressions[2].expressions[2] + + local _, s, t = PolynomialRing.extendedgcd(d, d:derivative()) + s = s * n + t = t * n + V = V - t / ((p-Integer.one()) * BinaryOperation.POWEXP({d, p-Integer.one()})) + term.expressions[j+1].expressions[1] = term.expressions[j+1].expressions[1] + s + t:derivative() / (p-Integer.one()) + end + end + end + + --Lazard-Rioboo-Trager method. + local W = Integer.zero() + for _, term in ipairs(pfd.expressions) do + local a = term.expressions[#term.expressions].expressions[1] + local b = term.expressions[1].expressions[2].expressions[1] + local y = a - b:derivative() * PolynomialRing({Integer.zero(), Integer.one()}, "_") + local r = PolynomialRing.resultant(b, y) + + + local rr = r:squarefreefactorization() + local remainders = PolynomialRing.monicgcdremainders(b, y) + for pos, factor in ipairs(rr.expressions) do + if pos > 1 then + local re = factor.expressions[1] + local e = factor.expressions[2] + local roots = re:roots() + for _, root in ipairs(roots) do + local w + for _, remainder in ipairs(remainders) do + if remainder.degree == e then + w = remainder + break + end + end + W = W + root*LN(w:substitute({[SymbolExpression("_")] = root})) + end + end + end + end + + return U + V + W +end + + +--- Attempts integration by parts for expressions with a polynomial factor in them. Other product expressions use Euler's formula. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.partsmethod(integral) + local expression = integral.expression + local symbol = integral.symbol + local es = integral.enhancedsubstitution + + if expression:type() ~= BinaryOperation or expression.operation ~= BinaryOperation.MUL then + return + end + + local u + local vp = Integer.one() + --looking for ILATE + for _, exp in ipairs(expression:subexpressions()) do + if exp:type() == TrigExpression and (exp.name == "arctan" or exp.name == "arccos" or exp.name == "arcsin" or exp.name == "arccot" or exp.name == "arcsec" or exp.name == "arccsc") then + u = exp + else + vp = vp * exp + end + end + + if not u or u:freeof(symbol) then + goto skipI + else + vp = vp:autosimplify() + end + + --if vp:type() == Logarithm or vp.topolynomial or (vp:type() == TrigExpression and (vp.name == "cos" or vp.name == "sin")) or (vp.operation == BinaryOperation.POW and vp.expressions[1]:freeof(symbol)) then + if select(2,vp:topolynomial()) then + integral.expression = vp + integral.symbol = symbol + integral.enhancedsubstitution = es + local v = IntegralExpression.integrate(integral) + if not v then + goto skipI + end + + local up = DerivativeExpression(u, symbol):autosimplify() + + integral.expression = v*up + integral.symbol = symbol + integral.enhancedsubstitution = es + local vup = IntegralExpression.integrate(integral) + if not vup then + goto skipI + end + + local result = u*v - vup + + return result:autosimplify() + end + ::skipI:: + + local u + local vp = Integer.one() + --looking for LATE + for _, exp in ipairs(expression:subexpressions()) do + if exp:type() == Logarithm then + u = exp + else + vp = vp * exp + end + end + + if not u or u:freeof(symbol) then + goto skipL + else + vp = vp:autosimplify() + end + + --if vp.topolynomial or (vp:type() == TrigExpression and (vp.name == "cos" or vp.name == "sin")) or (vp.operation == BinaryOperation.POW and vp.expressions[1]:freeof(symbol)) then + if select(2,vp:topolynomial()) then + integral.expression = vp + integral.symbol = symbol + integral.enhancedsubstitution = es + local v = IntegralExpression.integrate(integral) + if not v then + goto skipL + end + + local up = DerivativeExpression(u, symbol):autosimplify() + + integral.expression = v*up + integral.symbol = symbol + integral.enhancedsubstitution = es + local vup = IntegralExpression.integrate(integral) + if not vup then + goto skipL + end + + local result = u*v - vup + + return result:autosimplify() + end + ::skipL:: + + local u + local vp = Integer.one() + --looking for ATE + for _, exp in ipairs(expression:subexpressions()) do + local _, bool = exp:topolynomial() + if bool then + u = exp + else + vp = vp * exp + end + end + + if not u or u:freeof(symbol) then + return + else + vp = vp:autosimplify() + end + + if (vp:type() == TrigExpression and (vp.name == "cos" or vp.name == "sin")) or (vp.operation == BinaryOperation.POW and vp.expressions[1]:freeof(symbol)) then + local results = {} + while u ~= Integer.zero() do + integral.expression = vp + integral.symbol = symbol + integral.enhancedsubstitution = es + local v = IntegralExpression.integrate(integral):unlock():autosimplify() + if not v then + return + end + local up = DerivativeExpression(u, symbol):autosimplify() + + if not integral.recursive then + return (u*v - IntegralExpression(v*up, symbol):nonrecursive():lock(Expression.NIL, true)):autosimplify() + end + + results[#results+1] = u*v + u = up + vp = v + end + + local result = results[#results] + for i=#results-1,1,-1 do + result = results[i] - result + end + + return result:autosimplify() + end +end + +--- Attempts integration using Euler's formula and kind. Alternative for integration by parts for many expressions. +--- @param integral IntegralExpression +--- @return Expression|nil +function IntegralExpression.eulersformula(integral) + local expression = integral.expression + local symbol = integral.symbol + local es = integral.enhancedsubstitution + + local new = expression:substitute({[COS(symbol)] = (E^(I*symbol) + E^(-I*symbol))/Integer(2), + [SIN(symbol)] = (E^(I*symbol) - E^(-I*symbol))/(Integer(2)*I)}) + + if new == expression then + return + end + + integral.expression = new:autosimplify():expand() + integral.symbol = symbol + integral.enhancedsubstitution = es + local complexresult = IntegralExpression.integrate(integral) + if not complexresult then + return + end + + -- TODO: Proper complex number conversion methods + local function converttorectangular(exp) + exp = exp:expand() + local results = {} + for index, sub in ipairs(exp:subexpressions()) do + results[index] = converttorectangular(sub) + end + local converted = exp:setsubexpressions(results) + + if converted.operation == BinaryOperation.POW and converted.expressions[1] == E and converted.expressions[2].operation == BinaryOperation.MUL then + local ipart + local rest = Integer.one() + for _, factor in ipairs(converted.expressions[2]:subexpressions()) do + if factor == I then + ipart = true + else + rest = rest * factor + end + end + if ipart then + return (COS(rest) + I*SIN(rest)):autosimplify() + end + end + + return converted + end + + return converttorectangular(complexresult:autosimplify()):expand():autosimplify() + +end + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new integral operation with the given symbol and expression. +--- @param expression Expression +--- @param symbol SymbolExpression +--- @param lower Expression +--- @param upper Expression +function IntegralExpression:new(expression, symbol, lower, upper) + local o = {} + local __o = Copy(__ExpressionOperations) + + if not symbol or not expression then + error("Send wrong number of parameters: integrals must have a variable to integrate with respect to and an expression to integrate.") + end + + if lower and not upper then + error("Send wrong number of parameters: definite integrals must have an upper and a lower bound.") + end + + o.symbol = symbol + o.expression = Copy(expression) + o.upper = Copy(upper) + o.lower = Copy(lower) + o.recursive = true + + o.attempts = {} + o.results = {} + o.enhancedsubstitution = IntegralExpression.ENHANCEDSUBSTITUTIONRECURSIONLIMIT + + __o.__index = IntegralExpression + __o.__tostring = function(a) + if a:isdefinite() then + return 'int(' .. tostring(a.expression) .. ", " .. tostring(a.symbol) .. ", ".. tostring(a.lower) .. ', ' .. tostring(a.upper) .. ')' + end + return 'int(' .. tostring(a.expression) .. ", " .. tostring(a.symbol) .. ')' + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always rungs this anyway + if not b:type() == IntegralExpression then + return false + end + return a.symbol == b.symbol and a.expression == b.expression and a.upper == b.upper and a.lower == b.lower + end + o = setmetatable(o, __o) + + return o +end + +--- Returns true if the integral is definite, i.e., has an upper and lower bound. +--- @return boolean +function IntegralExpression:isdefinite() + return self.upper ~= nil +end + +--- Sets the integral to not autosimplify other integral expressions that are produced by the integration process. +--- THIS METHOD MUTATES THE OBJECT IT IS CALLED ON. +function IntegralExpression:nonrecursive() + self.recursive = false + return self +end + +--- @return Expression +function IntegralExpression:autosimplify() + local arg = IntegralExpression(self.expression, self.symbol) + local integrated = IntegralExpression.integrate(arg) + + -- Our expression could not be integrated. + if not integrated then + return self + end + + if not self.recursive then + return integrated:autosimplify():unlock(true) + end + + if self:isdefinite() then + return (integrated:substitute({[self.symbol]=self.upper}) - integrated:substitute({[self.symbol]=self.lower})):autosimplify() + end + + return integrated:autosimplify() +end + + +--- @return table +function IntegralExpression:subexpressions() + if self:isdefinite() then + return {self.expression, self.symbol, self.lower, self.upper} + end + + return {self.expression, self.symbol} +end + +--- @param subexpressions table +--- @return IntegralExpression +function IntegralExpression:setsubexpressions(subexpressions) + local out = IntegralExpression(subexpressions[1], subexpressions[2], subexpressions[3], subexpressions[4]) + + return out; +end + +-- function IntegralExpression:freeof(symbol) +-- if self:isdefinite() then +-- return self.expression:freeof(symbol) and self.upper:freeof(symbol) and self.lower:freeof(symbol) +-- end +-- return self.expression:freeof(symbol) +-- end + +-- -- Substitutes each expression for a new one. +-- function IntegralExpression:substitute(map) +-- for expression, replacement in pairs(map) do +-- if self == expression then +-- return replacement +-- end +-- end +-- -- Typically, we only perform substitution on autosimplified expressions, so this won't get called. May give strange results, i.e., +-- -- substituting and then evaluating the integral may not return the same thing as evaluating the integral and then substituting. +-- if self:isdefinite() then +-- return IntegralExpression(self.symbol, self.expression:substitute(map), self.upper:substitute(map), self.lower:substitute(map)) +-- end +-- return IntegralExpression(self.symbol, self.expression:substitute(map)) +-- end + +--- @param other Expression +--- @return boolean +function IntegralExpression:order(other) + if other:type() ~= IntegralExpression then + return false + end + + if self.symbol ~= other.symbol then + return self.symbol:order(other.symbol) + end + + return self.expression:order(other.expression) +end + +--- @return string +function IntegralExpression:tolatex() + if self:isdefinite() then + return '\\int_{' .. self.lower:tolatex() .. '}^{' .. self.upper:tolatex() .. '}{' .. self.expression:tolatex() .. '\\mathop{d' .. self.symbol:tolatex() .. '}}' + end + return '\\int{' .. self.expression:tolatex() .. '\\mathop{d' .. self.symbol:tolatex() .. '}}' +end + + +----------------- +-- Inheritance -- +----------------- +__IntegralExpression.__index = CompoundExpression +__IntegralExpression.__call = IntegralExpression.new +IntegralExpression = setmetatable(IntegralExpression, __IntegralExpression) + +---------------------- +-- Static constants -- +---------------------- +INT = function(symbol, expression, lower, upper) + return IntegralExpression(symbol, expression, lower, upper) +end + +---------------------- +-- Static constants -- +---------------------- + +-- Limit for the maximum number of full u-subs to attempts for any integral. +-- This should be low, since integrals are highly unlikely to need more than 1 or 2 u-subs, and gives exponentially worse performance the higher the number is. +IntegralExpression.ENHANCEDSUBSTITUTIONRECURSIONLIMIT = Integer(2) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/_init.lua b/macros/luatex/latex/luacas/tex/core/_init.lua deleted file mode 100644 index cfd640a214..0000000000 --- a/macros/luatex/latex/luacas/tex/core/_init.lua +++ /dev/null @@ -1,16 +0,0 @@ --- Loads core files in the correct order. - -require("core.expression") -require("core.atomicexpression") -require("core.compoundexpression") -require("core.constantexpression") -require("core.symbolexpression") -require("core.binaryoperation") -require("core.functionexpression") - - -require("core.binaryoperation.power") -require("core.binaryoperation.product") -require("core.binaryoperation.sum") -require("core.binaryoperation.quotient") -require("core.binaryoperation.difference") \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/atomicexpression.lua b/macros/luatex/latex/luacas/tex/core/atomicexpression.lua deleted file mode 100644 index 6cc6e4f58e..0000000000 --- a/macros/luatex/latex/luacas/tex/core/atomicexpression.lua +++ /dev/null @@ -1,70 +0,0 @@ ---- @class AtomicExpression ---- Interface for an atomic mathematical expression that has no sub-expressions. -AtomicExpression = {} -__AtomicExpression = {} - - ----------------------- --- Required methods -- ----------------------- - ---- Converts an atomic expression to its equivalent compound expression, if it has one. ---- @return Expression -function AtomicExpression:tocompoundexpression() - return self -end - ----------------------- --- Instance methods -- ----------------------- - ---- @return AtomicExpression -function AtomicExpression:evaluate() - return self -end - ---- @return AtomicExpression -function AtomicExpression:autosimplify() - return self -end - ---- @return table -function AtomicExpression:subexpressions() - return {} -end - ---- @param subexpressions table ---- @return AtomicExpression -function AtomicExpression:setsubexpressions(subexpressions) - return self -end - ---- @param map table ---- @return Expression -function AtomicExpression:substitute(map) - for expression, replacement in pairs(map) do - if self == expression then - return replacement - end - end - return self -end - ---- @return boolean -function AtomicExpression:isatomic() - return true -end - ---- @return string -function AtomicExpression:tolatex() - -- Most atomic expressions should have the same __tostring as LaTeX's output - return tostring(self) -end - - ------------------ --- Inheritance -- ------------------ - -__AtomicExpression.__index = Expression -AtomicExpression = setmetatable(AtomicExpression, __AtomicExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation.lua deleted file mode 100644 index 708a6c1540..0000000000 --- a/macros/luatex/latex/luacas/tex/core/binaryoperation.lua +++ /dev/null @@ -1,800 +0,0 @@ ---- @class BinaryOperation ---- Represents a binary operation with two inputs and one output. ---- Represents a generic function that takes zero or more expressions as inputs. ---- @field name string ---- @field operation function ---- @field expressions table -BinaryOperation = {} -__BinaryOperation = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new binary operation with the given operation. ---- @param operation function ---- @param expressions table ---- @return BinaryOperation -function BinaryOperation:new(operation, expressions) - local o = {} - local __o = Copy(__ExpressionOperations) - - if type(operation) ~= "function" then - error("Sent parameter of wrong type: operation must be a function") - end - - if type(expressions) ~= "table" then - error("Sent parameter of wrong type: expressions must be an array") - end - - o.name = BinaryOperation.DEFAULT_NAMES[operation] - o.operation = operation - o.expressions = Copy(expressions) - - if BinaryOperation.COMMUTATIVITY[operation] then - function o:iscommutative() - return true - end - else - function o:iscommutative() - return false - end - end - - if not o:iscommutative() and o.operation ~= BinaryOperation.SUB and #o.expressions ~= 2 then - error("Sent parameter of wrong type: noncommutative operations cannot have an arbitrary number of paramaters") - end - - __o.__index = BinaryOperation - __o.__tostring = function(a) - local expressionnames = '' - for index, expression in ipairs(a.expressions) do - if index == 1 and not a.expressions[index + 1] then - expressionnames = expressionnames .. a.name .. ' ' - end - if index > 1 then - expressionnames = expressionnames .. ' ' - end - if expression:isatomic() and not (a.operation == BinaryOperation.POW and expression:type() == Rational) then - expressionnames = expressionnames .. tostring(expression) - else - expressionnames = expressionnames .. '(' .. tostring(expression) .. ')' - end - if a.expressions[index + 1] then - expressionnames = expressionnames .. ' ' .. a.name - end - end - return expressionnames - end - __o.__eq = function(a, b) - -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway - if not a.operation or not b.operation then - return false - end - local loc = 1 - while a.expressions[loc] or b.expressions[loc] do - if not a.expressions[loc] or not b.expressions[loc] or - (a.expressions[loc] ~= b.expressions[loc]) then - return false - end - loc = loc + 1 - end - return a.operation == b.operation - end - o = setmetatable(o, __o) - - return o -end - ---- @return Expression -function BinaryOperation:evaluate() - local results = {} - local reducible = true - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:evaluate() - if not results[index]:isconstant() then - reducible = false - end - end - if not reducible then - return BinaryOperation(self.operation, results) - end - - if not self.expressions[1] then - error("Execution error: cannot perform binary operation on zero expressions") - end - - local result = results[1] - for index, expression in ipairs(results) do - if not (index == 1) then - result = self.operation(result, expression) - end - end - return result -end - ---- @return Expression -function BinaryOperation:autosimplify() - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:autosimplify() - end - local simplified = BinaryOperation(self.operation, results) - if simplified.operation == BinaryOperation.POW then - return simplified:simplifypower() - end - if simplified.operation == BinaryOperation.MUL then - return simplified:simplifyproduct() - end - if simplified.operation == BinaryOperation.ADD then - return simplified:simplifysum() - end - if simplified.operation == BinaryOperation.DIV then - return simplified:simplifyquotient() - end - if simplified.operation == BinaryOperation.SUB then - return simplified:simplifydifference() - end - return simplified -end - ---- @return table -function BinaryOperation:subexpressions() - return self.expressions -end - ---- @param subexpressions table ---- @return BinaryOperation -function BinaryOperation:setsubexpressions(subexpressions) - return BinaryOperation(self.operation, subexpressions) -end - ---- @return Expression -function BinaryOperation:expand() - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:expand() - end - local expanded = BinaryOperation(self.operation, results) - if expanded.operation == BinaryOperation.MUL then - local allsums = BinaryOperation(BinaryOperation.ADD, {Integer.one()}) - for _, expression in ipairs(expanded.expressions) do - allsums = allsums:expand2(expression) - end - return allsums:autosimplify() - end - if expanded.operation == BinaryOperation.POW and expanded.expressions[2]:type() == Integer then - if expanded.expressions[1]:type() ~= BinaryOperation then - return expanded:autosimplify() - end - local exp = BinaryOperation.MULEXP({Integer.one()}) - local pow = expanded.expressions[2]:asnumber() - for _ = 1, math.abs(pow) do - exp = exp:expand2(expanded.expressions[1]) - if _ > 1 then - exp = exp:autosimplify() - end - end - if pow < 0 then - exp = exp^Integer(-1) - end - return exp - end - if expanded.operation == BinaryOperation.POW and expanded.expressions[2].operation == BinaryOperation.ADD then - local exp = {} - for i = 1, #expanded.expressions[2].expressions do - exp[#exp+1] = (expanded.expressions[1]^expanded.expressions[2].expressions[i]):autosimplify() - end - return BinaryOperation.MULEXP(exp) - end - return expanded:autosimplify() -end - ---- Helper for expand - multiplies two addition expressions. ---- @return Expression -function BinaryOperation:expand2(other) - local result = {} - for _, expression in ipairs(self:subexpressions()) do - if other:type() == BinaryOperation and other.operation == BinaryOperation.ADD then - for _, expression2 in ipairs(other.expressions) do - result[#result+1] = expression * expression2 - end - else - result[#result+1] = expression * other - end - end - return BinaryOperation(BinaryOperation.ADD, result) -end - ---- @return Expression -function BinaryOperation:factor() - local results = {} - - -- Recursively factors sub-expressions - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:factor() - end - - -- Attempts to factor expressions as monovariate polynomials - local factoredsubs = BinaryOperation(self.operation, results) - local subs = factoredsubs:getsubexpressionsrec() - for index, sub in ipairs(subs) do - local substituted = factoredsubs:substitute({[sub]=SymbolExpression("_")}):autosimplify() - local polynomial, result = substituted:topolynomial() - if result then - local factored = polynomial:factor():autosimplify() - if factored ~= substituted then - return factored:substitute({[SymbolExpression("_")]=sub}) - end - end - end - - -- Pulls common sub-expressions out of sum expressions - if self.operation == BinaryOperation.ADD then - local gcf - for _, expression in ipairs(factoredsubs:subexpressions()) do - if expression.operation ~= BinaryOperation.MUL then - expression = BinaryOperation.MULEXP({expression}) - end - if not gcf then - gcf = expression - else - local newgcf = Integer.one() - for _, gcfterm in ipairs(gcf:subexpressions()) do - local gcfpower = Integer.one() - if gcfterm:type() == BinaryOperation and gcfterm.operation == BinaryOperation.POW and gcfterm.expressions[2]:type() == Integer then - gcfpower = gcfterm.expressions[2] - gcfterm = gcfterm.expressions[1] - end - for _, term in ipairs(expression:subexpressions()) do - local power = Integer.one() - if term:type() == BinaryOperation and term.operation == BinaryOperation.POW and term.expressions[2]:type() == Integer then - power = term.expressions[2] - term = term.expressions[1] - end - if term == gcfterm then - newgcf = newgcf * term^Integer.min(power, gcfpower) - end - end - end - gcf = newgcf - end - end - if gcf:type() ~= Integer then - local out = Integer.zero() - for _, expression in ipairs(factoredsubs:subexpressions()) do - out = out + expression/gcf - end - out = gcf*(out:autosimplify():factor()) - return out:autosimplify() - end - end - - return factoredsubs -end - ---- @return Expression -function BinaryOperation:combine() - local den, num, aux, mul, input = {}, {}, {}, {}, self:autosimplify():expand() - if input.operation ~= BinaryOperation.ADD then - return input - end - for _, expr in ipairs(input.expressions) do - local numpart, denpart = Integer.one(), Integer.one() - if expr.operation == BinaryOperation.POW and expr.expressions[2]:type() == Integer and expr.expressions[2] < Integer.zero() then - denpart = denpart*expr.expressions[1] ^ expr.expressions[2]:neg() - for index,term in ipairs(den) do - if expr.expressions[1] == den[index] then - if expr.expressions[2]:neg() > mul[index] then - mul[index] = expr.expressions[2]:neg() - goto continue - else - goto continue - end - end - end - table.insert(den,expr.expressions[1]) - table.insert(mul,expr.expressions[2]:neg()) - ::continue:: - end - if expr.operation == BinaryOperation.MUL then - for _,subexpr in ipairs(expr.expressions) do - if subexpr.operation == BinaryOperation.POW and subexpr.expressions[2]:type() == Integer and subexpr.expressions[2] < Integer.zero() then - denpart = denpart*subexpr.expressions[1] ^ subexpr.expressions[2]:neg() - for index,term in ipairs(den) do - if subexpr.expressions[1] == den[index] then - if subexpr.expressions[2]:neg() > mul[index] then - mul[index] = subexpr.expressions[2]:neg() - goto continue - else - goto continue - end - end - end - table.insert(den,subexpr.expressions[1]) - table.insert(mul,subexpr.expressions[2]:neg()) - ::continue:: - else - numpart = numpart*subexpr - end - end - end - if expr.operation ~= BinaryOperation.POW and expr.operation ~= BinaryOperation.MUL then - numpart = expr - end - table.insert(num,numpart) - table.insert(aux,denpart) - end - local denominator = Integer.one() - local numerator = Integer.zero() - for index,expr in ipairs(den) do - denominator = denominator*den[index] ^ mul[index] - end - denominator = denominator:autosimplify() - for index,expr in ipairs(num) do - local uncommon = denominator/aux[index] - uncommon = uncommon:factor():simplify() - numerator = numerator + expr*uncommon - end - numerator = numerator:simplify():factor() - if denominator == Integer.one() then - return numerator - else - return numerator/denominator - end -end - ---- @param collect Expression ---- @return Expression -function BinaryOperation:collect(collect) - -- Constant expressions cannot be collected - if collect:isconstant() then - return self - end - - -- Recusively collect subexpressions - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:collect(collect) - end - local collected = BinaryOperation(self.operation, results) - - if not (collected.operation == BinaryOperation.ADD) then - return collected:autosimplify() - end - - local coefficients = {} - - -- TODO: Add an expression map class - setmetatable(coefficients, {__index = - function(table, key) - local out = rawget(table, tostring(key)) - return out or Integer.zero() - end, - __newindex = - function (table, key, value) - rawset(table, tostring(key), value) - end - }) - - -- Finds all instances of a constant power of the expression to be collected, and maps each power to all terms it is multiplied by - for _, expression in ipairs(collected:subexpressions()) do - if expression == collect then - coefficients[Integer.one()] = coefficients[Integer.one()] + Integer.one() - elseif expression.operation == BinaryOperation.POW and expression:subexpressions()[1] == collect and expression:subexpressions()[2]:isconstant() then - coefficients[expression:subexpressions()[2]] = coefficients[expression:subexpressions()[2]] + Integer.one() - elseif collect:type() == BinaryOperation and collect.operation == BinaryOperation.POW and - expression.operation == BinaryOperation.POW and expression:subexpressions()[1] == collect:subexpressions()[1] then - -- Handle the fact that autosimplify turns (a^x^n -> a^(xn)), this is needed if the term to collect is itself an exponential - local power = (expression:subexpressions()[2] / collect:subexpressions()[2]):autosimplify() - if power:isconstant() then - coefficients[power] = coefficients[power] + Integer.one() - else - coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression - end - elseif expression.operation == BinaryOperation.MUL then - local varpart - local coeffpart = Integer.one() - for _, term in ipairs(expression:subexpressions()) do - if term == collect then - varpart = Integer.one() - elseif (term.operation == BinaryOperation.POW and term:subexpressions()[1] == collect and term:subexpressions()[2]:isconstant()) then - varpart = term:subexpressions()[2] - elseif collect:type() == BinaryOperation and collect.operation == BinaryOperation.POW and - term.operation == BinaryOperation.POW and term:subexpressions()[1] == collect:subexpressions()[1] then - local power = (term:subexpressions()[2] / collect:subexpressions()[2]):autosimplify() - if power:isconstant() then - varpart = power - end - else - coeffpart = coeffpart * term - end - end - if varpart then - coefficients[varpart] = coefficients[varpart] + coeffpart - else - coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression - end - else - coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression - end - - - end - - local out = Integer.zero() - for index, value in pairs(coefficients) do - out = out + collect ^ Rational.fromstring(index) * value - end - - return out:autosimplify() -end - ---- @param other Expression ---- @return boolean -function BinaryOperation:order(other) - if other:isconstant() then - return false - end - - if other:isatomic() then - if self.operation == BinaryOperation.POW then - return self:order(BinaryOperation(BinaryOperation.POW, {other, Integer.one()})) - end - - if self.operation == BinaryOperation.MUL then - return self:order(BinaryOperation(BinaryOperation.MUL, {other})) - end - - if self.operation == BinaryOperation.ADD then - return self:order(BinaryOperation(BinaryOperation.ADD, {other})) - end - end - - if self.operation == BinaryOperation.POW and other.operation == BinaryOperation.POW then - if self.expressions[1] ~= other.expressions[1] then - return self.expressions[1]:order(other.expressions[1]) - end - return self.expressions[2]:order(other.expressions[2]) - end - - if (self.operation == BinaryOperation.MUL and other.operation == BinaryOperation.MUL) or - (self.operation == BinaryOperation.ADD and other.operation == BinaryOperation.ADD) then - local k = 0 - while #self.expressions - k > 0 and #other.expressions - k > 0 do - if self.expressions[#self.expressions - k] ~= other.expressions[#other.expressions - k] then - return self.expressions[#self.expressions - k]:order(other.expressions[#other.expressions - k]) - end - k = k + 1 - end - return #self.expressions < #other.expressions - end - - if (self.operation == BinaryOperation.MUL) and (other.operation == BinaryOperation.POW or other.operation == BinaryOperation.ADD) then - return self:order(BinaryOperation(BinaryOperation.MUL, {other})) - end - - if (self.operation == BinaryOperation.POW) and (other.operation == BinaryOperation.MUL) then - return BinaryOperation(BinaryOperation.MUL, {self}):order(other) - end - - if (self.operation == BinaryOperation.POW) and (other.operation == BinaryOperation.ADD) then - return self:order(BinaryOperation(BinaryOperation.POW, {other, Integer.one()})) - end - - if (self.operation == BinaryOperation.ADD) and (other.operation == BinaryOperation.MUL) then - return BinaryOperation(BinaryOperation.MUL, {self}):order(other) - end - - if (self.operation == BinaryOperation.ADD) and (other.operation == BinaryOperation.POW) then - return BinaryOperation(BinaryOperation.POW, {self, Integer.one()}):order(other) - end - - if other:type() == FunctionExpression or other:type() == TrigExpression or other:type() == Logarithm then - if self.operation == BinaryOperation.ADD or self.operation == BinaryOperation.MUL then - return self:order(BinaryOperation(self.operation, {other})) - end - - if self.operation == BinaryOperation.POW then - return self:order(other^Integer.one()) - end - end - - return true -end - ---- Returns whether the binary operation is commutative. ---- @return boolean -function BinaryOperation:iscommutative() - error("Called unimplemented method: iscommutative()") -end - ---- @return PolynomialRing, boolean -function BinaryOperation:topolynomial() - local addexp = self - if not self.operation or self.operation ~= BinaryOperation.ADD then - addexp = BinaryOperation(BinaryOperation.ADD, {self}) - end - - local poly = {} - local degree = 0 - local symbol - for _, expression in ipairs(addexp.expressions) do - local coefficient - local sym - local power - -- Expressions of the form c - if expression:isconstant() then - coefficient = expression - power = 0 - -- Expressions of the form x - elseif expression:type() == SymbolExpression then - coefficient = Integer.one() - sym = expression.symbol - power = 1 - -- Expressions of the form c*x - elseif expression.operation and expression.operation == BinaryOperation.MUL and #expression.expressions == 2 - and expression.expressions[1]:isconstant() and expression.expressions[2]:type() == SymbolExpression then - - coefficient = expression.expressions[1] - sym = expression.expressions[2].symbol - power = 1 - -- Expressions of the form c*x^n (totally not confusing) - elseif expression.operation and expression.operation == BinaryOperation.MUL and #expression.expressions == 2 - and expression.expressions[1]:isconstant() and expression.expressions[2].operation and - expression.expressions[2].operation == BinaryOperation.POW and #expression.expressions[2].expressions == 2 - and expression.expressions[2].expressions[1]:type() == SymbolExpression and expression.expressions[2].expressions[2].getring - and expression.expressions[2].expressions[2]:getring() == Integer.getring() and expression.expressions[2].expressions[2] > Integer.zero() then - - coefficient = expression.expressions[1] - sym = expression.expressions[2].expressions[1].symbol - power = expression.expressions[2].expressions[2]:asnumber() - -- Expressions of the form x^n - elseif expression.operation and expression.operation == BinaryOperation.POW and #expression.expressions == 2 - and expression.expressions[1]:type() == SymbolExpression and expression.expressions[2].getring - and expression.expressions[2]:getring() == Integer.getring() and expression.expressions[2] > Integer.zero() then - - coefficient = Integer.one() - sym = expression.expressions[1].symbol - power = expression.expressions[2]:asnumber() - else - return self, false - end - - if symbol and sym and symbol ~= sym then - return self, false - end - if not symbol then - symbol = sym - end - poly[power + 1] = coefficient - if power > degree then - degree = power - end - end - - for i = 1,degree+1 do - poly[i] = poly[i] or Integer.zero() - end - - return PolynomialRing(poly, symbol), true -end - -function BinaryOperation:tolatex() - if self.operation == BinaryOperation.POW then - if self.expressions[2]:type() == Integer and self.expressions[2] < Integer.zero() then - local base = self.expressions[1] - local exponent = self.expressions[2] - if exponent == Integer(-1) then - return "\\frac{1}{" .. base:tolatex() .. "}" - else - if base:isatomic() then - return "\\frac{1}{" .. base:tolatex() .. "^{" .. exponent:neg():tolatex() .. "}}" - else - return "\\frac{1}{\\left(" .. base:tolatex() .. "\\right)^{" .. exponent:neg():tolatex() .. "}}" - end - end - end - if self.expressions[1]:isatomic() then - if self.expressions[2]:isconstant() and self.expressions[2]:getring() == Rational:getring() and self.expressions[2].numerator == Integer.one() then - if self.expressions[2].denominator == Integer(2) then - return "\\sqrt{" .. self.expressions[1]:tolatex() .. '}' - end - return "\\sqrt[" .. self.expressions[2].denominator:tolatex() .. ']{' .. self.expressions[1]:tolatex() .. '}' - end - return self.expressions[1]:tolatex() .. '^{' .. self.expressions[2]:tolatex() .. '}' - else - if self.expressions[2]:isconstant() and self.expressions[2]:getring() == Rational:getring() and self.expressions[2].numerator == Integer.one() then - if self.expressions[2].denominator == Integer(2) then - return "\\sqrt{" .. self.expressions[1]:tolatex() .. '}' - end - return "\\sqrt[" .. self.expressions[2].denominator:tolatex() .. ']{' .. self.expressions[1]:tolatex() .. '}' - end - return "\\left(" .. self.expressions[1]:tolatex() .. "\\right)" .. '^{' .. self.expressions[2]:tolatex() .. '}' - end - end - if self.operation == BinaryOperation.MUL then - local sign = '' - local out = '' - local denom = '' - if self:autosimplify():isconstant() then - for index, expression in ipairs(self.expressions) do - if index == 1 then - out = out .. expression:tolatex() - else - out = out .. "\\cdot " .. expression:tolatex() - end - end - return out - end - if #self.expressions == 2 and self.expressions[2]:type() == BinaryOperation and self.expressions[2].operation == BinaryOperation.POW and self.expressions[2].expressions[2] == -Integer.one() then - out = '\\frac{' .. self.expressions[1]:tolatex() .. '}{' .. self.expressions[2].expressions[1]:tolatex() .. '}' - return out - end - for _, expression in ipairs(self.expressions) do - if expression:type() == BinaryOperation then - if expression.operation == BinaryOperation.POW and expression.expressions[2]:isconstant() and expression.expressions[2] < Integer.zero() then - local reversed = (Integer.one() / expression):autosimplify() - if reversed.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB then - denom = denom .. '\\left('.. reversed:tolatex() .. '\\right)' - else - denom = denom .. reversed:tolatex() - end - elseif expression.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB then - out = out .. '\\left(' .. expression:tolatex() .. '\\right)' - else - out = out .. expression:tolatex() - end - else - if expression == Integer(-1) then - out = out .. '-' - elseif expression:type() == Rational and expression.numerator == Integer.one() then - denom = denom .. expression.denominator:tolatex() - elseif expression:type() == Rational and expression.numerator == Integer(-1) then - out = out .. '-' - denom = denom .. expression.denominator:tolatex() - elseif expression:type() == Rational then - out = out .. expression.numerator:tolatex() - denom = denom .. expression.denominator:tolatex() - else - out = out .. expression:tolatex() - end - end - end - if string.sub(out,1,1) == '-' then - sign = '-' - out = string.sub(out,2,-1) - end - if denom ~= '' and out == '' then - return sign .. '\\frac{' .. '1' .. '}{' .. denom .. '}' - end - if denom ~= '' then - return sign .. '\\frac{' .. out .. '}{' .. denom .. '}' - end - return sign..out - end - if self.operation == BinaryOperation.ADD then - local out = '' - for index, expression in ipairs(self.expressions) do - out = out .. expression:tolatex() - if self.expressions[index + 1] and string.sub(self.expressions[index + 1]:tolatex(), 1, 1) ~= "-" then - out = out .. '+' - end - end - return out - end - if self.operation == BinaryOperation.DIV then - return '\\frac{' .. self.expressions[1]:tolatex() .. '}{' .. self.expressions[2]:tolatex() .. '}' - end - if self.operation == BinaryOperation.SUB then - local out = '' - if not self.expressions[2] then - if not self.expressions[1]:isatomic() then - out = '-\\left(' .. self.expressions[1]:tolatex() .. '\\right)' - else - out = '-' .. self.expressions[1]:tolatex() - end - else - for index, expression in ipairs(self.expressions) do - if expression.operation and (expression.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB) and index >1 then - out = out .. "\\left(" .. expression:tolatex() .. "\\right)" - else - out = out .. expression:tolatex() - end - if self.expressions[index + 1] then - out = out .. '-' - end - end - end - return out - end - return self -end - ------------------ --- Inheritance -- ------------------ - -__BinaryOperation.__index = CompoundExpression -__BinaryOperation.__call = BinaryOperation.new -BinaryOperation = setmetatable(BinaryOperation, __BinaryOperation) - ----------------------- --- Static constants -- ----------------------- - -BinaryOperation.ADD = function(a, b) - return a + b -end - -BinaryOperation.SUB = function(a, b) - return a - b -end - -BinaryOperation.MUL = function(a, b) - return a * b -end - -BinaryOperation.DIV = function(a, b) - return a / b -end - -BinaryOperation.IDIV = function(a, b) - return a // b -end - -BinaryOperation.MOD = function(a, b) - return a % b -end - -BinaryOperation.POW = function(a, b) - return a ^ b -end - -BinaryOperation.DEFAULT_NAMES = { - [BinaryOperation.ADD] = "+", - [BinaryOperation.SUB] = "-", - [BinaryOperation.MUL] = "*", - [BinaryOperation.DIV] = "/", - [BinaryOperation.IDIV] = "//", - [BinaryOperation.MOD] = "%", - [BinaryOperation.POW] = "^" -} - -BinaryOperation.COMMUTATIVITY = { - [BinaryOperation.ADD] = true, - [BinaryOperation.SUB] = false, - [BinaryOperation.MUL] = true, - [BinaryOperation.DIV] = false, - [BinaryOperation.IDIV] = false, - [BinaryOperation.MOD] = false, - [BinaryOperation.POW] = false -} - -BinaryOperation.ADDEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.ADD, expressions, name) -end - -BinaryOperation.SUBEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.SUB, expressions, name) -end - -BinaryOperation.MULEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.MUL, expressions, name) -end - -BinaryOperation.DIVEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.DIV, expressions, name) -end - -BinaryOperation.IDIVEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.IDIV, expressions, name) -end - -BinaryOperation.MODEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.MOD, expressions, name) -end - -BinaryOperation.POWEXP = function(expressions, name) - return BinaryOperation(BinaryOperation.POW, expressions, name) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua deleted file mode 100644 index 2cc56b6600..0000000000 --- a/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Seperates the various binary operations into their own files for readability - ---- Automatic simplification of difference expressions. ---- @return BinaryOperation -function BinaryOperation:simplifydifference() - local term1 = self.expressions[1] - local term2 = self.expressions[2] - - if not term2 then - return BinaryOperation(BinaryOperation.MUL, {Integer(-1), term1}):autosimplify() - end - - return BinaryOperation(BinaryOperation.ADD, {term1, BinaryOperation(BinaryOperation.MUL, {Integer(-1), term2}):autosimplify()}):autosimplify() -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-difference.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-difference.lua new file mode 100644 index 0000000000..2cc56b6600 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-difference.lua @@ -0,0 +1,14 @@ +-- Seperates the various binary operations into their own files for readability + +--- Automatic simplification of difference expressions. +--- @return BinaryOperation +function BinaryOperation:simplifydifference() + local term1 = self.expressions[1] + local term2 = self.expressions[2] + + if not term2 then + return BinaryOperation(BinaryOperation.MUL, {Integer(-1), term1}):autosimplify() + end + + return BinaryOperation(BinaryOperation.ADD, {term1, BinaryOperation(BinaryOperation.MUL, {Integer(-1), term2}):autosimplify()}):autosimplify() +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-power.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-power.lua new file mode 100644 index 0000000000..35df72c440 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-power.lua @@ -0,0 +1,169 @@ +-- Seperates the various binary operations into their own files for readability + +--- Automatic simplification of power expressions. +--- @return BinaryOperation +function BinaryOperation:simplifypower() + local base = self.expressions[1] + local exponent = self.expressions[2] + + if base:isconstant() and exponent:isconstant() and exponent:getring() ~= Rational:getring() then + return self:evaluate() + end + + -- Simplifies i^x for x integer. + if base == I and exponent:isconstant() and exponent:getring() == Integer:getring() then + if exponent % Integer(4) == Integer(0) then + return Integer(1) + end + if exponent % Integer(4) == Integer(1) then + return I + end + if exponent % Integer(4) == Integer(2) then + return Integer(-1) + end + if exponent % Integer(4) == Integer(3) then + return -I + end + end + + -- Simplifies complex numbers raised to negative integer powers + if not base:isrealconstant() and base:iscomplexconstant() and exponent:isconstant() and exponent:getring() == Integer:getring() and exponent < Integer.zero() then + local a + local b + if base.operation == BinaryOperation.MUL then + a = Integer.zero() + b = base.expressions[1] + elseif base.operation == BinaryOperation.ADD and base.expressions[2] == I then + a = base.expressions[1] + b = Integer.one() + else + a = base.expressions[1] + b = base.expressions[2].expressions[1] + end + return (((a-b*I)/(a^Integer(2)+b^Integer(2)))^(-exponent)):expand():autosimplify() + end + + -- Uses the property that 0^x = 0 if x does not equal 0 + if base:isconstant() and base == base:zero() then + return Integer.zero() + end + + -- Uses the property that 1^x = 1 + if base:isconstant() and base == base:one() then + return base:one() + end + + -- Uses the property that x^0 = 1 + if exponent:isconstant() and exponent == exponent:zero() then + return exponent:one() + end + + -- Uses the property that x^1 = x + if exponent:isconstant() and exponent == exponent:one() then + return base + end + + -- Uses the property that b ^ (log(b, x)) == x + if exponent:type() == Logarithm and exponent.base == base then + return exponent.expression + end + + -- Uses the property that b ^ (a * log(b, x)) == x ^ a + if exponent.operation == BinaryOperation.MUL then + local x + local rest = Integer.one() + for _, expression in ipairs(exponent.expressions) do + if expression:type() == Logarithm and expression.base == base and not log then + x = expression.expression + else + rest = rest * expression + end + end + if x then + return (x ^ rest):autosimplify() + end + end + + -- Uses the property that (x^a)^b = x^(a*b) + if not base:isatomic() and base.operation == BinaryOperation.POW and exponent:isconstant() then + base, exponent = base.expressions[1], BinaryOperation(BinaryOperation.MUL, {exponent, base.expressions[2]}):autosimplify() + return BinaryOperation(BinaryOperation.POW, {base, exponent}):autosimplify() + end + + -- Uses the property that (x_1*x_2*...*x_n)^a = x_1^a*x_2^a*..x_n^a if a is an integer + if base.operation == BinaryOperation.MUL and exponent:type() == Integer then + local results = {} + for index, expression in ipairs(base.expressions) do + results[index] = BinaryOperation(BinaryOperation.POW, {expression, exponent}):autosimplify() + end + return BinaryOperation(BinaryOperation.MUL, results):autosimplify() + end + + -- Uses the property that sqrt(x,r)^d == sqrt(x,r/d) + if base:type() == SqrtExpression and exponent:type() == Integer and exponent > Integer.zero() then + local root = base.root + local expr = base.expression + local comm = Integer.gcd(root,exponent) + root = root / comm + local expo = exponent / comm + expr = expr ^ expo + return SqrtExpression(expr,root):autosimplify() + end + + -- Rationalizing SqrtExpressions + if base:type() == SqrtExpression and exponent:type() == Integer and base.expression:type() == Integer and exponent < Integer.zero() then + local root = base.root + local expr = base.expression + local result = (SqrtExpression(expr ^ (root - Integer.one()),root) / expr) ^ exponent:neg() + return result:autosimplify() + end + + if base:isconstant() and exponent:isconstant() and exponent:getring() == Rational.getring() then + return self --:simplifyrationalpower() + end + + -- Our expression cannot be simplified + return self +end + +-- Automatic simplification of rational power expressions +function BinaryOperation:simplifyrationalpower() + local base = self.expressions[1] + local exponent = self.expressions[2] + + if base:getring() == Rational.getring() then + return (BinaryOperation(BinaryOperation.POW, {base.numerator, exponent}):simplifyrationalpower()) / + (BinaryOperation(BinaryOperation.POW, {base.denominator, exponent}):simplifyrationalpower()) + end + + if base == Integer(-1) then + if exponent == Integer(1) / Integer(2) then + return I + end + + return self + end + + local primes = base:primefactorization() + + if primes.expressions[1] and not primes.expressions[2] then + local primeexponent = primes.expressions[1].expressions[2] + local primebase = primes.expressions[1].expressions[1] + local newexponent = primeexponent * exponent + local integerpart + if newexponent.getring() == Rational.getring() then + integerpart = newexponent.numerator // newexponent.denominator + else + integerpart = newexponent + end + + if integerpart == Integer.zero() then + return BinaryOperation(BinaryOperation.POW, {primebase, newexponent}) + end + return BinaryOperation(BinaryOperation.MUL, + {BinaryOperation(BinaryOperation.POW, {primebase, integerpart}), + BinaryOperation(BinaryOperation.POW, {primebase, newexponent - integerpart})}):autosimplify() + end + + return BinaryOperation(BinaryOperation.POW, {primes:autosimplify(), exponent}) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-product.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-product.lua new file mode 100644 index 0000000000..b104cb5e8a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-product.lua @@ -0,0 +1,231 @@ +-- Seperates the various binary operations into their own files for readability + +--- Automatic simplification of multiplication expressions. +--- @return BinaryOperation +function BinaryOperation:simplifyproduct() + if not self.expressions[1] then + error("Execution error: attempted to simplify empty product") + end + + if not self.expressions[2] then + return self.expressions[1] + end + + -- Uses the property that x*0=0 + for _, expression in ipairs(self.expressions) do + if expression:isconstant() and expression == expression:zero() then + return expression:zero() + end + end + + local result = self:simplifyproductrec() + + -- We don't really know what ring we are working in here, so just assume the integer ring + if not result.expressions[1] then + return Integer.one() + end + + if not result.expressions[2] then + return result.expressions[1] + end + + return result +end + +function BinaryOperation:simplifyproductrec() + local term1 = self.expressions[1] + local term2 = self.expressions[2] + + if not self.expressions[3] then + if (term1:isconstant() or not (term1.operation == BinaryOperation.MUL)) and + (term2:isconstant() or not (term2.operation == BinaryOperation.MUL)) then + + if term1:isconstant() and term2:isconstant() then + local result = self:evaluate() + if result == result:one() then + return BinaryOperation(BinaryOperation.MUL, {}) + end + return BinaryOperation(BinaryOperation.MUL, {result}) + end + + -- Uses the property that x*1 = x + if term1:isconstant() and term1 == term1:one() then + return BinaryOperation(BinaryOperation.MUL, {term2}) + end + + if term2:isconstant() and term2 == term2:one() then + return BinaryOperation(BinaryOperation.MUL, {term1}) + end + + -- Distributes constants if the other term is a sum expression. + if term1:isconstant() and term2.operation == BinaryOperation.ADD then + local distributed = BinaryOperation(BinaryOperation.ADD, {}) + for i, exp in ipairs(term2:subexpressions()) do + distributed.expressions[i] = term1 * exp + end + return BinaryOperation(BinaryOperation.MUL, {distributed:autosimplify()}) + end + + if term2:isconstant() and term1.operation == BinaryOperation.ADD then + local distributed = BinaryOperation(BinaryOperation.ADD, {}) + for i, exp in ipairs(term1:subexpressions()) do + distributed.expressions[i] = term2 * exp + end + return BinaryOperation(BinaryOperation.MUL, {distributed:autosimplify()}) + end + + -- Uses the property that sqrt(a,r)*sqrt(b,r) = sqrt(a*b,r) if a,r are positive integers + if term1:type() == SqrtExpression and term2:type() == SqrtExpression and term1.expression:isconstant() and term2.expression:isconstant() and term1.root:type() == Integer then + if term1.root == term2.root and term1.expression > Integer.zero() then + local expression = term1.expression*term2.expression + local result = SqrtExpression(expression,term1.root):autosimplify() + if result == Integer.one() then + return BinaryOperation(BinaryOperation.MUL,{}) + else + return BinaryOperation(BinaryOperation.MUL,{result}) + end + end + end + + --if term1.operation == BinaryOperation.POW and term2.operation == BinaryOperation.POW and term1.expressions[1]:type() == SqrtExpression and term2.expressions[1]:type() == SqrtExpression and term1.expressions[2]:type() == Integer and term2.expressions[2]:type() == Integer and term1.expressions[2] < Integer.zero() and term2.expressions[2] < Integer.zero() and term1.expressions[1].root == term2.expressions[1].root then + -- local expo1 = term1.expressions[2]:neg() + -- local expo2 = term2.expressions[2]:neg() + -- local root = term1.expressions[1].root + -- local expr1 = term1.expressions[1].expression + -- local expr2 = term2.expressions[1].expression + -- local result1 = BinaryOperation(BinaryOperation.POW,{SqrtExpression(expr1,root),expo1}):simplifypower() + -- local result2 = BinaryOperation(BinaryOperation.POW,{SqrtExpression(expr2,root),expo2}):simplifypower() + -- local result = BinaryOperation(BinaryOperation.MUL,{result1,result2}):autosimplify() + -- if result == Integer.one() then + -- return BinaryOperation(BinaryOperation.MUL,{}) + -- end + -- if result:type() == Integer then + -- return BinaryOperation(BinaryOperation.MUL,{Rational(Integer.one(),result)}) + -- end + -- return BinaryOperation(BinaryOperation.MUL,{BinaryOperation(BinaryOperation.POW, {result,Integer(-1)})}):autosimplify() + --end + + -- Uses the property that x^a*x^b=x^(a+b) + local revertterm1 = false + local revertterm2 = false + if term1.operation ~= BinaryOperation.POW then + term1 = BinaryOperation(BinaryOperation.POW, {term1, Integer.one()}) + revertterm1 = true + end + if term2.operation ~= BinaryOperation.POW then + term2 = BinaryOperation(BinaryOperation.POW, {term2, Integer.one()}) + revertterm2 = true + end + if term1.expressions[1] == term2.expressions[1] + --and not + -- (term1.expressions[1]:type() == Integer and + -- term1.expressions[2]:type() ~= term2.--expressions[2]:type()) + then + local result = BinaryOperation(BinaryOperation.POW, + {term1.expressions[1], + BinaryOperation(BinaryOperation.ADD, + {term1.expressions[2], term2.expressions[2]}):autosimplify()}):autosimplify() + if result:isconstant() and result == result:one() then + return BinaryOperation(BinaryOperation.MUL, {}) + end + return BinaryOperation(BinaryOperation.MUL, {result}) + end + + if revertterm1 then + term1 = term1.expressions[1] + end + if revertterm2 then + term2 = term2.expressions[1] + end + + if term2:order(term1) then + return BinaryOperation(BinaryOperation.MUL, {term2, term1}) + end + + return self + end + + if term1.operation == BinaryOperation.MUL and not (term2.operation == BinaryOperation.MUL) then + return term1:mergeproducts(BinaryOperation(BinaryOperation.MUL, {term2})) + end + + if not (term1.operation == BinaryOperation.MUL) and term2.operation == BinaryOperation.MUL then + return BinaryOperation(BinaryOperation.MUL, {term1}):mergeproducts(term2) + end + + return term1:mergeproducts(term2) + end + + local rest = {} + for index, expression in ipairs(self.expressions) do + if index > 1 then + rest[index - 1] = expression + end + end + + local result = BinaryOperation(BinaryOperation.MUL, rest):simplifyproductrec() + + if term1.operation ~= BinaryOperation.MUL then + term1 = BinaryOperation(BinaryOperation.MUL, {term1}) + end + if result.operation ~= BinaryOperation.MUL then + result = BinaryOperation(BinaryOperation.MUL, {result}) + end + return term1:mergeproducts(result) +end + +-- Merges two lists of products +function BinaryOperation:mergeproducts(other) + if not self.expressions[1] then + return other + end + + if not other.expressions[1] then + return self + end + + local first = BinaryOperation(BinaryOperation.MUL, {self.expressions[1], other.expressions[1]}):simplifyproductrec() + + local selfrest = {} + for index, expression in ipairs(self.expressions) do + if index > 1 then + selfrest[index - 1] = expression + end + end + + local otherrest = {} + for index, expression in ipairs(other.expressions) do + if index > 1 then + otherrest[index - 1] = expression + end + end + + if first.operation ~= BinaryOperation.MUL or not first.expressions[2] then + local result = BinaryOperation(self.operation, selfrest):mergeproducts(BinaryOperation(other.operation, otherrest)) + if not first.expressions[1] then + return result + end + table.insert(result.expressions, 1, first.expressions[1]) + + if result.operation == BinaryOperation.MUL and not result.expressions[3] and result.expressions[1] and result.expressions[2] then + if result.expressions[1]:isconstant() and result.expressions[2]:isconstant() then + return result:simplifyproductrec() + end + end + return result + end + + local result + if first.expressions[1] == self.expressions[1] then + result = BinaryOperation(self.operation, selfrest):mergeproducts(other) + else + result = self:mergeproducts(BinaryOperation(other.operation, otherrest)) + end + table.insert(result.expressions, 1, first.expressions[1]) + if result.operation == BinaryOperation.MUL and not result.expressions[3] and result.expressions[1] and result.expressions[2] then + if result.expressions[1]:isconstant() and result.expressions[2]:isconstant() then + return result:simplifyproductrec() + end + end + return result +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-quotient.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-quotient.lua new file mode 100644 index 0000000000..4dac489bf5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-quotient.lua @@ -0,0 +1,14 @@ +-- Seperates the various binary operations into their own files for readability + +-- Automatic simplification of quotient expressions. +--- @return BinaryOperation +function BinaryOperation:simplifyquotient() + local numerator = self.expressions[1] + local denominator = self.expressions[2] + + if numerator:isconstant() and denominator:isconstant() then + return self:evaluate() + end + + return BinaryOperation(BinaryOperation.MUL, {numerator, BinaryOperation(BinaryOperation.POW, {denominator, Integer(-1)}):autosimplify()}):autosimplify() +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-sum.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-sum.lua new file mode 100644 index 0000000000..5bb204066f --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/luacas-sum.lua @@ -0,0 +1,226 @@ +-- Seperates the various binary operations into their own files for readability + +-- Automatic simplification of addition expressions. +--- @return BinaryOperation +function BinaryOperation:simplifysum() + if not self.expressions[1] then + error("Execution error: Attempted to simplify empty sum") + end + + if not self.expressions[2] then + return self.expressions[1] + end + + local result = self:simplifysumrec() + + -- We don't really know what ring we are working in here, so just assume the integer ring + if not result.expressions[1] then + return Integer.zero() + end + + -- Simplifies single sums to their operands + if not result.expressions[2] then + return result.expressions[1] + end + + return result +end + +function BinaryOperation:simplifysumrec() + local term1 = self.expressions[1] + local term2 = self.expressions[2] + + if self.expressions[1] and self.expressions[2] and not self.expressions[3] then + if (term1:isconstant() or not (term1.operation == BinaryOperation.ADD)) and + (term2:isconstant() or not (term2.operation == BinaryOperation.ADD)) then + + if term1:isconstant() and term2:isconstant() then + return BinaryOperation(BinaryOperation.ADD, {self:evaluate()}) + end + + -- Uses the property that x + 0 = x + if term1:isconstant() and term1 == term1:zero() then + return BinaryOperation(BinaryOperation.ADD, {term2}) + end + + if term2:isconstant() and term2 == term2:zero() then + return BinaryOperation(BinaryOperation.ADD, {term1}) + end + + local revertterm1 = false + local revertterm2 = false + -- Uses the property that a*x+b*x= (a+b)*x + -- This is only done if a and b are constant, since otherwise this could be counterproductive + -- We SHOULD be okay to only check left distributivity, since constants always come first when ordered + if term1.operation == BinaryOperation.MUL and term2.operation == BinaryOperation.MUL then + local findex = 2 + local sindex = 2 + if not term1.expressions[1]:isconstant() then + revertterm1 = true + findex = 1 + end + if not term2.expressions[1]:isconstant() then + revertterm2 = true + sindex = 1 + end + if FancyArrayEqual(term1.expressions,term2.expressions,findex,sindex) then + local result + if not revertterm1 and not revertterm2 then + result = BinaryOperation( + BinaryOperation.ADD, + {term1.expressions[1],term2.expressions[1]} + ) + end + if revertterm1 and not revertterm2 then + result = BinaryOperation( + BinaryOperation.ADD, + {Integer.one(),term2.expressions[1]} + ) + end + if not revertterm1 and revertterm2 then + result = BinaryOperation( + BinaryOperation.ADD, + {term1.expressions[1],Integer.one()} + ) + end + if revertterm1 and revertterm2 then + result = Integer(2) + end + result = result:autosimplify() + for i=findex,#term1.expressions do + result = BinaryOperation( + BinaryOperation.MUL, + {result,term1.expressions[i]} + ) + end + result = result:autosimplify() + if result:isconstant() and result == result:zero() then + return BinaryOperation(BinaryOperation.ADD, {}) + end + return BinaryOperation(BinaryOperation.ADD, {result}) + end + end + + if term1.operation ~= BinaryOperation.MUL or not term1.expressions[1]:isconstant() then + term1 = BinaryOperation(BinaryOperation.MUL,{Integer.one(), term1}) + revertterm1 = true + end + if term2.operation ~= BinaryOperation.MUL or not term2.expressions[1]:isconstant() then + term2 = BinaryOperation(BinaryOperation.MUL, {Integer.one(), term2}) + revertterm2 = true + end + if ArrayEqual(term1.expressions, term2.expressions, 2) then + local result = BinaryOperation( + BinaryOperation.ADD, + {term1.expressions[1],term2.expressions[1]} + ) + result = result:autosimplify() + for i=2,#term1.expressions do + result = BinaryOperation( + BinaryOperation.MUL, + {result,term1.expressions[i]} + ) + end + result = result:autosimplify() + --local result = BinaryOperation(BinaryOperation.MUL, + -- {BinaryOperation(BinaryOperation.ADD, + -- {term1.expressions[1], + -- term2.expressions[1]}):autosimplify(), + -- term1.expressions[2]}):autosimplify() + if result:isconstant() and result == result:zero() then + return BinaryOperation(BinaryOperation.ADD, {}) + end + return BinaryOperation(BinaryOperation.ADD, {result}) + end + + if revertterm1 then + term1 = term1.expressions[2] + end + if revertterm2 then + term2 = term2.expressions[2] + end + + if term2:order(term1) then + return BinaryOperation(BinaryOperation.ADD, {term2, term1}) + end + + return self + end + + if term1.operation == BinaryOperation.ADD and not (term2.operation == BinaryOperation.ADD) then + return term1:mergesums(BinaryOperation(BinaryOperation.ADD, {term2})) + end + + if not (term1.operation == BinaryOperation.ADD) and term2.operation == BinaryOperation.ADD then + return BinaryOperation(BinaryOperation.ADD, {term1}):mergesums(term2) + end + + return term1:mergesums(term2) + end + + local rest = {} + for index, expression in ipairs(self.expressions) do + if index > 1 then + rest[index - 1] = expression + end + end + + local result = BinaryOperation(BinaryOperation.ADD, rest):simplifysumrec() + + if term1.operation ~= BinaryOperation.ADD then + term1 = BinaryOperation(BinaryOperation.ADD, {term1}) + end + if result.operation ~= BinaryOperation.ADD then + result = BinaryOperation(BinaryOperation.ADD, {result}) + end + return term1:mergesums(result) +end + +-- Merges two lists of sums +function BinaryOperation:mergesums(other) + if not self.expressions[1] then + return other + end + + if not other.expressions[1] then + return self + end + + local first = BinaryOperation(BinaryOperation.ADD, {self.expressions[1], other.expressions[1]}):simplifysumrec() + + local selfrest = {} + for index, expression in ipairs(self.expressions) do + if index > 1 then + selfrest[index - 1] = expression + end + end + + local otherrest = {} + for index, expression in ipairs(other.expressions) do + if index > 1 then + otherrest[index - 1] = expression + end + end + + if first.operation ~= BinaryOperation.ADD or not first.expressions[2] then + local result = BinaryOperation(self.operation, selfrest):mergesums(BinaryOperation(other.operation, otherrest)) + if not first.expressions[1] then + return result + end + if first.expressions[1] ~= Integer.zero(0) then + table.insert(result.expressions, 1, first.expressions[1]) + end + return result + end + + local result + if first.expressions[1] == self.expressions[1] then + result = BinaryOperation(self.operation, selfrest):mergesums(other) + else + result = self:mergesums(BinaryOperation(other.operation, otherrest)) + end + + table.insert(result.expressions, 1, first.expressions[1]) + + return result +end diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua deleted file mode 100644 index 35df72c440..0000000000 --- a/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua +++ /dev/null @@ -1,169 +0,0 @@ --- Seperates the various binary operations into their own files for readability - ---- Automatic simplification of power expressions. ---- @return BinaryOperation -function BinaryOperation:simplifypower() - local base = self.expressions[1] - local exponent = self.expressions[2] - - if base:isconstant() and exponent:isconstant() and exponent:getring() ~= Rational:getring() then - return self:evaluate() - end - - -- Simplifies i^x for x integer. - if base == I and exponent:isconstant() and exponent:getring() == Integer:getring() then - if exponent % Integer(4) == Integer(0) then - return Integer(1) - end - if exponent % Integer(4) == Integer(1) then - return I - end - if exponent % Integer(4) == Integer(2) then - return Integer(-1) - end - if exponent % Integer(4) == Integer(3) then - return -I - end - end - - -- Simplifies complex numbers raised to negative integer powers - if not base:isrealconstant() and base:iscomplexconstant() and exponent:isconstant() and exponent:getring() == Integer:getring() and exponent < Integer.zero() then - local a - local b - if base.operation == BinaryOperation.MUL then - a = Integer.zero() - b = base.expressions[1] - elseif base.operation == BinaryOperation.ADD and base.expressions[2] == I then - a = base.expressions[1] - b = Integer.one() - else - a = base.expressions[1] - b = base.expressions[2].expressions[1] - end - return (((a-b*I)/(a^Integer(2)+b^Integer(2)))^(-exponent)):expand():autosimplify() - end - - -- Uses the property that 0^x = 0 if x does not equal 0 - if base:isconstant() and base == base:zero() then - return Integer.zero() - end - - -- Uses the property that 1^x = 1 - if base:isconstant() and base == base:one() then - return base:one() - end - - -- Uses the property that x^0 = 1 - if exponent:isconstant() and exponent == exponent:zero() then - return exponent:one() - end - - -- Uses the property that x^1 = x - if exponent:isconstant() and exponent == exponent:one() then - return base - end - - -- Uses the property that b ^ (log(b, x)) == x - if exponent:type() == Logarithm and exponent.base == base then - return exponent.expression - end - - -- Uses the property that b ^ (a * log(b, x)) == x ^ a - if exponent.operation == BinaryOperation.MUL then - local x - local rest = Integer.one() - for _, expression in ipairs(exponent.expressions) do - if expression:type() == Logarithm and expression.base == base and not log then - x = expression.expression - else - rest = rest * expression - end - end - if x then - return (x ^ rest):autosimplify() - end - end - - -- Uses the property that (x^a)^b = x^(a*b) - if not base:isatomic() and base.operation == BinaryOperation.POW and exponent:isconstant() then - base, exponent = base.expressions[1], BinaryOperation(BinaryOperation.MUL, {exponent, base.expressions[2]}):autosimplify() - return BinaryOperation(BinaryOperation.POW, {base, exponent}):autosimplify() - end - - -- Uses the property that (x_1*x_2*...*x_n)^a = x_1^a*x_2^a*..x_n^a if a is an integer - if base.operation == BinaryOperation.MUL and exponent:type() == Integer then - local results = {} - for index, expression in ipairs(base.expressions) do - results[index] = BinaryOperation(BinaryOperation.POW, {expression, exponent}):autosimplify() - end - return BinaryOperation(BinaryOperation.MUL, results):autosimplify() - end - - -- Uses the property that sqrt(x,r)^d == sqrt(x,r/d) - if base:type() == SqrtExpression and exponent:type() == Integer and exponent > Integer.zero() then - local root = base.root - local expr = base.expression - local comm = Integer.gcd(root,exponent) - root = root / comm - local expo = exponent / comm - expr = expr ^ expo - return SqrtExpression(expr,root):autosimplify() - end - - -- Rationalizing SqrtExpressions - if base:type() == SqrtExpression and exponent:type() == Integer and base.expression:type() == Integer and exponent < Integer.zero() then - local root = base.root - local expr = base.expression - local result = (SqrtExpression(expr ^ (root - Integer.one()),root) / expr) ^ exponent:neg() - return result:autosimplify() - end - - if base:isconstant() and exponent:isconstant() and exponent:getring() == Rational.getring() then - return self --:simplifyrationalpower() - end - - -- Our expression cannot be simplified - return self -end - --- Automatic simplification of rational power expressions -function BinaryOperation:simplifyrationalpower() - local base = self.expressions[1] - local exponent = self.expressions[2] - - if base:getring() == Rational.getring() then - return (BinaryOperation(BinaryOperation.POW, {base.numerator, exponent}):simplifyrationalpower()) / - (BinaryOperation(BinaryOperation.POW, {base.denominator, exponent}):simplifyrationalpower()) - end - - if base == Integer(-1) then - if exponent == Integer(1) / Integer(2) then - return I - end - - return self - end - - local primes = base:primefactorization() - - if primes.expressions[1] and not primes.expressions[2] then - local primeexponent = primes.expressions[1].expressions[2] - local primebase = primes.expressions[1].expressions[1] - local newexponent = primeexponent * exponent - local integerpart - if newexponent.getring() == Rational.getring() then - integerpart = newexponent.numerator // newexponent.denominator - else - integerpart = newexponent - end - - if integerpart == Integer.zero() then - return BinaryOperation(BinaryOperation.POW, {primebase, newexponent}) - end - return BinaryOperation(BinaryOperation.MUL, - {BinaryOperation(BinaryOperation.POW, {primebase, integerpart}), - BinaryOperation(BinaryOperation.POW, {primebase, newexponent - integerpart})}):autosimplify() - end - - return BinaryOperation(BinaryOperation.POW, {primes:autosimplify(), exponent}) -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua deleted file mode 100644 index b104cb5e8a..0000000000 --- a/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua +++ /dev/null @@ -1,231 +0,0 @@ --- Seperates the various binary operations into their own files for readability - ---- Automatic simplification of multiplication expressions. ---- @return BinaryOperation -function BinaryOperation:simplifyproduct() - if not self.expressions[1] then - error("Execution error: attempted to simplify empty product") - end - - if not self.expressions[2] then - return self.expressions[1] - end - - -- Uses the property that x*0=0 - for _, expression in ipairs(self.expressions) do - if expression:isconstant() and expression == expression:zero() then - return expression:zero() - end - end - - local result = self:simplifyproductrec() - - -- We don't really know what ring we are working in here, so just assume the integer ring - if not result.expressions[1] then - return Integer.one() - end - - if not result.expressions[2] then - return result.expressions[1] - end - - return result -end - -function BinaryOperation:simplifyproductrec() - local term1 = self.expressions[1] - local term2 = self.expressions[2] - - if not self.expressions[3] then - if (term1:isconstant() or not (term1.operation == BinaryOperation.MUL)) and - (term2:isconstant() or not (term2.operation == BinaryOperation.MUL)) then - - if term1:isconstant() and term2:isconstant() then - local result = self:evaluate() - if result == result:one() then - return BinaryOperation(BinaryOperation.MUL, {}) - end - return BinaryOperation(BinaryOperation.MUL, {result}) - end - - -- Uses the property that x*1 = x - if term1:isconstant() and term1 == term1:one() then - return BinaryOperation(BinaryOperation.MUL, {term2}) - end - - if term2:isconstant() and term2 == term2:one() then - return BinaryOperation(BinaryOperation.MUL, {term1}) - end - - -- Distributes constants if the other term is a sum expression. - if term1:isconstant() and term2.operation == BinaryOperation.ADD then - local distributed = BinaryOperation(BinaryOperation.ADD, {}) - for i, exp in ipairs(term2:subexpressions()) do - distributed.expressions[i] = term1 * exp - end - return BinaryOperation(BinaryOperation.MUL, {distributed:autosimplify()}) - end - - if term2:isconstant() and term1.operation == BinaryOperation.ADD then - local distributed = BinaryOperation(BinaryOperation.ADD, {}) - for i, exp in ipairs(term1:subexpressions()) do - distributed.expressions[i] = term2 * exp - end - return BinaryOperation(BinaryOperation.MUL, {distributed:autosimplify()}) - end - - -- Uses the property that sqrt(a,r)*sqrt(b,r) = sqrt(a*b,r) if a,r are positive integers - if term1:type() == SqrtExpression and term2:type() == SqrtExpression and term1.expression:isconstant() and term2.expression:isconstant() and term1.root:type() == Integer then - if term1.root == term2.root and term1.expression > Integer.zero() then - local expression = term1.expression*term2.expression - local result = SqrtExpression(expression,term1.root):autosimplify() - if result == Integer.one() then - return BinaryOperation(BinaryOperation.MUL,{}) - else - return BinaryOperation(BinaryOperation.MUL,{result}) - end - end - end - - --if term1.operation == BinaryOperation.POW and term2.operation == BinaryOperation.POW and term1.expressions[1]:type() == SqrtExpression and term2.expressions[1]:type() == SqrtExpression and term1.expressions[2]:type() == Integer and term2.expressions[2]:type() == Integer and term1.expressions[2] < Integer.zero() and term2.expressions[2] < Integer.zero() and term1.expressions[1].root == term2.expressions[1].root then - -- local expo1 = term1.expressions[2]:neg() - -- local expo2 = term2.expressions[2]:neg() - -- local root = term1.expressions[1].root - -- local expr1 = term1.expressions[1].expression - -- local expr2 = term2.expressions[1].expression - -- local result1 = BinaryOperation(BinaryOperation.POW,{SqrtExpression(expr1,root),expo1}):simplifypower() - -- local result2 = BinaryOperation(BinaryOperation.POW,{SqrtExpression(expr2,root),expo2}):simplifypower() - -- local result = BinaryOperation(BinaryOperation.MUL,{result1,result2}):autosimplify() - -- if result == Integer.one() then - -- return BinaryOperation(BinaryOperation.MUL,{}) - -- end - -- if result:type() == Integer then - -- return BinaryOperation(BinaryOperation.MUL,{Rational(Integer.one(),result)}) - -- end - -- return BinaryOperation(BinaryOperation.MUL,{BinaryOperation(BinaryOperation.POW, {result,Integer(-1)})}):autosimplify() - --end - - -- Uses the property that x^a*x^b=x^(a+b) - local revertterm1 = false - local revertterm2 = false - if term1.operation ~= BinaryOperation.POW then - term1 = BinaryOperation(BinaryOperation.POW, {term1, Integer.one()}) - revertterm1 = true - end - if term2.operation ~= BinaryOperation.POW then - term2 = BinaryOperation(BinaryOperation.POW, {term2, Integer.one()}) - revertterm2 = true - end - if term1.expressions[1] == term2.expressions[1] - --and not - -- (term1.expressions[1]:type() == Integer and - -- term1.expressions[2]:type() ~= term2.--expressions[2]:type()) - then - local result = BinaryOperation(BinaryOperation.POW, - {term1.expressions[1], - BinaryOperation(BinaryOperation.ADD, - {term1.expressions[2], term2.expressions[2]}):autosimplify()}):autosimplify() - if result:isconstant() and result == result:one() then - return BinaryOperation(BinaryOperation.MUL, {}) - end - return BinaryOperation(BinaryOperation.MUL, {result}) - end - - if revertterm1 then - term1 = term1.expressions[1] - end - if revertterm2 then - term2 = term2.expressions[1] - end - - if term2:order(term1) then - return BinaryOperation(BinaryOperation.MUL, {term2, term1}) - end - - return self - end - - if term1.operation == BinaryOperation.MUL and not (term2.operation == BinaryOperation.MUL) then - return term1:mergeproducts(BinaryOperation(BinaryOperation.MUL, {term2})) - end - - if not (term1.operation == BinaryOperation.MUL) and term2.operation == BinaryOperation.MUL then - return BinaryOperation(BinaryOperation.MUL, {term1}):mergeproducts(term2) - end - - return term1:mergeproducts(term2) - end - - local rest = {} - for index, expression in ipairs(self.expressions) do - if index > 1 then - rest[index - 1] = expression - end - end - - local result = BinaryOperation(BinaryOperation.MUL, rest):simplifyproductrec() - - if term1.operation ~= BinaryOperation.MUL then - term1 = BinaryOperation(BinaryOperation.MUL, {term1}) - end - if result.operation ~= BinaryOperation.MUL then - result = BinaryOperation(BinaryOperation.MUL, {result}) - end - return term1:mergeproducts(result) -end - --- Merges two lists of products -function BinaryOperation:mergeproducts(other) - if not self.expressions[1] then - return other - end - - if not other.expressions[1] then - return self - end - - local first = BinaryOperation(BinaryOperation.MUL, {self.expressions[1], other.expressions[1]}):simplifyproductrec() - - local selfrest = {} - for index, expression in ipairs(self.expressions) do - if index > 1 then - selfrest[index - 1] = expression - end - end - - local otherrest = {} - for index, expression in ipairs(other.expressions) do - if index > 1 then - otherrest[index - 1] = expression - end - end - - if first.operation ~= BinaryOperation.MUL or not first.expressions[2] then - local result = BinaryOperation(self.operation, selfrest):mergeproducts(BinaryOperation(other.operation, otherrest)) - if not first.expressions[1] then - return result - end - table.insert(result.expressions, 1, first.expressions[1]) - - if result.operation == BinaryOperation.MUL and not result.expressions[3] and result.expressions[1] and result.expressions[2] then - if result.expressions[1]:isconstant() and result.expressions[2]:isconstant() then - return result:simplifyproductrec() - end - end - return result - end - - local result - if first.expressions[1] == self.expressions[1] then - result = BinaryOperation(self.operation, selfrest):mergeproducts(other) - else - result = self:mergeproducts(BinaryOperation(other.operation, otherrest)) - end - table.insert(result.expressions, 1, first.expressions[1]) - if result.operation == BinaryOperation.MUL and not result.expressions[3] and result.expressions[1] and result.expressions[2] then - if result.expressions[1]:isconstant() and result.expressions[2]:isconstant() then - return result:simplifyproductrec() - end - end - return result -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua deleted file mode 100644 index 4dac489bf5..0000000000 --- a/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Seperates the various binary operations into their own files for readability - --- Automatic simplification of quotient expressions. ---- @return BinaryOperation -function BinaryOperation:simplifyquotient() - local numerator = self.expressions[1] - local denominator = self.expressions[2] - - if numerator:isconstant() and denominator:isconstant() then - return self:evaluate() - end - - return BinaryOperation(BinaryOperation.MUL, {numerator, BinaryOperation(BinaryOperation.POW, {denominator, Integer(-1)}):autosimplify()}):autosimplify() -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua deleted file mode 100644 index 5bb204066f..0000000000 --- a/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua +++ /dev/null @@ -1,226 +0,0 @@ --- Seperates the various binary operations into their own files for readability - --- Automatic simplification of addition expressions. ---- @return BinaryOperation -function BinaryOperation:simplifysum() - if not self.expressions[1] then - error("Execution error: Attempted to simplify empty sum") - end - - if not self.expressions[2] then - return self.expressions[1] - end - - local result = self:simplifysumrec() - - -- We don't really know what ring we are working in here, so just assume the integer ring - if not result.expressions[1] then - return Integer.zero() - end - - -- Simplifies single sums to their operands - if not result.expressions[2] then - return result.expressions[1] - end - - return result -end - -function BinaryOperation:simplifysumrec() - local term1 = self.expressions[1] - local term2 = self.expressions[2] - - if self.expressions[1] and self.expressions[2] and not self.expressions[3] then - if (term1:isconstant() or not (term1.operation == BinaryOperation.ADD)) and - (term2:isconstant() or not (term2.operation == BinaryOperation.ADD)) then - - if term1:isconstant() and term2:isconstant() then - return BinaryOperation(BinaryOperation.ADD, {self:evaluate()}) - end - - -- Uses the property that x + 0 = x - if term1:isconstant() and term1 == term1:zero() then - return BinaryOperation(BinaryOperation.ADD, {term2}) - end - - if term2:isconstant() and term2 == term2:zero() then - return BinaryOperation(BinaryOperation.ADD, {term1}) - end - - local revertterm1 = false - local revertterm2 = false - -- Uses the property that a*x+b*x= (a+b)*x - -- This is only done if a and b are constant, since otherwise this could be counterproductive - -- We SHOULD be okay to only check left distributivity, since constants always come first when ordered - if term1.operation == BinaryOperation.MUL and term2.operation == BinaryOperation.MUL then - local findex = 2 - local sindex = 2 - if not term1.expressions[1]:isconstant() then - revertterm1 = true - findex = 1 - end - if not term2.expressions[1]:isconstant() then - revertterm2 = true - sindex = 1 - end - if FancyArrayEqual(term1.expressions,term2.expressions,findex,sindex) then - local result - if not revertterm1 and not revertterm2 then - result = BinaryOperation( - BinaryOperation.ADD, - {term1.expressions[1],term2.expressions[1]} - ) - end - if revertterm1 and not revertterm2 then - result = BinaryOperation( - BinaryOperation.ADD, - {Integer.one(),term2.expressions[1]} - ) - end - if not revertterm1 and revertterm2 then - result = BinaryOperation( - BinaryOperation.ADD, - {term1.expressions[1],Integer.one()} - ) - end - if revertterm1 and revertterm2 then - result = Integer(2) - end - result = result:autosimplify() - for i=findex,#term1.expressions do - result = BinaryOperation( - BinaryOperation.MUL, - {result,term1.expressions[i]} - ) - end - result = result:autosimplify() - if result:isconstant() and result == result:zero() then - return BinaryOperation(BinaryOperation.ADD, {}) - end - return BinaryOperation(BinaryOperation.ADD, {result}) - end - end - - if term1.operation ~= BinaryOperation.MUL or not term1.expressions[1]:isconstant() then - term1 = BinaryOperation(BinaryOperation.MUL,{Integer.one(), term1}) - revertterm1 = true - end - if term2.operation ~= BinaryOperation.MUL or not term2.expressions[1]:isconstant() then - term2 = BinaryOperation(BinaryOperation.MUL, {Integer.one(), term2}) - revertterm2 = true - end - if ArrayEqual(term1.expressions, term2.expressions, 2) then - local result = BinaryOperation( - BinaryOperation.ADD, - {term1.expressions[1],term2.expressions[1]} - ) - result = result:autosimplify() - for i=2,#term1.expressions do - result = BinaryOperation( - BinaryOperation.MUL, - {result,term1.expressions[i]} - ) - end - result = result:autosimplify() - --local result = BinaryOperation(BinaryOperation.MUL, - -- {BinaryOperation(BinaryOperation.ADD, - -- {term1.expressions[1], - -- term2.expressions[1]}):autosimplify(), - -- term1.expressions[2]}):autosimplify() - if result:isconstant() and result == result:zero() then - return BinaryOperation(BinaryOperation.ADD, {}) - end - return BinaryOperation(BinaryOperation.ADD, {result}) - end - - if revertterm1 then - term1 = term1.expressions[2] - end - if revertterm2 then - term2 = term2.expressions[2] - end - - if term2:order(term1) then - return BinaryOperation(BinaryOperation.ADD, {term2, term1}) - end - - return self - end - - if term1.operation == BinaryOperation.ADD and not (term2.operation == BinaryOperation.ADD) then - return term1:mergesums(BinaryOperation(BinaryOperation.ADD, {term2})) - end - - if not (term1.operation == BinaryOperation.ADD) and term2.operation == BinaryOperation.ADD then - return BinaryOperation(BinaryOperation.ADD, {term1}):mergesums(term2) - end - - return term1:mergesums(term2) - end - - local rest = {} - for index, expression in ipairs(self.expressions) do - if index > 1 then - rest[index - 1] = expression - end - end - - local result = BinaryOperation(BinaryOperation.ADD, rest):simplifysumrec() - - if term1.operation ~= BinaryOperation.ADD then - term1 = BinaryOperation(BinaryOperation.ADD, {term1}) - end - if result.operation ~= BinaryOperation.ADD then - result = BinaryOperation(BinaryOperation.ADD, {result}) - end - return term1:mergesums(result) -end - --- Merges two lists of sums -function BinaryOperation:mergesums(other) - if not self.expressions[1] then - return other - end - - if not other.expressions[1] then - return self - end - - local first = BinaryOperation(BinaryOperation.ADD, {self.expressions[1], other.expressions[1]}):simplifysumrec() - - local selfrest = {} - for index, expression in ipairs(self.expressions) do - if index > 1 then - selfrest[index - 1] = expression - end - end - - local otherrest = {} - for index, expression in ipairs(other.expressions) do - if index > 1 then - otherrest[index - 1] = expression - end - end - - if first.operation ~= BinaryOperation.ADD or not first.expressions[2] then - local result = BinaryOperation(self.operation, selfrest):mergesums(BinaryOperation(other.operation, otherrest)) - if not first.expressions[1] then - return result - end - if first.expressions[1] ~= Integer.zero(0) then - table.insert(result.expressions, 1, first.expressions[1]) - end - return result - end - - local result - if first.expressions[1] == self.expressions[1] then - result = BinaryOperation(self.operation, selfrest):mergesums(other) - else - result = self:mergesums(BinaryOperation(other.operation, otherrest)) - end - - table.insert(result.expressions, 1, first.expressions[1]) - - return result -end diff --git a/macros/luatex/latex/luacas/tex/core/compoundexpression.lua b/macros/luatex/latex/luacas/tex/core/compoundexpression.lua deleted file mode 100644 index 792c36cfc2..0000000000 --- a/macros/luatex/latex/luacas/tex/core/compoundexpression.lua +++ /dev/null @@ -1,52 +0,0 @@ ---- @class CompoundExpression ---- Interface for an expression consisting of one or more subexpressions. -CompoundExpression = {} -__CompoundExpression = {} - ----------------------- --- Instance methods -- ----------------------- - ---- @param symbol SymbolExpression ---- @return boolean -function CompoundExpression:freeof(symbol) - for _, expression in ipairs(self:subexpressions()) do - if not expression:freeof(symbol) then - return false - end - end - return true - end - ---- @param map table ---- @return Expression -function CompoundExpression:substitute(map) - for expression, replacement in pairs(map) do - if self == expression then - return replacement - end - end - - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:substitute(map) - end - return self:setsubexpressions(results) -end - ---- @return boolean -function CompoundExpression:isatomic() - return false -end - ---- @return boolean -function CompoundExpression:isconstant() - return false -end - ------------------ --- Inheritance -- ------------------ - -__CompoundExpression.__index = Expression -CompoundExpression = setmetatable(CompoundExpression, __CompoundExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/constantexpression.lua b/macros/luatex/latex/luacas/tex/core/constantexpression.lua deleted file mode 100644 index 0f91a037a9..0000000000 --- a/macros/luatex/latex/luacas/tex/core/constantexpression.lua +++ /dev/null @@ -1,62 +0,0 @@ ---- @class ConstantExpression ---- @alias Constant ConstantExpression ---- Interface for a mathematical expression without any symbols. ---- ConstantExpressions are AtomicExpressions by default, but individual classes may overwrite that inheritance. -ConstantExpression = {} -__ConstantExpression = {} - ----------------------- --- Instance methods -- ----------------------- - - ---- @param symbol SymbolExpression ---- @return boolean -function ConstantExpression:freeof(symbol) - return true -end - ---- @return boolean -function ConstantExpression:isconstant() - return true -end - ---- @param other Expression ---- @return boolean -function ConstantExpression:order(other) - - -- Constants come before non-constants. - if not other:isconstant() then - return true - end - - if self ~= E and self ~= PI and self ~= I then - if other ~= E and other ~= PI and other ~= I then - -- If both self and other are ring elements, we use the total order on the ring to sort. - return self < other - end - -- Special constants come after ring elements. - return true - end - - -- Special constants come after ring elements. - if other ~= E and other ~= PI and other ~= I then - return false - end - - -- Ensures E < PI < I. - - if self == E then return true end - - if self == I then return false end - - return other == I -end - ------------------ --- Inheritance -- ------------------ - -__ConstantExpression.__index = AtomicExpression -ConstantExpression = setmetatable(ConstantExpression, __ConstantExpression) - diff --git a/macros/luatex/latex/luacas/tex/core/expression.lua b/macros/luatex/latex/luacas/tex/core/expression.lua deleted file mode 100644 index 8ff4659f79..0000000000 --- a/macros/luatex/latex/luacas/tex/core/expression.lua +++ /dev/null @@ -1,280 +0,0 @@ ---- @class Expression ---- Interface for an arbitrary mathematical expression. -Expression = {} - ----------------------- --- Required methods -- ----------------------- - ---- Evaluates the current expression recursively by evaluating each sub-expression. ---- @return Expression -function Expression:evaluate() - error("Called unimplemented method : evaluate()") -end - ---- Performs automatic simplification of an expression. Called on every expression before being output from the CAS. ---- @return Expression -function Expression:autosimplify() - error("Called unimplemented method : autosimplify()") -end - ---- Performs more rigorous simplification of an expression. Checks different equivalent forms and determines the 'smallest' expresion. ---- @return Expression -function Expression:simplify() - local me = self:unlock():autosimplify() - local results = {} - for index, expression in ipairs(me:subexpressions()) do - results[index] = expression:simplify() - end - me = me:setsubexpressions(results) - - local out = me - local minsize = self:size() - - local test = me:expand() - if test:size() < minsize then - out = test - minsize = test:size() - end - - test = me:factor() - if test:size() < minsize then - out = test - minsize = test:size() - end - - return out -end - ---- Changes the autosimplify behavior of an expression depending on its parameters. ---- THIS METHOD MUTATES THE OBJECT IT ACTS ON. ---- @param mode number ---- @param permanent boolean ---- @param recursive boolean ---- @return Expression -function Expression:lock(mode, permanent, recursive) - function self:autosimplify() - if not permanent then - self.autosimplify = nil - end - - if mode == Expression.NIL then - return self - elseif mode == Expression.SUBS then - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:autosimplify() - end - self = self:setsubexpressions(results, true) -- TODO: Add mutate setsubexpressions - return self - end - end - if recursive then - for _, expression in ipairs(self:subexpressions()) do - expression:lock(mode, permanent, recursive) - end - end - return self -end - ---- Frees any locks on expressions. ---- THIS METHOD MUTATES THE OBJECT IT ACTS ON. ---- @param recursive boolean ---- @return Expression -function Expression:unlock(recursive) - self.autosimplify = nil - if recursive then - for _, expression in ipairs(self:subexpressions()) do - expression:unlock(recursive) - end - end - return self -end - ---- Returns a list of all subexpressions of an expression. ---- @return table -function Expression:subexpressions() - error("Called unimplemented method : subexpressions()") -end - ---- Returns the total number of atomic and compound expressions that make up an expression, or the number of nodes in the expression tree. ---- @return Integer -function Expression:size() - local out = Integer.one() - for _, expression in ipairs(self:subexpressions()) do - out = out + expression:size() - end - return out -end - ---- Returns a copy of the original expression with each subexpression substituted with a new one, or a mutated version if mutate is true. ---- @param subexpressions table ---- @param mutate boolean ---- @return Expression -function Expression:setsubexpressions(subexpressions, mutate) - error("Called unimplemented method : setsubexpressions()") -end - ---- Determines whether or not an expression contains a particular symbol. ---- @param symbol SymbolExpression ---- @return boolean -function Expression:freeof(symbol) - error("Called unimplemented method : freeof()") -end - ---- Substitutes occurances of specified sub-expressions with other sub-expressions. ---- @param map table ---- @return Expression -function Expression:substitute(map) - error("Called unimplemented method : substitute()") -end - ---- Algebraically expands an expression by turning products of sums into sums of products and expanding powers. ---- @return Expression -function Expression:expand() - return self -end - ---- Attempts to factor an expression by turning sums of products into products of sums, and identical terms multiplied together into factors. ---- @return Expression -function Expression:factor() - return self -end - ---- Attempts to combine an expression by collapsing sums of expressions together into a single factor, e.g. common denominator ---- @return Expression -function Expression:combine() - return self -end - ---- Attempts to collect all occurances of an expression in this expression. ---- @param collect Expression ---- @return Expression -function Expression:collect(collect) - return self -end - ---- Returns all non-constant subexpressions of this expression - helper method for factor. ---- @return table -function Expression:getsubexpressionsrec() - local result = {} - - for _, expression in ipairs(self:subexpressions()) do - if not expression:isconstant() then - result[#result+1] = expression - end - result = JoinArrays(result, expression:getsubexpressionsrec()) - end - - return result -end - ---- Determines whether an expression is atomic. ---- Atomic expressions are not necessarily constant, since polynomial rings, for instance, are atomic parts that contain symbols. ---- @return boolean -function Expression:isatomic() - error("Called unimplemented method: isatomic()") -end - ---- Determines whether an expression is a constant, i.e., an atomic expression that is not a varaible and cannot be converted into an equivalent compound expression. ---- @return boolean -function Expression:isconstant() - error("Called unimplemented method: isconstant()") -end - ---- Determines whether an expression is a 'proper' real constant, i.e., is free of every varaible. -function Expression:isrealconstant() - if self:isconstant() or self == PI or self == E then - return true - end - - for _, expression in ipairs(self:subexpressions()) do - if not expression:isrealconstant() then - return false - end - end - - return self:type() ~= SymbolExpression -end - ---- Determines whether an expression is a 'proper' complex constant, i.e., is free of every varaible and is of the form a + bI for nonzero a and b. ---- @return boolean -function Expression:iscomplexconstant() - return self:isrealconstant() or (self.operation == BinaryOperation.ADD and #self.expressions == 2 and self.expressions[1]:isrealconstant() - and ((self.expressions[2].operation == BinaryOperation.MUL and #self.expressions[2].expressions == 2 and self.expressions[2].expressions[1]:isrealconstant() and self.expressions[2].expressions[2] == I) - or self.expressions[2] == I)) or (self.operation == BinaryOperation.MUL and #self.expressions == 2 and self.expressions[1]:isrealconstant() and self.expressions[2] == I) -end - ---- A total order on autosimplified expressions. Returns true if self < other. ---- @param other Expression ---- @return boolean -function Expression:order(other) - error("Called unimplemented method: order()") -end - ---- Returns an autosimplified expression as a single-variable polynomial in a ring, if it can be converted. Returns itself otherwise. ---- @return PolynomialRing, boolean -function Expression:topolynomial() - return self, false -end - ---- Converts this expression to LaTeX code. ---- @return string -function Expression:tolatex() - error("Called Unimplemented method: tolatex()") -end - ----------------------- --- Instance methods -- ----------------------- - ---- Returns the type of the expression, i.e., the table used to create objects of that type. ---- @return table -function Expression:type() - return getmetatable(self).__index -end - --------------------------- --- Instance metamethods -- --------------------------- - -__ExpressionOperations = {} - -__ExpressionOperations.__unm = function(a) - return BinaryOperation.SUBEXP({a}) -end - -__ExpressionOperations.__add = function(a, b) - return BinaryOperation.ADDEXP({a, b}) -end - -__ExpressionOperations.__sub = function(a, b) - return BinaryOperation.SUBEXP({a, b}) -end - -__ExpressionOperations.__mul = function(a, b) - return BinaryOperation.MULEXP({a, b}) -end - -__ExpressionOperations.__div = function(a, b) - return BinaryOperation.DIVEXP({a, b}) -end - -__ExpressionOperations.__pow = function(a, b) - return BinaryOperation.POWEXP({a, b}) -end - --- For iterating over the subexpressions of a easily. -__ExpressionOperations.__call = function(a, ...) - if a:type() == SymbolExpression then - return FunctionExpression(a, table.pack(...)) - end - return BinaryOperation.MULEXP({a, table.pack(...)[1]}) -end - ----------------------- --- Static constants -- ----------------------- - -Expression.NIL = 0 -Expression.SUBS = 1 \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/functionexpression.lua b/macros/luatex/latex/luacas/tex/core/functionexpression.lua deleted file mode 100644 index dc94790b81..0000000000 --- a/macros/luatex/latex/luacas/tex/core/functionexpression.lua +++ /dev/null @@ -1,295 +0,0 @@ ---- @class FunctionExpression ---- Represents a generic function that takes zero or more expressions as inputs. ---- @field name SymbolExpression ---- @field expressions table ---- @field orders table ---- @field variables table ---- @alias Function FunctionExpression -FunctionExpression = {} -__FunctionExpression = {} - ----------------------------- --- Instance functionality -- ----------------------------- - ---- Creates a new function expression with the given operation. ---- @param name string|SymbolExpression ---- @param expressions table ---- @param derivatives table ---- @return FunctionExpression -function FunctionExpression:new(name, expressions, derivatives) - local o = {} - local __o = Copy(__ExpressionOperations) - - if type(name) == "table" and name:type() == SymbolExpression then - name = name.symbol - end - - if TrigExpression.NAMES[name] and #expressions == 1 then - return TrigExpression(name, expressions[1]) - end - - -- TODO: Symbol Checking For Constructing derivatives like this - --if string.sub(name, #name, #name) == "'" and #expressions == 1 then - -- return DerivativeExpression(FunctionExpression(string.sub(name, 1, #name - 1), expressions), SymbolExpression("x"), true) - --end - - o.name = name - o.expressions = Copy(expressions) - o.variables = Copy(expressions) - for _,expression in ipairs(o.variables) do - if not expression:isatomic() then - o.variables = {} - if #o.expressions < 4 then - local defaultvars = {SymbolExpression('x'),SymbolExpression('y'),SymbolExpression('z')} - for i=1,#o.expressions do - o.variables[i] = defaultvars[i] - end - else - for i=1,#o.expressions do - o.variables[i] = SymbolExpression('x_'..tostring(i)) - end - end - end - end - if derivatives then - o.derivatives = Copy(derivatives) - else - o.derivatives = {} - for i=1,#o.variables do - o.derivatives[i] = Integer.zero() - end - end - - __o.__index = FunctionExpression - __o.__tostring = function(a) - local total = Integer.zero() - for _,integer in ipairs(a.derivatives) do - total = total + integer - end - if total == Integer.zero() then - local out = a.name .. '(' - for index, expression in ipairs(a.expressions) do - out = out .. tostring(expression) - if a.expressions[index + 1] then - out = out .. ', ' - end - end - return out .. ')' - else - local out = 'd' - if total > Integer.one() then - out = out ..'^' .. tostring(total) - end - out = out .. a.name .. '/' - for index,integer in ipairs(a.derivatives) do - if integer > Integer.zero() then - out = out .. 'd' .. tostring(a.variables[index]) - if integer > Integer.one() then - out = out .. '^' .. tostring(integer) - end - end - end - out = out .. '(' - for index, expression in ipairs(a.expressions) do - out = out .. tostring(expression) - if a.expressions[index + 1] then - out = out .. ', ' - end - end - return out .. ')' - end - end - __o.__eq = function(a, b) - -- if b:type() == TrigExpression then - -- return a == b:tofunction() - -- end - if b:type() ~= FunctionExpression then - return false - end - if #a.expressions ~= #b.expressions then - return false - end - for index, _ in ipairs(a.expressions) do - if a.expressions[index] ~= b.expressions[index] then - return false - end - end - for index,_ in ipairs(a.derivatives) do - if a.derivatives[index] ~= b.derivatives[index] then - return false - end - end - return a.name == b.name - end - - o = setmetatable(o, __o) - - return o -end - ---- @return FunctionExpression -function FunctionExpression:evaluate() - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:evaluate() - end - local result = FunctionExpression(self.name, results, self.derivatives) - result.variables = self.variables - return result -end - ---- @return FunctionExpression -function FunctionExpression:autosimplify() - -- Since the function is completely generic, we can't really do anything execpt autosimplify subexpressions. - local results = {} - for index, expression in ipairs(self:subexpressions()) do - results[index] = expression:autosimplify() - end - local result = FunctionExpression(self.name, results, self.derivatives) - result.variables = self.variables - return result -end - ---- @return table -function FunctionExpression:subexpressions() - return self.expressions -end - ---- @param subexpressions table ---- @return FunctionExpression -function FunctionExpression:setsubexpressions(subexpressions) - local result = FunctionExpression(self.name, subexpressions, self.derivatives) - result.variables = self.variables - return result -end - ---- @param other Expression ---- @return boolean -function FunctionExpression:order(other) - if other:isatomic() then - return false - end - - -- CASC Autosimplfication has some symbols appearing before functions, but that looks bad to me, so all symbols appear before products now. - -- if other:type() == SymbolExpression then - -- return SymbolExpression(self.name):order(other) - -- end - - if other:type() == BinaryOperation then - if other.operation == BinaryOperation.ADD or other.operation == BinaryOperation.MUL then - return BinaryOperation(other.operation, {self}):order(other) - end - - if other.operation == BinaryOperation.POW then - return (self^Integer.one()):order(other) - end - end - - if other:type() == SqrtExpression then - return self:order(other:topower()) - end - - -- TODO: Make Logarithm and AbsExpression inherit from function expression to reduce code duplication - if other:type() == Logarithm then - return self:order(FunctionExpression("log", {other.base, other.expression})) - end - - if other:type() ~= FunctionExpression and other:type() ~= TrigExpression then - return true - end - - if self.name ~= other.name then - return SymbolExpression(self.name):order(SymbolExpression(other.name)) - end - - local k = 1 - while self:subexpressions()[k] and other:subexpressions()[k] do - if self:subexpressions()[k] ~= other:subexpressions()[k] then - return self:subexpressions()[k]:order(other:subexpressions()[k]) - end - k = k + 1 - end - return #self.expressions < #other.expressions -end - ---- @return string -function FunctionExpression:tolatex() - local out = tostring(self.name) - if self:type() == TrigExpression then - out = "\\" .. out - end - if self:type() ~= TrigExpression and #self.name>1 then - --if out:sub(2,2) ~= "'" then - --local fp = out:find("'") - --if fp then - -- out = '\\operatorname{' .. out:sub(1,fp-1) .. '}' .. out:sub(fp,-1) - --else - out = '\\operatorname{' .. out .. '}' - --end - --end - end - local total = Integer.zero() - for _,integer in ipairs(self.derivatives) do - total = total + integer - end - if #self.expressions == 1 then - if total == Integer.zero() then - goto continue - else - if total < Integer(5) then - while total > Integer.zero() do - out = out .. "'" - total = total - Integer.one() - end - else - out = out .. '^{(' .. total:tolatex() .. ')}' - end - end - end - if #self.expressions > 1 then - if total == Integer.zero() then - goto continue - else - if total < Integer(4) then - out = out .. '_{' - for index,integer in ipairs(self.derivatives) do - local i = integer:asnumber() - while i > 0 do - out = out .. self.variables[index]:tolatex() - i = i - 1 - end - end - out = out .. '}' - else - out = '\\frac{\\partial^{' .. total:tolatex() .. '}' .. out .. '}{' - for index, integer in ipairs(self.derivatives) do - if integer > Integer.zero() then - out = out .. '\\partial ' .. self.variables[index]:tolatex() - if integer ~= Integer.one() then - out = out .. '^{' .. integer:tolatex() .. '}' - end - end - end - out = out .. '}' - end - end - end - ::continue:: - out = out ..'\\mathopen{}' .. '\\left(' - for index, expression in ipairs(self:subexpressions()) do - out = out .. expression:tolatex() - if self:subexpressions()[index + 1] then - out = out .. ', ' - end - end - return out .. '\\right)' -end - ------------------ --- Inheritance -- ------------------ - -__FunctionExpression.__index = CompoundExpression -__FunctionExpression.__call = FunctionExpression.new -FunctionExpression = setmetatable(FunctionExpression, __FunctionExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-atomicexpression.lua b/macros/luatex/latex/luacas/tex/core/luacas-atomicexpression.lua new file mode 100644 index 0000000000..6cc6e4f58e --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-atomicexpression.lua @@ -0,0 +1,70 @@ +--- @class AtomicExpression +--- Interface for an atomic mathematical expression that has no sub-expressions. +AtomicExpression = {} +__AtomicExpression = {} + + +---------------------- +-- Required methods -- +---------------------- + +--- Converts an atomic expression to its equivalent compound expression, if it has one. +--- @return Expression +function AtomicExpression:tocompoundexpression() + return self +end + +---------------------- +-- Instance methods -- +---------------------- + +--- @return AtomicExpression +function AtomicExpression:evaluate() + return self +end + +--- @return AtomicExpression +function AtomicExpression:autosimplify() + return self +end + +--- @return table +function AtomicExpression:subexpressions() + return {} +end + +--- @param subexpressions table +--- @return AtomicExpression +function AtomicExpression:setsubexpressions(subexpressions) + return self +end + +--- @param map table +--- @return Expression +function AtomicExpression:substitute(map) + for expression, replacement in pairs(map) do + if self == expression then + return replacement + end + end + return self +end + +--- @return boolean +function AtomicExpression:isatomic() + return true +end + +--- @return string +function AtomicExpression:tolatex() + -- Most atomic expressions should have the same __tostring as LaTeX's output + return tostring(self) +end + + +----------------- +-- Inheritance -- +----------------- + +__AtomicExpression.__index = Expression +AtomicExpression = setmetatable(AtomicExpression, __AtomicExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-binaryoperation.lua b/macros/luatex/latex/luacas/tex/core/luacas-binaryoperation.lua new file mode 100644 index 0000000000..708a6c1540 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-binaryoperation.lua @@ -0,0 +1,800 @@ +--- @class BinaryOperation +--- Represents a binary operation with two inputs and one output. +--- Represents a generic function that takes zero or more expressions as inputs. +--- @field name string +--- @field operation function +--- @field expressions table +BinaryOperation = {} +__BinaryOperation = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new binary operation with the given operation. +--- @param operation function +--- @param expressions table +--- @return BinaryOperation +function BinaryOperation:new(operation, expressions) + local o = {} + local __o = Copy(__ExpressionOperations) + + if type(operation) ~= "function" then + error("Sent parameter of wrong type: operation must be a function") + end + + if type(expressions) ~= "table" then + error("Sent parameter of wrong type: expressions must be an array") + end + + o.name = BinaryOperation.DEFAULT_NAMES[operation] + o.operation = operation + o.expressions = Copy(expressions) + + if BinaryOperation.COMMUTATIVITY[operation] then + function o:iscommutative() + return true + end + else + function o:iscommutative() + return false + end + end + + if not o:iscommutative() and o.operation ~= BinaryOperation.SUB and #o.expressions ~= 2 then + error("Sent parameter of wrong type: noncommutative operations cannot have an arbitrary number of paramaters") + end + + __o.__index = BinaryOperation + __o.__tostring = function(a) + local expressionnames = '' + for index, expression in ipairs(a.expressions) do + if index == 1 and not a.expressions[index + 1] then + expressionnames = expressionnames .. a.name .. ' ' + end + if index > 1 then + expressionnames = expressionnames .. ' ' + end + if expression:isatomic() and not (a.operation == BinaryOperation.POW and expression:type() == Rational) then + expressionnames = expressionnames .. tostring(expression) + else + expressionnames = expressionnames .. '(' .. tostring(expression) .. ')' + end + if a.expressions[index + 1] then + expressionnames = expressionnames .. ' ' .. a.name + end + end + return expressionnames + end + __o.__eq = function(a, b) + -- This shouldn't be needed, since __eq should only fire if both metamethods have the same function, but for some reason Lua always runs this anyway + if not a.operation or not b.operation then + return false + end + local loc = 1 + while a.expressions[loc] or b.expressions[loc] do + if not a.expressions[loc] or not b.expressions[loc] or + (a.expressions[loc] ~= b.expressions[loc]) then + return false + end + loc = loc + 1 + end + return a.operation == b.operation + end + o = setmetatable(o, __o) + + return o +end + +--- @return Expression +function BinaryOperation:evaluate() + local results = {} + local reducible = true + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:evaluate() + if not results[index]:isconstant() then + reducible = false + end + end + if not reducible then + return BinaryOperation(self.operation, results) + end + + if not self.expressions[1] then + error("Execution error: cannot perform binary operation on zero expressions") + end + + local result = results[1] + for index, expression in ipairs(results) do + if not (index == 1) then + result = self.operation(result, expression) + end + end + return result +end + +--- @return Expression +function BinaryOperation:autosimplify() + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:autosimplify() + end + local simplified = BinaryOperation(self.operation, results) + if simplified.operation == BinaryOperation.POW then + return simplified:simplifypower() + end + if simplified.operation == BinaryOperation.MUL then + return simplified:simplifyproduct() + end + if simplified.operation == BinaryOperation.ADD then + return simplified:simplifysum() + end + if simplified.operation == BinaryOperation.DIV then + return simplified:simplifyquotient() + end + if simplified.operation == BinaryOperation.SUB then + return simplified:simplifydifference() + end + return simplified +end + +--- @return table +function BinaryOperation:subexpressions() + return self.expressions +end + +--- @param subexpressions table +--- @return BinaryOperation +function BinaryOperation:setsubexpressions(subexpressions) + return BinaryOperation(self.operation, subexpressions) +end + +--- @return Expression +function BinaryOperation:expand() + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:expand() + end + local expanded = BinaryOperation(self.operation, results) + if expanded.operation == BinaryOperation.MUL then + local allsums = BinaryOperation(BinaryOperation.ADD, {Integer.one()}) + for _, expression in ipairs(expanded.expressions) do + allsums = allsums:expand2(expression) + end + return allsums:autosimplify() + end + if expanded.operation == BinaryOperation.POW and expanded.expressions[2]:type() == Integer then + if expanded.expressions[1]:type() ~= BinaryOperation then + return expanded:autosimplify() + end + local exp = BinaryOperation.MULEXP({Integer.one()}) + local pow = expanded.expressions[2]:asnumber() + for _ = 1, math.abs(pow) do + exp = exp:expand2(expanded.expressions[1]) + if _ > 1 then + exp = exp:autosimplify() + end + end + if pow < 0 then + exp = exp^Integer(-1) + end + return exp + end + if expanded.operation == BinaryOperation.POW and expanded.expressions[2].operation == BinaryOperation.ADD then + local exp = {} + for i = 1, #expanded.expressions[2].expressions do + exp[#exp+1] = (expanded.expressions[1]^expanded.expressions[2].expressions[i]):autosimplify() + end + return BinaryOperation.MULEXP(exp) + end + return expanded:autosimplify() +end + +--- Helper for expand - multiplies two addition expressions. +--- @return Expression +function BinaryOperation:expand2(other) + local result = {} + for _, expression in ipairs(self:subexpressions()) do + if other:type() == BinaryOperation and other.operation == BinaryOperation.ADD then + for _, expression2 in ipairs(other.expressions) do + result[#result+1] = expression * expression2 + end + else + result[#result+1] = expression * other + end + end + return BinaryOperation(BinaryOperation.ADD, result) +end + +--- @return Expression +function BinaryOperation:factor() + local results = {} + + -- Recursively factors sub-expressions + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:factor() + end + + -- Attempts to factor expressions as monovariate polynomials + local factoredsubs = BinaryOperation(self.operation, results) + local subs = factoredsubs:getsubexpressionsrec() + for index, sub in ipairs(subs) do + local substituted = factoredsubs:substitute({[sub]=SymbolExpression("_")}):autosimplify() + local polynomial, result = substituted:topolynomial() + if result then + local factored = polynomial:factor():autosimplify() + if factored ~= substituted then + return factored:substitute({[SymbolExpression("_")]=sub}) + end + end + end + + -- Pulls common sub-expressions out of sum expressions + if self.operation == BinaryOperation.ADD then + local gcf + for _, expression in ipairs(factoredsubs:subexpressions()) do + if expression.operation ~= BinaryOperation.MUL then + expression = BinaryOperation.MULEXP({expression}) + end + if not gcf then + gcf = expression + else + local newgcf = Integer.one() + for _, gcfterm in ipairs(gcf:subexpressions()) do + local gcfpower = Integer.one() + if gcfterm:type() == BinaryOperation and gcfterm.operation == BinaryOperation.POW and gcfterm.expressions[2]:type() == Integer then + gcfpower = gcfterm.expressions[2] + gcfterm = gcfterm.expressions[1] + end + for _, term in ipairs(expression:subexpressions()) do + local power = Integer.one() + if term:type() == BinaryOperation and term.operation == BinaryOperation.POW and term.expressions[2]:type() == Integer then + power = term.expressions[2] + term = term.expressions[1] + end + if term == gcfterm then + newgcf = newgcf * term^Integer.min(power, gcfpower) + end + end + end + gcf = newgcf + end + end + if gcf:type() ~= Integer then + local out = Integer.zero() + for _, expression in ipairs(factoredsubs:subexpressions()) do + out = out + expression/gcf + end + out = gcf*(out:autosimplify():factor()) + return out:autosimplify() + end + end + + return factoredsubs +end + +--- @return Expression +function BinaryOperation:combine() + local den, num, aux, mul, input = {}, {}, {}, {}, self:autosimplify():expand() + if input.operation ~= BinaryOperation.ADD then + return input + end + for _, expr in ipairs(input.expressions) do + local numpart, denpart = Integer.one(), Integer.one() + if expr.operation == BinaryOperation.POW and expr.expressions[2]:type() == Integer and expr.expressions[2] < Integer.zero() then + denpart = denpart*expr.expressions[1] ^ expr.expressions[2]:neg() + for index,term in ipairs(den) do + if expr.expressions[1] == den[index] then + if expr.expressions[2]:neg() > mul[index] then + mul[index] = expr.expressions[2]:neg() + goto continue + else + goto continue + end + end + end + table.insert(den,expr.expressions[1]) + table.insert(mul,expr.expressions[2]:neg()) + ::continue:: + end + if expr.operation == BinaryOperation.MUL then + for _,subexpr in ipairs(expr.expressions) do + if subexpr.operation == BinaryOperation.POW and subexpr.expressions[2]:type() == Integer and subexpr.expressions[2] < Integer.zero() then + denpart = denpart*subexpr.expressions[1] ^ subexpr.expressions[2]:neg() + for index,term in ipairs(den) do + if subexpr.expressions[1] == den[index] then + if subexpr.expressions[2]:neg() > mul[index] then + mul[index] = subexpr.expressions[2]:neg() + goto continue + else + goto continue + end + end + end + table.insert(den,subexpr.expressions[1]) + table.insert(mul,subexpr.expressions[2]:neg()) + ::continue:: + else + numpart = numpart*subexpr + end + end + end + if expr.operation ~= BinaryOperation.POW and expr.operation ~= BinaryOperation.MUL then + numpart = expr + end + table.insert(num,numpart) + table.insert(aux,denpart) + end + local denominator = Integer.one() + local numerator = Integer.zero() + for index,expr in ipairs(den) do + denominator = denominator*den[index] ^ mul[index] + end + denominator = denominator:autosimplify() + for index,expr in ipairs(num) do + local uncommon = denominator/aux[index] + uncommon = uncommon:factor():simplify() + numerator = numerator + expr*uncommon + end + numerator = numerator:simplify():factor() + if denominator == Integer.one() then + return numerator + else + return numerator/denominator + end +end + +--- @param collect Expression +--- @return Expression +function BinaryOperation:collect(collect) + -- Constant expressions cannot be collected + if collect:isconstant() then + return self + end + + -- Recusively collect subexpressions + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:collect(collect) + end + local collected = BinaryOperation(self.operation, results) + + if not (collected.operation == BinaryOperation.ADD) then + return collected:autosimplify() + end + + local coefficients = {} + + -- TODO: Add an expression map class + setmetatable(coefficients, {__index = + function(table, key) + local out = rawget(table, tostring(key)) + return out or Integer.zero() + end, + __newindex = + function (table, key, value) + rawset(table, tostring(key), value) + end + }) + + -- Finds all instances of a constant power of the expression to be collected, and maps each power to all terms it is multiplied by + for _, expression in ipairs(collected:subexpressions()) do + if expression == collect then + coefficients[Integer.one()] = coefficients[Integer.one()] + Integer.one() + elseif expression.operation == BinaryOperation.POW and expression:subexpressions()[1] == collect and expression:subexpressions()[2]:isconstant() then + coefficients[expression:subexpressions()[2]] = coefficients[expression:subexpressions()[2]] + Integer.one() + elseif collect:type() == BinaryOperation and collect.operation == BinaryOperation.POW and + expression.operation == BinaryOperation.POW and expression:subexpressions()[1] == collect:subexpressions()[1] then + -- Handle the fact that autosimplify turns (a^x^n -> a^(xn)), this is needed if the term to collect is itself an exponential + local power = (expression:subexpressions()[2] / collect:subexpressions()[2]):autosimplify() + if power:isconstant() then + coefficients[power] = coefficients[power] + Integer.one() + else + coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression + end + elseif expression.operation == BinaryOperation.MUL then + local varpart + local coeffpart = Integer.one() + for _, term in ipairs(expression:subexpressions()) do + if term == collect then + varpart = Integer.one() + elseif (term.operation == BinaryOperation.POW and term:subexpressions()[1] == collect and term:subexpressions()[2]:isconstant()) then + varpart = term:subexpressions()[2] + elseif collect:type() == BinaryOperation and collect.operation == BinaryOperation.POW and + term.operation == BinaryOperation.POW and term:subexpressions()[1] == collect:subexpressions()[1] then + local power = (term:subexpressions()[2] / collect:subexpressions()[2]):autosimplify() + if power:isconstant() then + varpart = power + end + else + coeffpart = coeffpart * term + end + end + if varpart then + coefficients[varpart] = coefficients[varpart] + coeffpart + else + coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression + end + else + coefficients[Integer.zero()] = coefficients[Integer.zero()] + expression + end + + + end + + local out = Integer.zero() + for index, value in pairs(coefficients) do + out = out + collect ^ Rational.fromstring(index) * value + end + + return out:autosimplify() +end + +--- @param other Expression +--- @return boolean +function BinaryOperation:order(other) + if other:isconstant() then + return false + end + + if other:isatomic() then + if self.operation == BinaryOperation.POW then + return self:order(BinaryOperation(BinaryOperation.POW, {other, Integer.one()})) + end + + if self.operation == BinaryOperation.MUL then + return self:order(BinaryOperation(BinaryOperation.MUL, {other})) + end + + if self.operation == BinaryOperation.ADD then + return self:order(BinaryOperation(BinaryOperation.ADD, {other})) + end + end + + if self.operation == BinaryOperation.POW and other.operation == BinaryOperation.POW then + if self.expressions[1] ~= other.expressions[1] then + return self.expressions[1]:order(other.expressions[1]) + end + return self.expressions[2]:order(other.expressions[2]) + end + + if (self.operation == BinaryOperation.MUL and other.operation == BinaryOperation.MUL) or + (self.operation == BinaryOperation.ADD and other.operation == BinaryOperation.ADD) then + local k = 0 + while #self.expressions - k > 0 and #other.expressions - k > 0 do + if self.expressions[#self.expressions - k] ~= other.expressions[#other.expressions - k] then + return self.expressions[#self.expressions - k]:order(other.expressions[#other.expressions - k]) + end + k = k + 1 + end + return #self.expressions < #other.expressions + end + + if (self.operation == BinaryOperation.MUL) and (other.operation == BinaryOperation.POW or other.operation == BinaryOperation.ADD) then + return self:order(BinaryOperation(BinaryOperation.MUL, {other})) + end + + if (self.operation == BinaryOperation.POW) and (other.operation == BinaryOperation.MUL) then + return BinaryOperation(BinaryOperation.MUL, {self}):order(other) + end + + if (self.operation == BinaryOperation.POW) and (other.operation == BinaryOperation.ADD) then + return self:order(BinaryOperation(BinaryOperation.POW, {other, Integer.one()})) + end + + if (self.operation == BinaryOperation.ADD) and (other.operation == BinaryOperation.MUL) then + return BinaryOperation(BinaryOperation.MUL, {self}):order(other) + end + + if (self.operation == BinaryOperation.ADD) and (other.operation == BinaryOperation.POW) then + return BinaryOperation(BinaryOperation.POW, {self, Integer.one()}):order(other) + end + + if other:type() == FunctionExpression or other:type() == TrigExpression or other:type() == Logarithm then + if self.operation == BinaryOperation.ADD or self.operation == BinaryOperation.MUL then + return self:order(BinaryOperation(self.operation, {other})) + end + + if self.operation == BinaryOperation.POW then + return self:order(other^Integer.one()) + end + end + + return true +end + +--- Returns whether the binary operation is commutative. +--- @return boolean +function BinaryOperation:iscommutative() + error("Called unimplemented method: iscommutative()") +end + +--- @return PolynomialRing, boolean +function BinaryOperation:topolynomial() + local addexp = self + if not self.operation or self.operation ~= BinaryOperation.ADD then + addexp = BinaryOperation(BinaryOperation.ADD, {self}) + end + + local poly = {} + local degree = 0 + local symbol + for _, expression in ipairs(addexp.expressions) do + local coefficient + local sym + local power + -- Expressions of the form c + if expression:isconstant() then + coefficient = expression + power = 0 + -- Expressions of the form x + elseif expression:type() == SymbolExpression then + coefficient = Integer.one() + sym = expression.symbol + power = 1 + -- Expressions of the form c*x + elseif expression.operation and expression.operation == BinaryOperation.MUL and #expression.expressions == 2 + and expression.expressions[1]:isconstant() and expression.expressions[2]:type() == SymbolExpression then + + coefficient = expression.expressions[1] + sym = expression.expressions[2].symbol + power = 1 + -- Expressions of the form c*x^n (totally not confusing) + elseif expression.operation and expression.operation == BinaryOperation.MUL and #expression.expressions == 2 + and expression.expressions[1]:isconstant() and expression.expressions[2].operation and + expression.expressions[2].operation == BinaryOperation.POW and #expression.expressions[2].expressions == 2 + and expression.expressions[2].expressions[1]:type() == SymbolExpression and expression.expressions[2].expressions[2].getring + and expression.expressions[2].expressions[2]:getring() == Integer.getring() and expression.expressions[2].expressions[2] > Integer.zero() then + + coefficient = expression.expressions[1] + sym = expression.expressions[2].expressions[1].symbol + power = expression.expressions[2].expressions[2]:asnumber() + -- Expressions of the form x^n + elseif expression.operation and expression.operation == BinaryOperation.POW and #expression.expressions == 2 + and expression.expressions[1]:type() == SymbolExpression and expression.expressions[2].getring + and expression.expressions[2]:getring() == Integer.getring() and expression.expressions[2] > Integer.zero() then + + coefficient = Integer.one() + sym = expression.expressions[1].symbol + power = expression.expressions[2]:asnumber() + else + return self, false + end + + if symbol and sym and symbol ~= sym then + return self, false + end + if not symbol then + symbol = sym + end + poly[power + 1] = coefficient + if power > degree then + degree = power + end + end + + for i = 1,degree+1 do + poly[i] = poly[i] or Integer.zero() + end + + return PolynomialRing(poly, symbol), true +end + +function BinaryOperation:tolatex() + if self.operation == BinaryOperation.POW then + if self.expressions[2]:type() == Integer and self.expressions[2] < Integer.zero() then + local base = self.expressions[1] + local exponent = self.expressions[2] + if exponent == Integer(-1) then + return "\\frac{1}{" .. base:tolatex() .. "}" + else + if base:isatomic() then + return "\\frac{1}{" .. base:tolatex() .. "^{" .. exponent:neg():tolatex() .. "}}" + else + return "\\frac{1}{\\left(" .. base:tolatex() .. "\\right)^{" .. exponent:neg():tolatex() .. "}}" + end + end + end + if self.expressions[1]:isatomic() then + if self.expressions[2]:isconstant() and self.expressions[2]:getring() == Rational:getring() and self.expressions[2].numerator == Integer.one() then + if self.expressions[2].denominator == Integer(2) then + return "\\sqrt{" .. self.expressions[1]:tolatex() .. '}' + end + return "\\sqrt[" .. self.expressions[2].denominator:tolatex() .. ']{' .. self.expressions[1]:tolatex() .. '}' + end + return self.expressions[1]:tolatex() .. '^{' .. self.expressions[2]:tolatex() .. '}' + else + if self.expressions[2]:isconstant() and self.expressions[2]:getring() == Rational:getring() and self.expressions[2].numerator == Integer.one() then + if self.expressions[2].denominator == Integer(2) then + return "\\sqrt{" .. self.expressions[1]:tolatex() .. '}' + end + return "\\sqrt[" .. self.expressions[2].denominator:tolatex() .. ']{' .. self.expressions[1]:tolatex() .. '}' + end + return "\\left(" .. self.expressions[1]:tolatex() .. "\\right)" .. '^{' .. self.expressions[2]:tolatex() .. '}' + end + end + if self.operation == BinaryOperation.MUL then + local sign = '' + local out = '' + local denom = '' + if self:autosimplify():isconstant() then + for index, expression in ipairs(self.expressions) do + if index == 1 then + out = out .. expression:tolatex() + else + out = out .. "\\cdot " .. expression:tolatex() + end + end + return out + end + if #self.expressions == 2 and self.expressions[2]:type() == BinaryOperation and self.expressions[2].operation == BinaryOperation.POW and self.expressions[2].expressions[2] == -Integer.one() then + out = '\\frac{' .. self.expressions[1]:tolatex() .. '}{' .. self.expressions[2].expressions[1]:tolatex() .. '}' + return out + end + for _, expression in ipairs(self.expressions) do + if expression:type() == BinaryOperation then + if expression.operation == BinaryOperation.POW and expression.expressions[2]:isconstant() and expression.expressions[2] < Integer.zero() then + local reversed = (Integer.one() / expression):autosimplify() + if reversed.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB then + denom = denom .. '\\left('.. reversed:tolatex() .. '\\right)' + else + denom = denom .. reversed:tolatex() + end + elseif expression.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB then + out = out .. '\\left(' .. expression:tolatex() .. '\\right)' + else + out = out .. expression:tolatex() + end + else + if expression == Integer(-1) then + out = out .. '-' + elseif expression:type() == Rational and expression.numerator == Integer.one() then + denom = denom .. expression.denominator:tolatex() + elseif expression:type() == Rational and expression.numerator == Integer(-1) then + out = out .. '-' + denom = denom .. expression.denominator:tolatex() + elseif expression:type() == Rational then + out = out .. expression.numerator:tolatex() + denom = denom .. expression.denominator:tolatex() + else + out = out .. expression:tolatex() + end + end + end + if string.sub(out,1,1) == '-' then + sign = '-' + out = string.sub(out,2,-1) + end + if denom ~= '' and out == '' then + return sign .. '\\frac{' .. '1' .. '}{' .. denom .. '}' + end + if denom ~= '' then + return sign .. '\\frac{' .. out .. '}{' .. denom .. '}' + end + return sign..out + end + if self.operation == BinaryOperation.ADD then + local out = '' + for index, expression in ipairs(self.expressions) do + out = out .. expression:tolatex() + if self.expressions[index + 1] and string.sub(self.expressions[index + 1]:tolatex(), 1, 1) ~= "-" then + out = out .. '+' + end + end + return out + end + if self.operation == BinaryOperation.DIV then + return '\\frac{' .. self.expressions[1]:tolatex() .. '}{' .. self.expressions[2]:tolatex() .. '}' + end + if self.operation == BinaryOperation.SUB then + local out = '' + if not self.expressions[2] then + if not self.expressions[1]:isatomic() then + out = '-\\left(' .. self.expressions[1]:tolatex() .. '\\right)' + else + out = '-' .. self.expressions[1]:tolatex() + end + else + for index, expression in ipairs(self.expressions) do + if expression.operation and (expression.operation == BinaryOperation.ADD or expression.operation == BinaryOperation.SUB) and index >1 then + out = out .. "\\left(" .. expression:tolatex() .. "\\right)" + else + out = out .. expression:tolatex() + end + if self.expressions[index + 1] then + out = out .. '-' + end + end + end + return out + end + return self +end + +----------------- +-- Inheritance -- +----------------- + +__BinaryOperation.__index = CompoundExpression +__BinaryOperation.__call = BinaryOperation.new +BinaryOperation = setmetatable(BinaryOperation, __BinaryOperation) + +---------------------- +-- Static constants -- +---------------------- + +BinaryOperation.ADD = function(a, b) + return a + b +end + +BinaryOperation.SUB = function(a, b) + return a - b +end + +BinaryOperation.MUL = function(a, b) + return a * b +end + +BinaryOperation.DIV = function(a, b) + return a / b +end + +BinaryOperation.IDIV = function(a, b) + return a // b +end + +BinaryOperation.MOD = function(a, b) + return a % b +end + +BinaryOperation.POW = function(a, b) + return a ^ b +end + +BinaryOperation.DEFAULT_NAMES = { + [BinaryOperation.ADD] = "+", + [BinaryOperation.SUB] = "-", + [BinaryOperation.MUL] = "*", + [BinaryOperation.DIV] = "/", + [BinaryOperation.IDIV] = "//", + [BinaryOperation.MOD] = "%", + [BinaryOperation.POW] = "^" +} + +BinaryOperation.COMMUTATIVITY = { + [BinaryOperation.ADD] = true, + [BinaryOperation.SUB] = false, + [BinaryOperation.MUL] = true, + [BinaryOperation.DIV] = false, + [BinaryOperation.IDIV] = false, + [BinaryOperation.MOD] = false, + [BinaryOperation.POW] = false +} + +BinaryOperation.ADDEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.ADD, expressions, name) +end + +BinaryOperation.SUBEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.SUB, expressions, name) +end + +BinaryOperation.MULEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.MUL, expressions, name) +end + +BinaryOperation.DIVEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.DIV, expressions, name) +end + +BinaryOperation.IDIVEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.IDIV, expressions, name) +end + +BinaryOperation.MODEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.MOD, expressions, name) +end + +BinaryOperation.POWEXP = function(expressions, name) + return BinaryOperation(BinaryOperation.POW, expressions, name) +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-compoundexpression.lua b/macros/luatex/latex/luacas/tex/core/luacas-compoundexpression.lua new file mode 100644 index 0000000000..792c36cfc2 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-compoundexpression.lua @@ -0,0 +1,52 @@ +--- @class CompoundExpression +--- Interface for an expression consisting of one or more subexpressions. +CompoundExpression = {} +__CompoundExpression = {} + +---------------------- +-- Instance methods -- +---------------------- + +--- @param symbol SymbolExpression +--- @return boolean +function CompoundExpression:freeof(symbol) + for _, expression in ipairs(self:subexpressions()) do + if not expression:freeof(symbol) then + return false + end + end + return true + end + +--- @param map table +--- @return Expression +function CompoundExpression:substitute(map) + for expression, replacement in pairs(map) do + if self == expression then + return replacement + end + end + + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:substitute(map) + end + return self:setsubexpressions(results) +end + +--- @return boolean +function CompoundExpression:isatomic() + return false +end + +--- @return boolean +function CompoundExpression:isconstant() + return false +end + +----------------- +-- Inheritance -- +----------------- + +__CompoundExpression.__index = Expression +CompoundExpression = setmetatable(CompoundExpression, __CompoundExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-constantexpression.lua b/macros/luatex/latex/luacas/tex/core/luacas-constantexpression.lua new file mode 100644 index 0000000000..0f91a037a9 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-constantexpression.lua @@ -0,0 +1,62 @@ +--- @class ConstantExpression +--- @alias Constant ConstantExpression +--- Interface for a mathematical expression without any symbols. +--- ConstantExpressions are AtomicExpressions by default, but individual classes may overwrite that inheritance. +ConstantExpression = {} +__ConstantExpression = {} + +---------------------- +-- Instance methods -- +---------------------- + + +--- @param symbol SymbolExpression +--- @return boolean +function ConstantExpression:freeof(symbol) + return true +end + +--- @return boolean +function ConstantExpression:isconstant() + return true +end + +--- @param other Expression +--- @return boolean +function ConstantExpression:order(other) + + -- Constants come before non-constants. + if not other:isconstant() then + return true + end + + if self ~= E and self ~= PI and self ~= I then + if other ~= E and other ~= PI and other ~= I then + -- If both self and other are ring elements, we use the total order on the ring to sort. + return self < other + end + -- Special constants come after ring elements. + return true + end + + -- Special constants come after ring elements. + if other ~= E and other ~= PI and other ~= I then + return false + end + + -- Ensures E < PI < I. + + if self == E then return true end + + if self == I then return false end + + return other == I +end + +----------------- +-- Inheritance -- +----------------- + +__ConstantExpression.__index = AtomicExpression +ConstantExpression = setmetatable(ConstantExpression, __ConstantExpression) + diff --git a/macros/luatex/latex/luacas/tex/core/luacas-core_init.lua b/macros/luatex/latex/luacas/tex/core/luacas-core_init.lua new file mode 100644 index 0000000000..c9c3b89126 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-core_init.lua @@ -0,0 +1,16 @@ +-- Loads core files in the correct order. + +require("core.luacas-expression") +require("core.luacas-atomicexpression") +require("core.luacas-compoundexpression") +require("core.luacas-constantexpression") +require("core.luacas-symbolexpression") +require("core.luacas-binaryoperation") +require("core.luacas-functionexpression") + + +require("core.binaryoperation.luacas-power") +require("core.binaryoperation.luacas-product") +require("core.binaryoperation.luacas-sum") +require("core.binaryoperation.luacas-quotient") +require("core.binaryoperation.luacas-difference") \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-expression.lua b/macros/luatex/latex/luacas/tex/core/luacas-expression.lua new file mode 100644 index 0000000000..8ff4659f79 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-expression.lua @@ -0,0 +1,280 @@ +--- @class Expression +--- Interface for an arbitrary mathematical expression. +Expression = {} + +---------------------- +-- Required methods -- +---------------------- + +--- Evaluates the current expression recursively by evaluating each sub-expression. +--- @return Expression +function Expression:evaluate() + error("Called unimplemented method : evaluate()") +end + +--- Performs automatic simplification of an expression. Called on every expression before being output from the CAS. +--- @return Expression +function Expression:autosimplify() + error("Called unimplemented method : autosimplify()") +end + +--- Performs more rigorous simplification of an expression. Checks different equivalent forms and determines the 'smallest' expresion. +--- @return Expression +function Expression:simplify() + local me = self:unlock():autosimplify() + local results = {} + for index, expression in ipairs(me:subexpressions()) do + results[index] = expression:simplify() + end + me = me:setsubexpressions(results) + + local out = me + local minsize = self:size() + + local test = me:expand() + if test:size() < minsize then + out = test + minsize = test:size() + end + + test = me:factor() + if test:size() < minsize then + out = test + minsize = test:size() + end + + return out +end + +--- Changes the autosimplify behavior of an expression depending on its parameters. +--- THIS METHOD MUTATES THE OBJECT IT ACTS ON. +--- @param mode number +--- @param permanent boolean +--- @param recursive boolean +--- @return Expression +function Expression:lock(mode, permanent, recursive) + function self:autosimplify() + if not permanent then + self.autosimplify = nil + end + + if mode == Expression.NIL then + return self + elseif mode == Expression.SUBS then + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:autosimplify() + end + self = self:setsubexpressions(results, true) -- TODO: Add mutate setsubexpressions + return self + end + end + if recursive then + for _, expression in ipairs(self:subexpressions()) do + expression:lock(mode, permanent, recursive) + end + end + return self +end + +--- Frees any locks on expressions. +--- THIS METHOD MUTATES THE OBJECT IT ACTS ON. +--- @param recursive boolean +--- @return Expression +function Expression:unlock(recursive) + self.autosimplify = nil + if recursive then + for _, expression in ipairs(self:subexpressions()) do + expression:unlock(recursive) + end + end + return self +end + +--- Returns a list of all subexpressions of an expression. +--- @return table +function Expression:subexpressions() + error("Called unimplemented method : subexpressions()") +end + +--- Returns the total number of atomic and compound expressions that make up an expression, or the number of nodes in the expression tree. +--- @return Integer +function Expression:size() + local out = Integer.one() + for _, expression in ipairs(self:subexpressions()) do + out = out + expression:size() + end + return out +end + +--- Returns a copy of the original expression with each subexpression substituted with a new one, or a mutated version if mutate is true. +--- @param subexpressions table +--- @param mutate boolean +--- @return Expression +function Expression:setsubexpressions(subexpressions, mutate) + error("Called unimplemented method : setsubexpressions()") +end + +--- Determines whether or not an expression contains a particular symbol. +--- @param symbol SymbolExpression +--- @return boolean +function Expression:freeof(symbol) + error("Called unimplemented method : freeof()") +end + +--- Substitutes occurances of specified sub-expressions with other sub-expressions. +--- @param map table +--- @return Expression +function Expression:substitute(map) + error("Called unimplemented method : substitute()") +end + +--- Algebraically expands an expression by turning products of sums into sums of products and expanding powers. +--- @return Expression +function Expression:expand() + return self +end + +--- Attempts to factor an expression by turning sums of products into products of sums, and identical terms multiplied together into factors. +--- @return Expression +function Expression:factor() + return self +end + +--- Attempts to combine an expression by collapsing sums of expressions together into a single factor, e.g. common denominator +--- @return Expression +function Expression:combine() + return self +end + +--- Attempts to collect all occurances of an expression in this expression. +--- @param collect Expression +--- @return Expression +function Expression:collect(collect) + return self +end + +--- Returns all non-constant subexpressions of this expression - helper method for factor. +--- @return table +function Expression:getsubexpressionsrec() + local result = {} + + for _, expression in ipairs(self:subexpressions()) do + if not expression:isconstant() then + result[#result+1] = expression + end + result = JoinArrays(result, expression:getsubexpressionsrec()) + end + + return result +end + +--- Determines whether an expression is atomic. +--- Atomic expressions are not necessarily constant, since polynomial rings, for instance, are atomic parts that contain symbols. +--- @return boolean +function Expression:isatomic() + error("Called unimplemented method: isatomic()") +end + +--- Determines whether an expression is a constant, i.e., an atomic expression that is not a varaible and cannot be converted into an equivalent compound expression. +--- @return boolean +function Expression:isconstant() + error("Called unimplemented method: isconstant()") +end + +--- Determines whether an expression is a 'proper' real constant, i.e., is free of every varaible. +function Expression:isrealconstant() + if self:isconstant() or self == PI or self == E then + return true + end + + for _, expression in ipairs(self:subexpressions()) do + if not expression:isrealconstant() then + return false + end + end + + return self:type() ~= SymbolExpression +end + +--- Determines whether an expression is a 'proper' complex constant, i.e., is free of every varaible and is of the form a + bI for nonzero a and b. +--- @return boolean +function Expression:iscomplexconstant() + return self:isrealconstant() or (self.operation == BinaryOperation.ADD and #self.expressions == 2 and self.expressions[1]:isrealconstant() + and ((self.expressions[2].operation == BinaryOperation.MUL and #self.expressions[2].expressions == 2 and self.expressions[2].expressions[1]:isrealconstant() and self.expressions[2].expressions[2] == I) + or self.expressions[2] == I)) or (self.operation == BinaryOperation.MUL and #self.expressions == 2 and self.expressions[1]:isrealconstant() and self.expressions[2] == I) +end + +--- A total order on autosimplified expressions. Returns true if self < other. +--- @param other Expression +--- @return boolean +function Expression:order(other) + error("Called unimplemented method: order()") +end + +--- Returns an autosimplified expression as a single-variable polynomial in a ring, if it can be converted. Returns itself otherwise. +--- @return PolynomialRing, boolean +function Expression:topolynomial() + return self, false +end + +--- Converts this expression to LaTeX code. +--- @return string +function Expression:tolatex() + error("Called Unimplemented method: tolatex()") +end + +---------------------- +-- Instance methods -- +---------------------- + +--- Returns the type of the expression, i.e., the table used to create objects of that type. +--- @return table +function Expression:type() + return getmetatable(self).__index +end + +-------------------------- +-- Instance metamethods -- +-------------------------- + +__ExpressionOperations = {} + +__ExpressionOperations.__unm = function(a) + return BinaryOperation.SUBEXP({a}) +end + +__ExpressionOperations.__add = function(a, b) + return BinaryOperation.ADDEXP({a, b}) +end + +__ExpressionOperations.__sub = function(a, b) + return BinaryOperation.SUBEXP({a, b}) +end + +__ExpressionOperations.__mul = function(a, b) + return BinaryOperation.MULEXP({a, b}) +end + +__ExpressionOperations.__div = function(a, b) + return BinaryOperation.DIVEXP({a, b}) +end + +__ExpressionOperations.__pow = function(a, b) + return BinaryOperation.POWEXP({a, b}) +end + +-- For iterating over the subexpressions of a easily. +__ExpressionOperations.__call = function(a, ...) + if a:type() == SymbolExpression then + return FunctionExpression(a, table.pack(...)) + end + return BinaryOperation.MULEXP({a, table.pack(...)[1]}) +end + +---------------------- +-- Static constants -- +---------------------- + +Expression.NIL = 0 +Expression.SUBS = 1 \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-functionexpression.lua b/macros/luatex/latex/luacas/tex/core/luacas-functionexpression.lua new file mode 100644 index 0000000000..dc94790b81 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-functionexpression.lua @@ -0,0 +1,295 @@ +--- @class FunctionExpression +--- Represents a generic function that takes zero or more expressions as inputs. +--- @field name SymbolExpression +--- @field expressions table +--- @field orders table +--- @field variables table +--- @alias Function FunctionExpression +FunctionExpression = {} +__FunctionExpression = {} + +---------------------------- +-- Instance functionality -- +---------------------------- + +--- Creates a new function expression with the given operation. +--- @param name string|SymbolExpression +--- @param expressions table +--- @param derivatives table +--- @return FunctionExpression +function FunctionExpression:new(name, expressions, derivatives) + local o = {} + local __o = Copy(__ExpressionOperations) + + if type(name) == "table" and name:type() == SymbolExpression then + name = name.symbol + end + + if TrigExpression.NAMES[name] and #expressions == 1 then + return TrigExpression(name, expressions[1]) + end + + -- TODO: Symbol Checking For Constructing derivatives like this + --if string.sub(name, #name, #name) == "'" and #expressions == 1 then + -- return DerivativeExpression(FunctionExpression(string.sub(name, 1, #name - 1), expressions), SymbolExpression("x"), true) + --end + + o.name = name + o.expressions = Copy(expressions) + o.variables = Copy(expressions) + for _,expression in ipairs(o.variables) do + if not expression:isatomic() then + o.variables = {} + if #o.expressions < 4 then + local defaultvars = {SymbolExpression('x'),SymbolExpression('y'),SymbolExpression('z')} + for i=1,#o.expressions do + o.variables[i] = defaultvars[i] + end + else + for i=1,#o.expressions do + o.variables[i] = SymbolExpression('x_'..tostring(i)) + end + end + end + end + if derivatives then + o.derivatives = Copy(derivatives) + else + o.derivatives = {} + for i=1,#o.variables do + o.derivatives[i] = Integer.zero() + end + end + + __o.__index = FunctionExpression + __o.__tostring = function(a) + local total = Integer.zero() + for _,integer in ipairs(a.derivatives) do + total = total + integer + end + if total == Integer.zero() then + local out = a.name .. '(' + for index, expression in ipairs(a.expressions) do + out = out .. tostring(expression) + if a.expressions[index + 1] then + out = out .. ', ' + end + end + return out .. ')' + else + local out = 'd' + if total > Integer.one() then + out = out ..'^' .. tostring(total) + end + out = out .. a.name .. '/' + for index,integer in ipairs(a.derivatives) do + if integer > Integer.zero() then + out = out .. 'd' .. tostring(a.variables[index]) + if integer > Integer.one() then + out = out .. '^' .. tostring(integer) + end + end + end + out = out .. '(' + for index, expression in ipairs(a.expressions) do + out = out .. tostring(expression) + if a.expressions[index + 1] then + out = out .. ', ' + end + end + return out .. ')' + end + end + __o.__eq = function(a, b) + -- if b:type() == TrigExpression then + -- return a == b:tofunction() + -- end + if b:type() ~= FunctionExpression then + return false + end + if #a.expressions ~= #b.expressions then + return false + end + for index, _ in ipairs(a.expressions) do + if a.expressions[index] ~= b.expressions[index] then + return false + end + end + for index,_ in ipairs(a.derivatives) do + if a.derivatives[index] ~= b.derivatives[index] then + return false + end + end + return a.name == b.name + end + + o = setmetatable(o, __o) + + return o +end + +--- @return FunctionExpression +function FunctionExpression:evaluate() + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:evaluate() + end + local result = FunctionExpression(self.name, results, self.derivatives) + result.variables = self.variables + return result +end + +--- @return FunctionExpression +function FunctionExpression:autosimplify() + -- Since the function is completely generic, we can't really do anything execpt autosimplify subexpressions. + local results = {} + for index, expression in ipairs(self:subexpressions()) do + results[index] = expression:autosimplify() + end + local result = FunctionExpression(self.name, results, self.derivatives) + result.variables = self.variables + return result +end + +--- @return table +function FunctionExpression:subexpressions() + return self.expressions +end + +--- @param subexpressions table +--- @return FunctionExpression +function FunctionExpression:setsubexpressions(subexpressions) + local result = FunctionExpression(self.name, subexpressions, self.derivatives) + result.variables = self.variables + return result +end + +--- @param other Expression +--- @return boolean +function FunctionExpression:order(other) + if other:isatomic() then + return false + end + + -- CASC Autosimplfication has some symbols appearing before functions, but that looks bad to me, so all symbols appear before products now. + -- if other:type() == SymbolExpression then + -- return SymbolExpression(self.name):order(other) + -- end + + if other:type() == BinaryOperation then + if other.operation == BinaryOperation.ADD or other.operation == BinaryOperation.MUL then + return BinaryOperation(other.operation, {self}):order(other) + end + + if other.operation == BinaryOperation.POW then + return (self^Integer.one()):order(other) + end + end + + if other:type() == SqrtExpression then + return self:order(other:topower()) + end + + -- TODO: Make Logarithm and AbsExpression inherit from function expression to reduce code duplication + if other:type() == Logarithm then + return self:order(FunctionExpression("log", {other.base, other.expression})) + end + + if other:type() ~= FunctionExpression and other:type() ~= TrigExpression then + return true + end + + if self.name ~= other.name then + return SymbolExpression(self.name):order(SymbolExpression(other.name)) + end + + local k = 1 + while self:subexpressions()[k] and other:subexpressions()[k] do + if self:subexpressions()[k] ~= other:subexpressions()[k] then + return self:subexpressions()[k]:order(other:subexpressions()[k]) + end + k = k + 1 + end + return #self.expressions < #other.expressions +end + +--- @return string +function FunctionExpression:tolatex() + local out = tostring(self.name) + if self:type() == TrigExpression then + out = "\\" .. out + end + if self:type() ~= TrigExpression and #self.name>1 then + --if out:sub(2,2) ~= "'" then + --local fp = out:find("'") + --if fp then + -- out = '\\operatorname{' .. out:sub(1,fp-1) .. '}' .. out:sub(fp,-1) + --else + out = '\\operatorname{' .. out .. '}' + --end + --end + end + local total = Integer.zero() + for _,integer in ipairs(self.derivatives) do + total = total + integer + end + if #self.expressions == 1 then + if total == Integer.zero() then + goto continue + else + if total < Integer(5) then + while total > Integer.zero() do + out = out .. "'" + total = total - Integer.one() + end + else + out = out .. '^{(' .. total:tolatex() .. ')}' + end + end + end + if #self.expressions > 1 then + if total == Integer.zero() then + goto continue + else + if total < Integer(4) then + out = out .. '_{' + for index,integer in ipairs(self.derivatives) do + local i = integer:asnumber() + while i > 0 do + out = out .. self.variables[index]:tolatex() + i = i - 1 + end + end + out = out .. '}' + else + out = '\\frac{\\partial^{' .. total:tolatex() .. '}' .. out .. '}{' + for index, integer in ipairs(self.derivatives) do + if integer > Integer.zero() then + out = out .. '\\partial ' .. self.variables[index]:tolatex() + if integer ~= Integer.one() then + out = out .. '^{' .. integer:tolatex() .. '}' + end + end + end + out = out .. '}' + end + end + end + ::continue:: + out = out ..'\\mathopen{}' .. '\\left(' + for index, expression in ipairs(self:subexpressions()) do + out = out .. expression:tolatex() + if self:subexpressions()[index + 1] then + out = out .. ', ' + end + end + return out .. '\\right)' +end + +----------------- +-- Inheritance -- +----------------- + +__FunctionExpression.__index = CompoundExpression +__FunctionExpression.__call = FunctionExpression.new +FunctionExpression = setmetatable(FunctionExpression, __FunctionExpression) \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/luacas-symbolexpression.lua b/macros/luatex/latex/luacas/tex/core/luacas-symbolexpression.lua new file mode 100644 index 0000000000..0d9192c084 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/luacas-symbolexpression.lua @@ -0,0 +1,132 @@ +--- @class SymbolExpression +--- An atomic expression corresponding to a symbol representing an arbitary value. +--- @field symbol string +--- @alias Symbol SymbolExpression +SymbolExpression = {} +__SymbolExpression = {} + +---------------------- +-- Instance methods -- +---------------------- + +--- Given the name of the symbol as a string, creates a new symbol. +--- @param symbol string +--- @return SymbolExpression +function SymbolExpression:new(symbol) + local o = {} + local __o = Copy(__ExpressionOperations) + __o.__index = SymbolExpression + __o.__tostring = function(a) + return a.symbol + end + __o.__eq = function(a, b) + return a.symbol == b.symbol + end + + if type(symbol) ~= "string" then + error("Sent parameter of wrong type: symbol must be a string") + end + + o.symbol = symbol + o = setmetatable(o, __o) + + return o +end + +--- @return boolean +function SymbolExpression:freeof(symbol) + return symbol~=self +end + +--- @return boolean +function SymbolExpression:isconstant() + return false +end + +--- @param other Expression +--- @return boolean +function SymbolExpression:order(other) + + -- Symbol Expressions come after constant expressions. + if other:isconstant() then + return false + end + + -- Lexographic order on symbols. + if other:type() == SymbolExpression then + for i = 1, math.min(#self.symbol, #other.symbol) do + if string.byte(self.symbol, i) ~= string.byte(other.symbol, i) then + return string.byte(self.symbol, i) < string.byte(other.symbol, i) + end + end + + return #self.symbol < #other.symbol + end + + if other.operation == BinaryOperation.POW then + return BinaryOperation(BinaryOperation.POW, {self, Integer.one()}):order(other) + end + + if other.operation == BinaryOperation.MUL then + return BinaryOperation(BinaryOperation.MUL, {self}):order(other) + end + + if other.operation == BinaryOperation.ADD then + return BinaryOperation(BinaryOperation.ADD, {self}):order(other) + end + + -- CASC Autosimplfication has some symbols appearing before functions, but that looks bad to me, so all symbols appear before products now. + if other:type() == FunctionExpression or other:type() == TrigExpression or other:type() == Logarithm then + return true + end + + return false +end + +--- Converts this symbol to an element of a polynomial ring. +--- @return PolynomialRing, boolean +function SymbolExpression:topolynomial() + return PolynomialRing({Integer.zero(), Integer.one()}, self.symbol), true +end + +----------------- +-- Inheritance -- +----------------- + +__SymbolExpression.__index = AtomicExpression +__SymbolExpression.__call = SymbolExpression.new +SymbolExpression = setmetatable(SymbolExpression, __SymbolExpression) + + +---------------------- +-- Static Constants -- +---------------------- + +-- The constant pi. +PI = SymbolExpression("pi") +function PI:tolatex() + return "\\pi " +end + +-- Approximates pi as a rational number. Uses continued fraction expansion. +function PI:approximate() + return Integer(313383936) / Integer(99753205) +end + +--function PI:isconstant() +-- return true +--end + +-- The constant e. +E = SymbolExpression("e") + +-- Approximates pi as a rational number. Uses continued fraction expansion. +function E:approximate() + return Integer(517656) / Integer(190435) +end + +-- The imaginary constant i. +I = SymbolExpression("i") +function I:tolatex() + return "i" +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/core/symbolexpression.lua b/macros/luatex/latex/luacas/tex/core/symbolexpression.lua deleted file mode 100644 index 0d9192c084..0000000000 --- a/macros/luatex/latex/luacas/tex/core/symbolexpression.lua +++ /dev/null @@ -1,132 +0,0 @@ ---- @class SymbolExpression ---- An atomic expression corresponding to a symbol representing an arbitary value. ---- @field symbol string ---- @alias Symbol SymbolExpression -SymbolExpression = {} -__SymbolExpression = {} - ----------------------- --- Instance methods -- ----------------------- - ---- Given the name of the symbol as a string, creates a new symbol. ---- @param symbol string ---- @return SymbolExpression -function SymbolExpression:new(symbol) - local o = {} - local __o = Copy(__ExpressionOperations) - __o.__index = SymbolExpression - __o.__tostring = function(a) - return a.symbol - end - __o.__eq = function(a, b) - return a.symbol == b.symbol - end - - if type(symbol) ~= "string" then - error("Sent parameter of wrong type: symbol must be a string") - end - - o.symbol = symbol - o = setmetatable(o, __o) - - return o -end - ---- @return boolean -function SymbolExpression:freeof(symbol) - return symbol~=self -end - ---- @return boolean -function SymbolExpression:isconstant() - return false -end - ---- @param other Expression ---- @return boolean -function SymbolExpression:order(other) - - -- Symbol Expressions come after constant expressions. - if other:isconstant() then - return false - end - - -- Lexographic order on symbols. - if other:type() == SymbolExpression then - for i = 1, math.min(#self.symbol, #other.symbol) do - if string.byte(self.symbol, i) ~= string.byte(other.symbol, i) then - return string.byte(self.symbol, i) < string.byte(other.symbol, i) - end - end - - return #self.symbol < #other.symbol - end - - if other.operation == BinaryOperation.POW then - return BinaryOperation(BinaryOperation.POW, {self, Integer.one()}):order(other) - end - - if other.operation == BinaryOperation.MUL then - return BinaryOperation(BinaryOperation.MUL, {self}):order(other) - end - - if other.operation == BinaryOperation.ADD then - return BinaryOperation(BinaryOperation.ADD, {self}):order(other) - end - - -- CASC Autosimplfication has some symbols appearing before functions, but that looks bad to me, so all symbols appear before products now. - if other:type() == FunctionExpression or other:type() == TrigExpression or other:type() == Logarithm then - return true - end - - return false -end - ---- Converts this symbol to an element of a polynomial ring. ---- @return PolynomialRing, boolean -function SymbolExpression:topolynomial() - return PolynomialRing({Integer.zero(), Integer.one()}, self.symbol), true -end - ------------------ --- Inheritance -- ------------------ - -__SymbolExpression.__index = AtomicExpression -__SymbolExpression.__call = SymbolExpression.new -SymbolExpression = setmetatable(SymbolExpression, __SymbolExpression) - - ----------------------- --- Static Constants -- ----------------------- - --- The constant pi. -PI = SymbolExpression("pi") -function PI:tolatex() - return "\\pi " -end - --- Approximates pi as a rational number. Uses continued fraction expansion. -function PI:approximate() - return Integer(313383936) / Integer(99753205) -end - ---function PI:isconstant() --- return true ---end - --- The constant e. -E = SymbolExpression("e") - --- Approximates pi as a rational number. Uses continued fraction expansion. -function E:approximate() - return Integer(517656) / Integer(190435) -end - --- The imaginary constant i. -I = SymbolExpression("i") -function I:tolatex() - return "i" -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/luacas.sty b/macros/luatex/latex/luacas/tex/luacas.sty index 461fae6efa..d46ee847fc 100644 --- a/macros/luatex/latex/luacas/tex/luacas.sty +++ b/macros/luatex/latex/luacas/tex/luacas.sty @@ -9,7 +9,7 @@ \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{luacas} - [2022/05/07 v1.0 CAS written in Lua for LaTeX] + [2022/11/15 v1.0.1 CAS written in Lua for LaTeX] \RequirePackage{iftex} \ifluatex @@ -30,8 +30,8 @@ \RequirePackage{mathtools} %These files contain Lua code for parsing luacas output; they also initialize the CAS itself -\directlua{require('test.parser') - require('test.helper') +\directlua{require('test.luacas-parser') + require('test.luacas-helper') } \NewDocumentEnvironment{CAS}% diff --git a/macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua b/macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua deleted file mode 100644 index 5d5429f59b..0000000000 --- a/macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua +++ /dev/null @@ -1,30 +0,0 @@ -local a = DD(SymbolExpression("x") * SymbolExpression("y"), SymbolExpression("x")) -local b = DD(Integer(3) * SymbolExpression("x") ^ Integer(2) + Integer(2) * SymbolExpression("x") + Integer(6), SymbolExpression("x")) -local c = DD(E ^ SymbolExpression("x"), SymbolExpression("x")) -local d = DD(FunctionExpression("f", {SymbolExpression("x") ^ Integer(2)})) -local e = DD(SymbolExpression("x") ^ SymbolExpression("x")) -local f = DD(PolynomialRing({Integer(3), Integer(4), Integer(5)}, "x")) -local g = DD(LN(SymbolExpression("y")), SymbolExpression("y")) -local h = DD(SymbolExpression("x") ^ SymbolExpression("n")) -local i = DD(SIN((SymbolExpression("x")))) -local j = DD(SIN(Integer(2) * COS(SymbolExpression("x")))) -local k = DD(ARCTAN(SymbolExpression("x") ^ (Integer(1) / Integer(2)))) -local l = DD(ARCSEC(SymbolExpression("x"))) - -starttest("derivatives") - -testeq(a, dparse("DD(x*y, x)")) -testeq(a:autosimplify(), parse("y"), a) -testeq(b:autosimplify(), parse("6 * x + 2"), b) -testeq(c:autosimplify(), parse("e^x"), c) -testeq(d:autosimplify(), (Integer(2) * SymbolExpression("x") * FunctionExpression("f", {SymbolExpression("x")^Integer(2)}, {Integer(1)})):autosimplify(), d) -testeq(e:autosimplify(), parse("x^x * (1 + ln(x))"), e) -testeq(f:autosimplify(), parse("4 + 10 * x"), f) -testeq(g:autosimplify(), parse("y ^ -1"), g) -testeq(h:autosimplify(), parse("n * x ^ (-1 + n)"), h) -testeq(i:autosimplify(), parse("cos(x)"), i) -testeq(j:autosimplify(), parse("-2 * cos(2 * cos(x)) * sin(x)"), j) -testeq(k:autosimplify(), parse("1/2 * x^(-1/2) * (1+x) ^ -1"), k) -testeq(l:autosimplify(), parse("abs(x)^-1 * (1 + -(x^2))^ (-1/2)"), l) - -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/calculus/integrals.lua b/macros/luatex/latex/luacas/tex/test/calculus/integrals.lua deleted file mode 100644 index 0c12f7a787..0000000000 --- a/macros/luatex/latex/luacas/tex/test/calculus/integrals.lua +++ /dev/null @@ -1,54 +0,0 @@ -local a = dparse("int(x^2, x)") -local b = dparse("int(x^-1, x, 1, e)") -local c = dparse("int(3*x^2+2*x+6, x)") -local d = dparse("int(sin(x)*cos(x), x)") -local e = dparse("int(2*x*cos(x^2), x)") -local f = dparse("int(sin(2*x), x)") -local g = dparse("int(e^sin(x), x)") -local h = dparse("int((1 / (1 + (1 / x))), x)") -local i = dparse("int(e^(x^(1/2)), x)") -local j = dparse("int((x^3+1)/(x-2), x)") -local k = dparse("int((x^2-x+1)/(x^3+3*x^2+3*x+1), x)") -local l = dparse("int(1 / (x^3+6*x), x)") -local m = dparse("int(1/(x^2+x+1), x)") -local n = dparse("int(1/(x^3+2*x+2), x, 0, 1)") - -local o = dparse("int(x^2*e^x, x)") -local p = dparse("int((x^2+6*x+3)*sin(x), x)") -local q = dparse("int(x*e^x*sin(x),x)") -local r = dparse("int(cos(x)^3, x)") -local s = dparse("int(1/(e^x+1), x)") -local t = dparse("int(e^(2*x)*cos(3*x), x)") -local u = dparse("int((x^2-1)^2, x, -1, 1)") - -starttest("integration") -testeq(a, dparse("int(x ^ 2, x)")) -testeq(a:autosimplify(), parse("x^3/3"), a) -testeq(b:autosimplify(), parse("1"), b) -testeq(c:autosimplify(), parse("x^3+x^2+6*x"), c) -testeq(d:autosimplify(), parse("(-1/2 * (cos(x) ^ 2))"), d) -testeq(e:autosimplify(), parse("sin((x ^ 2))"), e) -testeq(f:autosimplify(), parse("(-1/2 * cos((2 * x)))"), f) --- testeq(g:autosimplify(), dparse("int(e ^ (sin(x)), x)"), g) -testeq(h:autosimplify(), parse("x + (-1 * (log(e, 1 + (x ^ -1)))) + (-1 * (log(e, x)))"), h) -testeq(i:autosimplify(), parse("-2 * (e ^ (x ^ (1/2))) + 2 * (e ^ (x ^ (1/2))) * (x ^ (1/2))"), i) -testeq(j:autosimplify(), parse("((4 * x) + (x ^ 2) + (1/3 * (x ^ 3)) + (9 * log(e, (-2 + x))))"), j) -testeq(k:autosimplify(), parse("((-3/2 * ((1 + x) ^ -2)) + (3 * ((1 + x) ^ -1)) + log(e, (1 + x)))"), k) -testeq(l:autosimplify(), parse("((1/6 * log(e, x)) + (-1/12 * log(e, (6 + (x ^ 2)))))"), l) -testeq(m:autosimplify(), parse("2/3 * (3 ^ (1/2)) * (arctan((3 ^ (1/2)) * (1/3 + (2/3 * x))))"), m) --- test(n:autosimplify(), [[((((6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) --- * log(e, (18 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-264600 + (1/2 --- * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1)))) + (((6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((-1/840 + (1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (18 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/280 + (-1/280 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1)))) + (((6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (18 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1)))) + (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (1 + (18 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1))))) + (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (1 + (18 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/280 + (-1/280 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 --- ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1))))) + (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * --- ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (1 + (18 * --- (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-1/2 + (1/2 * (-3 ^ 1/2))) --- ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1))))))]], n) - -testeq(o:autosimplify(), parse("((2 * (e ^ x)) + (-2 * (e ^ x) * x) + ((e ^ x) * (x ^ 2)))"), o) -testeq(p:autosimplify(), parse("((2 * cos(x)) + ((-3 + (-6 * x) + (-1 * (x ^ 2))) * cos(x)) + ((6 + (2 * x)) * sin(x)))"), p) -testeq(q:autosimplify(), parse("(1/2 * (e ^ x) * (cos(x))) + (-1/2 * (e ^ x) * x * (cos(x))) + (1/2 * (e ^ x) * x * (sin(x)))"), q) -testeq(r:autosimplify(), parse("((3/4 * sin(x)) + (1/12 * sin((3 * x))))"), r) -testeq(s:autosimplify(), parse("log(e, 1 + (-1 * ((1 + (e ^ x)) ^ -1)))"), s) -testeq(t:autosimplify(), parse("(2/13 * (e ^ (2 * x)) * (cos(3 * x))) + (3/13 * (e ^ (2 * x)) * (sin(3 * x)))"), t) -testeq(u:autosimplify(), parse("16/15"), u) -endtest() diff --git a/macros/luatex/latex/luacas/tex/test/calculus/luacas-derivatives.lua b/macros/luatex/latex/luacas/tex/test/calculus/luacas-derivatives.lua new file mode 100644 index 0000000000..5d5429f59b --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/calculus/luacas-derivatives.lua @@ -0,0 +1,30 @@ +local a = DD(SymbolExpression("x") * SymbolExpression("y"), SymbolExpression("x")) +local b = DD(Integer(3) * SymbolExpression("x") ^ Integer(2) + Integer(2) * SymbolExpression("x") + Integer(6), SymbolExpression("x")) +local c = DD(E ^ SymbolExpression("x"), SymbolExpression("x")) +local d = DD(FunctionExpression("f", {SymbolExpression("x") ^ Integer(2)})) +local e = DD(SymbolExpression("x") ^ SymbolExpression("x")) +local f = DD(PolynomialRing({Integer(3), Integer(4), Integer(5)}, "x")) +local g = DD(LN(SymbolExpression("y")), SymbolExpression("y")) +local h = DD(SymbolExpression("x") ^ SymbolExpression("n")) +local i = DD(SIN((SymbolExpression("x")))) +local j = DD(SIN(Integer(2) * COS(SymbolExpression("x")))) +local k = DD(ARCTAN(SymbolExpression("x") ^ (Integer(1) / Integer(2)))) +local l = DD(ARCSEC(SymbolExpression("x"))) + +starttest("derivatives") + +testeq(a, dparse("DD(x*y, x)")) +testeq(a:autosimplify(), parse("y"), a) +testeq(b:autosimplify(), parse("6 * x + 2"), b) +testeq(c:autosimplify(), parse("e^x"), c) +testeq(d:autosimplify(), (Integer(2) * SymbolExpression("x") * FunctionExpression("f", {SymbolExpression("x")^Integer(2)}, {Integer(1)})):autosimplify(), d) +testeq(e:autosimplify(), parse("x^x * (1 + ln(x))"), e) +testeq(f:autosimplify(), parse("4 + 10 * x"), f) +testeq(g:autosimplify(), parse("y ^ -1"), g) +testeq(h:autosimplify(), parse("n * x ^ (-1 + n)"), h) +testeq(i:autosimplify(), parse("cos(x)"), i) +testeq(j:autosimplify(), parse("-2 * cos(2 * cos(x)) * sin(x)"), j) +testeq(k:autosimplify(), parse("1/2 * x^(-1/2) * (1+x) ^ -1"), k) +testeq(l:autosimplify(), parse("abs(x)^-1 * (1 + -(x^2))^ (-1/2)"), l) + +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/calculus/luacas-integrals.lua b/macros/luatex/latex/luacas/tex/test/calculus/luacas-integrals.lua new file mode 100644 index 0000000000..0c12f7a787 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/calculus/luacas-integrals.lua @@ -0,0 +1,54 @@ +local a = dparse("int(x^2, x)") +local b = dparse("int(x^-1, x, 1, e)") +local c = dparse("int(3*x^2+2*x+6, x)") +local d = dparse("int(sin(x)*cos(x), x)") +local e = dparse("int(2*x*cos(x^2), x)") +local f = dparse("int(sin(2*x), x)") +local g = dparse("int(e^sin(x), x)") +local h = dparse("int((1 / (1 + (1 / x))), x)") +local i = dparse("int(e^(x^(1/2)), x)") +local j = dparse("int((x^3+1)/(x-2), x)") +local k = dparse("int((x^2-x+1)/(x^3+3*x^2+3*x+1), x)") +local l = dparse("int(1 / (x^3+6*x), x)") +local m = dparse("int(1/(x^2+x+1), x)") +local n = dparse("int(1/(x^3+2*x+2), x, 0, 1)") + +local o = dparse("int(x^2*e^x, x)") +local p = dparse("int((x^2+6*x+3)*sin(x), x)") +local q = dparse("int(x*e^x*sin(x),x)") +local r = dparse("int(cos(x)^3, x)") +local s = dparse("int(1/(e^x+1), x)") +local t = dparse("int(e^(2*x)*cos(3*x), x)") +local u = dparse("int((x^2-1)^2, x, -1, 1)") + +starttest("integration") +testeq(a, dparse("int(x ^ 2, x)")) +testeq(a:autosimplify(), parse("x^3/3"), a) +testeq(b:autosimplify(), parse("1"), b) +testeq(c:autosimplify(), parse("x^3+x^2+6*x"), c) +testeq(d:autosimplify(), parse("(-1/2 * (cos(x) ^ 2))"), d) +testeq(e:autosimplify(), parse("sin((x ^ 2))"), e) +testeq(f:autosimplify(), parse("(-1/2 * cos((2 * x)))"), f) +-- testeq(g:autosimplify(), dparse("int(e ^ (sin(x)), x)"), g) +testeq(h:autosimplify(), parse("x + (-1 * (log(e, 1 + (x ^ -1)))) + (-1 * (log(e, x)))"), h) +testeq(i:autosimplify(), parse("-2 * (e ^ (x ^ (1/2))) + 2 * (e ^ (x ^ (1/2))) * (x ^ (1/2))"), i) +testeq(j:autosimplify(), parse("((4 * x) + (x ^ 2) + (1/3 * (x ^ 3)) + (9 * log(e, (-2 + x))))"), j) +testeq(k:autosimplify(), parse("((-3/2 * ((1 + x) ^ -2)) + (3 * ((1 + x) ^ -1)) + log(e, (1 + x)))"), k) +testeq(l:autosimplify(), parse("((1/6 * log(e, x)) + (-1/12 * log(e, (6 + (x ^ 2)))))"), l) +testeq(m:autosimplify(), parse("2/3 * (3 ^ (1/2)) * (arctan((3 ^ (1/2)) * (1/3 + (2/3 * x))))"), m) +-- test(n:autosimplify(), [[((((6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) +-- * log(e, (18 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-264600 + (1/2 +-- * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1)))) + (((6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((-1/840 + (1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (18 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/280 + (-1/280 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1)))) + (((6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (18 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1)))) + (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (1 + (18 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1))))) + (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (1 + (18 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + ((1/280 + (-1/280 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -1) * ((-264600 + (1/2 * (216040608000 +-- ^ 1/2))) ^ -1/3)) + ((1/840 + (-1/840 * (-3 ^ 1/2))) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1))))) + (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * +-- ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) * log(e, (1 + (18 * +-- (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2) * (((-18 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/140 * ((-1/2 + (1/2 * (-3 ^ 1/2))) +-- ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3)) + (12 * (((-6 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ -2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ -1/3)) + (-1/420 * ((-1/2 + (1/2 * (-3 ^ 1/2))) ^ 2) * ((-264600 + (1/2 * (216040608000 ^ 1/2))) ^ 1/3))) ^ 2))) ^ -1))))))]], n) + +testeq(o:autosimplify(), parse("((2 * (e ^ x)) + (-2 * (e ^ x) * x) + ((e ^ x) * (x ^ 2)))"), o) +testeq(p:autosimplify(), parse("((2 * cos(x)) + ((-3 + (-6 * x) + (-1 * (x ^ 2))) * cos(x)) + ((6 + (2 * x)) * sin(x)))"), p) +testeq(q:autosimplify(), parse("(1/2 * (e ^ x) * (cos(x))) + (-1/2 * (e ^ x) * x * (cos(x))) + (1/2 * (e ^ x) * x * (sin(x)))"), q) +testeq(r:autosimplify(), parse("((3/4 * sin(x)) + (1/12 * sin((3 * x))))"), r) +testeq(s:autosimplify(), parse("log(e, 1 + (-1 * ((1 + (e ^ x)) ^ -1)))"), s) +testeq(t:autosimplify(), parse("(2/13 * (e ^ (2 * x)) * (cos(3 * x))) + (3/13 * (e ^ (2 * x)) * (sin(3 * x)))"), t) +testeq(u:autosimplify(), parse("16/15"), u) +endtest() diff --git a/macros/luatex/latex/luacas/tex/test/expressions/autosimplify.lua b/macros/luatex/latex/luacas/tex/test/expressions/autosimplify.lua deleted file mode 100644 index 09ad9874cd..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/autosimplify.lua +++ /dev/null @@ -1,199 +0,0 @@ -local a = BinaryOperation.ADDEXP - ({Integer(3), - Integer(5)}) - -local b = BinaryOperation.MULEXP - ({BinaryOperation.ADDEXP - ({Integer(13), - Integer(12)}), - Integer(-4)}) - -local c = BinaryOperation.DIVEXP - ({SymbolExpression("x"), - SymbolExpression("y")}) - -local d = BinaryOperation.DIVEXP - ({BinaryOperation.ADDEXP - ({Integer(4), - Integer(-3)}), - SymbolExpression("y")}) - -local e = BinaryOperation.ADDEXP - ({Integer(3), - Integer(4), - Integer(5), - Integer(6)}) - -starttest("expression construction") -testeq(a, "3 + 5") -testeq(b, "(13 + 12) * -4") -testeq(c, "x / y") -testeq(d, "(4 + -3) / y") -testeq(e, "3 + 4 + 5 + 6") -endtest() - -starttest("expression evaluation...") -testeq(a:evaluate(), dparse("8")) -testeq(b:evaluate(), dparse("-100")) -testeq(c:evaluate(), dparse("(x / y)")) -testeq(d:evaluate(), dparse("(1 / y)")) -testeq(e:evaluate(), dparse("18")) -endtest() - -local g = BinaryOperation.POWEXP - ({Integer(0), - SymbolExpression("x")}) - -local h = BinaryOperation.POWEXP - ({Integer(1), - SymbolExpression("x")}) - -local i = BinaryOperation.POWEXP - ({SymbolExpression("x"), - Integer(0)}) - -local j = BinaryOperation.POWEXP - ({SymbolExpression("x"), - Integer(1)}) - -local k = BinaryOperation.POWEXP - ({SymbolExpression("x"), - SymbolExpression("y")}) - -local l = BinaryOperation.POWEXP - ({BinaryOperation.POWEXP - ({BinaryOperation.POWEXP - ({SymbolExpression("x"), - Integer(3)}), - Integer(4)}), - Integer(5)}) - -local m = BinaryOperation.POWEXP - ({BinaryOperation.MULEXP - ({SymbolExpression("x"), - SymbolExpression("y")}), - SymbolExpression("a")}) - - local n = BinaryOperation.MULEXP - ({SymbolExpression("x"), - SymbolExpression("y"), - Integer(0), - Integer(-2) - }) - -local o = BinaryOperation.MULEXP - ({SymbolExpression("x"), - BinaryOperation.MULEXP - ({SymbolExpression("y"), - SymbolExpression("z")})}) - -local p = BinaryOperation.MULEXP - ({SymbolExpression("x")}) - -local q = BinaryOperation.MULEXP - ({SymbolExpression("x"), SymbolExpression("x"), SymbolExpression("x"), SymbolExpression("x")}) - -local r = BinaryOperation.MULEXP - ({SymbolExpression("x"), Integer(3), SymbolExpression("a")}) - - local s = BinaryOperation.ADDEXP - ({SymbolExpression("x")}) - -local t = BinaryOperation.ADDEXP - ({SymbolExpression("x"), - BinaryOperation.ADDEXP - ({Integer(3), - SymbolExpression("y")})}) - -local u = BinaryOperation.ADDEXP - ({BinaryOperation.MULEXP - ({SymbolExpression("x"), - SymbolExpression("y")}), - BinaryOperation.MULEXP - ({SymbolExpression("y"), - SymbolExpression("x")})}) - -local v = BinaryOperation.ADDEXP - ({Integer(3), - BinaryOperation.ADDEXP - ({BinaryOperation.MULEXP - ({Integer(2), - BinaryOperation.POWEXP - ({SymbolExpression("x"), - Integer(2)})}), - BinaryOperation.ADDEXP - ({BinaryOperation.MULEXP - ({Integer(1), - SymbolExpression("y")}), - BinaryOperation.MULEXP - ({Integer(0), - SymbolExpression("x")})})}), - Integer(6)}) - - local w = BinaryOperation.MULEXP - ({BinaryOperation.DIVEXP - ({Integer(1), - SymbolExpression("x")}), - SymbolExpression("x")}) - -local x = BinaryOperation.MULEXP - ({BinaryOperation.DIVEXP - ({SymbolExpression("y"), - SymbolExpression("x")}), - BinaryOperation.DIVEXP - ({SymbolExpression("x"), - SymbolExpression("y")})}) - -local y = BinaryOperation.MULEXP - ({BinaryOperation.DIVEXP - ({Integer(1), - Integer(3)}), - SymbolExpression("x")}) - -local z = BinaryOperation.ADDEXP - ({SymbolExpression("x"), - SymbolExpression("y"), - BinaryOperation.SUBEXP - ({SymbolExpression("x"), - SymbolExpression("y")})}) - -local A = dparse("(-aa-x)+(x+aa)") - -starttest("expression autosimplification") -testeq(g:autosimplify(), parse("0"), g) -testeq(h:autosimplify(), parse("1"), h) -testeq(i:autosimplify(), parse("1"), i) -testeq(j:autosimplify(), parse("x"), j) -testeq(k:autosimplify(), parse("x ^ y"), k) -testeq(l:autosimplify(), parse("(x ^ 60)"), l) -testeq(m:autosimplify(), parse("((x * y) ^ a)"), m) -testeq(n:autosimplify(), parse("0"), n) -testeq(o:autosimplify(), parse("(x * y * z)"), o) -testeq(p:autosimplify(), parse("x"), p) -testeq(q:autosimplify(), parse("(x ^ 4)"), q) -testeq(r:autosimplify(), parse("(3 * a * x)"), r) -testeq(s:autosimplify(), parse("x"), s) -testeq(t:autosimplify(), parse("(3 + x + y)"), t) -testeq(u:autosimplify(), parse("(2 * x * y)"), u) -testeq(v:autosimplify(), parse("(9 + (2 * (x ^ 2)) + y)"), v) -testeq(w:autosimplify(), parse("1"), w) -testeq(x:autosimplify(), parse("1"), x) -testeq(y:autosimplify(), parse("(1/3 * x)"), y) -testeq(z:autosimplify(), parse("(2 * x)"), z) -testeq(A:autosimplify(), parse("0"), A) -endtest() - - -local aa = SymbolExpression("x") + SymbolExpression("y") + SymbolExpression("z") -local ab = -(SymbolExpression("x") / SymbolExpression("y")) -local ac = Integer(2)*SymbolExpression("x")*SymbolExpression("y") - Integer(3)*SymbolExpression("x")*SymbolExpression("z") - -starttest("metamethod expressions") - -testeq(aa, dparse("(x + y) + z")) -testeq(aa:autosimplify(), parse("(x + y + z)"), aa) -testeq(ab, dparse("- (x / y)")) -testeq(ab:autosimplify(), parse("(-1 * x * (y ^ -1))"), ab) -testeq(ac:autosimplify(), parse("((2 * x * y) + (-3 * x * z))"), ac) - -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/collect.lua b/macros/luatex/latex/luacas/tex/test/expressions/collect.lua deleted file mode 100644 index b40e8e3d09..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/collect.lua +++ /dev/null @@ -1,26 +0,0 @@ -local x = SymbolExpression("x") -local ex = parse("e^x") -local lnx = parse("ln(x)") - -local a = parse("y^2") -local b = parse("x + y + 1") -local c = parse("x*(y+1)+x+3*x*y^2") -local d = parse("x^2+2*x*y+y^2+x") -local e = parse("(x*y+x)^2+x^2") -local f = parse("-x^2/e^x-2*x/e^x-2/e^x+x^2*e^x-2*x*e^x+2*e^x") -local g = parse("x^(-2)+y*x^(-2)+z*x^2+2*x^2") -local h = parse("a*ln(x)-ln(x)*x-x") - - -starttest("collect method") - -testeq(a:collect(x), parse("y^2"), a) -testeq(b:collect(x), parse("x + y + 1"), b) -testeq(c:collect(x), parse("(3*y^2+y+2)*x"), c) -testeq(d:collect(x), parse("x^2+(2*y+1)*x+y^2"), d) -testeq(e:collect(x), parse("((y+1)^2+1)*x^2"), e) -testeq(f:collect(ex), parse("(x^2-2*x+2)*e^x+(-x^2-2*x-2)/e^x"), f) -testeq(g:collect(x), parse("(y+1)*x^(-2)+(z+2)*x^2"), g) -testeq(h:collect(lnx), parse("(a-x)*ln(x)-x"), h) - -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/equations.lua b/macros/luatex/latex/luacas/tex/test/expressions/equations.lua deleted file mode 100644 index 3d6aa131fc..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/equations.lua +++ /dev/null @@ -1,17 +0,0 @@ -local a = Equation(parse("2^x"), parse("1")) -local b = Equation(parse("x^2+2*x+1"), parse("0")) -local c = Equation(parse("2*x^x"), parse("3*y")) -local d = Equation(parse("e^x+1"), parse("y")) -local e = Equation(parse("z*sin(x/2)"), parse("4")) -local f = Equation(parse("4"), parse("0")) - - -starttest("equation solving") -testeq(a:solvefor(parse("x")), Equation(parse("x"), parse("0")), a) -testeq(b:solvefor(parse("x")), Equation(parse("x"), parse("-1")), b) -- This will need to be fixed once set expressions are woring -testeq(c:solvefor(parse("x")), Equation(parse("x^x"), parse("3/2*y")), c) -testeq(c:solvefor(parse("y")), Equation(parse("y"), parse("2/3*x^x")), c) -testeq(d:solvefor(parse("x")), Equation(parse("x"), parse("ln(y - 1)")), d) -testeq(e:solvefor(parse("x")), Equation(parse("x"), parse("2*arcsin(4/z)")), e) -testeq(f:autosimplify(), "false", f) -- Same, with boolean expressions -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/functions.lua b/macros/luatex/latex/luacas/tex/test/expressions/functions.lua deleted file mode 100644 index e2c15096d6..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/functions.lua +++ /dev/null @@ -1,17 +0,0 @@ -local a = FunctionExpression("f", - {SymbolExpression("x"), - BinaryOperation.MULEXP - ({SymbolExpression("x"), - Integer(2)})}) - -local b = BinaryOperation.ADDEXP - ({FunctionExpression("g", - {SymbolExpression("x")}), - FunctionExpression("f", - {SymbolExpression("x")}), - Integer(4)}) - -starttest("function expressions") -testeq(a:autosimplify(), parse("f(x, (2 * x))"), a) -testeq(b:autosimplify(), parse("(4 + f(x) + g(x))"), b) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/logarithms.lua b/macros/luatex/latex/luacas/tex/test/expressions/logarithms.lua deleted file mode 100644 index 2813a6b8f5..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/logarithms.lua +++ /dev/null @@ -1,26 +0,0 @@ -local a = LN(SymbolExpression("x")) -local b = LN(BinaryOperation.POWEXP({E, SymbolExpression("x")})) -local c = BinaryOperation.POWEXP({Integer(2), LOG(Integer(2), SymbolExpression("y"))}) -local d = dparse("e^(-x*ln(x))") - -local e = Logarithm(Integer(2), Integer(256)) -local f = Logarithm(Integer(4), Integer(8)) -local g = Logarithm(Integer(1)/Integer(5), Integer((125))) -local h = Logarithm(Integer(1)/Integer(9), Integer(1)/Integer(243)) -local i = Logarithm(Integer(1)/Integer(25), Integer(3125)) - -local k = Logarithm(E, Integer(1)/Integer(9)) - -starttest("logarithms") -testeq(a, "log(e, x)") -testeq(a:autosimplify(), "log(e, x)", a) -testeq(b:autosimplify(), "x", b) -testeq(c:autosimplify(), "y", c) -testeq(d:autosimplify(), parse("x^(-x)"), d) -testeq(e:autosimplify(), parse("8"), e) -testeq(f:autosimplify(), parse("3/2"), f) -testeq(g:autosimplify(), parse("-3"), g) -testeq(h:autosimplify(), parse("5/2"), h) -testeq(i:autosimplify(), parse("-5/2"), i) -testeq(k:autosimplify(), parse("-ln(9)"), k) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-autosimplify.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-autosimplify.lua new file mode 100644 index 0000000000..09ad9874cd --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-autosimplify.lua @@ -0,0 +1,199 @@ +local a = BinaryOperation.ADDEXP + ({Integer(3), + Integer(5)}) + +local b = BinaryOperation.MULEXP + ({BinaryOperation.ADDEXP + ({Integer(13), + Integer(12)}), + Integer(-4)}) + +local c = BinaryOperation.DIVEXP + ({SymbolExpression("x"), + SymbolExpression("y")}) + +local d = BinaryOperation.DIVEXP + ({BinaryOperation.ADDEXP + ({Integer(4), + Integer(-3)}), + SymbolExpression("y")}) + +local e = BinaryOperation.ADDEXP + ({Integer(3), + Integer(4), + Integer(5), + Integer(6)}) + +starttest("expression construction") +testeq(a, "3 + 5") +testeq(b, "(13 + 12) * -4") +testeq(c, "x / y") +testeq(d, "(4 + -3) / y") +testeq(e, "3 + 4 + 5 + 6") +endtest() + +starttest("expression evaluation...") +testeq(a:evaluate(), dparse("8")) +testeq(b:evaluate(), dparse("-100")) +testeq(c:evaluate(), dparse("(x / y)")) +testeq(d:evaluate(), dparse("(1 / y)")) +testeq(e:evaluate(), dparse("18")) +endtest() + +local g = BinaryOperation.POWEXP + ({Integer(0), + SymbolExpression("x")}) + +local h = BinaryOperation.POWEXP + ({Integer(1), + SymbolExpression("x")}) + +local i = BinaryOperation.POWEXP + ({SymbolExpression("x"), + Integer(0)}) + +local j = BinaryOperation.POWEXP + ({SymbolExpression("x"), + Integer(1)}) + +local k = BinaryOperation.POWEXP + ({SymbolExpression("x"), + SymbolExpression("y")}) + +local l = BinaryOperation.POWEXP + ({BinaryOperation.POWEXP + ({BinaryOperation.POWEXP + ({SymbolExpression("x"), + Integer(3)}), + Integer(4)}), + Integer(5)}) + +local m = BinaryOperation.POWEXP + ({BinaryOperation.MULEXP + ({SymbolExpression("x"), + SymbolExpression("y")}), + SymbolExpression("a")}) + + local n = BinaryOperation.MULEXP + ({SymbolExpression("x"), + SymbolExpression("y"), + Integer(0), + Integer(-2) + }) + +local o = BinaryOperation.MULEXP + ({SymbolExpression("x"), + BinaryOperation.MULEXP + ({SymbolExpression("y"), + SymbolExpression("z")})}) + +local p = BinaryOperation.MULEXP + ({SymbolExpression("x")}) + +local q = BinaryOperation.MULEXP + ({SymbolExpression("x"), SymbolExpression("x"), SymbolExpression("x"), SymbolExpression("x")}) + +local r = BinaryOperation.MULEXP + ({SymbolExpression("x"), Integer(3), SymbolExpression("a")}) + + local s = BinaryOperation.ADDEXP + ({SymbolExpression("x")}) + +local t = BinaryOperation.ADDEXP + ({SymbolExpression("x"), + BinaryOperation.ADDEXP + ({Integer(3), + SymbolExpression("y")})}) + +local u = BinaryOperation.ADDEXP + ({BinaryOperation.MULEXP + ({SymbolExpression("x"), + SymbolExpression("y")}), + BinaryOperation.MULEXP + ({SymbolExpression("y"), + SymbolExpression("x")})}) + +local v = BinaryOperation.ADDEXP + ({Integer(3), + BinaryOperation.ADDEXP + ({BinaryOperation.MULEXP + ({Integer(2), + BinaryOperation.POWEXP + ({SymbolExpression("x"), + Integer(2)})}), + BinaryOperation.ADDEXP + ({BinaryOperation.MULEXP + ({Integer(1), + SymbolExpression("y")}), + BinaryOperation.MULEXP + ({Integer(0), + SymbolExpression("x")})})}), + Integer(6)}) + + local w = BinaryOperation.MULEXP + ({BinaryOperation.DIVEXP + ({Integer(1), + SymbolExpression("x")}), + SymbolExpression("x")}) + +local x = BinaryOperation.MULEXP + ({BinaryOperation.DIVEXP + ({SymbolExpression("y"), + SymbolExpression("x")}), + BinaryOperation.DIVEXP + ({SymbolExpression("x"), + SymbolExpression("y")})}) + +local y = BinaryOperation.MULEXP + ({BinaryOperation.DIVEXP + ({Integer(1), + Integer(3)}), + SymbolExpression("x")}) + +local z = BinaryOperation.ADDEXP + ({SymbolExpression("x"), + SymbolExpression("y"), + BinaryOperation.SUBEXP + ({SymbolExpression("x"), + SymbolExpression("y")})}) + +local A = dparse("(-aa-x)+(x+aa)") + +starttest("expression autosimplification") +testeq(g:autosimplify(), parse("0"), g) +testeq(h:autosimplify(), parse("1"), h) +testeq(i:autosimplify(), parse("1"), i) +testeq(j:autosimplify(), parse("x"), j) +testeq(k:autosimplify(), parse("x ^ y"), k) +testeq(l:autosimplify(), parse("(x ^ 60)"), l) +testeq(m:autosimplify(), parse("((x * y) ^ a)"), m) +testeq(n:autosimplify(), parse("0"), n) +testeq(o:autosimplify(), parse("(x * y * z)"), o) +testeq(p:autosimplify(), parse("x"), p) +testeq(q:autosimplify(), parse("(x ^ 4)"), q) +testeq(r:autosimplify(), parse("(3 * a * x)"), r) +testeq(s:autosimplify(), parse("x"), s) +testeq(t:autosimplify(), parse("(3 + x + y)"), t) +testeq(u:autosimplify(), parse("(2 * x * y)"), u) +testeq(v:autosimplify(), parse("(9 + (2 * (x ^ 2)) + y)"), v) +testeq(w:autosimplify(), parse("1"), w) +testeq(x:autosimplify(), parse("1"), x) +testeq(y:autosimplify(), parse("(1/3 * x)"), y) +testeq(z:autosimplify(), parse("(2 * x)"), z) +testeq(A:autosimplify(), parse("0"), A) +endtest() + + +local aa = SymbolExpression("x") + SymbolExpression("y") + SymbolExpression("z") +local ab = -(SymbolExpression("x") / SymbolExpression("y")) +local ac = Integer(2)*SymbolExpression("x")*SymbolExpression("y") - Integer(3)*SymbolExpression("x")*SymbolExpression("z") + +starttest("metamethod expressions") + +testeq(aa, dparse("(x + y) + z")) +testeq(aa:autosimplify(), parse("(x + y + z)"), aa) +testeq(ab, dparse("- (x / y)")) +testeq(ab:autosimplify(), parse("(-1 * x * (y ^ -1))"), ab) +testeq(ac:autosimplify(), parse("((2 * x * y) + (-3 * x * z))"), ac) + +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-collect.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-collect.lua new file mode 100644 index 0000000000..b40e8e3d09 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-collect.lua @@ -0,0 +1,26 @@ +local x = SymbolExpression("x") +local ex = parse("e^x") +local lnx = parse("ln(x)") + +local a = parse("y^2") +local b = parse("x + y + 1") +local c = parse("x*(y+1)+x+3*x*y^2") +local d = parse("x^2+2*x*y+y^2+x") +local e = parse("(x*y+x)^2+x^2") +local f = parse("-x^2/e^x-2*x/e^x-2/e^x+x^2*e^x-2*x*e^x+2*e^x") +local g = parse("x^(-2)+y*x^(-2)+z*x^2+2*x^2") +local h = parse("a*ln(x)-ln(x)*x-x") + + +starttest("collect method") + +testeq(a:collect(x), parse("y^2"), a) +testeq(b:collect(x), parse("x + y + 1"), b) +testeq(c:collect(x), parse("(3*y^2+y+2)*x"), c) +testeq(d:collect(x), parse("x^2+(2*y+1)*x+y^2"), d) +testeq(e:collect(x), parse("((y+1)^2+1)*x^2"), e) +testeq(f:collect(ex), parse("(x^2-2*x+2)*e^x+(-x^2-2*x-2)/e^x"), f) +testeq(g:collect(x), parse("(y+1)*x^(-2)+(z+2)*x^2"), g) +testeq(h:collect(lnx), parse("(a-x)*ln(x)-x"), h) + +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-equations.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-equations.lua new file mode 100644 index 0000000000..3d6aa131fc --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-equations.lua @@ -0,0 +1,17 @@ +local a = Equation(parse("2^x"), parse("1")) +local b = Equation(parse("x^2+2*x+1"), parse("0")) +local c = Equation(parse("2*x^x"), parse("3*y")) +local d = Equation(parse("e^x+1"), parse("y")) +local e = Equation(parse("z*sin(x/2)"), parse("4")) +local f = Equation(parse("4"), parse("0")) + + +starttest("equation solving") +testeq(a:solvefor(parse("x")), Equation(parse("x"), parse("0")), a) +testeq(b:solvefor(parse("x")), Equation(parse("x"), parse("-1")), b) -- This will need to be fixed once set expressions are woring +testeq(c:solvefor(parse("x")), Equation(parse("x^x"), parse("3/2*y")), c) +testeq(c:solvefor(parse("y")), Equation(parse("y"), parse("2/3*x^x")), c) +testeq(d:solvefor(parse("x")), Equation(parse("x"), parse("ln(y - 1)")), d) +testeq(e:solvefor(parse("x")), Equation(parse("x"), parse("2*arcsin(4/z)")), e) +testeq(f:autosimplify(), "false", f) -- Same, with boolean expressions +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-functions.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-functions.lua new file mode 100644 index 0000000000..e2c15096d6 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-functions.lua @@ -0,0 +1,17 @@ +local a = FunctionExpression("f", + {SymbolExpression("x"), + BinaryOperation.MULEXP + ({SymbolExpression("x"), + Integer(2)})}) + +local b = BinaryOperation.ADDEXP + ({FunctionExpression("g", + {SymbolExpression("x")}), + FunctionExpression("f", + {SymbolExpression("x")}), + Integer(4)}) + +starttest("function expressions") +testeq(a:autosimplify(), parse("f(x, (2 * x))"), a) +testeq(b:autosimplify(), parse("(4 + f(x) + g(x))"), b) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-logarithms.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-logarithms.lua new file mode 100644 index 0000000000..2813a6b8f5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-logarithms.lua @@ -0,0 +1,26 @@ +local a = LN(SymbolExpression("x")) +local b = LN(BinaryOperation.POWEXP({E, SymbolExpression("x")})) +local c = BinaryOperation.POWEXP({Integer(2), LOG(Integer(2), SymbolExpression("y"))}) +local d = dparse("e^(-x*ln(x))") + +local e = Logarithm(Integer(2), Integer(256)) +local f = Logarithm(Integer(4), Integer(8)) +local g = Logarithm(Integer(1)/Integer(5), Integer((125))) +local h = Logarithm(Integer(1)/Integer(9), Integer(1)/Integer(243)) +local i = Logarithm(Integer(1)/Integer(25), Integer(3125)) + +local k = Logarithm(E, Integer(1)/Integer(9)) + +starttest("logarithms") +testeq(a, "log(e, x)") +testeq(a:autosimplify(), "log(e, x)", a) +testeq(b:autosimplify(), "x", b) +testeq(c:autosimplify(), "y", c) +testeq(d:autosimplify(), parse("x^(-x)"), d) +testeq(e:autosimplify(), parse("8"), e) +testeq(f:autosimplify(), parse("3/2"), f) +testeq(g:autosimplify(), parse("-3"), g) +testeq(h:autosimplify(), parse("5/2"), h) +testeq(i:autosimplify(), parse("-5/2"), i) +testeq(k:autosimplify(), parse("-ln(9)"), k) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-rationalexponent.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-rationalexponent.lua new file mode 100644 index 0000000000..85690f8c66 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-rationalexponent.lua @@ -0,0 +1,15 @@ +local a = BinaryOperation.POWEXP({Integer(8), Integer(1) / Integer(2)}) +local b = BinaryOperation.POWEXP({Integer(27), Integer(1) / Integer(3)}) +local c = BinaryOperation.POWEXP({Integer(36), Integer(1) / Integer(2)}) +local d = BinaryOperation.POWEXP({Integer(36264691), Integer(1) / Integer(2)}) +local e = BinaryOperation.POWEXP({Integer(357911), Integer(1) / Integer(2)}) +local f = BinaryOperation.ADDEXP({BinaryOperation.POWEXP({Integer(8), Integer(1) / Integer(2)}), BinaryOperation.POWEXP({Integer(32), Integer(1) / Integer(2)})}) + +starttest("rational powers") +testeq(a:autosimplify(), "(2 * (2 ^ 1/2))", a) +testeq(b:autosimplify(), "3", b) +testeq(c:autosimplify(), "6", c) +testeq(d:autosimplify(), "(331 * (331 ^ 1/2))", d) +testeq(e:autosimplify(), "(71 * (71 ^ 1/2))", e) +testeq(f:autosimplify(), "(6 * (2 ^ 1/2))", f) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-simplify.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-simplify.lua new file mode 100644 index 0000000000..ac5582901d --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-simplify.lua @@ -0,0 +1,22 @@ +local a = SymbolExpression("x")*(SymbolExpression("y") + SymbolExpression("z")) +local b = SymbolExpression("x")*(Integer(1)+ SymbolExpression("z")) +local c = parse("((2*x+1)*(3*x-1)+6)*(6*y-z)") +local d = parse("(x+1)*(x+2)*(x+3)") + +local e = parse("x*y*z+x^2") +local f = parse("x + 1/x^2") +local g = parse("e^x - e^x*x^2") + +starttest("expression expansion") +testeq(a:expand(), parse("((x * y) + (x * z))"), a) +testeq(b:expand(), parse("(x + (x * z))"), b) +testeq(c:expand(), parse("((30 * y) + (6 * x * y) + (36 * (x ^ 2) * y) + (-5 * z) + (-1 * x * z) + (-6 * (x ^ 2) * z))"), c) +testeq(d:expand(), parse("(6 + (11 * x) + (6 * (x ^ 2)) + (x ^ 3))")) +endtest() + +starttest("expression factoring beyond monovariate polynomials") +testeq(e:factor(), parse("(x * (x + (y * z)))")) +testeq(f:factor(), parse("((x ^ -2) * (1 + x) * (1 + (-1 * x) + (x ^ 2)))")) +testeq(g:factor(), parse("((e ^ x) * (1 + x) * (1 + (-1 * x)))")) + +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/luacas-substitute.lua b/macros/luatex/latex/luacas/tex/test/expressions/luacas-substitute.lua new file mode 100644 index 0000000000..4d1f3496bf --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/luacas-substitute.lua @@ -0,0 +1,10 @@ +local a = parse("3*(x+1)^1/2-6*y+3*z^2") +local b = parse("sin(e^x - 1) + e^x") + +starttest("substitution") +testeq(a:substitute({[parse("x")] = Integer(3), + [parse("y")] = Integer(-1), + [parse("z")] = Integer(4)/Integer(3)}):autosimplify(), parse("52/3")) + +testeq(b:substitute({[parse("e^x")] = parse("x^e")}), parse("((x ^ e) + sin((-1 + (x ^ e))))")) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/rationalexponent.lua b/macros/luatex/latex/luacas/tex/test/expressions/rationalexponent.lua deleted file mode 100644 index 85690f8c66..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/rationalexponent.lua +++ /dev/null @@ -1,15 +0,0 @@ -local a = BinaryOperation.POWEXP({Integer(8), Integer(1) / Integer(2)}) -local b = BinaryOperation.POWEXP({Integer(27), Integer(1) / Integer(3)}) -local c = BinaryOperation.POWEXP({Integer(36), Integer(1) / Integer(2)}) -local d = BinaryOperation.POWEXP({Integer(36264691), Integer(1) / Integer(2)}) -local e = BinaryOperation.POWEXP({Integer(357911), Integer(1) / Integer(2)}) -local f = BinaryOperation.ADDEXP({BinaryOperation.POWEXP({Integer(8), Integer(1) / Integer(2)}), BinaryOperation.POWEXP({Integer(32), Integer(1) / Integer(2)})}) - -starttest("rational powers") -testeq(a:autosimplify(), "(2 * (2 ^ 1/2))", a) -testeq(b:autosimplify(), "3", b) -testeq(c:autosimplify(), "6", c) -testeq(d:autosimplify(), "(331 * (331 ^ 1/2))", d) -testeq(e:autosimplify(), "(71 * (71 ^ 1/2))", e) -testeq(f:autosimplify(), "(6 * (2 ^ 1/2))", f) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/simplify.lua b/macros/luatex/latex/luacas/tex/test/expressions/simplify.lua deleted file mode 100644 index ac5582901d..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/simplify.lua +++ /dev/null @@ -1,22 +0,0 @@ -local a = SymbolExpression("x")*(SymbolExpression("y") + SymbolExpression("z")) -local b = SymbolExpression("x")*(Integer(1)+ SymbolExpression("z")) -local c = parse("((2*x+1)*(3*x-1)+6)*(6*y-z)") -local d = parse("(x+1)*(x+2)*(x+3)") - -local e = parse("x*y*z+x^2") -local f = parse("x + 1/x^2") -local g = parse("e^x - e^x*x^2") - -starttest("expression expansion") -testeq(a:expand(), parse("((x * y) + (x * z))"), a) -testeq(b:expand(), parse("(x + (x * z))"), b) -testeq(c:expand(), parse("((30 * y) + (6 * x * y) + (36 * (x ^ 2) * y) + (-5 * z) + (-1 * x * z) + (-6 * (x ^ 2) * z))"), c) -testeq(d:expand(), parse("(6 + (11 * x) + (6 * (x ^ 2)) + (x ^ 3))")) -endtest() - -starttest("expression factoring beyond monovariate polynomials") -testeq(e:factor(), parse("(x * (x + (y * z)))")) -testeq(f:factor(), parse("((x ^ -2) * (1 + x) * (1 + (-1 * x) + (x ^ 2)))")) -testeq(g:factor(), parse("((e ^ x) * (1 + x) * (1 + (-1 * x)))")) - -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/expressions/substitute.lua b/macros/luatex/latex/luacas/tex/test/expressions/substitute.lua deleted file mode 100644 index 4d1f3496bf..0000000000 --- a/macros/luatex/latex/luacas/tex/test/expressions/substitute.lua +++ /dev/null @@ -1,10 +0,0 @@ -local a = parse("3*(x+1)^1/2-6*y+3*z^2") -local b = parse("sin(e^x - 1) + e^x") - -starttest("substitution") -testeq(a:substitute({[parse("x")] = Integer(3), - [parse("y")] = Integer(-1), - [parse("z")] = Integer(4)/Integer(3)}):autosimplify(), parse("52/3")) - -testeq(b:substitute({[parse("e^x")] = parse("x^e")}), parse("((x ^ e) + sin((-1 + (x ^ e))))")) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/helper.lua b/macros/luatex/latex/luacas/tex/test/helper.lua deleted file mode 100644 index b550967da9..0000000000 --- a/macros/luatex/latex/luacas/tex/test/helper.lua +++ /dev/null @@ -1,322 +0,0 @@ --- helper functions - - - function whatis(a) - if a == nil then - return nil - end - if a:type() == SymbolExpression then - return "Sym" - end - if a:type() == BinaryOperation then - return "BinOp" - end - if a:type() == FunctionExpression then - return "FncExp" - end - if a:type() == TrigExpression then - return "TrigExp" - end - if a:type() == Integer then - return "Int" - end - if a:type() == Rational then - return "Ratl" - end - if a:type() == DerivativeExpression then - return "DervExp" - end - if a:type() == DiffExpression then - return "DiffExp" - end - if a:type() == IntegralExpression then - return "Intgrl" - end - if a:type() == SqrtExpression then - return "Sqrt" - end - if a:type() == PolynomialRing then - return "Poly" - end - if a:type() == AbsExpression then - return "ABS" - end - if a:type() == Logarithm then - return "LOG" - end - if a:type() == RootExpression then - return "RootOf" - end - if a:type() == Equation then - return "=" - end - return "No Clue" -end - -function longwhatis(a) - if a == nil then - return nil - end - if a:type() == SymbolExpression then - return "SymbolExpression" - end - if a:type() == BinaryOperation then - return "BinaryOperation" - end - if a:type() == FunctionExpression then - return "FunctionExpression" - end - if a:type() == TrigExpression then - return "TrigExpression" - end - if a:type() == Integer then - return "Integer" - end - if a:type() == Rational then - return "Rational" - end - if a:type() == DerivativeExpression then - return "DerivativeExpression" - end - if a:type() == DiffExpression then - return "DiffExpression" - end - if a:type() == IntegralExpression then - return "IntegralExpression" - end - if a:type() == SqrtExpression then - return "SqrtExpression" - end - if a:type() == PolynomialRing then - return "PolynomialRing" - end - if a:type() == AbsExpression then - return "AbsExpression" - end - if a:type() == Logarithm then - return "Logarithm" - end - if a:type() == RootExpression then - return "RootExpression" - end - if a:type() == Equation then - return "Equation" - end - return "No Clue" -end - -function whatring(a) - if a:getring() == Rational.makering() then - return "Rational" - end - if a:getring() == PolynomialRing.makering() then - return "PolynomialRing" - end - if a:getring() == Integer.makering() then - return "Integer" - end - if a:getring() == IntegerModN.makering() then - return "IntegerModN" - end - return "No Clue" -end - -function nameof(sym) - if sym == nil then - return nil - end - if sym:type() == BinaryOperation then - local binops = {BinaryOperation.ADD, - BinaryOperation.MUL, - BinaryOperation.SUB, - BinaryOperation.DIV, - BinaryOperation.POW, - BinaryOperation.IDIV, - BinaryOperation.MOD} - local obslab = {"ADD", - "MUL", - "SUB", - "DIV", - "POW", - "IDIV", - "MOD"} - for i,j in pairs(binops) do - if sym.operation == j then - return obslab[i] - end - end - end - if sym:type() == FunctionExpression or sym:type() == TrigExpression then - return tostring(sym.name) - end - if sym:type() == SymbolExpression or sym:type() == Integer then - return tostring(sym) - end - if sym:type() == Rational then - return tostring(sym.numerator).."/"..tostring(sym.denominator) - end - if sym:type() == DerivativeExpression then - return "DD" - end - if sym:type() == DiffExpression then - return "diff" - end - if sym:type() == IntegralExpression then - return "$\\mathtt{\\int}$" - end - if sym:type() == SqrtExpression then - return "$\\mathtt{\\sqrt{\\phantom{x}}}$" - end - if sym:type() == PolynomialRing then - return "Poly" - end - if sym:type() == AbsExpression then - return "abs" - end - if sym:type() == Logarithm then - return "log" - end - if sym:type() == RootExpression then - return "RootOf" - end - if sym:type() == Equation then - return "$\\mathtt{=}$" - end - return "No Clue" -end - -function Expression:getfullsubexpressionsrec() - local result = {} - for _, expression in ipairs(self:subexpressions()) do - result[#result+1] = expression - result = JoinArrays(result, expression:getfullsubexpressionsrec()) - end - return result -end - -function Expression:gettheshrub() - local string = "" - for index, expression in ipairs(self:subexpressions()) do - string = string.."child {node [label=-90:{expr["..tostring(index).."]}] {$\\mathtt{"..expression:tolatex().."}$}}" - end - return string -end - -function Expression:getthetree() - local string = "" - for _, expression in ipairs(self:subexpressions()) do - if expression:isatomic() then - string = string.."child {node{"..nameof(expression).."}}" - else - string = string.."child {node{"..nameof(expression).."}"..expression:getthetree().."}" - end - end - return string -end - -function Expression:gettheforest() - local string = "" - for _, expression in ipairs(self:subexpressions()) do - if expression:isatomic() then - string = string.." [ "..nameof(expression).." ] " - else - string = string.." [ "..nameof(expression)..expression:gettheforest().." ] " - end - end - return string -end - -function Expression:getthefancyshrub() - local string = "" - if self:type() == DiffExpression then - for _, expression in ipairs(self:subexpressions()) do - string = string.." [ $\\mathtt{"..expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ] " - end - string = string.." [ $\\mathtt{\\{" - for _,symbol in ipairs(self.symbols) do - if next(self.symbols,_) == nil then - string = string .. symbol:tolatex().."\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.symbols};} ] " - else - string = string .. symbol:tolatex() .. "," - end - end - return string - end - if self:type() == IntegralExpression then - string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ] " - string = string .. "[ $\\mathtt{"..self.symbol:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.symbol};} ]" - if self:isdefinite() then - string = string .. "[ $\\mathtt{"..self.lower:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.lower};} ] " - string = string .. "[ $\\mathtt{"..self.upper:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.upper};} ] " - return string - end - return string - end - if self:type() == PolynomialRing then - string = string .. " [ $\\mathtt{\\{" - for index=0, self.degree:asnumber() do - string = string .. tostring(self.coefficients[index]) - if index < self.degree:asnumber() then - string = string .. "," - end - end - string = string .. "\\} }$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.coefficients}; \\node[anchor=south west, font=\\ttfamily\\footnotesize,gray] at (.north west) {.ring "..whatring(self).."};} ]" - string = string .. " [ $\\mathtt{"..self.symbol.. "}$, tikz+={\\node[anchor=north, font=\\ttfamily\\footnotesize,gray] at (.south) {.symbol};} ]" - return string - end - if self:type() == SqrtExpression then - string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" - string = string .. "[ $\\mathtt{"..self.root:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.root};} ]" - return string - end - if self:type() == TrigExpression then - string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" - return string - end - if self:type() == AbsExpression then - string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ] " - return string - end - if self:type() == Logarithm then - string = string .. " [$\\mathtt{" ..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" - string = string .. " [$\\mathtt{" ..self.base:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.base};} ]" - return string - end - if self:type() == RootExpression then - string = string .. "[$\\mathtt{" ..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" - return string - end - if self:type() == FunctionExpression then - local string1 = '' - local string2 = '' - local string3 = '' - for index=1, #self.variables do - string1 = string1 .. tostring(self.expressions[index]) - if index < #self.variables then - string1 = string1 .. "," - end - string2 = string2 .. tostring(self.variables[index]) - if index < #self.variables then - string2 = string2 .. "," - end - string3 = string3 .. tostring(self.derivatives[index]) - if index < #self.variables then - string3 = string3 .. "," - end - end - string = string .. "[$\\mathtt{ \\{" .. string1 .. "\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expressions};} ]" - string = string .. "[$\\mathtt{ \\{" .. string2 .. "\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.variables};} ]" - string = string .. "[$\\mathtt{ \\{" .. string3 .. "\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.derivatives};} ]" - return string - end - if self:type() == Equation then - string = string .. " [$\\mathtt{" ..self.lhs:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.lhs};} ]" - string = string .. " [$\\mathtt{" ..self.rhs:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.rhs};} ]" - return string - end - for index, expression in ipairs(self:subexpressions()) do - string = string.." [ $\\mathtt{"..expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression["..index.."]};} ] " - end - return string -end - diff --git a/macros/luatex/latex/luacas/tex/test/luacas-helper.lua b/macros/luatex/latex/luacas/tex/test/luacas-helper.lua new file mode 100644 index 0000000000..b550967da9 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/luacas-helper.lua @@ -0,0 +1,322 @@ +-- helper functions + + + function whatis(a) + if a == nil then + return nil + end + if a:type() == SymbolExpression then + return "Sym" + end + if a:type() == BinaryOperation then + return "BinOp" + end + if a:type() == FunctionExpression then + return "FncExp" + end + if a:type() == TrigExpression then + return "TrigExp" + end + if a:type() == Integer then + return "Int" + end + if a:type() == Rational then + return "Ratl" + end + if a:type() == DerivativeExpression then + return "DervExp" + end + if a:type() == DiffExpression then + return "DiffExp" + end + if a:type() == IntegralExpression then + return "Intgrl" + end + if a:type() == SqrtExpression then + return "Sqrt" + end + if a:type() == PolynomialRing then + return "Poly" + end + if a:type() == AbsExpression then + return "ABS" + end + if a:type() == Logarithm then + return "LOG" + end + if a:type() == RootExpression then + return "RootOf" + end + if a:type() == Equation then + return "=" + end + return "No Clue" +end + +function longwhatis(a) + if a == nil then + return nil + end + if a:type() == SymbolExpression then + return "SymbolExpression" + end + if a:type() == BinaryOperation then + return "BinaryOperation" + end + if a:type() == FunctionExpression then + return "FunctionExpression" + end + if a:type() == TrigExpression then + return "TrigExpression" + end + if a:type() == Integer then + return "Integer" + end + if a:type() == Rational then + return "Rational" + end + if a:type() == DerivativeExpression then + return "DerivativeExpression" + end + if a:type() == DiffExpression then + return "DiffExpression" + end + if a:type() == IntegralExpression then + return "IntegralExpression" + end + if a:type() == SqrtExpression then + return "SqrtExpression" + end + if a:type() == PolynomialRing then + return "PolynomialRing" + end + if a:type() == AbsExpression then + return "AbsExpression" + end + if a:type() == Logarithm then + return "Logarithm" + end + if a:type() == RootExpression then + return "RootExpression" + end + if a:type() == Equation then + return "Equation" + end + return "No Clue" +end + +function whatring(a) + if a:getring() == Rational.makering() then + return "Rational" + end + if a:getring() == PolynomialRing.makering() then + return "PolynomialRing" + end + if a:getring() == Integer.makering() then + return "Integer" + end + if a:getring() == IntegerModN.makering() then + return "IntegerModN" + end + return "No Clue" +end + +function nameof(sym) + if sym == nil then + return nil + end + if sym:type() == BinaryOperation then + local binops = {BinaryOperation.ADD, + BinaryOperation.MUL, + BinaryOperation.SUB, + BinaryOperation.DIV, + BinaryOperation.POW, + BinaryOperation.IDIV, + BinaryOperation.MOD} + local obslab = {"ADD", + "MUL", + "SUB", + "DIV", + "POW", + "IDIV", + "MOD"} + for i,j in pairs(binops) do + if sym.operation == j then + return obslab[i] + end + end + end + if sym:type() == FunctionExpression or sym:type() == TrigExpression then + return tostring(sym.name) + end + if sym:type() == SymbolExpression or sym:type() == Integer then + return tostring(sym) + end + if sym:type() == Rational then + return tostring(sym.numerator).."/"..tostring(sym.denominator) + end + if sym:type() == DerivativeExpression then + return "DD" + end + if sym:type() == DiffExpression then + return "diff" + end + if sym:type() == IntegralExpression then + return "$\\mathtt{\\int}$" + end + if sym:type() == SqrtExpression then + return "$\\mathtt{\\sqrt{\\phantom{x}}}$" + end + if sym:type() == PolynomialRing then + return "Poly" + end + if sym:type() == AbsExpression then + return "abs" + end + if sym:type() == Logarithm then + return "log" + end + if sym:type() == RootExpression then + return "RootOf" + end + if sym:type() == Equation then + return "$\\mathtt{=}$" + end + return "No Clue" +end + +function Expression:getfullsubexpressionsrec() + local result = {} + for _, expression in ipairs(self:subexpressions()) do + result[#result+1] = expression + result = JoinArrays(result, expression:getfullsubexpressionsrec()) + end + return result +end + +function Expression:gettheshrub() + local string = "" + for index, expression in ipairs(self:subexpressions()) do + string = string.."child {node [label=-90:{expr["..tostring(index).."]}] {$\\mathtt{"..expression:tolatex().."}$}}" + end + return string +end + +function Expression:getthetree() + local string = "" + for _, expression in ipairs(self:subexpressions()) do + if expression:isatomic() then + string = string.."child {node{"..nameof(expression).."}}" + else + string = string.."child {node{"..nameof(expression).."}"..expression:getthetree().."}" + end + end + return string +end + +function Expression:gettheforest() + local string = "" + for _, expression in ipairs(self:subexpressions()) do + if expression:isatomic() then + string = string.." [ "..nameof(expression).." ] " + else + string = string.." [ "..nameof(expression)..expression:gettheforest().." ] " + end + end + return string +end + +function Expression:getthefancyshrub() + local string = "" + if self:type() == DiffExpression then + for _, expression in ipairs(self:subexpressions()) do + string = string.." [ $\\mathtt{"..expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ] " + end + string = string.." [ $\\mathtt{\\{" + for _,symbol in ipairs(self.symbols) do + if next(self.symbols,_) == nil then + string = string .. symbol:tolatex().."\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.symbols};} ] " + else + string = string .. symbol:tolatex() .. "," + end + end + return string + end + if self:type() == IntegralExpression then + string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ] " + string = string .. "[ $\\mathtt{"..self.symbol:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.symbol};} ]" + if self:isdefinite() then + string = string .. "[ $\\mathtt{"..self.lower:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.lower};} ] " + string = string .. "[ $\\mathtt{"..self.upper:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.upper};} ] " + return string + end + return string + end + if self:type() == PolynomialRing then + string = string .. " [ $\\mathtt{\\{" + for index=0, self.degree:asnumber() do + string = string .. tostring(self.coefficients[index]) + if index < self.degree:asnumber() then + string = string .. "," + end + end + string = string .. "\\} }$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.coefficients}; \\node[anchor=south west, font=\\ttfamily\\footnotesize,gray] at (.north west) {.ring "..whatring(self).."};} ]" + string = string .. " [ $\\mathtt{"..self.symbol.. "}$, tikz+={\\node[anchor=north, font=\\ttfamily\\footnotesize,gray] at (.south) {.symbol};} ]" + return string + end + if self:type() == SqrtExpression then + string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" + string = string .. "[ $\\mathtt{"..self.root:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.root};} ]" + return string + end + if self:type() == TrigExpression then + string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" + return string + end + if self:type() == AbsExpression then + string = string .. " [ $\\mathtt{"..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ] " + return string + end + if self:type() == Logarithm then + string = string .. " [$\\mathtt{" ..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" + string = string .. " [$\\mathtt{" ..self.base:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.base};} ]" + return string + end + if self:type() == RootExpression then + string = string .. "[$\\mathtt{" ..self.expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression};} ]" + return string + end + if self:type() == FunctionExpression then + local string1 = '' + local string2 = '' + local string3 = '' + for index=1, #self.variables do + string1 = string1 .. tostring(self.expressions[index]) + if index < #self.variables then + string1 = string1 .. "," + end + string2 = string2 .. tostring(self.variables[index]) + if index < #self.variables then + string2 = string2 .. "," + end + string3 = string3 .. tostring(self.derivatives[index]) + if index < #self.variables then + string3 = string3 .. "," + end + end + string = string .. "[$\\mathtt{ \\{" .. string1 .. "\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expressions};} ]" + string = string .. "[$\\mathtt{ \\{" .. string2 .. "\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.variables};} ]" + string = string .. "[$\\mathtt{ \\{" .. string3 .. "\\}}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.derivatives};} ]" + return string + end + if self:type() == Equation then + string = string .. " [$\\mathtt{" ..self.lhs:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.lhs};} ]" + string = string .. " [$\\mathtt{" ..self.rhs:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.rhs};} ]" + return string + end + for index, expression in ipairs(self:subexpressions()) do + string = string.." [ $\\mathtt{"..expression:tolatex().."}$, tikz+={\\node[anchor=north,font=\\ttfamily\\footnotesize,gray] at (.south) {.expression["..index.."]};} ] " + end + return string +end + diff --git a/macros/luatex/latex/luacas/tex/test/luacas-main.lua b/macros/luatex/latex/luacas/tex/test/luacas-main.lua new file mode 100644 index 0000000000..95f77885e4 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/luacas-main.lua @@ -0,0 +1,154 @@ +---@diagnostic disable: lowercase-global +-- Runs test code from test files. + +require("calculus.luacas-calculus_init") +require("_lib.luacas-pepperfish") + +-- Stuff required for the basic parser. +local constants = {e="E", pi = "PI", ln = "LN", log = "LOG", Integer = "Integer", DD = "DD", int = "INT", abs = "ABS", fact="FACT"} + +local function parser(s) + if string.find(s, "[0-9]+") then + return "Integer(\"" .. s .. "\")" + end + + if s.find(s, "[%^%\\%[%]]") then + return string.gsub(s, "[^%^%\\%[%]]+", parser) + end + + for string, replace in pairs(constants) do + if s == string then + return replace + end + end + + return "SymbolExpression(\"" .. s .. "\")" +end + +function parse(input) + local parsed = string.gsub(input, "[0-9]+", parser) + parsed = string.gsub(parsed, "[A-z']+", parser) + local exe, err = load("return " .. parsed) + if exe then + return exe():autosimplify() + else + print(err) + end +end + +function dparse(input) + local parsed = string.gsub(input, "[0-9]+", parser) + parsed = string.gsub(parsed, "[A-z']+", parser) + local exe, err = load("return " .. parsed) + if exe then + return exe() + else + print(err) + end +end + +-- Stuff required for test code. +local tests +local failures +local totaltests = 0 +local totalfailures = 0 +function starttest(name) + print("Testing " .. name .. "...") + print() + tests = 0 + failures = 0 +end + +-- Tests two objects for equality, irrespective of order. If the object is a table or expression, the objects may be sorted to ensure the correct order. +function testeq(actual, expected, initial, sort) + if sort and type(actual) == "table" and not actual.type then + table.sort(actual, function (a, b) + return a:order(b) + end) + elseif sort and type(actual) == "table" and actual.type and actual:type() == BinaryOperation and actual:iscommutative() then + table.sort(actual.expressions, function (a, b) + return a:order(b) + end) + end + + if initial then + if ToStringArray(expected) == ToStringArray(actual) then + print(ToStringArray(initial) .. " -> " .. ToStringArray(actual)) + else + print(ToStringArray(initial) .. " -> " .. ToStringArray(actual) .. " (Expected: " .. ToStringArray(expected) .. ")") + failures = failures + 1 + end + else + if ToStringArray(expected) == ToStringArray(actual) then + print("Result: " .. ToStringArray(actual)) + else + print("Result: ".. ToStringArray(actual) .. " (Expected: " .. ToStringArray(expected) .. ")") + failures = failures + 1 + end + end + tests = tests + 1 +end + +-- Tests whether converting an element to a different ring produces the expected object in the expected ring +function testringconvert(expression, toring, expected, expectedring) + testeq(expression:inring(toring), expected, expression) + testeq(expression:inring(toring):getring(), expectedring) +end + +function endtest() + print() + print("Finished test without errors.") + print() + totaltests = totaltests + tests + totalfailures = totalfailures + failures + if failures == 0 then + print("Performed " .. tests .. " tests, all of which passed!") + else + print("Performed tests, " .. failures .. "/" .. tests .. " failed.") + end + print("=====================================================================================================================") +end + +function endall() + if totalfailures == 0 then + print("Performed " .. totaltests .. " tests in total, all of which passed!") + else + print("Performed tests, " .. totalfailures .. "/" .. totaltests .. " failed.") + end +end + + +-- TODO: Add profiling and error catching options. +-- Comment out these lines to only run certain test code. + +-- profiler = newProfiler() +-- profiler:start() + +require("test.calculus.luacas-derivatives") +require("test.calculus.luacas-integrals") + +require("test.expressions.luacas-autosimplify") +require("test.expressions.luacas-collect") +require("test.expressions.luacas-equations") +require("test.expressions.luacas-simplify") +require("test.expressions.luacas-functions") +require("test.expressions.luacas-logarithms") +-- require("test.expressions.luacas-rationalexponent") +require("test.expressions.luacas-substitute") + +require("test.polynomials.luacas-polynomial") +require("test.polynomials.luacas-partialfractions") +require("test.polynomials.luacas-polynomialmod") +require("test.polynomials.luacas-roots") + +require("test.rings.luacas-conversion") +require("test.rings.luacas-modulararithmetic") +require("test.rings.luacas-number") + +endall() + +-- profiler:stop() + +-- local outfile = io.open( "profile.txt", "w+" ) +-- profiler:report( outfile ) +-- outfile:close() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/luacas-parser.lua b/macros/luatex/latex/luacas/tex/test/luacas-parser.lua new file mode 100644 index 0000000000..3380549620 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/luacas-parser.lua @@ -0,0 +1,323 @@ +-- Rudimentary parser for making the CAS easier to use. Essentially just wraps SymbolExpression() around symbols and Integer() around integers. + + + +require("calculus.luacas-calculus_init") + +-- Splits a string on a seperator. +function split(str, sep) + local t={} + for match in string.gmatch(str, "([^".. sep .."]+)") do + t[#t+1] = match + end + return t +end + +-- Displays an expression. For use in the parser. +function disp(expression, inline, simple) + if type(expression) ~= "table" then + tex.print(tostring(expression)) + elseif expression.autosimplify then + if inline then + if simple then + tex.print('$' .. expression:autosimplify():tolatex() .. '$') + else + tex.print('$' .. expression:tolatex() .. '$') + end + else + if simple then + tex.print('\\[' .. expression:autosimplify():tolatex() .. '\\]') + else + tex.print('\\[' .. expression:tolatex() .. '\\]') + end + end + else + tex.print(tostring(expression)) + end +end + +-- Displays an expression. For use in the parser. +function displua(expression) + if type(expression) ~= "table" then + print(tostring(expression)) + elseif expression.autosimplify then + print(expression:autosimplify():tolatex()) + else + print(tostring(expression)) + end +end + +function vars(...) + for _, string in ipairs(table.pack(...)) do + if string ~= "_" then + _G[string] = SymbolExpression(string) + end + end +end + +function clearvars() + for index, value in pairs(_G) do + if type(value) == "table" and value.type and value:type() == SymbolExpression then + _G[index] = nil + end + end +end + +function range(a, b, step) + if not b then + b = a + a = Integer.one() + end + step = step or Integer.one() + local f = + step > Integer.zero() and + function(_, lastvalue) + local nextvalue = lastvalue + step + if nextvalue <= b then return nextvalue end + end or + step < Integer.zero() and + function(_, lastvalue) + local nextvalue = lastvalue + step + if nextvalue >= b then return nextvalue end + end or + function(_, lastvalue) return lastvalue end + return f, nil, a - step + end + +function factor(exp,squarefrei) + if exp:type() == Integer then + return exp:primefactorization() + end + if exp:type() == PolynomialRing then + if not squarefrei then + return exp:factor() + else + if exp.ring == Integer.getring() or Rational.getring() then + return exp:squarefreefactorization() + end + if exp.ring == IntegerModN.getring() then + return exp:modularsquarefreefactorization() + end + return exp:factor() + end + end + return exp:autosimplify():factor() +end + +function expand(exp) + return exp:autosimplify():expand() +end + +function simplify(exp) + return exp:simplify() +end + +function exp(x) + return e^x +end + +function substitute(tbl,expr) + return expr:substitute(tbl) +end + +function roots(expression) + poly,ispoly = topoly(expression) + if ispoly then + return poly:roots() + end + return RootExpression(expression) +end + +function combine(expr) + return expr:combine() +end + +function Mod(f,n) + if f:type() == Integer then + return IntegerModN(f,n) + end + if f:type() == PolynomialRing and f.ring == Integer.getring() then + local coeffs = {} + for i=0,f.degree:asnumber() do + coeffs[i] = IntegerModN(f.coefficients[i],n) + end + return PolynomialRing(coeffs,f.symbol,f.degree) + end +end + +function Poly(coefficients,symbol,degree) + local variable = symbol or 'x' + return PolynomialRing:new(coefficients,variable,degree) +end + +function topoly(a) + a = a:expand():autosimplify() + return a:topolynomial() +end + +function gcd(a,b) + if a:type() == Integer and b:type() == Integer then + return Integer.gcd(a,b) + end + if a:type() == PolynomialRing and b:type() == PolynomialRing then + return PolynomialRing.gcd(a,b) + end +end + +function gcdext(a,b) + if a:type() == Integer and b:type() == Integer then + return Integer.extendedgcd(a,b) + end + A, ATF = topoly(a) + B, BTF = topoly(b) + if ATF and BTF then + return PolynomialRing.extendedgcd(A,B) + end + return nil,nil,nil +end + +function parfrac(f,g,ffactor) + local f,check1 = topoly(f) + local g,check2 = topoly(g) + if check1 and check2 then + if f.degree >= g.degree then + local q,r + q,r = f:divremainder(g) + return q + PolynomialRing.partialfractions(r,g,ffactor) + else + return PolynomialRing.partialfractions(f,g,ffactor) + end + else + return f/g + end +end + +function factorial(a) + return FactorialExpression(a) +end + +-- Constants for the CAS. We may not want these in Lua itself, but in the latex end the user probably expects them. +e = E +pi = PI +-- sqrt = SQRT +ln = LN +log = LOG +int = INT +sin = SIN +cos = COS +tan = TAN +csc = CSC +sec = SEC +cot = COT +arcsin = ARCSIN +arccos = ARCCOS +arctan = ARCTAN +arccsc = ARCCSC +arcsec = ARCSEC +arccot = ARCCOT +abs = ABS + +function ZTable(t) + t = t or {} + return setmetatable(t, JoinTables(getmetatable(t), + {__index = function (t, k) + if type(k) == "table" and k.type and k:type() == Integer then + return rawget(t, k:asnumber()) + else + return rawget(t, k) + end + end, + __newindex = function (t, k, v) + if type(k) == "table" and k.type and k:type() == Integer then + rawset(t, k:asnumber(), v) + else + rawset(t, k, v) + end + end})) +end + +function RR(n) + if type(n) == "number" then + return n + end + + if type(n) == "string" then + return tonumber(n) + end + + if type(n) == "table" and n.asnumber then + return n:asnumber() + end + + error("Could not convert to a real number.") +end + +function ZZ(n) + if type(n) == "table" and n.type and n:type() == Rational then + return n.numerator // n.denominator + end + return Integer(n) +end + +function QQ(n) + if type(n) == "table" then + return n + end + + if type(n) == "number" then + n = tostring(n) + end + + if type(n) == "string" then + local parts = split(n, "%.") + if #parts == 1 then + return Integer(parts[1]) + else + return Integer(parts[1])..Integer(parts[2]) + end + end + + error("Could not convert to a rational number.") +end + +--- Parses raw input into Lua code and executes it. +--- @param input string +function CASparse(input) + + -- First, we replace any occurance of a number with an integer or rational version of itself. + local str = string.gsub(input, ".?[0-9]+", function (s) + -- Here, we are part of an identifier, so we don't replace anything + if string.match(string.sub(s, 1, 1), "[A-Z]") or string.match(string.sub(s, 1, 1), "[a-z]") or string.match(string.sub(s, 1, 1), "_") then + return + end + + if string.match(string.sub(s, 1, 1), "[0-9]") then + return "Integer('" .. s .. "')" + end + + return string.sub(s, 1, 1) .. "Integer('" .. string.sub(s, 2, #s) .. "')" + end) + + -------------------------- + -- HERE COMES THE JANK. -- + -------------------------- + + -- Replaces each instance of a decimal with .., so we can use integer metatables to convert it into a rational properly. + str = string.gsub(str, "Integer%('[0-9]+'%)%.Integer%('[0-9]+'%)", function (s) + local ints = split(s, "%.") + return ints[1] .. ".." .. ints[2] + end) + str = string.gsub(str, ".?%.Integer%('[0-9]+'%)", function (s) + if string.sub(s, 1, 2) == ".." then + return + end + return string.sub(s, 1, 1) .. "Integer('0')." .. string.sub(s, 2, #s) + end) + + local exe, err = load(str .. "\n return true") + if exe then + exe() + else + print(err) + end +end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/main.lua b/macros/luatex/latex/luacas/tex/test/main.lua deleted file mode 100644 index 638f7734b5..0000000000 --- a/macros/luatex/latex/luacas/tex/test/main.lua +++ /dev/null @@ -1,154 +0,0 @@ ----@diagnostic disable: lowercase-global --- Runs test code from test files. - -require("calculus._init") -require("_lib.pepperfish") - --- Stuff required for the basic parser. -local constants = {e="E", pi = "PI", ln = "LN", log = "LOG", Integer = "Integer", DD = "DD", int = "INT", abs = "ABS", fact="FACT"} - -local function parser(s) - if string.find(s, "[0-9]+") then - return "Integer(\"" .. s .. "\")" - end - - if s.find(s, "[%^%\\%[%]]") then - return string.gsub(s, "[^%^%\\%[%]]+", parser) - end - - for string, replace in pairs(constants) do - if s == string then - return replace - end - end - - return "SymbolExpression(\"" .. s .. "\")" -end - -function parse(input) - local parsed = string.gsub(input, "[0-9]+", parser) - parsed = string.gsub(parsed, "[A-z']+", parser) - local exe, err = load("return " .. parsed) - if exe then - return exe():autosimplify() - else - print(err) - end -end - -function dparse(input) - local parsed = string.gsub(input, "[0-9]+", parser) - parsed = string.gsub(parsed, "[A-z']+", parser) - local exe, err = load("return " .. parsed) - if exe then - return exe() - else - print(err) - end -end - --- Stuff required for test code. -local tests -local failures -local totaltests = 0 -local totalfailures = 0 -function starttest(name) - print("Testing " .. name .. "...") - print() - tests = 0 - failures = 0 -end - --- Tests two objects for equality, irrespective of order. If the object is a table or expression, the objects may be sorted to ensure the correct order. -function testeq(actual, expected, initial, sort) - if sort and type(actual) == "table" and not actual.type then - table.sort(actual, function (a, b) - return a:order(b) - end) - elseif sort and type(actual) == "table" and actual.type and actual:type() == BinaryOperation and actual:iscommutative() then - table.sort(actual.expressions, function (a, b) - return a:order(b) - end) - end - - if initial then - if ToStringArray(expected) == ToStringArray(actual) then - print(ToStringArray(initial) .. " -> " .. ToStringArray(actual)) - else - print(ToStringArray(initial) .. " -> " .. ToStringArray(actual) .. " (Expected: " .. ToStringArray(expected) .. ")") - failures = failures + 1 - end - else - if ToStringArray(expected) == ToStringArray(actual) then - print("Result: " .. ToStringArray(actual)) - else - print("Result: ".. ToStringArray(actual) .. " (Expected: " .. ToStringArray(expected) .. ")") - failures = failures + 1 - end - end - tests = tests + 1 -end - --- Tests whether converting an element to a different ring produces the expected object in the expected ring -function testringconvert(expression, toring, expected, expectedring) - testeq(expression:inring(toring), expected, expression) - testeq(expression:inring(toring):getring(), expectedring) -end - -function endtest() - print() - print("Finished test without errors.") - print() - totaltests = totaltests + tests - totalfailures = totalfailures + failures - if failures == 0 then - print("Performed " .. tests .. " tests, all of which passed!") - else - print("Performed tests, " .. failures .. "/" .. tests .. " failed.") - end - print("=====================================================================================================================") -end - -function endall() - if totalfailures == 0 then - print("Performed " .. totaltests .. " tests in total, all of which passed!") - else - print("Performed tests, " .. totalfailures .. "/" .. totaltests .. " failed.") - end -end - - --- TODO: Add profiling and error catching options. --- Comment out these lines to only run certain test code. - --- profiler = newProfiler() --- profiler:start() - -require("test.calculus.derivatives") -require("test.calculus.integrals") - -require("test.expressions.autosimplify") -require("test.expressions.collect") -require("test.expressions.equations") -require("test.expressions.simplify") -require("test.expressions.functions") -require("test.expressions.logarithms") --- require("test.expressions.rationalexponent") -require("test.expressions.substitute") - -require("test.polynomials.polynomial") -require("test.polynomials.partialfractions") -require("test.polynomials.polynomialmod") -require("test.polynomials.roots") - -require("test.rings.conversion") -require("test.rings.modulararithmetic") -require("test.rings.number") - -endall() - --- profiler:stop() - --- local outfile = io.open( "profile.txt", "w+" ) --- profiler:report( outfile ) --- outfile:close() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/parser.lua b/macros/luatex/latex/luacas/tex/test/parser.lua deleted file mode 100644 index 6cb22a1bdb..0000000000 --- a/macros/luatex/latex/luacas/tex/test/parser.lua +++ /dev/null @@ -1,323 +0,0 @@ --- Rudimentary parser for making the CAS easier to use. Essentially just wraps SymbolExpression() around symbols and Integer() around integers. - - - -require("calculus._init") - --- Splits a string on a seperator. -function split(str, sep) - local t={} - for match in string.gmatch(str, "([^".. sep .."]+)") do - t[#t+1] = match - end - return t -end - --- Displays an expression. For use in the parser. -function disp(expression, inline, simple) - if type(expression) ~= "table" then - tex.print(tostring(expression)) - elseif expression.autosimplify then - if inline then - if simple then - tex.print('$' .. expression:autosimplify():tolatex() .. '$') - else - tex.print('$' .. expression:tolatex() .. '$') - end - else - if simple then - tex.print('\\[' .. expression:autosimplify():tolatex() .. '\\]') - else - tex.print('\\[' .. expression:tolatex() .. '\\]') - end - end - else - tex.print(tostring(expression)) - end -end - --- Displays an expression. For use in the parser. -function displua(expression) - if type(expression) ~= "table" then - print(tostring(expression)) - elseif expression.autosimplify then - print(expression:autosimplify():tolatex()) - else - print(tostring(expression)) - end -end - -function vars(...) - for _, string in ipairs(table.pack(...)) do - if string ~= "_" then - _G[string] = SymbolExpression(string) - end - end -end - -function clearvars() - for index, value in pairs(_G) do - if type(value) == "table" and value.type and value:type() == SymbolExpression then - _G[index] = nil - end - end -end - -function range(a, b, step) - if not b then - b = a - a = Integer.one() - end - step = step or Integer.one() - local f = - step > Integer.zero() and - function(_, lastvalue) - local nextvalue = lastvalue + step - if nextvalue <= b then return nextvalue end - end or - step < Integer.zero() and - function(_, lastvalue) - local nextvalue = lastvalue + step - if nextvalue >= b then return nextvalue end - end or - function(_, lastvalue) return lastvalue end - return f, nil, a - step - end - -function factor(exp,squarefrei) - if exp:type() == Integer then - return exp:primefactorization() - end - if exp:type() == PolynomialRing then - if not squarefrei then - return exp:factor() - else - if exp.ring == Integer.getring() or Rational.getring() then - return exp:squarefreefactorization() - end - if exp.ring == IntegerModN.getring() then - return exp:modularsquarefreefactorization() - end - return exp:factor() - end - end - return exp:autosimplify():factor() -end - -function expand(exp) - return exp:autosimplify():expand() -end - -function simplify(exp) - return exp:simplify() -end - -function exp(x) - return e^x -end - -function substitute(tbl,expr) - return expr:substitute(tbl) -end - -function roots(expression) - poly,ispoly = topoly(expression) - if ispoly then - return poly:roots() - end - return RootExpression(expression) -end - -function combine(expr) - return expr:combine() -end - -function Mod(f,n) - if f:type() == Integer then - return IntegerModN(f,n) - end - if f:type() == PolynomialRing and f.ring == Integer.getring() then - local coeffs = {} - for i=0,f.degree:asnumber() do - coeffs[i] = IntegerModN(f.coefficients[i],n) - end - return PolynomialRing(coeffs,f.symbol,f.degree) - end -end - -function Poly(coefficients,symbol,degree) - local variable = symbol or 'x' - return PolynomialRing:new(coefficients,variable,degree) -end - -function topoly(a) - a = a:expand():autosimplify() - return a:topolynomial() -end - -function gcd(a,b) - if a:type() == Integer and b:type() == Integer then - return Integer.gcd(a,b) - end - if a:type() == PolynomialRing and b:type() == PolynomialRing then - return PolynomialRing.gcd(a,b) - end -end - -function gcdext(a,b) - if a:type() == Integer and b:type() == Integer then - return Integer.extendedgcd(a,b) - end - A, ATF = topoly(a) - B, BTF = topoly(b) - if ATF and BTF then - return PolynomialRing.extendedgcd(A,B) - end - return nil,nil,nil -end - -function parfrac(f,g,ffactor) - local f,check1 = topoly(f) - local g,check2 = topoly(g) - if check1 and check2 then - if f.degree >= g.degree then - local q,r - q,r = f:divremainder(g) - return q + PolynomialRing.partialfractions(r,g,ffactor) - else - return PolynomialRing.partialfractions(f,g,ffactor) - end - else - return f/g - end -end - -function factorial(a) - return FactorialExpression(a) -end - --- Constants for the CAS. We may not want these in Lua itself, but in the latex end the user probably expects them. -e = E -pi = PI --- sqrt = SQRT -ln = LN -log = LOG -int = INT -sin = SIN -cos = COS -tan = TAN -csc = CSC -sec = SEC -cot = COT -arcsin = ARCSIN -arccos = ARCCOS -arctan = ARCTAN -arccsc = ARCCSC -arcsec = ARCSEC -arccot = ARCCOT -abs = ABS - -function ZTable(t) - t = t or {} - return setmetatable(t, JoinTables(getmetatable(t), - {__index = function (t, k) - if type(k) == "table" and k.type and k:type() == Integer then - return rawget(t, k:asnumber()) - else - return rawget(t, k) - end - end, - __newindex = function (t, k, v) - if type(k) == "table" and k.type and k:type() == Integer then - rawset(t, k:asnumber(), v) - else - rawset(t, k, v) - end - end})) -end - -function RR(n) - if type(n) == "number" then - return n - end - - if type(n) == "string" then - return tonumber(n) - end - - if type(n) == "table" and n.asnumber then - return n:asnumber() - end - - error("Could not convert to a real number.") -end - -function ZZ(n) - if type(n) == "table" and n.type and n:type() == Rational then - return n.numerator // n.denominator - end - return Integer(n) -end - -function QQ(n) - if type(n) == "table" then - return n - end - - if type(n) == "number" then - n = tostring(n) - end - - if type(n) == "string" then - local parts = split(n, "%.") - if #parts == 1 then - return Integer(parts[1]) - else - return Integer(parts[1])..Integer(parts[2]) - end - end - - error("Could not convert to a rational number.") -end - ---- Parses raw input into Lua code and executes it. ---- @param input string -function CASparse(input) - - -- First, we replace any occurance of a number with an integer or rational version of itself. - local str = string.gsub(input, ".?[0-9]+", function (s) - -- Here, we are part of an identifier, so we don't replace anything - if string.match(string.sub(s, 1, 1), "[A-Z]") or string.match(string.sub(s, 1, 1), "[a-z]") or string.match(string.sub(s, 1, 1), "_") then - return - end - - if string.match(string.sub(s, 1, 1), "[0-9]") then - return "Integer('" .. s .. "')" - end - - return string.sub(s, 1, 1) .. "Integer('" .. string.sub(s, 2, #s) .. "')" - end) - - -------------------------- - -- HERE COMES THE JANK. -- - -------------------------- - - -- Replaces each instance of a decimal with .., so we can use integer metatables to convert it into a rational properly. - str = string.gsub(str, "Integer%('[0-9]+'%)%.Integer%('[0-9]+'%)", function (s) - local ints = split(s, "%.") - return ints[1] .. ".." .. ints[2] - end) - str = string.gsub(str, ".?%.Integer%('[0-9]+'%)", function (s) - if string.sub(s, 1, 2) == ".." then - return - end - return string.sub(s, 1, 1) .. "Integer('0')." .. string.sub(s, 2, #s) - end) - - local exe, err = load(str .. "\n return true") - if exe then - exe() - else - print(err) - end -end \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/luacas-partialfractions.lua b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-partialfractions.lua new file mode 100644 index 0000000000..280d4d56f3 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-partialfractions.lua @@ -0,0 +1,12 @@ +local g1 = parse("x^3+4*x^2-x-2"):topolynomial() +local f1 = parse("x^4-x^2"):topolynomial() + +local g2 = parse("2*x^6-4*x^5+5*x^4-3*x^3+x^2+3*x"):topolynomial() +local f2 = parse("x^7-3*x^6+5*x^5-7*x^4+7*x^3-5*x^2+3*x-1"):topolynomial() + +starttest("partial fraction decomposition") + +testeq(PolynomialRing.partialfractions(g1, f1):autosimplify(), parse("((2 * (x ^ -2)) + (x ^ -1) + ((-1 + x) ^ -1) + (-1 * ((1 + x) ^ -1)))")) +testeq(PolynomialRing.partialfractions(g2, f2):autosimplify(), parse("(((-1 + x) ^ -3) + ((-1 + x) ^ -1) + ((1 + (x ^ 2)) ^ -2) + ((1 + x) * ((1 + (x ^ 2)) ^ -1)))")) + +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomial.lua b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomial.lua new file mode 100644 index 0000000000..545538c764 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomial.lua @@ -0,0 +1,153 @@ + +local a = PolynomialRing({ + Integer(1), + Integer(2), + Integer(3), + Integer(4), + Integer(5) +}, "x") + +local b = PolynomialRing({ + Integer(1) / Integer(3), + Integer(1) / Integer(12), + Integer(6) / Integer(3), +}, "x") + +local c = PolynomialRing({ + Integer(12), + Integer(4) +}, "x") + +local h = PolynomialRing({Integer(2), Integer(3), Integer(1)}, "x") +local i = PolynomialRing({Integer(8), Integer(20), Integer(18), Integer(7), Integer(1)}, "x") +local j = PolynomialRing({Integer(108), Integer(324), Integer(387), Integer(238), Integer(80), Integer(14), Integer(1)}, "x") +local k = PolynomialRing({Integer(30), Integer(11), Integer(1)}, "x") +local l = PolynomialRing({Integer(6), Integer(11), Integer(6), Integer(1)}, "z") +local m = PolynomialRing({Integer(1), Integer(10), Integer(45), Integer(120), Integer(210), Integer(252), Integer(210), Integer(120), Integer(45), Integer(10), Integer(1)}, "x") +local n = PolynomialRing({Integer(24), Integer(50), Integer(35), Integer(10), Integer(1), Integer(0), Integer(24), Integer(50), Integer(35), Integer(10), Integer(1)}, "x") +local o = PolynomialRing({Integer(24), Integer(50), Integer(59), Integer(60), Integer(36), Integer(10), Integer(1)}, "x") +local p = PolynomialRing({Integer(-110592), Integer(59904), Integer(-5760), Integer(720), Integer(-48), Integer(1)}, "x") + +local d = PolynomialRing({Integer(21), Integer(10), Integer(1)}, "x") +local e = PolynomialRing({Integer(-6), Integer(1), Integer(1)}, "x") + +local f = PolynomialRing({Integer(-1), Integer(-2), Integer(15), Integer(36)}, "x") +local g = PolynomialRing({Integer(1), Integer(7), Integer(15), Integer(9)}, "x") + +local q = PolynomialRing({Integer(3), Integer(-9), Integer(27), Integer(-36), Integer(36)}, "z") +local r = PolynomialRing({Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(4)}, "x"); +local s = PolynomialRing({Integer(1), Integer(0), Integer(-4), Integer(0), Integer(1)}, "x") + +local x = Integer(3) +local y = Integer(-1) / Integer(6) + +local multia = PolynomialRing({Integer(4), + Integer(0), + PolynomialRing({Integer(0), Integer(0), Integer(-6)}, "y"), + PolynomialRing({Integer(1), Integer(3)}, "y")}, "x") +local multib = PolynomialRing({PolynomialRing({Integer(0), Integer(6)}, "y"), + Integer(0), + PolynomialRing({Integer(-4), Integer(12)}, "y")}, "x") + +starttest("polynomial construction") +testeq(a, "5x^4+4x^3+3x^2+2x^1+1x^0") +testeq(a.degree, 4) +testeq(b, "2x^2+1/12x^1+1/3x^0") +testeq(b.degree, 2) +testeq(multia, "(3y^1+1y^0)x^3+(-6y^2+0y^1+0y^0)x^2+(0)x^1+(4)x^0") +testeq(multia.degree, 3) +endtest() + +starttest("polynomial-expression conversion") +testeq(a:tocompoundexpression():autosimplify():topolynomial(), a) +testeq(b:tocompoundexpression():autosimplify():topolynomial(), b) +testeq(c:tocompoundexpression():autosimplify():topolynomial(), c) +endtest() + +starttest("polynomial arithmetic") +testeq(a + a, "10x^4+8x^3+6x^2+4x^1+2x^0") +testeq(a + b, "5x^4+4x^3+5x^2+25/12x^1+4/3x^0") +testeq(b + a, "5x^4+4x^3+5x^2+25/12x^1+4/3x^0") +testeq(a - a, "0x^0") +testeq(a - b, "5x^4+4x^3+1x^2+23/12x^1+2/3x^0") +testeq(b:multiplyDegree(4), "2x^6+1/12x^5+1/3x^4+0x^3+0x^2+0x^1+0x^0") +testeq(a:multiplyDegree(12), "5x^16+4x^15+3x^14+2x^13+1x^12+0x^11+0x^10+0x^9+0x^8+0x^7+0x^6+0x^5+0x^4+0x^3+0x^2+0x^1+0x^0") +testeq(c * c, "16x^2+96x^1+144x^0") +testeq(a * c, "20x^5+76x^4+60x^3+44x^2+28x^1+12x^0") +testeq(c * a, "20x^5+76x^4+60x^3+44x^2+28x^1+12x^0") +testeq(b * c, "8x^3+73/3x^2+7/3x^1+4x^0") +local qq, rr = a:divremainder(c) +testeq(qq, "5/4x^3+-11/4x^2+9x^1+-53/2x^0") +testeq(rr, "319x^0") +qq, rr = a:divremainder(b) +testeq(qq, "5/2x^2+91/48x^1+1157/1152x^0") +testeq(rr, "17755/13824x^1+2299/3456x^0") +endtest() + +starttest("polynomial pseudodivision") +local pq, pr = a:pseudodivide(c) +testeq(pq, "320x^3+-704x^2+2304x^1+-6784x^0") +testeq(pr, "81664x^0") + +pq, pr = multia:pseudodivide(multib) +testeq(pq, "(36y^2+0y^1+-4y^0)x^1+(-72y^3+24y^2+0y^1+0y^0)x^0") +testeq(pr, "(-216y^3+0y^2+24y^1+0y^0)x^1+(432y^4+-144y^3+576y^2+-384y^1+64y^0)x^0") +endtest() + + +starttest("combined polynomial/coefficient operations") +testeq(a + x, "5x^4+4x^3+3x^2+2x^1+4x^0") +testeq(x + a, "5x^4+4x^3+3x^2+2x^1+4x^0") +testeq(b - y, "2x^2+1/12x^1+1/2x^0") +testeq(a * x, "15x^4+12x^3+9x^2+6x^1+3x^0") +testeq(x * a, "15x^4+12x^3+9x^2+6x^1+3x^0") +endtest() + +starttest("polynomial formal derivatives") +testeq(a:derivative(), "20x^3+12x^2+6x^1+2x^0") +testeq(b:derivative(), "4x^1+1/12x^0") +testeq(c:derivative():derivative(), "0x^0") +endtest() + + +starttest("polynomial gcd...") +testeq(PolynomialRing.gcd(d, e), "1x^1+3x^0") +testeq(PolynomialRing.gcd(b, c), "1x^0") +testeq(PolynomialRing.gcd(f, g), "1x^2+2/3x^1+1/9x^0") +endtest() + +starttest("square-free factorization") +testeq(h:squarefreefactorization():autosimplify(), parse("(2 + (3 * x) + (x ^ 2))"), h) +testeq(i:squarefreefactorization():autosimplify(), parse("((1 + x) * ((2 + x) ^ 3))"), i) +testeq((Integer(2)*i):squarefreefactorization():autosimplify(), parse("(((2 + x) ^ 3) * (2 + (2 * x)))"), (Integer(2)*i), true) +testeq(j:squarefreefactorization():autosimplify(), parse("((1 + x) * ((2 + x) ^ 2) * ((3 + x) ^ 3))"), j) +testeq(o:squarefreefactorization():autosimplify(), parse("(24 + (50 * x) + (59 * (x ^ 2)) + (60 * (x ^ 3)) + (36 * (x ^ 4)) + (10 * (x ^ 5)) + (x ^ 6))"), o) +endtest() + +starttest("polynomial factorization") +testeq(c:factor(), BinaryOperation.MULEXP({Integer(4), BinaryOperation.POWEXP({PolynomialRing({Integer(3), Integer(1)}, "x"), Integer(1)})}), c) +testeq(h:factor():autosimplify(), parse("((1 + x) * (2 + x))"), h) +testeq(k:factor():autosimplify(), parse("((5 + x) * (6 + x))"), k) +testeq(j:factor():autosimplify(), parse("((1 + x) * ((2 + x) ^ 2) * ((3 + x) ^ 3))"), j) +testeq(p:factor():autosimplify(), parse("((-24 + x) * (96 + (x ^ 2)) * (48 + (-24 * x) + (x ^ 2)))"), p) +testeq(l:factor():autosimplify(), parse("((1 + z) * (2 + z) * (3 + z))"), l) +testeq(m:factor():autosimplify(), parse("((1 + x) ^ 10)"), m) +testeq(b:factor(), BinaryOperation.MULEXP({Integer(1)/Integer(12), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1), Integer(24)}, "x"), Integer(1)})}), b) +testeq(o:factor():autosimplify(), parse("((1 + x) * (2 + x) * (3 + x) * (4 + x) * (1 + (x ^ 2)))"), o) +testeq(n:factor():autosimplify(), parse("((1 + x) * (2 + x) * (3 + x) * (4 + x) * (1 + (x ^ 2)) * (1 + (-1 * (x ^ 2)) + (x ^ 4)))"), n) +endtest() + +starttest("polynomial decomposition") +testeq(c:decompose(), "{4x^1+12x^0}", c, true) +testeq(h:decompose(), "{1x^2+3x^1+2x^0}", h, true) +testeq(k:decompose(), "{1x^2+11x^1+30x^0}", k, true) +testeq(j:decompose(), "{1x^6+14x^5+80x^4+238x^3+387x^2+324x^1+108x^0}", j, true) +testeq(l:decompose(), "{1z^3+6z^2+11z^1+6z^0}", l, true) +testeq(m:decompose(), "{1x^5+5x^4+10x^3+10x^2+5x^1+1x^0, 1x^2+2x^1+0x^0}", m, true) +testeq(b:decompose(), "{2x^2+1/12x^1+1/3x^0}", b, true) +testeq(o:decompose(), "{1x^6+10x^5+36x^4+60x^3+59x^2+50x^1+24x^0}", o, true) +testeq(n:decompose(), "{1x^10+10x^9+35x^8+50x^7+24x^6+0x^5+1x^4+10x^3+35x^2+50x^1+24x^0}", n, true) +testeq(q:decompose(), "{36z^2+18z^1+3z^0, 1z^2+-1/2z^1+0z^0}", q, true) +testeq(r:decompose(),"{4x^3+0x^2+0x^1+0x^0, 1x^2+0x^1+0x^0}", r, true) +testeq(s:decompose(), "{1x^2+4x^1+1x^0, 1x^2+0x^1+-4x^0}", s, true) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomialmod.lua b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomialmod.lua new file mode 100644 index 0000000000..1906ad2d72 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-polynomialmod.lua @@ -0,0 +1,76 @@ +local a = PolynomialRing({IntegerModN(Integer(1), Integer(11)), + IntegerModN(Integer(6), Integer(11)), + IntegerModN(Integer(1), Integer(11)), + IntegerModN(Integer(9), Integer(11)), + IntegerModN(Integer(1), Integer(11))}, "y") + +local b = PolynomialRing({IntegerModN(Integer(7), Integer(11)), + IntegerModN(Integer(7), Integer(11)), + IntegerModN(Integer(6), Integer(11)), + IntegerModN(Integer(2), Integer(11)), + IntegerModN(Integer(1), Integer(11))}, "y") + +local q = PolynomialRing({IntegerModN(Integer(2), Integer(13)), + IntegerModN(Integer(6), Integer(13)), + IntegerModN(Integer(4), Integer(13))}, "z") + +local p = PolynomialRing({IntegerModN(Integer(4), Integer(13)), + IntegerModN(Integer(11), Integer(13)), + IntegerModN(Integer(1), Integer(13)), + IntegerModN(Integer(12), Integer(13)), + IntegerModN(Integer(1), Integer(13))}, "x") + +local r = PolynomialRing({IntegerModN(Integer(1), Integer(3)), + IntegerModN(Integer(0), Integer(3)), + IntegerModN(Integer(0), Integer(3)), + IntegerModN(Integer(2), Integer(3)), + IntegerModN(Integer(0), Integer(3)), + IntegerModN(Integer(0), Integer(3)), + IntegerModN(Integer(1), Integer(3))}, "x") + +local s = PolynomialRing({IntegerModN(Integer(1), Integer(5)), + IntegerModN(Integer(1), Integer(5)), + IntegerModN(Integer(1), Integer(5)), + IntegerModN(Integer(1), Integer(5)), + IntegerModN(Integer(1), Integer(5))}, "x") + +local t = PolynomialRing({IntegerModN(Integer(1), Integer(7))}, "x"):multiplyDegree(7) - PolynomialRing({IntegerModN(Integer(1), Integer(7))}, "x"):multiplyDegree(1) + +local u = PolynomialRing({IntegerModN(Integer(1), Integer(13)), + IntegerModN(Integer(5), Integer(13)), + IntegerModN(Integer(6), Integer(13)), + IntegerModN(Integer(5), Integer(13)), + IntegerModN(Integer(1), Integer(13))}, "x") + +local v = PolynomialRing({IntegerModN(Integer(24), Integer(7)), + IntegerModN(Integer(50), Integer(7)), + IntegerModN(Integer(59), Integer(7)), + IntegerModN(Integer(60), Integer(7)), + IntegerModN(Integer(36), Integer(7)), + IntegerModN(Integer(10), Integer(7)), + IntegerModN(Integer(1), Integer(7))}, "z") + +starttest("modular polynomial operations") +testeq(q*q, "3z^4+9z^3+0z^2+11z^1+4z^0") +testeq(PolynomialRing.gcd(a, b), "1y^0") +local Q, R, S = PolynomialRing.extendedgcd(a, b) +testeq(Q, "1y^0") +testeq(R, "4y^3+5y^2+1y^1+3y^0") +testeq(S, "7y^3+0y^2+7y^1+6y^0") +endtest() + +starttest("modular square free factoring") +testeq(p:squarefreefactorization(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(2), Integer(6), Integer(1)}, SymbolExpression("x"))), Integer(2)})) +testeq(r:squarefreefactorization(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(1), Integer(0), Integer(0), Integer(1)}, SymbolExpression("x"))), Integer(2)})) +testeq(q:squarefreefactorization(), Integer(4) * BinaryOperation.POWEXP({(PolynomialRing({Integer(7), Integer(8), Integer(1)}, SymbolExpression("z"))), Integer(1)})) +testeq(s:squarefreefactorization(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x"))), Integer(4)})) +endtest() + +starttest("modular polynomial factoring") +testeq(q:factor(), BinaryOperation.MULEXP({Integer(4), BinaryOperation.POWEXP({PolynomialRing({Integer(7), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(1)}, SymbolExpression("z")), Integer(1)})}), q) +testeq(p:factor(), Integer(1) * BinaryOperation.POWEXP({PolynomialRing({Integer(2), Integer(6), Integer(1)}, SymbolExpression("x")), Integer(2)}), p) +testeq(r:factor(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(1), Integer(0), Integer(0), Integer(1)}, SymbolExpression("x"))), Integer(2)}), r) +testeq(t:factor(), BinaryOperation.MULEXP({Integer(1), BinaryOperation.POWEXP({PolynomialRing({Integer(0), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(6), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(5), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(3), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(2), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(1)}, SymbolExpression("x")), Integer(1)})}), t) +testeq(u:factor(), BinaryOperation.MULEXP({Integer(1), BinaryOperation.POWEXP({PolynomialRing({Integer(11), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(10), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(6), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x")), Integer(1)})}), u) +testeq(v:factor(), BinaryOperation.MULEXP({Integer(1), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(2), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(0), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(3), Integer(1)}, SymbolExpression("z")), Integer(1)})}), v) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/luacas-roots.lua b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-roots.lua new file mode 100644 index 0000000000..01975ab1ec --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/luacas-roots.lua @@ -0,0 +1,43 @@ +local a = PolynomialRing({Integer(1), Integer(2), Integer(3), Integer(4), Integer(5)}, "x") +local b = PolynomialRing({Integer(1) / Integer(3), Integer(1) / Integer(12), Integer(6) / Integer(3)}, "x") +local c = PolynomialRing({Integer(12), Integer(4)}, "x") +local d = PolynomialRing({Integer(21), Integer(10), Integer(1)}, "x") +local e = PolynomialRing({Integer(-6), Integer(1), Integer(1)}, "x") +local f = PolynomialRing({Integer(-1), Integer(-2), Integer(15), Integer(36)}, "x") +local g = PolynomialRing({Integer(1), Integer(7), Integer(15), Integer(9)}, "x") +local h = PolynomialRing({Integer(2), Integer(3), Integer(1)}, "x") +local i = PolynomialRing({Integer(8), Integer(20), Integer(18), Integer(7), Integer(1)}, "x") +local j = PolynomialRing({Integer(108), Integer(324), Integer(387), Integer(238), Integer(80), Integer(14), Integer(1)}, "x") +local k = PolynomialRing({Integer(30), Integer(11), Integer(1)}, "x") +local l = PolynomialRing({Integer(6), Integer(11), Integer(6), Integer(1)}, "z") +local m = PolynomialRing({Integer(1), Integer(10), Integer(45), Integer(120), Integer(210), Integer(252), Integer(210), Integer(120), Integer(45), Integer(10), Integer(1)}, "x") +local n = PolynomialRing({Integer(24), Integer(50), Integer(35), Integer(10), Integer(1), Integer(0), Integer(24), Integer(50), Integer(35), Integer(10), Integer(1)}, "x") +local o = PolynomialRing({Integer(24), Integer(50), Integer(59), Integer(60), Integer(36), Integer(10), Integer(1)}, "x") +local p = PolynomialRing({Integer(-110592), Integer(59904), Integer(-5760), Integer(720), Integer(-48), Integer(1)}, "x") +local q = PolynomialRing({Integer(3), Integer(-9), Integer(27), Integer(-36), Integer(36)}, "z") +local r = PolynomialRing({Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(4)}, "x") +local s = PolynomialRing({Integer(1), Integer(0), Integer(-4), Integer(0), Integer(1)}, "x") +local t = PolynomialRing({Integer(1), Integer(-1), Integer(1), Integer(1)}, "t") + +starttest("polynomial root-finding") +testeq(a:roots(), "{Root Of: (5x^4+4x^3+3x^2+2x^1+1x^0)}", a, true) +testeq(b:roots(), "{-1/48 + (-1/48 * (383 ^ (1/2)) * i), -1/48 + (1/48 * (383 ^ (1/2)) * i)}", b, true) +testeq(c:roots(), "{-3}", c, true) +testeq(d:roots(), "{-7, -3}", d, true) +testeq(e:roots(), "{-3, 2}", e, true) +testeq(f:roots(), "{-1/3, 1/4}", f, true) +testeq(g:roots(), "{-1, -1/3}", g, true) +testeq(h:roots(), "{-2, -1}", h, true) +testeq(i:roots(), "{-2, -1}", i, true) +testeq(j:roots(), "{-3, -2, -1}", j, true) +testeq(k:roots(), "{-6, -5}", k, true) +testeq(l:roots(), "{-3, -2, -1}", l, true) +testeq(m:roots(), "{-1}", m, true) +testeq(n:roots(), "{-4, -3, -2, -1, i, -1 * i, -1/2 * ((2 + (-2 * (3 ^ (1/2)) * i)) ^ (1/2)), 1/2 * ((2 + (-2 * (3 ^ (1/2)) * i)) ^ (1/2)), -1/2 * ((2 + (2 * (3 ^ (1/2)) * i)) ^ (1/2)), 1/2 * ((2 + (2 * (3 ^ (1/2)) * i)) ^ (1/2))}", n, true) +testeq(o:roots(), "{-4, -3, -2, -1, i, -1 * i}", o, true) +testeq(p:roots(), "{24, 12 + (-4 * (6 ^ (1/2))), 12 + (4 * (6 ^ (1/2))), -4 * (6 ^ (1/2)) * i, 4 * (6 ^ (1/2)) * i}", p, true) +testeq(q:roots(), "{1/4 + (-1/2 * ((-3/4 + (-1/3 * (3 ^ (1/2)) * i)) ^ (1/2))), 1/4 + (1/2 * ((-3/4 + (-1/3 * (3 ^ (1/2)) * i)) ^ (1/2))), 1/4 + (-1/2 * ((-3/4 + (1/3 * (3 ^ (1/2)) * i)) ^ (1/2))), 1/4 + (1/2 * ((-3/4 + (1/3 * (3 ^ (1/2)) * i)) ^ (1/2)))}", q, true) +testeq(r:roots(), "{0}", r, true) +testeq(s:roots(), "{-1/2 * ((8 + (-4 * (3 ^ (1/2)))) ^ (1/2)), 1/2 * ((8 + (-4 * (3 ^ (1/2)))) ^ (1/2)), -1/2 * ((8 + (4 * (3 ^ (1/2)))) ^ (1/2)), 1/2 * ((8 + (4 * (3 ^ (1/2)))) ^ (1/2))}", s, true) +testeq(t:roots(), "{-1/3 + (-4/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (-1/3))) + (-1/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (1/3))), -1/3 + (-4/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (-1/3)) * ((-1/2 + (1/2 * (3 ^ (1/2)) * i)) ^ -1)) + (-1/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (1/3)) * (-1/2 + (1/2 * (3 ^ (1/2)) * i))), -1/3 + (-4/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (-1/3)) * ((-1/2 + (1/2 * (3 ^ (1/2)) * i)) ^ -2)) + (-1/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (1/3)) * ((-1/2 + (1/2 * (3 ^ (1/2)) * i)) ^ 2))}", t, true) +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/partialfractions.lua b/macros/luatex/latex/luacas/tex/test/polynomials/partialfractions.lua deleted file mode 100644 index 280d4d56f3..0000000000 --- a/macros/luatex/latex/luacas/tex/test/polynomials/partialfractions.lua +++ /dev/null @@ -1,12 +0,0 @@ -local g1 = parse("x^3+4*x^2-x-2"):topolynomial() -local f1 = parse("x^4-x^2"):topolynomial() - -local g2 = parse("2*x^6-4*x^5+5*x^4-3*x^3+x^2+3*x"):topolynomial() -local f2 = parse("x^7-3*x^6+5*x^5-7*x^4+7*x^3-5*x^2+3*x-1"):topolynomial() - -starttest("partial fraction decomposition") - -testeq(PolynomialRing.partialfractions(g1, f1):autosimplify(), parse("((2 * (x ^ -2)) + (x ^ -1) + ((-1 + x) ^ -1) + (-1 * ((1 + x) ^ -1)))")) -testeq(PolynomialRing.partialfractions(g2, f2):autosimplify(), parse("(((-1 + x) ^ -3) + ((-1 + x) ^ -1) + ((1 + (x ^ 2)) ^ -2) + ((1 + x) * ((1 + (x ^ 2)) ^ -1)))")) - -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/polynomial.lua b/macros/luatex/latex/luacas/tex/test/polynomials/polynomial.lua deleted file mode 100644 index 545538c764..0000000000 --- a/macros/luatex/latex/luacas/tex/test/polynomials/polynomial.lua +++ /dev/null @@ -1,153 +0,0 @@ - -local a = PolynomialRing({ - Integer(1), - Integer(2), - Integer(3), - Integer(4), - Integer(5) -}, "x") - -local b = PolynomialRing({ - Integer(1) / Integer(3), - Integer(1) / Integer(12), - Integer(6) / Integer(3), -}, "x") - -local c = PolynomialRing({ - Integer(12), - Integer(4) -}, "x") - -local h = PolynomialRing({Integer(2), Integer(3), Integer(1)}, "x") -local i = PolynomialRing({Integer(8), Integer(20), Integer(18), Integer(7), Integer(1)}, "x") -local j = PolynomialRing({Integer(108), Integer(324), Integer(387), Integer(238), Integer(80), Integer(14), Integer(1)}, "x") -local k = PolynomialRing({Integer(30), Integer(11), Integer(1)}, "x") -local l = PolynomialRing({Integer(6), Integer(11), Integer(6), Integer(1)}, "z") -local m = PolynomialRing({Integer(1), Integer(10), Integer(45), Integer(120), Integer(210), Integer(252), Integer(210), Integer(120), Integer(45), Integer(10), Integer(1)}, "x") -local n = PolynomialRing({Integer(24), Integer(50), Integer(35), Integer(10), Integer(1), Integer(0), Integer(24), Integer(50), Integer(35), Integer(10), Integer(1)}, "x") -local o = PolynomialRing({Integer(24), Integer(50), Integer(59), Integer(60), Integer(36), Integer(10), Integer(1)}, "x") -local p = PolynomialRing({Integer(-110592), Integer(59904), Integer(-5760), Integer(720), Integer(-48), Integer(1)}, "x") - -local d = PolynomialRing({Integer(21), Integer(10), Integer(1)}, "x") -local e = PolynomialRing({Integer(-6), Integer(1), Integer(1)}, "x") - -local f = PolynomialRing({Integer(-1), Integer(-2), Integer(15), Integer(36)}, "x") -local g = PolynomialRing({Integer(1), Integer(7), Integer(15), Integer(9)}, "x") - -local q = PolynomialRing({Integer(3), Integer(-9), Integer(27), Integer(-36), Integer(36)}, "z") -local r = PolynomialRing({Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(4)}, "x"); -local s = PolynomialRing({Integer(1), Integer(0), Integer(-4), Integer(0), Integer(1)}, "x") - -local x = Integer(3) -local y = Integer(-1) / Integer(6) - -local multia = PolynomialRing({Integer(4), - Integer(0), - PolynomialRing({Integer(0), Integer(0), Integer(-6)}, "y"), - PolynomialRing({Integer(1), Integer(3)}, "y")}, "x") -local multib = PolynomialRing({PolynomialRing({Integer(0), Integer(6)}, "y"), - Integer(0), - PolynomialRing({Integer(-4), Integer(12)}, "y")}, "x") - -starttest("polynomial construction") -testeq(a, "5x^4+4x^3+3x^2+2x^1+1x^0") -testeq(a.degree, 4) -testeq(b, "2x^2+1/12x^1+1/3x^0") -testeq(b.degree, 2) -testeq(multia, "(3y^1+1y^0)x^3+(-6y^2+0y^1+0y^0)x^2+(0)x^1+(4)x^0") -testeq(multia.degree, 3) -endtest() - -starttest("polynomial-expression conversion") -testeq(a:tocompoundexpression():autosimplify():topolynomial(), a) -testeq(b:tocompoundexpression():autosimplify():topolynomial(), b) -testeq(c:tocompoundexpression():autosimplify():topolynomial(), c) -endtest() - -starttest("polynomial arithmetic") -testeq(a + a, "10x^4+8x^3+6x^2+4x^1+2x^0") -testeq(a + b, "5x^4+4x^3+5x^2+25/12x^1+4/3x^0") -testeq(b + a, "5x^4+4x^3+5x^2+25/12x^1+4/3x^0") -testeq(a - a, "0x^0") -testeq(a - b, "5x^4+4x^3+1x^2+23/12x^1+2/3x^0") -testeq(b:multiplyDegree(4), "2x^6+1/12x^5+1/3x^4+0x^3+0x^2+0x^1+0x^0") -testeq(a:multiplyDegree(12), "5x^16+4x^15+3x^14+2x^13+1x^12+0x^11+0x^10+0x^9+0x^8+0x^7+0x^6+0x^5+0x^4+0x^3+0x^2+0x^1+0x^0") -testeq(c * c, "16x^2+96x^1+144x^0") -testeq(a * c, "20x^5+76x^4+60x^3+44x^2+28x^1+12x^0") -testeq(c * a, "20x^5+76x^4+60x^3+44x^2+28x^1+12x^0") -testeq(b * c, "8x^3+73/3x^2+7/3x^1+4x^0") -local qq, rr = a:divremainder(c) -testeq(qq, "5/4x^3+-11/4x^2+9x^1+-53/2x^0") -testeq(rr, "319x^0") -qq, rr = a:divremainder(b) -testeq(qq, "5/2x^2+91/48x^1+1157/1152x^0") -testeq(rr, "17755/13824x^1+2299/3456x^0") -endtest() - -starttest("polynomial pseudodivision") -local pq, pr = a:pseudodivide(c) -testeq(pq, "320x^3+-704x^2+2304x^1+-6784x^0") -testeq(pr, "81664x^0") - -pq, pr = multia:pseudodivide(multib) -testeq(pq, "(36y^2+0y^1+-4y^0)x^1+(-72y^3+24y^2+0y^1+0y^0)x^0") -testeq(pr, "(-216y^3+0y^2+24y^1+0y^0)x^1+(432y^4+-144y^3+576y^2+-384y^1+64y^0)x^0") -endtest() - - -starttest("combined polynomial/coefficient operations") -testeq(a + x, "5x^4+4x^3+3x^2+2x^1+4x^0") -testeq(x + a, "5x^4+4x^3+3x^2+2x^1+4x^0") -testeq(b - y, "2x^2+1/12x^1+1/2x^0") -testeq(a * x, "15x^4+12x^3+9x^2+6x^1+3x^0") -testeq(x * a, "15x^4+12x^3+9x^2+6x^1+3x^0") -endtest() - -starttest("polynomial formal derivatives") -testeq(a:derivative(), "20x^3+12x^2+6x^1+2x^0") -testeq(b:derivative(), "4x^1+1/12x^0") -testeq(c:derivative():derivative(), "0x^0") -endtest() - - -starttest("polynomial gcd...") -testeq(PolynomialRing.gcd(d, e), "1x^1+3x^0") -testeq(PolynomialRing.gcd(b, c), "1x^0") -testeq(PolynomialRing.gcd(f, g), "1x^2+2/3x^1+1/9x^0") -endtest() - -starttest("square-free factorization") -testeq(h:squarefreefactorization():autosimplify(), parse("(2 + (3 * x) + (x ^ 2))"), h) -testeq(i:squarefreefactorization():autosimplify(), parse("((1 + x) * ((2 + x) ^ 3))"), i) -testeq((Integer(2)*i):squarefreefactorization():autosimplify(), parse("(((2 + x) ^ 3) * (2 + (2 * x)))"), (Integer(2)*i), true) -testeq(j:squarefreefactorization():autosimplify(), parse("((1 + x) * ((2 + x) ^ 2) * ((3 + x) ^ 3))"), j) -testeq(o:squarefreefactorization():autosimplify(), parse("(24 + (50 * x) + (59 * (x ^ 2)) + (60 * (x ^ 3)) + (36 * (x ^ 4)) + (10 * (x ^ 5)) + (x ^ 6))"), o) -endtest() - -starttest("polynomial factorization") -testeq(c:factor(), BinaryOperation.MULEXP({Integer(4), BinaryOperation.POWEXP({PolynomialRing({Integer(3), Integer(1)}, "x"), Integer(1)})}), c) -testeq(h:factor():autosimplify(), parse("((1 + x) * (2 + x))"), h) -testeq(k:factor():autosimplify(), parse("((5 + x) * (6 + x))"), k) -testeq(j:factor():autosimplify(), parse("((1 + x) * ((2 + x) ^ 2) * ((3 + x) ^ 3))"), j) -testeq(p:factor():autosimplify(), parse("((-24 + x) * (96 + (x ^ 2)) * (48 + (-24 * x) + (x ^ 2)))"), p) -testeq(l:factor():autosimplify(), parse("((1 + z) * (2 + z) * (3 + z))"), l) -testeq(m:factor():autosimplify(), parse("((1 + x) ^ 10)"), m) -testeq(b:factor(), BinaryOperation.MULEXP({Integer(1)/Integer(12), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1), Integer(24)}, "x"), Integer(1)})}), b) -testeq(o:factor():autosimplify(), parse("((1 + x) * (2 + x) * (3 + x) * (4 + x) * (1 + (x ^ 2)))"), o) -testeq(n:factor():autosimplify(), parse("((1 + x) * (2 + x) * (3 + x) * (4 + x) * (1 + (x ^ 2)) * (1 + (-1 * (x ^ 2)) + (x ^ 4)))"), n) -endtest() - -starttest("polynomial decomposition") -testeq(c:decompose(), "{4x^1+12x^0}", c, true) -testeq(h:decompose(), "{1x^2+3x^1+2x^0}", h, true) -testeq(k:decompose(), "{1x^2+11x^1+30x^0}", k, true) -testeq(j:decompose(), "{1x^6+14x^5+80x^4+238x^3+387x^2+324x^1+108x^0}", j, true) -testeq(l:decompose(), "{1z^3+6z^2+11z^1+6z^0}", l, true) -testeq(m:decompose(), "{1x^5+5x^4+10x^3+10x^2+5x^1+1x^0, 1x^2+2x^1+0x^0}", m, true) -testeq(b:decompose(), "{2x^2+1/12x^1+1/3x^0}", b, true) -testeq(o:decompose(), "{1x^6+10x^5+36x^4+60x^3+59x^2+50x^1+24x^0}", o, true) -testeq(n:decompose(), "{1x^10+10x^9+35x^8+50x^7+24x^6+0x^5+1x^4+10x^3+35x^2+50x^1+24x^0}", n, true) -testeq(q:decompose(), "{36z^2+18z^1+3z^0, 1z^2+-1/2z^1+0z^0}", q, true) -testeq(r:decompose(),"{4x^3+0x^2+0x^1+0x^0, 1x^2+0x^1+0x^0}", r, true) -testeq(s:decompose(), "{1x^2+4x^1+1x^0, 1x^2+0x^1+-4x^0}", s, true) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/polynomialmod.lua b/macros/luatex/latex/luacas/tex/test/polynomials/polynomialmod.lua deleted file mode 100644 index 1906ad2d72..0000000000 --- a/macros/luatex/latex/luacas/tex/test/polynomials/polynomialmod.lua +++ /dev/null @@ -1,76 +0,0 @@ -local a = PolynomialRing({IntegerModN(Integer(1), Integer(11)), - IntegerModN(Integer(6), Integer(11)), - IntegerModN(Integer(1), Integer(11)), - IntegerModN(Integer(9), Integer(11)), - IntegerModN(Integer(1), Integer(11))}, "y") - -local b = PolynomialRing({IntegerModN(Integer(7), Integer(11)), - IntegerModN(Integer(7), Integer(11)), - IntegerModN(Integer(6), Integer(11)), - IntegerModN(Integer(2), Integer(11)), - IntegerModN(Integer(1), Integer(11))}, "y") - -local q = PolynomialRing({IntegerModN(Integer(2), Integer(13)), - IntegerModN(Integer(6), Integer(13)), - IntegerModN(Integer(4), Integer(13))}, "z") - -local p = PolynomialRing({IntegerModN(Integer(4), Integer(13)), - IntegerModN(Integer(11), Integer(13)), - IntegerModN(Integer(1), Integer(13)), - IntegerModN(Integer(12), Integer(13)), - IntegerModN(Integer(1), Integer(13))}, "x") - -local r = PolynomialRing({IntegerModN(Integer(1), Integer(3)), - IntegerModN(Integer(0), Integer(3)), - IntegerModN(Integer(0), Integer(3)), - IntegerModN(Integer(2), Integer(3)), - IntegerModN(Integer(0), Integer(3)), - IntegerModN(Integer(0), Integer(3)), - IntegerModN(Integer(1), Integer(3))}, "x") - -local s = PolynomialRing({IntegerModN(Integer(1), Integer(5)), - IntegerModN(Integer(1), Integer(5)), - IntegerModN(Integer(1), Integer(5)), - IntegerModN(Integer(1), Integer(5)), - IntegerModN(Integer(1), Integer(5))}, "x") - -local t = PolynomialRing({IntegerModN(Integer(1), Integer(7))}, "x"):multiplyDegree(7) - PolynomialRing({IntegerModN(Integer(1), Integer(7))}, "x"):multiplyDegree(1) - -local u = PolynomialRing({IntegerModN(Integer(1), Integer(13)), - IntegerModN(Integer(5), Integer(13)), - IntegerModN(Integer(6), Integer(13)), - IntegerModN(Integer(5), Integer(13)), - IntegerModN(Integer(1), Integer(13))}, "x") - -local v = PolynomialRing({IntegerModN(Integer(24), Integer(7)), - IntegerModN(Integer(50), Integer(7)), - IntegerModN(Integer(59), Integer(7)), - IntegerModN(Integer(60), Integer(7)), - IntegerModN(Integer(36), Integer(7)), - IntegerModN(Integer(10), Integer(7)), - IntegerModN(Integer(1), Integer(7))}, "z") - -starttest("modular polynomial operations") -testeq(q*q, "3z^4+9z^3+0z^2+11z^1+4z^0") -testeq(PolynomialRing.gcd(a, b), "1y^0") -local Q, R, S = PolynomialRing.extendedgcd(a, b) -testeq(Q, "1y^0") -testeq(R, "4y^3+5y^2+1y^1+3y^0") -testeq(S, "7y^3+0y^2+7y^1+6y^0") -endtest() - -starttest("modular square free factoring") -testeq(p:squarefreefactorization(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(2), Integer(6), Integer(1)}, SymbolExpression("x"))), Integer(2)})) -testeq(r:squarefreefactorization(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(1), Integer(0), Integer(0), Integer(1)}, SymbolExpression("x"))), Integer(2)})) -testeq(q:squarefreefactorization(), Integer(4) * BinaryOperation.POWEXP({(PolynomialRing({Integer(7), Integer(8), Integer(1)}, SymbolExpression("z"))), Integer(1)})) -testeq(s:squarefreefactorization(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x"))), Integer(4)})) -endtest() - -starttest("modular polynomial factoring") -testeq(q:factor(), BinaryOperation.MULEXP({Integer(4), BinaryOperation.POWEXP({PolynomialRing({Integer(7), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(1)}, SymbolExpression("z")), Integer(1)})}), q) -testeq(p:factor(), Integer(1) * BinaryOperation.POWEXP({PolynomialRing({Integer(2), Integer(6), Integer(1)}, SymbolExpression("x")), Integer(2)}), p) -testeq(r:factor(), Integer(1) * BinaryOperation.POWEXP({(PolynomialRing({Integer(1), Integer(0), Integer(0), Integer(1)}, SymbolExpression("x"))), Integer(2)}), r) -testeq(t:factor(), BinaryOperation.MULEXP({Integer(1), BinaryOperation.POWEXP({PolynomialRing({Integer(0), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(6), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(5), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(3), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(2), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(1)}, SymbolExpression("x")), Integer(1)})}), t) -testeq(u:factor(), BinaryOperation.MULEXP({Integer(1), BinaryOperation.POWEXP({PolynomialRing({Integer(11), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(10), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(6), Integer(1)}, SymbolExpression("x")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x")), Integer(1)})}), u) -testeq(v:factor(), BinaryOperation.MULEXP({Integer(1), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(2), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(1), Integer(0), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("z")), Integer(1)}), BinaryOperation.POWEXP({PolynomialRing({Integer(3), Integer(1)}, SymbolExpression("z")), Integer(1)})}), v) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/polynomials/roots.lua b/macros/luatex/latex/luacas/tex/test/polynomials/roots.lua deleted file mode 100644 index 01975ab1ec..0000000000 --- a/macros/luatex/latex/luacas/tex/test/polynomials/roots.lua +++ /dev/null @@ -1,43 +0,0 @@ -local a = PolynomialRing({Integer(1), Integer(2), Integer(3), Integer(4), Integer(5)}, "x") -local b = PolynomialRing({Integer(1) / Integer(3), Integer(1) / Integer(12), Integer(6) / Integer(3)}, "x") -local c = PolynomialRing({Integer(12), Integer(4)}, "x") -local d = PolynomialRing({Integer(21), Integer(10), Integer(1)}, "x") -local e = PolynomialRing({Integer(-6), Integer(1), Integer(1)}, "x") -local f = PolynomialRing({Integer(-1), Integer(-2), Integer(15), Integer(36)}, "x") -local g = PolynomialRing({Integer(1), Integer(7), Integer(15), Integer(9)}, "x") -local h = PolynomialRing({Integer(2), Integer(3), Integer(1)}, "x") -local i = PolynomialRing({Integer(8), Integer(20), Integer(18), Integer(7), Integer(1)}, "x") -local j = PolynomialRing({Integer(108), Integer(324), Integer(387), Integer(238), Integer(80), Integer(14), Integer(1)}, "x") -local k = PolynomialRing({Integer(30), Integer(11), Integer(1)}, "x") -local l = PolynomialRing({Integer(6), Integer(11), Integer(6), Integer(1)}, "z") -local m = PolynomialRing({Integer(1), Integer(10), Integer(45), Integer(120), Integer(210), Integer(252), Integer(210), Integer(120), Integer(45), Integer(10), Integer(1)}, "x") -local n = PolynomialRing({Integer(24), Integer(50), Integer(35), Integer(10), Integer(1), Integer(0), Integer(24), Integer(50), Integer(35), Integer(10), Integer(1)}, "x") -local o = PolynomialRing({Integer(24), Integer(50), Integer(59), Integer(60), Integer(36), Integer(10), Integer(1)}, "x") -local p = PolynomialRing({Integer(-110592), Integer(59904), Integer(-5760), Integer(720), Integer(-48), Integer(1)}, "x") -local q = PolynomialRing({Integer(3), Integer(-9), Integer(27), Integer(-36), Integer(36)}, "z") -local r = PolynomialRing({Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(0), Integer(4)}, "x") -local s = PolynomialRing({Integer(1), Integer(0), Integer(-4), Integer(0), Integer(1)}, "x") -local t = PolynomialRing({Integer(1), Integer(-1), Integer(1), Integer(1)}, "t") - -starttest("polynomial root-finding") -testeq(a:roots(), "{Root Of: (5x^4+4x^3+3x^2+2x^1+1x^0)}", a, true) -testeq(b:roots(), "{-1/48 + (-1/48 * (383 ^ (1/2)) * i), -1/48 + (1/48 * (383 ^ (1/2)) * i)}", b, true) -testeq(c:roots(), "{-3}", c, true) -testeq(d:roots(), "{-7, -3}", d, true) -testeq(e:roots(), "{-3, 2}", e, true) -testeq(f:roots(), "{-1/3, 1/4}", f, true) -testeq(g:roots(), "{-1, -1/3}", g, true) -testeq(h:roots(), "{-2, -1}", h, true) -testeq(i:roots(), "{-2, -1}", i, true) -testeq(j:roots(), "{-3, -2, -1}", j, true) -testeq(k:roots(), "{-6, -5}", k, true) -testeq(l:roots(), "{-3, -2, -1}", l, true) -testeq(m:roots(), "{-1}", m, true) -testeq(n:roots(), "{-4, -3, -2, -1, i, -1 * i, -1/2 * ((2 + (-2 * (3 ^ (1/2)) * i)) ^ (1/2)), 1/2 * ((2 + (-2 * (3 ^ (1/2)) * i)) ^ (1/2)), -1/2 * ((2 + (2 * (3 ^ (1/2)) * i)) ^ (1/2)), 1/2 * ((2 + (2 * (3 ^ (1/2)) * i)) ^ (1/2))}", n, true) -testeq(o:roots(), "{-4, -3, -2, -1, i, -1 * i}", o, true) -testeq(p:roots(), "{24, 12 + (-4 * (6 ^ (1/2))), 12 + (4 * (6 ^ (1/2))), -4 * (6 ^ (1/2)) * i, 4 * (6 ^ (1/2)) * i}", p, true) -testeq(q:roots(), "{1/4 + (-1/2 * ((-3/4 + (-1/3 * (3 ^ (1/2)) * i)) ^ (1/2))), 1/4 + (1/2 * ((-3/4 + (-1/3 * (3 ^ (1/2)) * i)) ^ (1/2))), 1/4 + (-1/2 * ((-3/4 + (1/3 * (3 ^ (1/2)) * i)) ^ (1/2))), 1/4 + (1/2 * ((-3/4 + (1/3 * (3 ^ (1/2)) * i)) ^ (1/2)))}", q, true) -testeq(r:roots(), "{0}", r, true) -testeq(s:roots(), "{-1/2 * ((8 + (-4 * (3 ^ (1/2)))) ^ (1/2)), 1/2 * ((8 + (-4 * (3 ^ (1/2)))) ^ (1/2)), -1/2 * ((8 + (4 * (3 ^ (1/2)))) ^ (1/2)), 1/2 * ((8 + (4 * (3 ^ (1/2)))) ^ (1/2))}", s, true) -testeq(t:roots(), "{-1/3 + (-4/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (-1/3))) + (-1/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (1/3))), -1/3 + (-4/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (-1/3)) * ((-1/2 + (1/2 * (3 ^ (1/2)) * i)) ^ -1)) + (-1/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (1/3)) * (-1/2 + (1/2 * (3 ^ (1/2)) * i))), -1/3 + (-4/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (-1/3)) * ((-1/2 + (1/2 * (3 ^ (1/2)) * i)) ^ -2)) + (-1/3 * ((19 + (3 * (33 ^ (1/2)))) ^ (1/3)) * ((-1/2 + (1/2 * (3 ^ (1/2)) * i)) ^ 2))}", t, true) -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/rings/conversion.lua b/macros/luatex/latex/luacas/tex/test/rings/conversion.lua deleted file mode 100644 index b9eb523c7a..0000000000 --- a/macros/luatex/latex/luacas/tex/test/rings/conversion.lua +++ /dev/null @@ -1,273 +0,0 @@ -local a = Integer(12) -local b = Integer(3) / Integer(2) -local c = IntegerModN(Integer(4), Integer(7)) -local d = IntegerModN(Integer(8), Integer(14)) -local e = PolynomialRing({Integer(6), Integer(0), Integer(3)}, SymbolExpression("x")) -local f = PolynomialRing({Integer(4)/Integer(5), Integer(12)}, SymbolExpression("x")) -local g = PolynomialRing({ - PolynomialRing({ - PolynomialRing({Integer(-4), Integer(12), Integer(1)}, SymbolExpression("x")), - PolynomialRing({Integer(0)}, SymbolExpression("x")), - PolynomialRing({Integer(8)}, SymbolExpression("x")) - }, - SymbolExpression("y")), - PolynomialRing({ - PolynomialRing({Integer(8), Integer(7), Integer(6), Integer(5)}, SymbolExpression("x")), - PolynomialRing({Integer(-1)}, SymbolExpression("x")), - }, - SymbolExpression("y")), - PolynomialRing({ - PolynomialRing({Integer(0)}, SymbolExpression("x")), - PolynomialRing({Integer(2), Integer(8), Integer(1)}, SymbolExpression("x")), - PolynomialRing({Integer(-16), Integer(4)}, SymbolExpression("x")), - PolynomialRing({Integer(1)}, SymbolExpression("x")) - }, - SymbolExpression("y")), - PolynomialRing({ - PolynomialRing({Integer(1)}, SymbolExpression("x")) - }, - SymbolExpression("y")) - }, SymbolExpression("z")) -local h = PolynomialRing({ - PolynomialRing({ - PolynomialRing({Integer(-4), Integer(4)/Integer(5), Integer(1)}, SymbolExpression("z")), - PolynomialRing({Integer(0)}, SymbolExpression("z")), - PolynomialRing({Integer(8)}, SymbolExpression("z")) - }, - SymbolExpression("y")), - PolynomialRing({ - PolynomialRing({Integer(8), Integer(7), Integer(6), Integer(5)}, SymbolExpression("z")), - PolynomialRing({Integer(-1)}, SymbolExpression("z")), - }, - SymbolExpression("y")), - PolynomialRing({ - PolynomialRing({Integer(0)}, SymbolExpression("z")), - PolynomialRing({Integer(2), Integer(8), Integer(1)}, SymbolExpression("z")), - PolynomialRing({Integer(-16), Integer(1)/Integer(9)}, SymbolExpression("z")), - PolynomialRing({Integer(1)}, SymbolExpression("z")) - }, - SymbolExpression("y")), - PolynomialRing({ - PolynomialRing({Integer(1)}, SymbolExpression("z")) - }, - SymbolExpression("y")) - }, SymbolExpression("x")) -local i = Rational(PolynomialRing({-Integer(2), Integer(1)}, SymbolExpression("x")), - PolynomialRing({Integer(3), Integer(3), Integer(1)}, SymbolExpression("x"))) -local j = Rational(PolynomialRing({-Integer(2), Integer(1)}, SymbolExpression("x")), - PolynomialRing({Integer(3)/Integer(4), Integer(3)/Integer(8), Integer(1)}, SymbolExpression("x"))) -local k = PolynomialRing({IntegerModN(Integer(0), Integer(5)), - IntegerModN(Integer(1), Integer(5)), - IntegerModN(Integer(3), Integer(5)), - IntegerModN(Integer(1), Integer(5))}, SymbolExpression("x")) - -local l = PolynomialRing({Rational( - PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x")), - PolynomialRing({Integer(0), Integer(4)}, SymbolExpression("x")) - ), - Rational( - PolynomialRing({Integer(3)/Integer(2)}, SymbolExpression("x")), - PolynomialRing({Integer(6), Integer(1)/Integer(2), Integer(8), Integer(1)}, SymbolExpression("x")) - ), - Rational( - PolynomialRing({Integer(6), Integer(6), Integer(4)}, SymbolExpression("x")), - PolynomialRing({Integer(3), Integer(1)}, SymbolExpression("x")) - ), - Rational( - PolynomialRing({Integer(7)/Integer(6)}, SymbolExpression("x")), - PolynomialRing({Integer(2), Integer(1)}, SymbolExpression("x")) - ) - }, SymbolExpression("y")) - -local aring = a:getring() -- ZZ -local bring = b:getring() -- QQ -local cring = c:getring() -- ZZ_7 -local dring = d:getring() -- ZZ_14 -local ering = e:getring() -- ZZ[x] -local fring = f:getring() -- QQ[x] -local gring = g:getring() -- ZZ[x][y][z] -local hring = h:getring() -- QQ[z][y][x] -local iring = i:getring() -- ZZ(x) -local jring = j:getring() -- QQ(x) -local kring = k:getring() -- ZZ_5[x] -local lring = l:getring() -- QQ(x)[y] - -starttest("ring construction") -testeq(aring, "ZZ") -testeq(bring, "QQ") -testeq(cring, "Z/Z7") -testeq(dring, "Z/Z14") -testeq(ering, "ZZ[x]") -testeq(fring, "QQ[x]") -testeq(gring, "ZZ[x][y][z]") -testeq(hring, "QQ[z][y][x]") -testeq(iring, "ZZ(x)") -testeq(jring, "QQ(x)") -testeq(kring, "Z/Z5[x]") -testeq(lring, "QQ(x)[y]") -endtest() - -starttest("ring conversion") - --- Commented-out tests denote elements whos rings are not subrings of the ring that is being converted to - -testringconvert(a, aring, "12", "ZZ") -testringconvert(a, bring, "12/1", "QQ") -testringconvert(a, cring, "5", "Z/Z7") -testringconvert(a, dring, "12", "Z/Z14") -testringconvert(a, ering, "12x^0", "ZZ[x]") -testringconvert(a, fring, "12/1x^0", "QQ[x]") -testringconvert(a, gring, "((12x^0)y^0)z^0", "ZZ[x][y][z]") -testringconvert(a, hring, "((12/1z^0)y^0)x^0", "QQ[z][y][x]") -testringconvert(a, iring, "(12x^0)/(1x^0)", "ZZ(x)") -testringconvert(a, jring, "(12x^0)/(1x^0)", "ZZ(x)") -testringconvert(a, kring, "2x^0", "Z/Z5[x]") -testringconvert(a, lring, "((12x^0)/(1x^0))y^0", "ZZ(x)[y]") - --- testringconvert(b, aring, "3/2", "ZZ") -testringconvert(b, bring, "3/2", "QQ") --- testringconvert(b, cring, "3/2", "Z/Z7") --- testringconvert(b, dring, "3/2", "Z/Z14") --- testringconvert(b, ering, "3/2", "ZZ[x]") -testringconvert(b, fring, "3/2x^0", "QQ[x]") --- testringconvert(b, gring, "3/2", "ZZ[x][y][z]") -testringconvert(b, hring, "((3/2z^0)y^0)x^0", "QQ[z][y][x]") --- testringconvert(b, iring, "3/2", "ZZ(x)") -testringconvert(b, jring, "(3/2x^0)/(1x^0)", "QQ(x)") --- testringconvert(b, kring, "3/2", "Z/Z5[x]") -testringconvert(b, lring, "((3/2x^0)/(1x^0))y^0", "QQ(x)[y]") - -testringconvert(c, aring, "4", "ZZ") --- testringconvert(c, bring, "4", "QQ") -testringconvert(c, cring, "4", "Z/Z7") -testringconvert(c, dring, "4", "Z/Z14") -testringconvert(c, ering, "4x^0", "ZZ[x]") --- testringconvert(c, fring, "4x", "QQ[x]") -testringconvert(c, gring, "((4x^0)y^0)z^0", "ZZ[x][y][z]") --- testringconvert(c, hring, "4", "QQ[z][y][x]") -testringconvert(c, iring, "(4x^0)/(1x^0)", "ZZ(x)") --- testringconvert(c, jring, "12/1x^0/1/1x^0", "QQ(x)") -testringconvert(c, kring, "4x^0", "Z/Z5[x]") --- testringconvert(c, lring, "4", "QQ(x)[y]") - -testringconvert(d, aring, "8", "ZZ") --- testringconvert(d, bring, "8", "QQ") -testringconvert(d, cring, "1", "Z/Z7") -testringconvert(d, dring, "8", "Z/Z14") -testringconvert(d, ering, "8x^0", "ZZ[x]") --- testringconvert(d, fring, "8", "QQ[x]") -testringconvert(d, gring, "((8x^0)y^0)z^0", "ZZ[x][y][z]") --- testringconvert(d, hring, "8", "QQ[z][y][x]") -testringconvert(d, iring, "(8x^0)/(1x^0)", "ZZ(x)") --- testringconvert(d, jring, "8", "QQ(x)") -testringconvert(d, kring, "3x^0", "Z/Z5[x]") --- testringconvert(d, lring, "8", "QQ(x)[y]") - --- testringconvert(e, aring, "3x^2+0x^1+6x^0", "ZZ") --- testringconvert(e, bring, "3x^2+0x^1+6x^0", "QQ") --- testringconvert(e, cring, "3x^2+0x^1+6x^0", "Z/Z7") --- testringconvert(e, dring, "3x^2+0x^1+6x^0", "Z/Z14") -testringconvert(e, ering, "3x^2+0x^1+6x^0", "ZZ[x]") -testringconvert(e, fring, "3/1x^2+0/1x^1+6/1x^0", "QQ[x]") -testringconvert(e, gring, "((3x^2+0x^1+6x^0)y^0)z^0", "ZZ[x][y][z]") -testringconvert(e, hring, "((3/1z^0)y^0)x^2+((0/1z^0)y^0)x^1+((6/1z^0)y^0)x^0", "QQ[z][y][x]") -testringconvert(e, iring, "(3x^2+0x^1+6x^0)/(1x^0)", "ZZ(x)") -testringconvert(e, jring, "(3x^2+0x^1+6x^0)/(1x^0)", "ZZ(x)") -testringconvert(e, kring, "3x^2+0x^1+1x^0", "Z/Z5[x]") -testringconvert(e, lring, "((3x^2+0x^1+6x^0)/(1x^0))y^0", "ZZ(x)[y]") - --- testringconvert(f, aring, "12x^1+4/5x^0", "ZZ") --- testringconvert(f, bring, "12x^1+4/5x^0", "QQ") --- testringconvert(f, cring, "12x^1+4/5x^0", "Z/Z7") --- testringconvert(f, dring, "12x^1+4/5x^0", "Z/Z14") --- testringconvert(f, ering, "12x^1+4/5x^0", "ZZ[x]") -testringconvert(f, fring, "12x^1+4/5x^0", "QQ[x]") --- testringconvert(f, gring, "12x^1+4/5x^0", "ZZ[x][y][z]") -testringconvert(f, hring, "((12/1z^0)y^0)x^1+((4/5z^0)y^0)x^0", "QQ[z][y][x]") --- testringconvert(f, iring, "12x^1+4/5x^0", "ZZ(x)") -testringconvert(f, jring, "(12x^1+4/5x^0)/(1x^0)", "QQ(x)") --- testringconvert(f, kring, "12x^1+4/5x^0", "Z/Z5[x]") -testringconvert(f, lring, "((12x^1+4/5x^0)/(1x^0))y^0", "QQ(x)[y]") - --- testringconvert(g, aring, "", "ZZ") --- testringconvert(g, bring, "", "QQ") --- testringconvert(g, cring, "", "Z/Z7") --- testringconvert(g, dring, "", "Z/Z14") --- testringconvert(g, ering, "", "ZZ[x]") --- testringconvert(g, fring, "", "QQ[x]") -testringconvert(g, gring, "((1x^0)y^0)z^3+((1x^0)y^3+(4x^1+-16x^0)y^2+(1x^2+8x^1+2x^0)y^1+(0x^0)y^0)z^2+((-1x^0)y^1+(5x^3+6x^2+7x^1+8x^0)y^0)z^1+((8x^0)y^2+(0x^0)y^1+(1x^2+12x^1+-4x^0)y^0)z^0", "ZZ[x][y][z]") --- testringconvert(g, hring, "", "QQ[z][y][x]") --- testringconvert(g, iring, "", "ZZ(x)") --- testringconvert(g, jring, "", "QQ(x)") --- testringconvert(g, kring, "", "Z/Z5[x]") --- testringconvert(g, lring, "", "QQ(x)[y]") - --- testringconvert(h, aring, "", "ZZ") --- testringconvert(h, bring, "", "QQ") --- testringconvert(h, cring, "", "Z/Z7") --- testringconvert(h, dring, "", "Z/Z14") --- testringconvert(h, ering, "", "ZZ[x]") --- testringconvert(h, fring, "", "QQ[x]") --- testringconvert(h, gring, "", "ZZ[x][y][z]") -testringconvert(h, hring, "((1z^0)y^0)x^3+((1z^0)y^3+(1/9z^1+-16z^0)y^2+(1z^2+8z^1+2z^0)y^1+(0z^0)y^0)x^2+((-1z^0)y^1+(5z^3+6z^2+7z^1+8z^0)y^0)x^1+((8z^0)y^2+(0z^0)y^1+(1z^2+4/5z^1+-4z^0)y^0)x^0", "QQ[z][y][x]") --- testringconvert(h, iring, "", "ZZ(x)") --- testringconvert(h, jring, "", "QQ(x)") --- testringconvert(h, kring, "", "Z/Z5[x]") --- testringconvert(h, lring, "", "QQ(x)[y]") - --- testringconvert(i, aring, "", "ZZ") --- testringconvert(i, bring, "", "QQ") --- testringconvert(i, cring, "", "Z/Z7") --- testringconvert(i, dring, "", "Z/Z14") --- testringconvert(i, ering, "", "ZZ[x]") --- testringconvert(i, fring, "", "QQ[x]") --- testringconvert(i, gring, "", "ZZ[x][y][z]") --- testringconvert(i, hring, "", "QQ[z][y][x]") -testringconvert(i, iring, "(1x^1+-2x^0)/(1x^2+3x^1+3x^0)", "ZZ(x)") -testringconvert(i, jring, "(1x^1+-2x^0)/(1x^2+3x^1+3x^0)", "ZZ(x)") --- testringconvert(i, kring, "", "Z/Z5[x]") -testringconvert(i, lring, "((1x^1+-2x^0)/(1x^2+3x^1+3x^0))y^0", "ZZ(x)[y]") - --- testringconvert(j, aring, "", "ZZ") --- testringconvert(j, bring, "", "QQ") --- testringconvert(j, cring, "", "Z/Z7") --- testringconvert(j, dring, "", "Z/Z14") --- testringconvert(j, ering, "", "ZZ[x]") --- testringconvert(j, fring, "", "QQ[x]") --- testringconvert(j, gring, "", "ZZ[x][y][z]") --- testringconvert(j, hring, "", "QQ[z][y][x]") --- testringconvert(j, iring, "", "ZZ(x)") -testringconvert(j, jring, "(1x^1+-2x^0)/(1x^2+3/8x^1+3/4x^0)", "QQ(x)") --- testringconvert(j, kring, "", "Z/Z5[x]") -testringconvert(j, lring, "((1x^1+-2x^0)/(1x^2+3/8x^1+3/4x^0))y^0", "QQ(x)[y]") - --- testringconvert(k, aring, "", "ZZ") --- testringconvert(k, bring, "", "QQ") --- testringconvert(k, cring, "", "Z/Z7") --- testringconvert(k, dring, "", "Z/Z14") -testringconvert(k, ering, "1x^3+3x^2+1x^1+0x^0", "ZZ[x]") --- testringconvert(k, fring, "", "QQ[x]") -testringconvert(k, gring, "((1x^3+3x^2+1x^1+0x^0)y^0)z^0", "ZZ[x][y][z]") --- testringconvert(k, hring, "", "QQ[z][y][x]") -testringconvert(k, iring, "(1x^3+3x^2+1x^1+0x^0)/(1x^0)", "ZZ(x)") --- testringconvert(k, jring, "", "QQ(x)") -testringconvert(k, kring, "1x^3+3x^2+1x^1+0x^0", "Z/Z5[x]") --- testringconvert(k, lring, "", "QQ(x)[y]") - --- testringconvert(l, aring, "", "ZZ") --- testringconvert(l, bring, "", "QQ") --- testringconvert(l, cring, "", "Z/Z7") --- testringconvert(l, dring, "", "Z/Z14") --- testringconvert(l, ering, "", "ZZ[x]") --- testringconvert(l, fring, "", "QQ[x]") --- testringconvert(l, gring, "", "ZZ[x][y][z]") --- testringconvert(l, hring, "", "QQ[z][y][x]") --- testringconvert(l, iring, "", "ZZ(x)") --- testringconvert(l, jring, "", "QQ(x)") --- testringconvert(j, kring, "", "Z/Z5[x]") -testringconvert(l, lring, "((7/6x^0)/(1x^1+2x^0))y^3+((4x^2+6x^1+6x^0)/(1x^1+3x^0))y^2+((3/2x^0)/(1x^3+8x^2+1/2x^1+6x^0))y^1+((1/4x^1+1x^0)/(1x^1+0x^0))y^0", "QQ(x)[y]") - - - - -endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/rings/luacas-conversion.lua b/macros/luatex/latex/luacas/tex/test/rings/luacas-conversion.lua new file mode 100644 index 0000000000..b9eb523c7a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/rings/luacas-conversion.lua @@ -0,0 +1,273 @@ +local a = Integer(12) +local b = Integer(3) / Integer(2) +local c = IntegerModN(Integer(4), Integer(7)) +local d = IntegerModN(Integer(8), Integer(14)) +local e = PolynomialRing({Integer(6), Integer(0), Integer(3)}, SymbolExpression("x")) +local f = PolynomialRing({Integer(4)/Integer(5), Integer(12)}, SymbolExpression("x")) +local g = PolynomialRing({ + PolynomialRing({ + PolynomialRing({Integer(-4), Integer(12), Integer(1)}, SymbolExpression("x")), + PolynomialRing({Integer(0)}, SymbolExpression("x")), + PolynomialRing({Integer(8)}, SymbolExpression("x")) + }, + SymbolExpression("y")), + PolynomialRing({ + PolynomialRing({Integer(8), Integer(7), Integer(6), Integer(5)}, SymbolExpression("x")), + PolynomialRing({Integer(-1)}, SymbolExpression("x")), + }, + SymbolExpression("y")), + PolynomialRing({ + PolynomialRing({Integer(0)}, SymbolExpression("x")), + PolynomialRing({Integer(2), Integer(8), Integer(1)}, SymbolExpression("x")), + PolynomialRing({Integer(-16), Integer(4)}, SymbolExpression("x")), + PolynomialRing({Integer(1)}, SymbolExpression("x")) + }, + SymbolExpression("y")), + PolynomialRing({ + PolynomialRing({Integer(1)}, SymbolExpression("x")) + }, + SymbolExpression("y")) + }, SymbolExpression("z")) +local h = PolynomialRing({ + PolynomialRing({ + PolynomialRing({Integer(-4), Integer(4)/Integer(5), Integer(1)}, SymbolExpression("z")), + PolynomialRing({Integer(0)}, SymbolExpression("z")), + PolynomialRing({Integer(8)}, SymbolExpression("z")) + }, + SymbolExpression("y")), + PolynomialRing({ + PolynomialRing({Integer(8), Integer(7), Integer(6), Integer(5)}, SymbolExpression("z")), + PolynomialRing({Integer(-1)}, SymbolExpression("z")), + }, + SymbolExpression("y")), + PolynomialRing({ + PolynomialRing({Integer(0)}, SymbolExpression("z")), + PolynomialRing({Integer(2), Integer(8), Integer(1)}, SymbolExpression("z")), + PolynomialRing({Integer(-16), Integer(1)/Integer(9)}, SymbolExpression("z")), + PolynomialRing({Integer(1)}, SymbolExpression("z")) + }, + SymbolExpression("y")), + PolynomialRing({ + PolynomialRing({Integer(1)}, SymbolExpression("z")) + }, + SymbolExpression("y")) + }, SymbolExpression("x")) +local i = Rational(PolynomialRing({-Integer(2), Integer(1)}, SymbolExpression("x")), + PolynomialRing({Integer(3), Integer(3), Integer(1)}, SymbolExpression("x"))) +local j = Rational(PolynomialRing({-Integer(2), Integer(1)}, SymbolExpression("x")), + PolynomialRing({Integer(3)/Integer(4), Integer(3)/Integer(8), Integer(1)}, SymbolExpression("x"))) +local k = PolynomialRing({IntegerModN(Integer(0), Integer(5)), + IntegerModN(Integer(1), Integer(5)), + IntegerModN(Integer(3), Integer(5)), + IntegerModN(Integer(1), Integer(5))}, SymbolExpression("x")) + +local l = PolynomialRing({Rational( + PolynomialRing({Integer(4), Integer(1)}, SymbolExpression("x")), + PolynomialRing({Integer(0), Integer(4)}, SymbolExpression("x")) + ), + Rational( + PolynomialRing({Integer(3)/Integer(2)}, SymbolExpression("x")), + PolynomialRing({Integer(6), Integer(1)/Integer(2), Integer(8), Integer(1)}, SymbolExpression("x")) + ), + Rational( + PolynomialRing({Integer(6), Integer(6), Integer(4)}, SymbolExpression("x")), + PolynomialRing({Integer(3), Integer(1)}, SymbolExpression("x")) + ), + Rational( + PolynomialRing({Integer(7)/Integer(6)}, SymbolExpression("x")), + PolynomialRing({Integer(2), Integer(1)}, SymbolExpression("x")) + ) + }, SymbolExpression("y")) + +local aring = a:getring() -- ZZ +local bring = b:getring() -- QQ +local cring = c:getring() -- ZZ_7 +local dring = d:getring() -- ZZ_14 +local ering = e:getring() -- ZZ[x] +local fring = f:getring() -- QQ[x] +local gring = g:getring() -- ZZ[x][y][z] +local hring = h:getring() -- QQ[z][y][x] +local iring = i:getring() -- ZZ(x) +local jring = j:getring() -- QQ(x) +local kring = k:getring() -- ZZ_5[x] +local lring = l:getring() -- QQ(x)[y] + +starttest("ring construction") +testeq(aring, "ZZ") +testeq(bring, "QQ") +testeq(cring, "Z/Z7") +testeq(dring, "Z/Z14") +testeq(ering, "ZZ[x]") +testeq(fring, "QQ[x]") +testeq(gring, "ZZ[x][y][z]") +testeq(hring, "QQ[z][y][x]") +testeq(iring, "ZZ(x)") +testeq(jring, "QQ(x)") +testeq(kring, "Z/Z5[x]") +testeq(lring, "QQ(x)[y]") +endtest() + +starttest("ring conversion") + +-- Commented-out tests denote elements whos rings are not subrings of the ring that is being converted to + +testringconvert(a, aring, "12", "ZZ") +testringconvert(a, bring, "12/1", "QQ") +testringconvert(a, cring, "5", "Z/Z7") +testringconvert(a, dring, "12", "Z/Z14") +testringconvert(a, ering, "12x^0", "ZZ[x]") +testringconvert(a, fring, "12/1x^0", "QQ[x]") +testringconvert(a, gring, "((12x^0)y^0)z^0", "ZZ[x][y][z]") +testringconvert(a, hring, "((12/1z^0)y^0)x^0", "QQ[z][y][x]") +testringconvert(a, iring, "(12x^0)/(1x^0)", "ZZ(x)") +testringconvert(a, jring, "(12x^0)/(1x^0)", "ZZ(x)") +testringconvert(a, kring, "2x^0", "Z/Z5[x]") +testringconvert(a, lring, "((12x^0)/(1x^0))y^0", "ZZ(x)[y]") + +-- testringconvert(b, aring, "3/2", "ZZ") +testringconvert(b, bring, "3/2", "QQ") +-- testringconvert(b, cring, "3/2", "Z/Z7") +-- testringconvert(b, dring, "3/2", "Z/Z14") +-- testringconvert(b, ering, "3/2", "ZZ[x]") +testringconvert(b, fring, "3/2x^0", "QQ[x]") +-- testringconvert(b, gring, "3/2", "ZZ[x][y][z]") +testringconvert(b, hring, "((3/2z^0)y^0)x^0", "QQ[z][y][x]") +-- testringconvert(b, iring, "3/2", "ZZ(x)") +testringconvert(b, jring, "(3/2x^0)/(1x^0)", "QQ(x)") +-- testringconvert(b, kring, "3/2", "Z/Z5[x]") +testringconvert(b, lring, "((3/2x^0)/(1x^0))y^0", "QQ(x)[y]") + +testringconvert(c, aring, "4", "ZZ") +-- testringconvert(c, bring, "4", "QQ") +testringconvert(c, cring, "4", "Z/Z7") +testringconvert(c, dring, "4", "Z/Z14") +testringconvert(c, ering, "4x^0", "ZZ[x]") +-- testringconvert(c, fring, "4x", "QQ[x]") +testringconvert(c, gring, "((4x^0)y^0)z^0", "ZZ[x][y][z]") +-- testringconvert(c, hring, "4", "QQ[z][y][x]") +testringconvert(c, iring, "(4x^0)/(1x^0)", "ZZ(x)") +-- testringconvert(c, jring, "12/1x^0/1/1x^0", "QQ(x)") +testringconvert(c, kring, "4x^0", "Z/Z5[x]") +-- testringconvert(c, lring, "4", "QQ(x)[y]") + +testringconvert(d, aring, "8", "ZZ") +-- testringconvert(d, bring, "8", "QQ") +testringconvert(d, cring, "1", "Z/Z7") +testringconvert(d, dring, "8", "Z/Z14") +testringconvert(d, ering, "8x^0", "ZZ[x]") +-- testringconvert(d, fring, "8", "QQ[x]") +testringconvert(d, gring, "((8x^0)y^0)z^0", "ZZ[x][y][z]") +-- testringconvert(d, hring, "8", "QQ[z][y][x]") +testringconvert(d, iring, "(8x^0)/(1x^0)", "ZZ(x)") +-- testringconvert(d, jring, "8", "QQ(x)") +testringconvert(d, kring, "3x^0", "Z/Z5[x]") +-- testringconvert(d, lring, "8", "QQ(x)[y]") + +-- testringconvert(e, aring, "3x^2+0x^1+6x^0", "ZZ") +-- testringconvert(e, bring, "3x^2+0x^1+6x^0", "QQ") +-- testringconvert(e, cring, "3x^2+0x^1+6x^0", "Z/Z7") +-- testringconvert(e, dring, "3x^2+0x^1+6x^0", "Z/Z14") +testringconvert(e, ering, "3x^2+0x^1+6x^0", "ZZ[x]") +testringconvert(e, fring, "3/1x^2+0/1x^1+6/1x^0", "QQ[x]") +testringconvert(e, gring, "((3x^2+0x^1+6x^0)y^0)z^0", "ZZ[x][y][z]") +testringconvert(e, hring, "((3/1z^0)y^0)x^2+((0/1z^0)y^0)x^1+((6/1z^0)y^0)x^0", "QQ[z][y][x]") +testringconvert(e, iring, "(3x^2+0x^1+6x^0)/(1x^0)", "ZZ(x)") +testringconvert(e, jring, "(3x^2+0x^1+6x^0)/(1x^0)", "ZZ(x)") +testringconvert(e, kring, "3x^2+0x^1+1x^0", "Z/Z5[x]") +testringconvert(e, lring, "((3x^2+0x^1+6x^0)/(1x^0))y^0", "ZZ(x)[y]") + +-- testringconvert(f, aring, "12x^1+4/5x^0", "ZZ") +-- testringconvert(f, bring, "12x^1+4/5x^0", "QQ") +-- testringconvert(f, cring, "12x^1+4/5x^0", "Z/Z7") +-- testringconvert(f, dring, "12x^1+4/5x^0", "Z/Z14") +-- testringconvert(f, ering, "12x^1+4/5x^0", "ZZ[x]") +testringconvert(f, fring, "12x^1+4/5x^0", "QQ[x]") +-- testringconvert(f, gring, "12x^1+4/5x^0", "ZZ[x][y][z]") +testringconvert(f, hring, "((12/1z^0)y^0)x^1+((4/5z^0)y^0)x^0", "QQ[z][y][x]") +-- testringconvert(f, iring, "12x^1+4/5x^0", "ZZ(x)") +testringconvert(f, jring, "(12x^1+4/5x^0)/(1x^0)", "QQ(x)") +-- testringconvert(f, kring, "12x^1+4/5x^0", "Z/Z5[x]") +testringconvert(f, lring, "((12x^1+4/5x^0)/(1x^0))y^0", "QQ(x)[y]") + +-- testringconvert(g, aring, "", "ZZ") +-- testringconvert(g, bring, "", "QQ") +-- testringconvert(g, cring, "", "Z/Z7") +-- testringconvert(g, dring, "", "Z/Z14") +-- testringconvert(g, ering, "", "ZZ[x]") +-- testringconvert(g, fring, "", "QQ[x]") +testringconvert(g, gring, "((1x^0)y^0)z^3+((1x^0)y^3+(4x^1+-16x^0)y^2+(1x^2+8x^1+2x^0)y^1+(0x^0)y^0)z^2+((-1x^0)y^1+(5x^3+6x^2+7x^1+8x^0)y^0)z^1+((8x^0)y^2+(0x^0)y^1+(1x^2+12x^1+-4x^0)y^0)z^0", "ZZ[x][y][z]") +-- testringconvert(g, hring, "", "QQ[z][y][x]") +-- testringconvert(g, iring, "", "ZZ(x)") +-- testringconvert(g, jring, "", "QQ(x)") +-- testringconvert(g, kring, "", "Z/Z5[x]") +-- testringconvert(g, lring, "", "QQ(x)[y]") + +-- testringconvert(h, aring, "", "ZZ") +-- testringconvert(h, bring, "", "QQ") +-- testringconvert(h, cring, "", "Z/Z7") +-- testringconvert(h, dring, "", "Z/Z14") +-- testringconvert(h, ering, "", "ZZ[x]") +-- testringconvert(h, fring, "", "QQ[x]") +-- testringconvert(h, gring, "", "ZZ[x][y][z]") +testringconvert(h, hring, "((1z^0)y^0)x^3+((1z^0)y^3+(1/9z^1+-16z^0)y^2+(1z^2+8z^1+2z^0)y^1+(0z^0)y^0)x^2+((-1z^0)y^1+(5z^3+6z^2+7z^1+8z^0)y^0)x^1+((8z^0)y^2+(0z^0)y^1+(1z^2+4/5z^1+-4z^0)y^0)x^0", "QQ[z][y][x]") +-- testringconvert(h, iring, "", "ZZ(x)") +-- testringconvert(h, jring, "", "QQ(x)") +-- testringconvert(h, kring, "", "Z/Z5[x]") +-- testringconvert(h, lring, "", "QQ(x)[y]") + +-- testringconvert(i, aring, "", "ZZ") +-- testringconvert(i, bring, "", "QQ") +-- testringconvert(i, cring, "", "Z/Z7") +-- testringconvert(i, dring, "", "Z/Z14") +-- testringconvert(i, ering, "", "ZZ[x]") +-- testringconvert(i, fring, "", "QQ[x]") +-- testringconvert(i, gring, "", "ZZ[x][y][z]") +-- testringconvert(i, hring, "", "QQ[z][y][x]") +testringconvert(i, iring, "(1x^1+-2x^0)/(1x^2+3x^1+3x^0)", "ZZ(x)") +testringconvert(i, jring, "(1x^1+-2x^0)/(1x^2+3x^1+3x^0)", "ZZ(x)") +-- testringconvert(i, kring, "", "Z/Z5[x]") +testringconvert(i, lring, "((1x^1+-2x^0)/(1x^2+3x^1+3x^0))y^0", "ZZ(x)[y]") + +-- testringconvert(j, aring, "", "ZZ") +-- testringconvert(j, bring, "", "QQ") +-- testringconvert(j, cring, "", "Z/Z7") +-- testringconvert(j, dring, "", "Z/Z14") +-- testringconvert(j, ering, "", "ZZ[x]") +-- testringconvert(j, fring, "", "QQ[x]") +-- testringconvert(j, gring, "", "ZZ[x][y][z]") +-- testringconvert(j, hring, "", "QQ[z][y][x]") +-- testringconvert(j, iring, "", "ZZ(x)") +testringconvert(j, jring, "(1x^1+-2x^0)/(1x^2+3/8x^1+3/4x^0)", "QQ(x)") +-- testringconvert(j, kring, "", "Z/Z5[x]") +testringconvert(j, lring, "((1x^1+-2x^0)/(1x^2+3/8x^1+3/4x^0))y^0", "QQ(x)[y]") + +-- testringconvert(k, aring, "", "ZZ") +-- testringconvert(k, bring, "", "QQ") +-- testringconvert(k, cring, "", "Z/Z7") +-- testringconvert(k, dring, "", "Z/Z14") +testringconvert(k, ering, "1x^3+3x^2+1x^1+0x^0", "ZZ[x]") +-- testringconvert(k, fring, "", "QQ[x]") +testringconvert(k, gring, "((1x^3+3x^2+1x^1+0x^0)y^0)z^0", "ZZ[x][y][z]") +-- testringconvert(k, hring, "", "QQ[z][y][x]") +testringconvert(k, iring, "(1x^3+3x^2+1x^1+0x^0)/(1x^0)", "ZZ(x)") +-- testringconvert(k, jring, "", "QQ(x)") +testringconvert(k, kring, "1x^3+3x^2+1x^1+0x^0", "Z/Z5[x]") +-- testringconvert(k, lring, "", "QQ(x)[y]") + +-- testringconvert(l, aring, "", "ZZ") +-- testringconvert(l, bring, "", "QQ") +-- testringconvert(l, cring, "", "Z/Z7") +-- testringconvert(l, dring, "", "Z/Z14") +-- testringconvert(l, ering, "", "ZZ[x]") +-- testringconvert(l, fring, "", "QQ[x]") +-- testringconvert(l, gring, "", "ZZ[x][y][z]") +-- testringconvert(l, hring, "", "QQ[z][y][x]") +-- testringconvert(l, iring, "", "ZZ(x)") +-- testringconvert(l, jring, "", "QQ(x)") +-- testringconvert(j, kring, "", "Z/Z5[x]") +testringconvert(l, lring, "((7/6x^0)/(1x^1+2x^0))y^3+((4x^2+6x^1+6x^0)/(1x^1+3x^0))y^2+((3/2x^0)/(1x^3+8x^2+1/2x^1+6x^0))y^1+((1/4x^1+1x^0)/(1x^1+0x^0))y^0", "QQ(x)[y]") + + + + +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/rings/luacas-modulararithmetic.lua b/macros/luatex/latex/luacas/tex/test/rings/luacas-modulararithmetic.lua new file mode 100644 index 0000000000..1bd315595a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/rings/luacas-modulararithmetic.lua @@ -0,0 +1,20 @@ +local a = IntegerModN(Integer(5), Integer(3)) +local b = IntegerModN(Integer(1), Integer(3)) +local c = IntegerModN(Integer(-12), Integer(3)) +local f = IntegerModN(Integer(100), Integer(62501)) +local d = IntegerModN(Integer(16), Integer(36)) +local e = IntegerModN(Integer(27), Integer(36)) + +starttest("modular arithmetic") +testeq(a, "2") +testeq(b, "1") +testeq(c, "0") +testeq(a + b, "0") +testeq(a - b, "1") +testeq(a * b, "2") +testeq(a:inv(), "2") +testeq(b:inv(), "1") +testeq(f:inv(), "61876") +testeq(d * e, "0") +testeq(a * d, "2") +endtest() \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/test/rings/luacas-number.lua b/macros/luatex/latex/luacas/tex/test/rings/luacas-number.lua new file mode 100644 index 0000000000..9afea0d364 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/rings/luacas-number.lua @@ -0,0 +1,118 @@ +local a = Integer(5) +local b = Integer(3) +local c = Integer(-12) +local d = Integer("-54321") +local e = Integer("99989999999999999989999999999999999999999999999999999999989999999999999999999999999998999999999999999999999999989999999998") +local f = Integer("-1267650600228229401496703205376") +local g = Integer(16) +local h = Integer(8) +local x = Integer(8) / Integer(5) +local y = Integer(1) / Integer(12) +local z = Integer(-7) / Integer(10) + + +starttest("integer construction") +testeq(a, 5) +testeq(b, 3) +testeq(c, -12) +testeq(d, "-54321") +testeq(e, "99989999999999999989999999999999999999999999999999999999989999999999999999999999999998999999999999999999999999989999999998") +testeq(f, "-1267650600228229401496703205376") +endtest() + +starttest("integer operations") +testeq(-c, 12) +testeq(a + b, 8) +testeq(b - c, 15) +testeq(d - d, 0) +testeq(e + f, "99989999999999999989999999999999999999999999999999999999989999999999999999999999999998999998732349399771770598493296794622") +testeq(a * c, -60) +testeq(f * f, "1606938044258990275541962092341162602522202993782792835301376") +testeq(e * f, "-126752383516820657842978847503263945985032967946239999999987323493997717705985032967944972349399771770598503296781947493995182404784576509143246593589248") +testeq(a // b, 1) +testeq(a % b, 2) +testeq(f // d, "23336289836862896513258283") +testeq(f % d, "-14533") +testeq(e // -f, "78878201913048970415230130190415677050906625793723347950240237316957169209243093407705758276") +testeq(e % -f, "1011644662020502370048160308222") +testeq(c ^ a, -248832) +testeq(d ^ a, "-472975648731213834575601") +testeq(a == b, false) +testeq(b < a, true) +testeq(a <= a, true) +testeq(f < d, true) +testeq(e <= f, false) +endtest() + + + +starttest("integer conversions") +testeq(a / b, "5/3") +testeq(g / c, "-4/3") +testeq(c / b, -4) +endtest() + + +starttest("rational operations") +testeq(-x, "-8/5") +testeq(x + y, "101/60") +testeq(z - y, "-47/60") +testeq(x * z, "-28/25") +testeq(x / y, "96/5") +testeq(y