From 0a7c9b85de9aeaffafa0cf8944fa81ffe9652d09 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Sun, 6 Nov 2022 03:01:22 +0000 Subject: CTAN sync 202211060301 --- macros/luatex/latex/luacas/README.md | 23 + .../luatex/latex/luacas/doc/appendix/latexcode.tex | 140 +++ macros/luatex/latex/luacas/doc/appendix/luacas.dat | 230 ++++ macros/luatex/latex/luacas/doc/intro/intro.tex | 143 +++ macros/luatex/latex/luacas/doc/intro/intropart.tex | 202 +++ macros/luatex/latex/luacas/doc/luacas.pdf | Bin 0 -> 758211 bytes macros/luatex/latex/luacas/doc/luacas.tex | 214 ++++ macros/luatex/latex/luacas/doc/reference/ref.tex | 236 ++++ .../doc/reference/ref_algebra/ref_algebra.tex | 336 +++++ .../ref_algebra_classes/ref_algebra_classes.tex | 977 +++++++++++++++ .../ref_algebra_methods/ref_algebra_methods.tex | 1305 ++++++++++++++++++++ .../doc/reference/ref_calculus/ref_calculus.tex | 225 ++++ .../ref_calculus_classes/ref_calculus_classes.tex | 277 +++++ .../ref_calculus_methods/ref_calculus_methods.tex | 473 +++++++ .../luacas/doc/reference/ref_core/ref_core.tex | 249 ++++ .../ref_core/ref_core_classes/ref_core_classes.tex | 455 +++++++ .../ref_core/ref_core_methods/ref_core_methods.tex | 1155 +++++++++++++++++ macros/luatex/latex/luacas/doc/tutorial/tut.tex | 217 ++++ .../luatex/latex/luacas/doc/tutorial/tut1/tut1.tex | 238 ++++ .../luatex/latex/luacas/doc/tutorial/tut2/tut2.tex | 476 +++++++ .../latex/luacas/doc/tutorial/tut3/demotut3.dat | 46 + .../luatex/latex/luacas/doc/tutorial/tut3/tut3.tex | 575 +++++++++ macros/luatex/latex/luacas/tex/_lib/inspect.lua | 335 +++++ 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 +++ .../latex/luacas/tex/algebra/polynomialring.lua | 860 +++++++++++++ .../algebra/polynomialring/berlekampfactoring.lua | 187 +++ .../tex/algebra/polynomialring/decomposition.lua | 93 ++ .../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 ++++++++++++++ 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 + .../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 +++++ .../latex/luacas/tex/core/symbolexpression.lua | 132 ++ macros/luatex/latex/luacas/tex/luacas.sty | 312 +++++ .../latex/luacas/tex/test/calculus/derivatives.lua | 30 + .../latex/luacas/tex/test/calculus/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/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 +++++ macros/luatex/latex/luacas/tex/test/main.lua | 154 +++ macros/luatex/latex/luacas/tex/test/parser.lua | 323 +++++ .../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/modulararithmetic.lua | 20 + .../luatex/latex/luacas/tex/test/rings/number.lua | 118 ++ 81 files changed, 19788 insertions(+) create mode 100644 macros/luatex/latex/luacas/README.md create mode 100644 macros/luatex/latex/luacas/doc/appendix/latexcode.tex create mode 100644 macros/luatex/latex/luacas/doc/appendix/luacas.dat create mode 100644 macros/luatex/latex/luacas/doc/intro/intro.tex create mode 100644 macros/luatex/latex/luacas/doc/intro/intropart.tex create mode 100644 macros/luatex/latex/luacas/doc/luacas.pdf create mode 100644 macros/luatex/latex/luacas/doc/luacas.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_classes/ref_algebra_classes.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_methods/ref_algebra_methods.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_classes/ref_calculus_classes.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_methods/ref_calculus_methods.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_core/ref_core.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_core/ref_core_classes/ref_core_classes.tex create mode 100644 macros/luatex/latex/luacas/doc/reference/ref_core/ref_core_methods/ref_core_methods.tex create mode 100644 macros/luatex/latex/luacas/doc/tutorial/tut.tex create mode 100644 macros/luatex/latex/luacas/doc/tutorial/tut1/tut1.tex create mode 100644 macros/luatex/latex/luacas/doc/tutorial/tut2/tut2.tex create mode 100644 macros/luatex/latex/luacas/doc/tutorial/tut3/demotut3.dat create mode 100644 macros/luatex/latex/luacas/doc/tutorial/tut3/tut3.tex create mode 100644 macros/luatex/latex/luacas/tex/_lib/inspect.lua create mode 100644 macros/luatex/latex/luacas/tex/_lib/pepperfish.lua create mode 100644 macros/luatex/latex/luacas/tex/_lib/table.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/_init.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/absexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/equation.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/euclideandomain.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/factorialexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/field.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/integer.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/integerquotientring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/logarithm.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/berlekampfactoring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/decomposition.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/polynomialring/zassenhausfactoring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/rational.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/ring.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/rootexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/sqrtexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/algebra/trigexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/_init.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/derivativeexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/diffexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/calculus/integralexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/_init.lua create mode 100644 macros/luatex/latex/luacas/tex/core/atomicexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua create mode 100644 macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua create mode 100644 macros/luatex/latex/luacas/tex/core/compoundexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/constantexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/expression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/functionexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/core/symbolexpression.lua create mode 100644 macros/luatex/latex/luacas/tex/luacas.sty create mode 100644 macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua create mode 100644 macros/luatex/latex/luacas/tex/test/calculus/integrals.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/autosimplify.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/collect.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/equations.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/functions.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/logarithms.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/rationalexponent.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/simplify.lua create mode 100644 macros/luatex/latex/luacas/tex/test/expressions/substitute.lua create mode 100644 macros/luatex/latex/luacas/tex/test/helper.lua create mode 100644 macros/luatex/latex/luacas/tex/test/main.lua create mode 100644 macros/luatex/latex/luacas/tex/test/parser.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/partialfractions.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/polynomial.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/polynomialmod.lua create mode 100644 macros/luatex/latex/luacas/tex/test/polynomials/roots.lua create mode 100644 macros/luatex/latex/luacas/tex/test/rings/conversion.lua create mode 100644 macros/luatex/latex/luacas/tex/test/rings/modulararithmetic.lua create mode 100644 macros/luatex/latex/luacas/tex/test/rings/number.lua (limited to 'macros/luatex') diff --git a/macros/luatex/latex/luacas/README.md b/macros/luatex/latex/luacas/README.md new file mode 100644 index 0000000000..8e757dfea9 --- /dev/null +++ b/macros/luatex/latex/luacas/README.md @@ -0,0 +1,23 @@ +# LuaLaTeX-CAS + +A computer algebra system written purely in Lua for embedding into LaTeX using the LuaLaTeX compiler. Includes symbolic root-finding, factoring, expansion, differentiation, and integration, as well as some number-theoretic and ring-theoretic functionality. Can even run in Overleaf. + +Performance is necessarily not extremely fast, given Lua is an interpreted language. Do not expect to be able to factor large-degree polynomials or large integers in a reasonable time (but this is not the point of this CAS). + +The Lua CAS itself can be downloaded or cloned from [https://github.com/cochraef/LuaCAS](https://github.com/cochraef/LuaCAS). + +# Installation + +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). + +# Authors + +This package is authored and maintained by Evan Cochrane and Timothy All. + +# License + +Permission is granted to copy, distribute and/or modify this +software under the terms of the LaTeX Project Public License +(LPPL), version 1.3c or any later version. + +![poweredbylua](http://www.lua.org/images/powered-by-lua.gif) diff --git a/macros/luatex/latex/luacas/doc/appendix/latexcode.tex b/macros/luatex/latex/luacas/doc/appendix/latexcode.tex new file mode 100644 index 0000000000..1f8ccd7471 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/appendix/latexcode.tex @@ -0,0 +1,140 @@ +\documentclass{article} + +\usepackage{standalone} +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\begin{document} + +\section{The \LaTeX{} code} + +As noted above, this package is really a Lua program; the package {\ttfamily luacas.sty} is merely a shell to make accessing that Lua program easy and manageable from within \LaTeX{}. +\inputminted[ + firstline=12, + lastline=14, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +We check to make sure the user is compiling with Lua\LaTeX{}; if not, an error message is printed and compilation is aborted. + +\inputminted[ + firstline=16, + lastline=24, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +The following packages are required for various macros: + +\inputminted[ + firstline=27, + lastline=32, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +The files \verb|helper.lua| and \verb|parser.lua| help bridge the gap between the Lua program and \LaTeX{}. + +\inputminted[ + firstline=35, + lastline=37, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +We now define the \mintinline{latex}{\begin{CAS}..\end{CAS}} environment: + +\inputminted[ + firstline=39, + lastline=42, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +{\bf Note:} The contents are wrapped in the function \mintinline{lua}{CASparse()}. We now define the retrieving macros \mintinline{latex}{\get}, \mintinline{latex}{\fetch}, and \mintinline{latex}{\store}: + +\inputminted[ + firstline=44, + lastline=67, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} +And now we define the printing macros \mintinline{latex}{\print}, \mintinline{latex}{\vprint}, and \mintinline{latex}{\lprint}: + +\inputminted[ + firstline=75, + lastline=128, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +And finally, we define the macros useful for printing expression trees: + +\inputminted[ + firstline=130, + lastline=230, + breaklines, + linenos, + numbersep=5pt] + {latex} + {luacas.dat} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/appendix/luacas.dat b/macros/luatex/latex/luacas/doc/appendix/luacas.dat new file mode 100644 index 0000000000..60866bae05 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/appendix/luacas.dat @@ -0,0 +1,230 @@ +% Copyright (c) ??? +% +% Permission is granted to copy, distribute and/or modify this +% software under the terms of the LaTeX Project Public License +% (LPPL), version 1.3c or any later version. +% +% This software is provided 'as is', without warranty of any kind, +% either expressed or implied, including, but not limited to, the +% implied warranties of merchantability and fitness for a +% particular purpose. + +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{luacas} + [2022/05/07 v1.0 CAS written in Lua for LaTeX] + +\RequirePackage{iftex} +\ifluatex + \RequirePackage{luacode} +\else + {\PackageError{luacas} + {Not running under LuaLaTeX} + {This package requires LuaLaTeX. Try compiling this document with\MessageBreak 'lualatex' instead of 'latex'. This is a fatal error; I'm aborting now.}% + }\stop +\fi + +%Required Packages +\RequirePackage{xparse} +\RequirePackage{pgfkeys} +\RequirePackage{verbatim} +\RequirePackage{tikz} +\RequirePackage{xcolor} +\RequirePackage{mathtools} + +%These files contain Lua code for parsing luacas output; they also initialize the CAS itself +\directlua{require('test.parser') + require('test.helper') +} + +\NewDocumentEnvironment{CAS}% + {+b}% + {\luaexec{CASparse([[#1]])}}% + {} + +\newcommand{\get}% + [2][true]% + {\directlua{disp(#2, #1)}} + +\newcommand{\fetch}[1]{ + \directlua{tex.print(tostring(#1))} +} + +\NewDocumentCommand{\store}{m O{#1}}{ + \expandafter\def\csname #2\endcsname{% + \directlua{ + input = #1 + if not input[1] then + tex.sprint{tostring(input)} + else + tex.sprint("{") + for _,entry in ipairs(input) do + tex.sprint(tostring(entry),",") + end + tex.sprint("}") + end + }% + }% +}% + +%%%%%%%%%% +%% Core %% +%%%%%%%%%% + +%pretty print + +\NewDocumentCommand{\print}{s m}{% + \IfBooleanTF{#1}{% + \directlua{ + local sym = #2 + if sym then + tex.print(sym:autosimplify():tolatex()) + else + tex.print('nil') + end + }% + }{% + \directlua{ + local sym = #2 + if sym then + tex.print(sym:tolatex()) + else + tex.print('nil') + end + }% + }% +} + +\NewDocumentCommand{\vprint}{s m}{% + \IfBooleanTF{#1}{% + \directlua{ + local sym = #2 + tex.sprint([[\unexpanded{\begin{verbatim}]] .. tostring(sym) .. [[\end{verbatim}}]]) + }% + }{% + \directlua{ + local sym = #2 + tex.sprint([[\unexpanded{\begin{verbatim}]] .. tostring(sym:autosimplify()) .. [[\end{verbatim}}]]) + }% + }% +} + +\NewDocumentCommand{\lprint}{m O{nil,nil}}{% + \luaexec{ + local tbl = #1 + local low,upp = #2 + local tmp =0 + if tbl[0] == nil then + tmp = 1 + end + upp = upp or \#tbl + low = low or tmp + for i=low,upp do + tex.print(tbl[i]:tolatex()) + if tbl[i+1] then + tex.print(",") + end + end + } +} + +%prints the first level of an expression tree; for use within a tikzpicture environment + +\NewDocumentCommand{\printshrub}{s m}{% + \IfBooleanTF{#1}{% + \directlua{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\node [label=90:", whatis(sym), "] {", nameof(sym), "}") + tex.print(sym:gettheshrub()) + tex.print(";") + }% + }{% + \directlua{ + local sym = #2 + tex.print("\\node [label=90:", whatis(sym), "] {", nameof(sym), "}") + tex.print(sym:gettheshrub()) + tex.print(";") + }% + } +} + +%prints the full expression tree; for use within a tikzpicture environment + +\NewDocumentCommand{\printtree}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\node {",nameof(sym),"}") + tex.print(sym:getthetree()) + tex.print(";") + }% + }{% + \luaexec{ + local sym = #2 + tex.print("\\node {",nameof(sym),"}") + tex.print(sym:getthetree()) + tex.print(";") + }% + } +} + +%parses an expression tree for use within the forest environment; result is stored in \forestresult + +\NewDocumentCommand{\parseforest}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\def\\forestresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(sym:gettheforest()) + tex.print("]") + tex.print("}") + }% + }{% + \luaexec { + local sym = #2 + tex.print("\\def\\forestresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(sym:gettheforest()) + tex.print("]") + tex.print("}") + }% + } +} + +\NewDocumentCommand{\parseshrub}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\def\\shrubresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(", tikz+={\\node[anchor=south] at (.north) {test};}") + tex.print(sym:getthefancyshrub()) + tex.print("]") + tex.print("}") + }% + }{% + \luaexec{ + local sym = #2 + tex.print("\\def\\shrubresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(", tikz+={\\node[anchor=south font=\\ttfamily\\footnotesize,gray] at (.north) {",longwhati (sym),"};}") + tex.print(sym:getthefancyshrub()) + tex.print("]") + tex.print("}") + }% + } +} + +\NewDocumentCommand{\whatis}{m}{% + \luaexec{ + tex.sprint("{\\ttfamily",longwhatis(#1),"}") + }% +} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/intro/intro.tex b/macros/luatex/latex/luacas/doc/intro/intro.tex new file mode 100644 index 0000000000..bf77ffad04 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/intro/intro.tex @@ -0,0 +1,143 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\begin{document} + +\section{What is \texttt{luacas}?} + +The package {\ttfamily luacas} allows for symbolic computation within \LaTeX{}. For example: +\begin{CAS} + vars('x','y') + f = 3*x*y - x^2*y + fxy = diff(f,x,y) +\end{CAS} +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','y') + f = 3*x*y - x^2*y + fxy = diff(f,x,y) +\end{CAS} +\end{minted} +The above code will compute the mixed partial derivative $f_{xy}$ of the function $f$ defined by +\[ f(x,y)=3xy-x^2y.\] +There are various methods for fetching and/or printing results from the CAS within your \LaTeX{} document: + +\begin{codebox} +\begin{minted}{latex} + \[ \print{fxy} = \print*{fxy} \] +\end{minted} +\tcblower +\[ \print{fxy} = \print*{fxy} \] +\end{codebox} + +\subsection{About} + +The core CAS program is written purely in Lua and integrated into \LaTeX{} via Lua\LaTeX{}. Currently, most existing computer algebra systems such as Maple and Mathematica allow for converting their stored expressions to \LaTeX{} code, but this still requires exporting code from \LaTeX{} to another program and importing it back, which can be tedious. + +The target audience for this package are mathematics students, instructors, and professionals who would like some ability to perform basic symbolic computations within \LaTeX{} without the need for laborious and technical setup. But truly, this package was born out of a desire from the authors to learn more about symbolic computation. What you're looking at here is the proverbial ``carrot at the end of the stick'' to keep our learning moving forward. + +Using a scripting language (like Lua) as opposed to a compiled language for the core CAS reduces performance dramatically, but the following considerations make it a good option for our intentions: + +\begin{itemize} + \item Compiled languages that can communicate with \LaTeX{} in some way (such as C through Lua) require compiling the code on each machine before running, reducing portability. + \item Our target usage would generally not involve computations that take longer than a second, such as factoring large primes or polynomials. + \item Lua is a fast scripting language, especially when compared to Python, and is designed to be compact and portable. + \item If C code could be used, we could tie into one of many open-source C symbolic calculators, but the point of this project was (and continues to be) to learn the mathematics of symbolic computation. The barebones but friendly nature of Lua made it an ideal language for those intents. +\end{itemize} + +\subsection{Features} + +Currently, {\ttfamily luacas} includes the following functionality: + +\begin{itemize} + \item Arbitrary-precision integer and rational arithmetic + \item Number-theoretic algorithms for factoring integers and determining primality + \item Constructors for arbitrary polynomial rings and integer mod rings, and arithmetic algorithms for both + \item Factoring univariate polynomials over the rationals and over finite fields + \item Polynomial decomposition and some multivariate functionality, such as pseudodivision + \item Basic symbolic root finding and equation solving + \item Symbolic expression manipulations such as expansion, substitution, and simplification + \item Symbolic differentiation and integration +\end{itemize} + +The CAS is written using object-oriented Lua, so it is modular and would be easy to extend its functionality. + +\subsection{Acknowledgements} + +We'd like to thank the faculty of the Department of Mathematics at Rose-Hulman Institute of Technology for offering constructive feedback as we worked on this project. A special thanks goes to Dr. Joseph Eichholz for his invaluable input and helpful suggestions. + +\section{Installation} + +\subsection{Requirements} + +The \texttt{luacas} package (naturally) requires you to compile with Lua\LaTeX{}. Lua 5.3 or higher is also required. Beyond that, the following packages are needed: +\begin{multicols}{2} +{\ttfamily +\begin{itemize} + \item xparse + \item pgfkeys + \item verbatim + \item mathtools + \item luacode + \item iftex + \item tikz/forest + \item xcolor +\end{itemize}} +\end{multicols} +The packages {\ttfamily tikz}, {\ttfamily forest}, and {\ttfamily xcolor} aren't strictly required, but they are needed for drawing expression trees. + +\subsection{Installing {\ttfamily luacas}} +The package manager for your local TeX distribution ought to install the package fine on its own. But for those who like to take matters into their own hands: unpack \texttt{luacas.zip} in the current working directory (or in a directory visible to TeX, like your local texmf directory), and in the preamble of your document, put: +\begin{minted}{latex} +\usepackage{luacas} +\end{minted} +That's it, you're ready to go. + +\subsection{Todo} + +Beyond squashing bugs that inevitably exist in any new piece of software, future enhancements to \texttt{luacas} may include: +\begin{itemize} + \item Improvements to existing functionality, e.g., a more powerful \texttt{simplify()} command and more powerful expression manipulation tools in general, particularly in relation to complex numbers, a designated class for multivariable polynomial rings, irreducible factorization over multivariable polynomial rings, and performance improvements; + \item New features in the existing packages, such as sum and product expressions \& symbolic evaluation of both, and symbolic differential equation solving; + \item New packages, such as for logic (boolean expressions), set theory (sets), and linear algebra (vectors and matrices), and autosimplification rules and algorithms for all of them; + \item Numeric functionality, such as numeric root-finding, linear algebra, integration, and differentiation; + \item A parser capable of evaluating arbitrary \LaTeX{} code and turning it into CAS expressions. +\end{itemize} + + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/intro/intropart.tex b/macros/luatex/latex/luacas/doc/intro/intropart.tex new file mode 100644 index 0000000000..bfeb4a79b8 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/intro/intropart.tex @@ -0,0 +1,202 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + +\makeindex + +\def\currref{Core Methods} + +\newcommand{\coderef}[2]{% +\index{\currref!\texttt{#1}}% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\index{\currref!\texttt{#1}}% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\begin{document} +\thispagestyle{empty} +\part{Introduction} + +\begin{multicols}{2} + \begin{minted}{latex} +\begin{CAS} + vars('x') + f = x + for i in range(1,9) do + f = f*x + end + f = f-1 +\end{CAS} +\parseforest{f} +\bracketset{action character = @} +\begin{center} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} +\end{center} + +\begin{CAS} + f = factor(f) +\end{CAS} +\parseforest{f} +\begin{center} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} +\end{center} +\end{minted} + +\columnbreak +\phantom{} +\vfill +\begin{CAS} + vars('x') + f = x + for i in range(1,9) do + f = f*x + end + f = f-1 +\end{CAS} +\parseforest{f} +\bracketset{action character = @} +\begin{center} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} +\end{center} +\vfill +\phantom{} +\end{multicols} +\vfill + +\begin{CAS} + f = factor(f) +\end{CAS} +\parseforest{f} +\begin{center} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} +\end{center} +\vfill + + +\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 new file mode 100644 index 0000000000..d3ea07adf3 Binary files /dev/null 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 new file mode 100644 index 0000000000..b5aad9c9f9 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/luacas.tex @@ -0,0 +1,214 @@ +\documentclass{article} + +\usepackage{standalone} +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage[edges]{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} +%\usepackage[toc]{multitoc} + +\renewcommand{\comment}[1]{} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle, + %frame hidden +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\DeclareTotalTCBox{\lilcoderef}{O{} m m}{ + enhanced, + frame hidden, + colback=rosegray!15, + enhanced, + nobeforeafter, + tcbox raise base, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=1mm, + right=1mm, + top=1mm, + bottom=1mm, + oversize, + #1 +}{\mintinline{lua}{#2} \mintinline{lua}{#3}} + + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} +\usepackage{makeidx} +\usepackage{fontawesome5} +\usepackage{marginnote} +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\def\error{\color{red}} +\def\self{\color{gray}} +\def\call{} + +%\includeonly{ +% reference/ref_calculus/ref_calculus_methods/ref_calculus_methods_test.tex +%} + +\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} + +\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{}. +\end{abstract} + +\tableofcontents + +\include{intro/intropart.tex} + +\include{intro/intro.tex} + +\include{tutorial/tut.tex} + +\include{tutorial/tut1/tut1.tex} + +\include{tutorial/tut2/tut2.tex} + +\include{tutorial/tut3/tut3.tex} + +\include{reference/ref.tex} + +\include{reference/ref_core/ref_core.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_algebra/ref_algebra.tex} + +\include{reference/ref_algebra/ref_algebra_classes/ref_algebra_classes.tex} + +\include{reference/ref_algebra/ref_algebra_methods/ref_algebra_methods.tex} + +\include{reference/ref_calculus/ref_calculus.tex} + +\include{reference/ref_calculus/ref_calculus_classes/ref_calculus_classes.tex} + +\include{reference/ref_calculus/ref_calculus_methods/ref_calculus_methods.tex} + +\appendix + +\include{appendix/latexcode.tex} + +\newpage + +\printindex + + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/reference/ref.tex b/macros/luatex/latex/luacas/doc/reference/ref.tex new file mode 100644 index 0000000000..426a1e17e5 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref.tex @@ -0,0 +1,236 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage[edges]{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\begin{document} +\thispagestyle{empty} +\part{Reference} +This part contains reference material for the classes and methods that incorporate the \texttt{luacas} package. Some classes are \emph{concrete} while others are \emph{abstract}. The concrete classes are essentially the objects that a user might reasonably interact with while using \texttt{luacas}. Thankfully, most of this interaction will be filtered through a rudimentary (but functional!) parser. Abstract classes exist for the purposes of inheritance. + +The classes in the diagram below are color-coded according to: +\begin{itemize} + \item (\tikz[baseline=-0.5ex]\node[text=rosenavy,fill=roseblue!40]{\ttfamily\bfseries Class};) {\color{rosenavy}\ttfamily\bfseries Class}: a (concrete) class belonging to the core module; + \item (\tikz[baseline=-0.5ex]\node[text=rose,fill=rose!30]{\ttfamily\bfseries Class};) {\color{rose}\ttfamily\bfseries Class}: a (concrete) class belonging to the algebra module; + \item (\tikz[baseline=-0.5ex]\node[text=roseorange,fill=roseorange!30]{\ttfamily\bfseries Class};) {\color{roseorange}\ttfamily\bfseries Class}: a (concrete) class belonging to the calculus module. +\end{itemize} +Inheritance is indicated with an arrow: + +\forestset{multiple directions/.style={for tree={#1}, phantom, for relative level=1{no edge, delay={!c.content/.pgfmath=content("!u")}, before computing xy={l=0,s=0}}}, + multiple directions/.default={}, + grow subtree/.style={for tree={grow=#1}}, + grow' subtree/.style={for tree={grow'=#1}}} + +\begin{center} + \begin{forest} + core/.style = {draw={rosenavy,thick}, + font={\ttfamily\bfseries\color{rosenavy}}, + fill = roseblue!#1}, + algebra/.style = {draw={rose,thick}, + font={\ttfamily\bfseries\color{rose}}, + fill = rose!#1}, + calculus/.style = {draw={roseorange,thick}, + font={\ttfamily\bfseries\color{roseorange}}, + fill = roseorange!#1}, + multiple directions, + for tree = {font = \ttfamily\bfseries, + draw, + rounded corners=1pt, + edge = {-stealth}, + s sep = 0.2cm} + [Expression + [, grow subtree = 90 + [AtomicExpression, + core = {0}, + for tree = {grow=east, + child anchor=west, + node options={anchor=west}}, + forked edges, + calign=last, + l sep = 0.5cm, + s sep = 0.2cm + [SymbolExpression, + core={50}] + [ConstantExpression, + core ={0}, + for tree = {grow = north, + node options = {anchor=north} + } + [Ring, + algebra ={0}, + for tree = {grow = east} + [EuclideanDomain, + algebra={0} + [Field, + algebra = {0} + [IntegerModN, + algebra={30}] + [Rational, + algebra={30}] + ] + [Integer, + algebra={30}] + ] + [PolynomialRing, + algebra={30}] + ] + ] + ]] + [, grow subtree = -90 + [CompoundExpression, + core = {0}, + for tree = {grow=east, + child anchor=west, + node options={anchor=west}},forked edges, + calign=last, + l sep = 0.5cm, + s sep = 0.2cm + [FunctionExpression, + core = {40} + [TrigExpression,algebra={30}] + ] + [BinaryOperation, + core = {40}] + [Equation, + algebra = {30}] + [Logarithm, + algebra={30}] + [FactorialExpression, + algebra={30}] + [RootExpression, + algebra={30}] + [SqrtExpression, + algebra={30}] + [AbsExpression, + algebra={30}] + [DerivativeExpression, + calculus={20}] + [DiffExpression, + calculus={20}] + [IntegralExpression, + calculus={20}] + ]] + ] + \end{forest} +\end{center} +Every object in \texttt{luacas} is an expression, meaning it inherits from the {\ttfamily Expression} type (class). Since the {\ttfamily Expression} type itself has no constructor and cannot be instantiated, it it closer to an interface in Java OOP terms.\footnote{In reality, interfaces are unnecessary in Lua due to its weak typing - Lua doesn't check whether an object has a method at compile time. The {\ttfamily Expression} type is really an abstract class in Java terms.} {\ttfamily Expression}s can store any number of other expressions as sub-expressions, depending on type. This means that {\ttfamily Expression} objects are really trees. Types that inherit from {\ttfamily Expression} that can not store other expressions are called \emph{atomic expressions}, and correspond to the leaf nodes of the tree. Other expression types are \emph{compound expressions}. Thus, every {\ttfamily Expression} type inherits from one of {\ttfamily AtomicExpression} or {\ttfamily CompoundExpression}. The {\ttfamily ConstantExpresssion} interface is a subinterface to {\ttfamily AtomicExpression}. Types that inherit from {\ttfamily ConstantExpression} roughly correspond to numbers (interpreted broadly). + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra.tex b/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra.tex new file mode 100644 index 0000000000..0e9bd011e8 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra.tex @@ -0,0 +1,336 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden, + size=small, + bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white} + }, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\usetikzlibrary{shapes.multipart} +\useforestlibrary{edges} + +\def\error{\color{red}} +\def\self{\color{gray}} +\def\call{$\star$ } + +\begin{document} +\thispagestyle{empty} + +\section{Algebra} + This section contains reference materials for the algebra functionality of \texttt{luacas}. The classes in this module are diagramed below according to inheritance along with the methods/functions one can call upon them. + \begin{itemize} + \item {\error\ttfamily\itshape method}: an abstract method; + \item {\self\ttfamily\itshape method}: a method that returns the expression unchanged; + \item {\ttfamily\itshape method}: method that is either unique, implements an abstract method, or overrides an abstract +method; + \item {\tikz[baseline=-0.5ex]\node[fill=rose!30] {\ttfamily\bfseries Class};}: a concrete class. + \end{itemize} +Here is an inhertiance diagram of the classes in the algebra module that are derived from the \texttt{AtomicExpression} branch of classes. However, not all of them are proper {\ttfamily ConstantExpression}s, so some of them override the {\ttfamily isconstant()} method. Most methods are stated, but some were omitted (because they inherit in the obvious way, they are auxiliary and not likely to be interesting to the end-user, etc). + \vfill +\forestset{ +rectcore/.style = {rectangle split, + rectangle split parts=2, + draw = {rosenavy,thick}, + rounded corners = 1pt, + font = \ttfamily\bfseries, + fill = roseblue!#1 + }, +rectalg/.style = {rectangle split, + rectangle split parts=2, + draw = {rose,thick}, + rounded corners = 1pt, + font = \ttfamily\bfseries, + fill = rose!#1 + } +} +\forestset{ + multiple directions/.style={ + for tree={#1}, + phantom, + for relative level=1{ + no edge, + delay={ + !c.content/.pgfmath=content("!u")}, + before computing xy={l=0,s=0} + } + }, + multiple directions/.default={}, + grow subtree/.style={for tree={grow=#1}}, + grow' subtree/.style={for tree={grow'=#1}}} +\tikzset{ + every two node part/.style={font=\ttfamily\itshape\footnotesize} +} +\begin{center} + \begin{forest} + for tree = {node options={align=left}, + edge = {-stealth} + }, + forked edges + [Expression\nodepart{two}$\cdots$,rectcore={0} + [AtomicExpression\nodepart{two}$\cdots$,rectcore={0} + [SymbolExpression\nodepart{two}$\cdots$,rectcore={50}] + [ConstantExpression\nodepart{two}$\cdots$,rectcore={0} + [Ring\nodepart{two}\begin{minipage}{0.45\textwidth}\begin{multicols}{3} + {\error :getring}\\ + {\error :inring(ring)}\\ + {\error :iscommutative}\\ + {\error :add(b)}\\ + :sub(b)\\ + {\error :neg(b)}\\ + {\error :mul(b)}\\ + :pow(n)\\ + {\error :eq(b)}\\ + {\error :lt(b)}\\ + {\error :le(b)}\\ + {\error :zero()}\\ + {\error :one()}\\ + {\error .makering}\\ + .resultantring + \end{multicols}\end{minipage} + ,rectalg={0} + [PolynomialRing\nodepart{two} + .makering\\ + .R\\ + .gcd\\ + .extendedgcd\\ + .resultant\\ + .resultantmulti\\ + .monicgcdremainders\\ + .partialfractions\\ + %:isatomic\\ + :new\\ + .mul{\textunderscore}rec\\ + :divremainder\\ + :psuedodivide\\ + :isconstant\\ + :isatomic\\ + :freeof\\ + :tocompoundexpression\\ + :evaluateat\\ + :derivative\\ + :squarefreefactorization\\ + :factor\\ + :rationalroots\\ + :roots\\ + $\cdots$ + ,rectalg={30}] + [EuclideanDomain\nodepart{two} + {\error :divremainder}\\ + :iscommutative + ,rectalg={0} + [Integer\nodepart{two} + .gcd\\ + .extendedgcd\\ + .max\\ + .min\\ + .ceillog\\ + .powmod\\ + :new\\ + :divremainder\\ + :asnumber\\ + :divisors\\ + :primefactorization\\ + :findafactor\\ + :isprime\\ + :abs\\ + $\cdots$ + ,rectalg={30}] + [Field\nodepart{two} + :div + ,rectalg={0} + [Rational\nodepart{two} + :new\\ + :reduce\\ + :isconstant\\ + :tocompoundexpression\\ + :asnumber\\ + :div + ,rectalg={30}] + [IntegerModN\nodepart{two} + :new + ,rectalg={30}] + ] + ] + ] + ] + ] + ] + \end{forest} +\end{center} +\vfill + +\newpage + +Here is an inhertiance diagram of the classes in the algebra module that are derived from the \texttt{CompoundExpression} branch of classes. Again, most methods are stated, but some were omitted (because they inherit in the obvious way, they are auxiliary and not likely to be interesting to the end-user, etc). +\vfill +\begin{center} + \begin{forest} + for tree = {node options={align=left}, + grow = south, + edge = {-stealth}, + child anchor = west + }, + forked edges + [Expression\nodepart{two}$\cdots$, + rectcore={0} + [CompoundExpression\nodepart{two} + $\cdots$, + rectcore={0}, + calign = first, + for tree = {node options = {anchor = west}, + grow' = east} + [BinaryOperation\nodepart{two} + $\cdots$, + rectcore={50}] + [FunctionExpression\nodepart{two} + $\cdots$, + rectcore={50} + [TrigExpression\nodepart{two} + :new + ,rectalg={30}] + ] + [AbsExpression\nodepart{two} + :new + ,rectalg={30}] + [SqrtExpression\nodepart{two} + :new\\ + :topower + ,rectalg={30}] + [RootExpression\nodepart{two} + :new + ,rectalg={30}] + [FactorialExpression\nodepart{two} + :new + ,rectalg={30}] + [Logarithm\nodepart{two} + :new + ,rectalg={30}] + [Equation\nodepart{two} + :new\\ + :solvefor() + ,rectalg={30}] + ] + ] + \end{forest} +\end{center} +\vfill +\end{document} + diff --git a/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_classes/ref_algebra_classes.tex b/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_classes/ref_algebra_classes.tex new file mode 100644 index 0000000000..58d9b69f84 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_classes/ref_algebra_classes.tex @@ -0,0 +1,977 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} +\usepackage{fontawesome} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\begin{document} +\setdescription{style=multiline, + topsep=10pt, + leftmargin=5cm, + } + +\subsection{Algebra Classes} + +The algebra package contains functionality for arbitrary-precision arithmetic, polynomial arithmetic and factoring, symbolic root finding, and logarithm and trigonometric expression classes. It requires the core package to be loaded. + +The abstract classes in the algebra module all inherit from the \texttt{ConstantExpression} branch in the inheritance tree: + +\begin{itemize} + \item \texttt{Ring} + \item \texttt{EuclideanDomain} + \item \texttt{Field} +\end{itemize} + +The {\ttfamily EuclideanDomain} class is a sub-class to the {\ttfamily Ring} class, and the {\ttfamily Field} class is a sub-class to the {\ttfamily EuclideanDomain} class. + +The following concrete classes inherit from the {\ttfamily Ring} class (or one of the sub-classes mentioned above). However, not all of them are proper {\ttfamily ConstantExpression}s, so some of them override the {\ttfamily isconstant()} method. + +\begin{itemize} + \item {\ttfamily Integer} + \item {\ttfamily IntegerModN} + \item {\ttfamily Rational} + \item {\ttfamily PolynomialRing} +\end{itemize} + +The other concrete classes in the Algebra package do not inherit from the {\ttfamily Ring} interface, instead they inherit from the {\ttfamily CompoundExpression} interface: + +\begin{multicols}{2} +\begin{itemize} + \item {\ttfamily AbsExpression} + \item {\ttfamily Logarithm} + \item {\ttfamily FactorialExpression} + \item {\ttfamily SqrtExpression} + \item {\ttfamily TrigExpression} + \item {\ttfamily RootExpression} + \item {\ttfamily Equation} +\end{itemize} +\end{multicols} + +\newcoderef{function Integer:new(n)}{return Integer}{n number|string|Integer} +\index{Algebra!Classes!\texttt{SymbolExpression}} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer} + +Takes a \texttt{string}, \texttt{number}, or {\ttfamily Integer} input and constructs an \texttt{Integer} expression. The \texttt{Integer} class allows us to perform exact arithmetic on integers. Indeed, since Lua can only store integers exactly up to a certain point, it is recommended to use strings to build large integers. + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +a = Integer(-12435) +b = Integer('-12435') +tex.print('\\[',a:tolatex(), + '=', + b:tolatex(), + '\\]') +\end{minted} +\tcblower +\directlua{ + a = Integer(-12435) + b = Integer('-12435') + tex.print('\\[',a:tolatex(), + '=', + b:tolatex(), + '\\]') +} +\end{codebox} +An {\ttfamily Integer} is a table 1-indexed by Lua numbers consisting of Lua numbers. For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +tex.print(tostring(b[1])) +\end{minted} +\tcblower +\directlua{ + tex.print(tostring(b[1])) +} +\end{codebox} +Whereas: +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +c = Integer('7240531360949381947528131508') +tex.print('The first 14 digits of c:', tostring(c[1]),'. ') +tex.print('The last 14 digits of c:', tostring([2]),'.') +\end{minted} +\tcblower +\directlua{ + c = Integer('7240531360949381947528131508') + tex.print('The first 14 digits of c:', tostring(c[1]),'. ') + tex.print('The last 14 digits of c:', tostring(c[2]),'.') +} +\end{codebox} + +The global field {\ttfamily DIGITSIZE} is set to \texttt{14} so that exact arithmetic on {\ttfamily Integer}s can be done as efficiently as possible while respecting Lua's limitations. + +\subsubsection*{Fields} +{\ttfamily Integer}s have a {\ttfamily .sign} field which contains the Lua number {\ttfamily 1} or {\ttfamily -1} depending on whether \texttt{Integer} is positive or negative. +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +tex.print('The sign of',tostring(b),'is:',tostring(b.sign)) +\end{minted} +\tcblower +\directlua{ + tex.print('The sign of', + tostring(b), + 'is:', + tostring(b.sign)) +} +\end{codebox} + +\subsubsection*{Parsing} + +The contents of the environment \mintinline{latex}{\begin{CAS}..\end{CAS}} are wrapped in the argument of a function \mintinline{lua}{CASparse()} which, among other things, seeks out digit strings intended to represent integers, and wraps those in \texttt{Integer('...')}. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + c = 7240531360949381947528131508 +\end{CAS} +\directlua{ + tex.print(tostring(c[1])) +} +\end{minted} +\tcblower +\begin{CAS} + c = 7240531360949381947528131508 +\end{CAS} +\directlua{ + tex.print(tostring(c[1])) +} +\end{codebox} + +\newcoderef{function IntegerModN:new(i,n)}{return IntegerModN}{i Integer, n Integer} +\index{Algebra!Classes!\texttt{IntegerModN}} +\addcontentsline{toc}{subsubsection}{\ttfamily IntegerModN} + +Takes an {\ttfamily Integer i} and {\ttfamily Integer n} and constructs an element in the ring $\mathbf{Z}/n\mathbf{Z}$, the integers modulo $n$. + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +i = Integer(143) +n = Integer(57) +a = IntegerModN(i,n) +tex.print('\\[',i:tolatex(),'\\equiv',a:tolatex(true),'\\]') +\end{minted} +\tcblower +\luaexec{ + i = Integer(143) + n = Integer(57) + a = IntegerModN(i,n) + tex.print('\\[',i:tolatex(),'\\equiv',a:tolatex(true),'\\]') +} +\end{codebox} + +\subsubsection*{Fields} + +{\ttfamily IntegerModN}s have two fields: {\ttfamily .element} and {\ttfamily .modulus}. The reduced input \texttt{i} is stored in {\ttfamily .element} while the input \texttt{n} is stored in {\ttfamily .modulus}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +tex.print(a.element:tolatex(),'\\newline') +tex.print(a.modulus:tolatex()) +\end{minted} +\tcblower +\luaexec{ + tex.print(a.element:tolatex(),'\\newline') + tex.print(a.modulus:tolatex()) +} +\end{codebox} + +\subsubsection*{Parsing} + +The function \texttt{Mod(,)} is a shortcut for \texttt{IntegerModN(,)}: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + i = 143 + n = 57 + a = Mod(i,n) +\end{CAS} +\[\print{i}\equiv\print{a}\bmod{\print{n}}\] +\end{minted} +\tcblower +\begin{CAS} + i = 143 + n = 57 + a = Mod(i,n) +\end{CAS} +\[ \print{i} \equiv \print{a} \bmod{\print{n}}\] +\end{codebox} + +\newcoderef{function PolynomialRing:new(coefficients, symbol, degree)}{return PolynomialRing}{coefficients table, symbol string|SymbolExpression, degree Integer} +\index{Algebra!Classes!\texttt{PolynomialRing}} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing} + +Takes a table of {\ttfamily coefficients}, not all necessarily in the same ring, and a {\ttfamily symbol} to create a polynomial in $\mathtt{R[x]}$ where $\mathtt{x}$ is {\ttfamily symbol} and $\mathtt{R}$ is the smallest {\ttfamily Ring} possible given the coefficients. If {\ttfamily degree} is omitted, it will calculate the degree of the polynomial automatically. The list can either be one-indexed or zero-indexed, but if it is one-indexed, the internal list of coefficients will still be zero-indexed. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = PolynomialRing({0,1/3,-1/2,1/6},'t') +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\begin{CAS} + f = PolynomialRing({0,1/3,-1/2,1/6},'t') +\end{CAS} +\[ \print{f} \] +\end{codebox} +The \texttt{PolynomialRing} class overwrites the \mintinline{lua}{isatomic()} and \mintinline{lua}{isconstant()} inheritances from the abstract class \texttt{ConstantExpression}. +\subsubsection*{Fields} + +\begin{multicols}{2} +{\ttfamily PolynomialRing}s have several fields: +\begin{itemize} + \item {\ttfamily f.coefficients} stores the 0-indexed table of coefficients of {\ttfamily f}; + \item {\ttfamily f.degree} stores the {\ttfamily Integer} that represents the degree of {\ttfamily f}; + \item {\ttfamily f.symbol} stores the {\ttfamily string} representing the variable or {\ttfamily symbol} of {\ttfamily f}. + \item {\ttfamily f.ring} stores the \texttt{RingIdentifier} for the ring of coefficients. +\end{itemize} + +\columnbreak + +\parseshrub{f} +\bracketset{action character = @} +\begin{center} +\begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners = 1pt, + fill=gray!20, + s sep = 1.5cm, + l sep = 2cm} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} +For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +for i=0,f.degree:asnumber() do + tex.print('\\[', + f.coefficients[i]:tolatex(), + f.symbol, + '^{', + tostring(i), + '}\\]') +end +if f.ring == Rational.getring() then + tex.print('Rational coefficients') +end +\end{minted} +\tcblower +\luaexec{ +for i=0,f.degree:asnumber() do + tex.print( + '\\[', + f.coefficients[i]:tolatex(), + f.symbol, + '^{', + tostring(i), + '}\\]' + ) + end + if f.ring == Rational.getring() then + tex.print('Rational coefficients') + end +} +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{Poly()} is a shortcut for \mintinline{lua}{PolynomialRing:new()}. If the second argument \texttt{symbol} is omitted, then the default is \texttt{'x'}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({0,1/3,-1/2,1/6}) +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({0,1/3,-1/2,1/6}) +\end{CAS} +\[ \print{f} \] +\end{codebox} + +Alternatively, one could typeset the polynomial naturally and use the \texttt{topoly()} function. This is the same as the \texttt{topolynomial()} method except that the \texttt{autosimplify()} method is automatically called first: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = 1/3*x - 1/2*x^2 + 1/6*x^3 + f = topoly(f) +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = 1/3*x - 1/2*x^2 + 1/6*x^3 + f = topoly(f) +\end{CAS} +\[ \print{f} \] +\end{codebox} + +\newcoderef{function Rational:new(n,d,keep)}{return Rational}{n Ring, d Ring, keep bool} +\index{Algebra!Classes!\texttt{Rational}} +\addcontentsline{toc}{subsubsection}{\ttfamily Rational} + +Takes a numerator {\ttfamily n} and denominator {\ttfamily d} in the same {\ttfamily Ring} and constructs a rational expression in the field of fractions over that ring. For the integers, this is the ring of rational numbers. If the {\ttfamily keep} flag is omitted, the constructed object will be simplified to have smallest possible denominator, possibly returning an object in the original {\ttfamily Ring}. Typically, the {\ttfamily Ring} will be either {\ttfamily Integer} or {\ttfamily PolynomialRing}, so {\ttfamily Rational} can be viewed as a constructor for either a rational number or a rational function. + +For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +a = Integer(6) +b = Integer(10) +c = Rational(a,b) +tex.print('\\[',c:tolatex(),'\\]') +\end{minted} +\tcblower +\luaexec{ + a = Integer(6) + b = Integer(10) + c = Rational(a,b) + tex.print('\\[',c:tolatex(),'\\]') +} +\end{codebox} +But also: +\begin{codebox} + \begin{minted}{lua} +a = Poly({Integer(2),Integer(3)}) +b = Poly({Integer(4),Integer(1)}) +c = Rational(a,b) +tex.print('\\[',c:tolatex(),'\\]') +\end{minted} +\tcblower +\luaexec{ +a = Poly({Integer(2),Integer(3)}) +b = Poly({Integer(4),Integer(1)}) +c = Rational(a,b) +tex.print('\\[',c:tolatex(),'\\]') +} +\end{codebox} + +\subsubsection*{Fields} + +\texttt{Rational}s naturally have the two fields: \texttt{numerator}, \texttt{denominator}. These fields store precisely what you think. \texttt{Rational}s also have a \texttt{ring} field which stores the \texttt{RingIdentifier} to which the numerator and denominator belong. (This is $\mathbb{Z}$ for the rational numbers.) + +If \texttt{numerator} or \texttt{denominator} are \texttt{PolynomialRing}s, then the constructed \texttt{Rational} will have an additional field: \texttt{symbol}. This stores the symbol the polynomial rings are constructed over. + +\begin{codebox}[] + \begin{minted}{lua} +if c.ring == PolynomialRing.getring() then + tex.print('$',c:tolatex(),'$ is a Rational Function in the variable',c.symbol) +end +\end{minted} +\tcblower +\luaexec{ +if c.ring == PolynomialRing.getring() then + tex.print('$',c:tolatex(),'$ is a Rational Function in the variable',c.symbol) +end +} +\end{codebox} + +\subsubsection*{Parsing} + +\texttt{Raional}s are constructed naturally using the \texttt{/} operator: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = Poly({2,3}) + b = Poly({4,1}) + c = a/b +\end{CAS} +\[ \print{c} \] +\end{minted} +\tcblower +\begin{CAS} + a = Poly({2,3}) + b = Poly({4,1}) + c = a/b +\end{CAS} +\[ \print{c} \] +\end{codebox} + +\coderef{function AbsExpression:new(expression)}{return AbsExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily AbsExpression} + +Creates a new absolute value expression with the given expression. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({1,1}) + g = Poly({-1,1}) + h = AbsExpression(f/g) +\end{CAS} +\[ h = \print{h} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({1,1}) + g = Poly({-1,1}) + h = AbsExpression(f/g) +\end{CAS} +\[ h = \print{h} \] +\end{codebox} + +\subsubsection*{Fields} + +\texttt{AbsExpression}s have only one field: \texttt{.expression}. This field simply holds the \texttt{Expression} inside the absolute value: +\begin{multicols}{2} +\begin{codebox}[] +\begin{minted}[fontsize=\small]{lua} +tex.print('\\[', + h.expression:tolatex(), + '\\]') +\end{minted} +\tcblower +\directlua{ + tex.print('\\[',h.expression:tolatex(),'\\]') +} +\end{codebox} +\parseshrub{h} +\bracketset{action character = @} +\begin{center} +\begin{forest} + for tree = {font=\ttfamily, + draw, + rounded corners=1pt, + fill=gray!20, + l sep =1.5cm} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{abs()} is a shortcut to \mintinline{lua}{AbsExpression:new()}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({1,1}) + g = Poly({-1,1}) + h = abs(f/g) +\end{CAS} +\[ h = \print{h} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({1,1}) + g = Poly({-1,1}) + h = abs(f/g) +\end{CAS} +\[ h = \print{h} \] +\end{codebox} + +\newcoderef{function Logarithm:new(base,arg)}{return Logarithm}{base Expression, arg Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily Logarithm} + +Creates a new \texttt{Logarithm} expression with the given \texttt{base} and \texttt{arg}ument. Some basic simplification rules are known to \texttt{autosimplify()}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('b','x','y') + f = Logarithm(b,x^y) +\end{CAS} +\[ \print{f} = \print*{f} \] +\end{minted} +\tcblower +\begin{CAS} + vars('b','x','y') + f = Logarithm(b,x^y) +\end{CAS} +\[ \print{f} = \print*{f} \] +\end{codebox} + +\subsubsection*{Fields} + +\begin{multicols}{2} +\texttt{Logarithm}s have two fields: \texttt{base} and \texttt{expression}; \texttt{base} naturally stores the base of the logarithm (i.e., the first argument of \texttt{Logarithm}) while \texttt{expression} stores the argument of the logarithm (i.e., the second argument of \texttt{Logarithm}). + +\begin{center} + \parseshrub{f} + \bracketset{action character = @} + \begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners=1pt, + fill = gray!20, + s sep = 1.5cm} + @\shrubresult + \end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{log()} is a shortcut to \texttt{Logarithm}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('b') + f = log(b,b) +\end{CAS} +\[ \print{f} = \print*{f} \] +\end{minted} +\tcblower +\begin{CAS} + vars('b') + f = log(b,b) +\end{CAS} +\[ \print{f} = \print*{f} \] +\end{codebox} + +There is also a \mintinline{lua}{ln()} function to shortcut \texttt{Logarithm} where the base is \texttt{e}, the natural exponent. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = ln(e) +\end{CAS} +\[ \print{f} = \print*{f} \] +\end{minted} +\tcblower +\begin{CAS} + f = ln(e) +\end{CAS} +\[ \print{f} = \print*{f} \] +\end{codebox} + +\newcoderef{function FactorialExpression:new(expression)}{return FactorialExpression}{expression Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily FactorialExpression} + +Creates a new \texttt{FactorialExpression} with the given \texttt{expression}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = FactorialExpression(5) +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + a = FactorialExpression(5) +\end{CAS} +\[ \print{a} \] +\end{codebox} +The \texttt{evaluate()} method will compute factorials of nonnegative \texttt{Integer}s: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = FactorialExpression(5) +\end{CAS} +\[ \print{a} = \print{a:evaluate()} \] +\end{minted} +\tcblower +\begin{CAS} + a = FactorialExpression(5) +\end{CAS} +\[ \print{a} = \print{a:evaluate()} \] +\end{codebox} + +\subsubsection*{Fields} + +\texttt{FactorialExpression}s have only one field: \texttt{expression}. This field stores the argument of \texttt{FactorialExpression()}. + +\subsubsection*{Parsing} + +The function \mintinline{lua}{factorial()} is a shortcut to \texttt{FactorialExpression()}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = factorial(5) +\end{CAS} +\[ \print{a} = \print{a:evaluate()} \] +\end{minted} +\tcblower +\begin{CAS} + a = factorial(5) +\end{CAS} +\[ \print{a} = \print{a:evaluate()} \] +\end{codebox} + +\newcoderef{function SqrtExpression:new(expression, root)}{return SqrtExpression}{expression Expression, root Integer} +\addcontentsline{toc}{subsubsection}{\ttfamily SqrtExpression} + +Creates a new \texttt{SqrtExpression} with the given \texttt{expression} and \texttt{root}. Typically, \texttt{expression} is an \texttt{Integer} or \texttt{Rational}, and \texttt{SqrtExpression} is intended to represent a positive real number. If \texttt{root} is omitted, then \texttt{root} defaults to \mintinline{lua}{Integer(2)}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +a = SqrtExpression(Integer(8)) +b = SqrtExpression(Integer(8),Integer(3)) +c = a+b +tex.print('\\[',c:tolatex(),'\\]') +\end{minted} +\tcblower +\directlua{ + a = SqrtExpression(Integer(8)) +b = SqrtExpression(Integer(8),Integer(3)) +c = a+b +tex.print('\\[',c:tolatex(),'\\]') +} +\end{codebox} +When \texttt{expression} and \texttt{root} are of the \texttt{Integer} or \texttt{Rational} types, then \texttt{autosimplify()} does a couple things. For example, with \texttt{a,b} as above, we get: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +c = c:autosimplify() +tex.print('\\[',c:tolatex(),'\\]') +\end{minted} +\tcblower +\directlua{ +c = c:autosimplify() +tex.print('\\[',c:tolatex(),'\\]') +} +\end{codebox} +On the other hand, if \texttt{root} or \texttt{expression} are not constants, then typically \mintinline{lua}{autosimplify()} will convert \texttt{SqrtExpression} to the appropriate \texttt{BinaryOperation}. For example: + +\directlua{ + vars('x') + a = SqrtExpression(x,Integer(3)) + b = a:autosimplify() +} + +\begin{multicols}{2} + \begin{center} + \underline{Tree for \texttt{a}} + +\parseshrub{a} +\bracketset{action character = @} +\begin{forest} + for tree = {s sep=2cm, + font=\ttfamily, + draw, + rounded corners = 1pt, + fill=gray!20} + @\shrubresult +\end{forest} + + \underline{Tree for \texttt{a:autosimplify()}} + +\parseshrub{a:autosimplify()} +\bracketset{action character = @} +\begin{forest} + for tree = {s sep=2cm, + font=\ttfamily} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{sqrt()} shortcuts \texttt{SqrtExpression()}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = sqrt(1/9) + b = sqrt(27/16,3) + c = a+b +\end{CAS} +\[ \print{c} = \print*{c} \] +\end{minted} +\tcblower +\begin{CAS} + a = sqrt(1/9) + b = sqrt(27/16,3) + c = a+b +\end{CAS} +\[ \print{c} = \print*{c} \] +\end{codebox} + +\newcoderef{function TrigExpression:new(name,expression)}{return TrigExpression}{name string|SymbolExpression, expression Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily TrigExpression} + +Creates a new trig expression with the given \texttt{name} and \texttt{expression}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('x') +f = TrigExpression('sin',x) +tex.print('\\[',f:tolatex(),'\\]') +\end{minted} +\tcblower +\directlua{ + vars('x') + f = TrigExpression('sin',x) + tex.print('\\[',f:tolatex(),'\\]') +} +\end{codebox} + +\subsubsection*{Fields} + +\begin{multicols}{2} + +\texttt{TrigExpression}s have many fields: +\begin{itemize} + \item \mintinline{lua}{TrigExpression.name} stores the string \texttt{name}, i.e. the first argument of \mintinline{lua}{TrigExpression()}; + \item \mintinline{lua}{TrigExpression.expression} stores the \texttt{Expression} \texttt{expression}, i.e. the second argument of \mintinline{lua}{TrigExpression()}; + \item and all fields inherited from \texttt{FunctionExpression} (e.g. \mintinline{lua}{TrigExpression.derivatives} which defaults to \mintinline{lua}{Integer.zero()}). +\end{itemize} + +\columnbreak + +\begin{center} +\parseshrub{f} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners = 1pt, + fill = gray!20, + l sep = 2cm} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +The usual trigonometric functions have the anticipated shortcut names. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = arctan(x^2) +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\begin{CAS} + f = arctan(x^2) +\end{CAS} +\[ \print{f} \] +\end{codebox} + +\newcoderef{function RootExpression:new(expression)}{return RootExpression}{expression Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily RootExpression} + +Creates a new \texttt{RootExpression} with the given \texttt{expression}. The method \mintinline{lua}{RootExpression:autosimplify()} attempts to return a list of zeros of \texttt{expression}. If no such set can be found, then + +\mintinline{lua}{RootExpression(expression:autosimplify())} + +is returned instead. At the moment, \texttt{expression} must be a univariate polynomial of degree $0,1,2$ or $3$ in order for the \texttt{autosimplify()} method to return anything interesting. Of course, \texttt{luacas} can find roots of higher degree polynomials, but this involves more machinery/methods within the \texttt{PolynomialRing} class. + +\subsubsection*{Fields} +\texttt{RootExpression}s have only one field: \texttt{.expression}. For example: +\begin{multicols}{2} + \begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({3,2,1}) + r = RootExpression(f) +\end{CAS} +\[ \print{r} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({3,2,1}) + r = RootExpression(f) +\end{CAS} +\[ \print{r} \] +\end{codebox} + +\begin{center} +\parseshrub{r} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners=1pt, + fill=gray!20, + l sep = 2cm} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{roots()} essentially shortcuts \texttt{RootExpression()}, but when \texttt{expression} is of the \texttt{PolynomialRing}-type, then \texttt{PolynomialRing:roots()} is returned. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + r = roots(f) +\end{CAS} +\[ \print{r[1]} \qquad \print{r[2]} \] +\end{minted} +\tcblower +\begin{CAS} + r = roots(f) +\end{CAS} +\[ \print{r[1]} \qquad \print{r[2]} \] +\end{codebox} + + +\newcoderef{function Equation:new(lhs, rhs)}{return Equation}{lhs Expression, rhs Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily Equation} + +Creates a new \texttt{Equation} expression with the given \texttt{lhs} (left hand side) and \texttt{rhs} (right hand side). If both sides of the equation are constants, or structurally identical, \texttt{autosimplify()} will return a boolean: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','y') + f = Equation(sin(x-y),sin(x-y)) + g = f:autosimplify() +\end{CAS} +\[ \print{f} \to \print{g} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y') + f = Equation(sin(x-y),sin(x-y)) + g = f:autosimplify() +\end{CAS} +\[ \print{f} \to true \] +\end{codebox} + +\subsubsection*{Fields} + +\begin{multicols}{2} +\texttt{Equation}s have two fields: \texttt{lhs} and \texttt{rhs}; which store the expressions on the left and right sides of the equation. + +\begin{center} + \parseshrub{f} + \bracketset{action character = @} + \begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners=1pt, + fill = gray!20, + s sep = 1.5cm} + @\shrubresult + \end{forest} +\end{center} +\end{multicols} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_methods/ref_algebra_methods.tex b/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_methods/ref_algebra_methods.tex new file mode 100644 index 0000000000..b576cdc1a3 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_algebra/ref_algebra_methods/ref_algebra_methods.tex @@ -0,0 +1,1305 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} +\usepackage{fontawesome5} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\DeclareTotalTCBox{\lilcoderef}{O{} m m}{ + enhanced, + frame hidden, + colback=rosegray!15, + enhanced, + nobeforeafter, + tcbox raise base, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=1mm, + right=1mm, + top=1mm, + bottom=1mm, + oversize, + #1 +}{\mintinline{lua}{#2} \mintinline{lua}{#3}} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} +\usepackage{marginnote} + +\begin{document} +\setdescription{style=multiline, + topsep=10pt, + leftmargin=6.5cm, + } + +\subsection{Algebra Methods} + +Many classes in the algebra package inherit from the {\ttfamily Ring} interface. The {\ttfamily Ring} interface requires the following arithmetic operations, which have corresponding abstract metamethods listed below. Of course, these abstract methods get passed to the appropriate concrete methods in the concrete classes that inherit from {\ttfamily Ring}. + +For {\ttfamily Ring} objects {\ttfamily a} and {\ttfamily b}: + +\SetLabelAlign{parright}{\parbox[t]{\labelwidth}{\raggedleft#1}} +\begin{description}%[labelwidth = 6.5cm,align=parright] + \item[\lilcoderef{function a:add(b)}{return a + b}] Adds two ring elements. + \item[\lilcoderef{function a:sub(b)}{return a - b}] Subtracts one ring element from another. Subtraction has a default implementation in {\ttfamily Ring.lua} as adding the additive inverse, but this can be overwritten if a faster performance method is available. + \item[\lilcoderef{function a:neg()}{return -a}] Returns the additive inverse of a ring element. + \item[\lilcoderef{function a:mul(b)}{return a * b}] Multiplies two ring elements. + \item[\lilcoderef{function a:pow(n)}{return a ^ n}] Raises one ring element to the power of an integer. Exponentiation has a default implementation as repeated multiplication, but this can (and probably should) be overwritten for faster performance. + \item[\lilcoderef{function a:eq(b)}{return a == b}] Tests if two ring elements are the same. + \item[\lilcoderef{function a:lt(b)}{return a < b}] Tests if one ring element is less than another under some total order. If the ring does not have a natural total order, this method does not need to be implemented. + \item[\lilcoderef{function a:le(b)}{return a <= b}] Tests if one ring element is less than or equal to another under some total order. If the ring does not have a natural total order, this method does not need to be implemented. + \item[\lilcoderef{function a:zero()}{return Ring}] Returns the additive identity of the ring to which \texttt{a} belongs. + \item[\lilcoderef{function a:one()}{return Ring}] Returns the multiplicative identity of the ring to which \texttt{a} belongs. +\end{description} + +\reversemarginpar +Arithmetic\marginnote{\color{rose}\large\faHandPointRight} of {\ttfamily Ring} elements will (generally) not form a {\ttfamily BinaryOperation}. Instead, the appropriate \mintinline{lua}{__RingOperation} is called which then passes the arithmetic to a specific ring, if possible. For example: +\begin{codebox} + \begin{minted}[breaklines,fontsize=\small]{latex} +\begin{CAS} + f = Poly({2,1}) + g = Poly({2,5}) + h = f+g +\end{CAS} +\[ (\print{f}) + (\print{g}) = \print{h} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({2,1}) + g = Poly({2,5}) + h = f+g +\end{CAS} +\[ (\print{f}) + (\print{g}) = \print{h} \] +\end{codebox} +So why have the {\ttfamily Ring} class to begin with? Many of the rings in the algebra package are subsets of one another. For instance, integers are subsets of rationals, which are subsets of polynomial rings over the rationals, etc. To smoothly convert objects from one ring to another, it's good to have a class like {\ttfamily Ring} to handle all the ``traffic.'' + +For example, the {\ttfamily RingIdentifier} object acts as a pseudo-class that stores information about the exact ring of an object, including the symbol the ring has if it's a polynomial ring. To perform operations on two elements of different rings, the CAS does the following: + +To get the generic {\ttfamily RingIdentifier} from a class, it uses the static method: +\coderef{function Ring.makering()}{return RingIdentifier} + +To get the {\ttfamily RingIdentifier} from a specific instance (element) of a ring, it uses the method: + +\coderef{function Ring:getring()}{return RingIdentifier} + +So, for example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +a = Integer(2)/Integer(3) +ring = a:getring() +if ring == Integer.makering() then + tex.print('same rings') +else + tex.print('different rings') +end +\end{minted} +\tcblower +\luaexec{ +a = Integer(2)/Integer(3) +ring = a:getring() +if ring == Integer.makering() then + tex.print('same rings') +else + tex.print('different rings') +end +} +\end{codebox} + +From there, the CAS computes the smallest {\ttfamily RingIdentifier} that contains the two {\ttfamily RingIdentifier}s as subsets using the static method: + +\newcoderef{function Ring.resultantring(ring1,ring2)}{return RingIdentifier}{ring1 RingIdentifier, ring2 RingIdentifier} + +So, for example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +a = Poly({Integer(2),Integer(1)}) +b = Integer(3) +ring1 = a:getring() +ring2 = b:getring() +ring = Ring.resultantring(ring1,ring2) +if ring == a:getring() then + tex.print('polynomial ring') +end +\end{minted} +\tcblower +\luaexec{ + a = Poly({Integer(2),Integer(1)}) + b = Integer(3) + ring1 = a:getring() + ring2 = b:getring() + ring = Ring.resultantring(ring1,ring2) + if ring == a:getring() then + tex.print('polynomial ring') + end +} +\end{codebox} + +Finally, the CAS converts both objects into the resultant {\ttfamily RingIdentifier}, if possible, using the method: + +\coderef{function Ring:inring(ring)}{return Ring} + +So, for example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +b = b:inring(ring) +if b:type() == PolynomialRing then + tex.print('b is a polynomial now') +end +\end{minted} +\tcblower +\luaexec{ + b = b:inring(ring) + if b:type() == PolynomialRing then + tex.print('b is a polynomial now') + end +} +\end{codebox} + +Finally, the CAS is able to perform the operation with the correct \mintinline{lua}{__RingOperation}. This all happens within the hierarchy of \texttt{Ring} classes automatically: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = Poly({1/2,3,1}) + b = 1/2 + c = a+b +\end{CAS} +\[ \print{a} + \print{b} = \print{c} \] +\end{minted} +\tcblower +\begin{CAS} + a = Poly({1/2,3,1}) + b = 2/3 + c = a+b +\end{CAS} +\[ \print{a} + \print{b} = \print{c} \] +\end{codebox} + + +To add another class that implements {\ttfamily Ring} and has proper conversion abilities, the {\ttfamily resultantring} method needs to be updated to include all possible resultant rings constructed from the new ring and existing rings. The other three methods need to be implemented as well. + +\hrulefill + +We now discuss the more arithmetic methods included in the algebra package beginning with the \texttt{PolynomialRing} class. + +\coderef{function PolynomialRing:decompose()}{return table} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:divisors} + +Returns a list of all monic divisors of positive degree of the polynomial, assuming the polynomial ring is a Euclidean Domain. For example: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = topoly(x^4 - 2*x^3 - x + 2) + d = f:divisors() +\end{CAS} +\[ \left\{ \lprint{d} \right\} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = topoly(x^4 - 2*x^3 - x + 2) + d = f:divisors() +\end{CAS} +\[ \left\{ \lprint{d} \right\} \] +\end{codebox} + +\newcoderef{function PolynomialRing:divremainder(poly1)}{return poly2,poly3}{poly1 PolynomialRing,..., poly3 PolynomialRing} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:divremainder} + +Uses synthetic division to return the quotient (\texttt{poly2}) and remainder (\texttt{poly3}) of \texttt{self/poly1}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({2,2,1}) + g = Poly({1,1}) + q,r = f:divremainder(g) +\end{CAS} +\[ \print{f} = (\print{g})(\print{q}) + + \print{r} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({2,2,1}) + g = Poly({1,1}) + q,r = f:divremainder(g) +\end{CAS} +\[ \print{f} = (\print{g})(\print{q}) + + \print{r} \] +\end{codebox} + +\newcoderef{function PolynomialRing.extendedgcd(poly1,poly2)}{return poly3, poly4, poly5}{poly1 PolynomialRing, poly2 PolynomialRing, ..., poly5 PolynomialRing} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:extendedgcd} + +Given two \texttt{PolynomialRing} elements \texttt{poly1,poly2} returns: +\begin{itemize} + \item \texttt{poly3}: the gcd of \texttt{poly1,poly2}; + \item \texttt{poly4,poly5}: the coefficients from Bezout's lemma via the extended gcd. +\end{itemize} +For example: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = topoly((x-1)*(x-2)*(x-3)) + g = topoly((x-1)*(x+2)*(x+3)) + h,a,b = PolynomialRing.extendedgcd(f,g) +\end{CAS} +\[ \print{f*a+g*b} = (\print{f})\left( \print{a} \right) + + (\print{g})\left(\print{b} \right)\] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = topoly((x-1)*(x-2)*(x-3)) + g = topoly((x-1)*(x+2)*(x+3)) + h,a,b = PolynomialRing.extendedgcd(f,g) +\end{CAS} +\[ \print{f*a+g*b} = (\print{f})\left( \print{a} \right) + + (\print{g})\left(\print{b} \right)\] +\end{codebox} + +\subsubsection*{Parsing} + +The function \texttt{gcdext()} is a shortcut to \texttt{Polynomial.extendedgcd()}: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = topoly((x+2)*(x-3)) + g = topoly((x+4)*(x-3)) + h,a,b = gcdext(f,g) +\end{CAS} +\[ \print{h} = (\print{f}) \left( \print{a} \right) + + (\print{g})\left( \print{b} \right). \] +\end{minted} +\tcblower +\begin{CAS} + f = topoly((x+2)*(x-3)) + g = topoly((x+4)*(x-3)) + h,a,b = gcdext(f,g) +\end{CAS} +\[ \print{h} = (\print{f}) \left( \print{a} \right) + (\print{g})\left( \print{b} \right). \] +\end{codebox} + +\coderef{function PolynomialRing:evaluateat(Expression)}{return Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:evaluateat} + +Uses Horner's rule to evaluate a polynomial at \texttt{Expression}. Typically, the input \texttt{Expression} is an \texttt{Integer} or \texttt{Rational}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({2,2,1}) + p = f:evaluateat(1/2) +\end{CAS} +\[ \left. \print{f} \right|_{x=1/2} + = \print{p} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({2,2,1}) + p = f:evaluateat(1/2) +\end{CAS} +\[ \left. \print{f} \right|_{x=1/2} + = \print{p} \] +\end{codebox} + +\coderef{function PolynomialRing:factor()}{return BinaryOperation} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:factor} + +Factors the given polynomial into irreducible terms over the polynomial ring to which the coefficients belong. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({8,24,32,24,10,2}) + a = f:factor() +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({8,24,32,24,10,2}) + a = f:factor() +\end{CAS} +\[ \print{a} \] +\end{codebox} + +On the other hand: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({Mod(1,5),Mod(0,5),Mod(1,5)}) + a = f:factor() +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({Mod(1,5),Mod(0,5),Mod(1,5)}) + a = f:factor() +\end{CAS} +\[ \print{a} \] +\end{codebox} + +The syntax \mintinline{lua}{f = Poly({Mod(1,5),Mod(0,5),Mod(1,5)})} is awkward. Alternatively, one can use the following instead: +\begin{codebox} + \begin{minted}{latex} +\begin{CAS} + f = Mod(Poly({1,0,1}),5) + a = f:factor() +\end{CAS} +\[ \print{f} = \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + f = Mod(Poly({1,0,1}),5) + a = f:factor() +\end{CAS} +\[ \print{f} = \print{a} \] +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{factor()} shortcuts \mintinline{lua}{PolynomialRing:factor()}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({8,24,32,24,10,2}) + a = factor(f) +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + f = Poly({8,24,32,24,10,2}) + a = factor(f) +\end{CAS} +\[ \print{a} \] +\end{codebox} + +\newcoderef{function PolynomialRing:freeof(symbol)}{return bool}{symbol SymbolExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:freeof} + +Checks the value of the field \mintinline{lua}{PolynomialRing.symbol} against \texttt{symbol}; returns \mintinline{lua}{true} if these symbols are not equal, and returns \mintinline{lua}{false} otherwise. + +Recall: the default symbol for \texttt{Poly} is \texttt{'x'}. So, for example: +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = Poly({2,2,1}) + vars('t') + if f:freeof(t) then + tex.print('$',f:tolatex(),'$ is free of $',t:tolatex(),'$') + else + tex.print('$',f:tolatex(),'$ is bound by $',t:tolatex(),'$') + end +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + f = Poly({2,2,1}) + vars('t') + if f:freeof(t) then + tex.print('$',f:tolatex(),'$ is free of $',t:tolatex(),'$') + else + tex.print('$',f:tolatex(),'$ is bound by $',t:tolatex(),'$') + end +\end{CAS} +\end{codebox} + +\newcoderef{function PolynomialRing.gcd(poly1,poly2)}{return poly3}{poly1 PolynomialRing,..., poly3 PolynomialRing} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing.gcd} + +Returns the greatest common divisor of two polynomials in a ring (assuming \texttt{poly1,poly2} belong to a Euclidean domain). For example: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = topoly((x^2+1)*(x-1)) + g = topoly((x^2+1)*(x+2)) + h = PolynomialRing.gcd(f,g) +\end{CAS} +\[ \gcd(\print{f},\print{g}) = \print{h} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = topoly((x^2+1)*(x-1)) + g = topoly((x^2+1)*(x+2)) + h = PolynomialRing.gcd(f,g) +\end{CAS} +\[ \gcd(\print{f},\print{g}) = \print{h} \] +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{gcd()} shortcuts \mintinline{lua}{PolynomialRing.gcd()}. For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = topoly(x^3 - x^2 + x - 1) + g = topoly(x^3 + 2*x^2 + x + 2) + h = gcd(f,g) +\end{CAS} +\[ \gcd(\print{f},\print{g}) = \print{h}.\] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = topoly(x^3 - x^2 + x - 1) + g = topoly(x^3 + 2*x^2 + x + 2) + h = gcd(f,g) +\end{CAS} +\[ \gcd(\print{f},\print{g}) = \print{h}.\] +\end{codebox} + +\coderef{function PolynomialRing:isatomic()}{return false} +\coderef{function PolynomialRing:isconstant()}{return false} + +The inheritances from \texttt{ConstantExpression} are overridden for the \texttt{PolynomialRing} class. + +\newcoderef{function PolynomialRing.monicgcdremainders(poly1,poly2)}{return table}{poly1 PolynomialRing, poly2 PolynomialRing} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing.monicgcdremainders} + +Given two polynomials \texttt{poly1} and \texttt{poly2}, returns a list of the remainders generated by the monic Euclidean algorithm. +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = topoly(x^13-1) + g = topoly(x^8-1) + r = PolynomialRing.monicgcdremainders(f,g) +\end{CAS} +\luaexec{ + for i=1,\#r do + tex.print('\\[', r[i]:tolatex(), '\\]') + end +} +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = topoly(x^13-1) + g = topoly(x^8-1) + r = PolynomialRing.monicgcdremainders(f,g) +\end{CAS} +\luaexec{ + for i=1,\#r do + tex.print('\\[', r[i]:tolatex(), '\\]') + end +} +\end{codebox} + +\coderef{function PolynomialRing.mul_rec(poly1,poly2)}{return PolynomialRing} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing.mul{\textunderscore}rec} + +Performs Karatsuba multiplication without constructing new polynomials recursively. But grade-school multiplication of polynomials is actually faster here up to a very large polynomial size due to Lua's overhead. + +\newcoderef{function PolynomialRing.partialfractions(g,f,ffactors)}{return BinaryOperation}{g PolynomialRing, f PolynomialRing, ffactors BinaryOperation} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing.partialfractions} + +Returns the partial fraction decomposition of the rational function \texttt{g/f} given \texttt{PolynomialRing}s \texttt{g, f}, and some (not necessarily irreducible) factorization \texttt{ffactors} of \texttt{f}. If the factorization is omitted, the irreducible factorization is used. The degree of \texttt{g} must be less than the degree of \texttt{f}. + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + g = topoly(4*x^2+2*x+2) + f = topoly((x^2+1)^2*(x+1)) + a = PolynomialRing.partialfractions(g,f) +\end{CAS} +\[ \print{g/f} = \print*{a} \] +\end{minted} +\tcblower +\begin{CAS} + g = topoly(4*x^2+2*x+2) + f = topoly((x^2+1)^2*(x+1)) + a = PolynomialRing.partialfractions(g,f) +\end{CAS} +\[ \print{g/f} = \print*{a} \] +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{parfrac()} shortcuts the more long winded \mintinline{lua}{PolynomialRing.partialfractions()}. Additionally, the \texttt{parfrac} function will automatically try to convert the first two arguments to the \texttt{PolynomialRing} type via \mintinline{lua}{topoly()}. +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + g = 4*x^2+2*x+2 + f = (x^2+1)^2*(x+1) + a = parfrac(g,f) +\end{CAS} +\[ \print{g/f} = \print*{a} \] +\end{minted} +\tcblower +\begin{CAS} + g = 4*x^2+2*x+2 + f = (x^2+1)^2*(x+1) + a = parfrac(g,f) +\end{CAS} +\[ \print{g/f} = \print*{a} \] +\end{codebox} + +\newcoderef{function PolynomialRing:rationalroots()}{return remaining, roots}{remaining PolynomialRing, roots table} +\addcontentsline{toc}{subsubsection}{\ttfamily PolynomialRing:rationalroots} + +This method finds the factors of \texttt{PolynomialRing} (up to multiplicity) that correspond to rational roots; these factors are stored in a table \texttt{roots} and returned in the second output of the method. Those factors are then divided out of \texttt{Polynomialring}; the \texttt{PolynomialRing} that remains is returned in the first output of the method. For example: + +\begin{codebox} + \begin{minted}[breaklines,fontsize=\small]{latex} +\begin{CAS} + f = topoly((x-1)^2*(x+1)*(x^2+1)) + g,r = f:rationalroots() +\end{CAS} +The factors of $f$ corresponding to rational roots are: +\luaexec{ + for i =1, \#r do + tex.print('\\[', r[i]:tolatex(), '\\]') + end +} +The part of $f$ that remains after dividing out these linear terms is: +\[ \print{g} \] +\end{minted} +\tcblower +\begin{CAS} + f = topoly((x-1)^2*(x+1)*(x^2+1)) + g,r = f:rationalroots() + \end{CAS} + The factors of $f$ corresponding to rational roots are: + \luaexec{ + for i =1, \#r do + tex.print('\\[', r[i]:tolatex(), '\\]') + end + } + The part of $f$ that remains after dividing out these linear terms is: + \[ \print{g} \] +\end{codebox} + +\coderef{function PolynomialRing:roots()}{return table a}$. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 101 + b = 10 + c = Integer.ceillog(a,b) +\end{CAS} +\[ \print{c} \] +\end{minted} +\tcblower +\begin{CAS} + a = 101 + b = 10 + c = Integer.ceillog(a,b) +\end{CAS} +\[ \print{c} \] +\end{codebox} + +\newcoderef{function Integer.powmod(a,b,n)}{return Integer}{a Integer,..., n Integer} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer.powmod} + +Returns the \texttt{Integer} $c$ such that $c \equiv a^b \bmod{n}$. This should be used when $a^b$ is potentially large. + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 12341 + b = 2^16+1 + p = 62501 + c = Integer.powmod(a,b,p) +\end{CAS} +\[ \print{c} \equiv \print{a}^{\print{b}} \bmod{\print{p}} \] +\end{minted} +\tcblower +\begin{CAS} + a = 12341 + b = 2^16+1 + p = 62501 + c = Integer.powmod(a,b,p) +\end{CAS} +\[ \print{c} \equiv \print{a}^{\print{b}} \bmod{\print{p}} \] +\end{codebox} + +\newcoderef{function Integer:divremainder(b)}{return Integer, Integer}{b Integer} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer:divremainder} + +Returns the quotient and remainder over the integers. Uses the standard base 10 long division algorithm. + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 408 + b = 252 + q,r = Integer.divremainder(a,b) +\end{CAS} +\[ \print{a} = \print{b} \cdot \print{q} + \print{r} \] +\end{minted} +\tcblower +\begin{CAS} + a = 408 + b = 252 + q,r = Integer.divremainder(a,b) +\end{CAS} +\[ \print{a} = \print{b} \cdot \print{q} + \print{r} \] +\end{codebox} + +\coderef{function Integer:asnumber()}{return number} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer:asnumber} + +Returns the integer as a floating point number. Can only approximate the value of large integers. + +\coderef{function Integer:divisors()}{return table} + +Returns all positive divisors of the integer. Not guaranteed to be in any order. + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 408 + d = a:divisors() +\end{CAS} +\[ \left\{ \lprint{d} \right\} \] +\end{minted} +\tcblower +\begin{CAS} + a = 408 + d = a:divisors() +\end{CAS} +\[ \left\{ \lprint{d} \right\} \] +\end{codebox} + +\coderef{function Integer:primefactorization()}{return BinaryOperation} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer:primefactorization} + +Returns the prime factorization of the integer as a \texttt{BinaryOperation}. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 408 + pf = a:primefactorization() +\end{CAS} +\[ \print{pf} \] +\end{minted} +\tcblower +\begin{CAS} + a = 408 + pf = a:primefactorization() +\end{CAS} +\[ \print{pf} \] +\end{codebox} + +\coderef{function Integer:findafactor()}{return Integer} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer:findafactor} + +Return a non-trivial factor of {\ttfamily Integer} via Pollard Rho, or returns {\ttfamily Integer} if {\ttfamily Integer} is prime. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 4199 + f = a:findafactor() +\end{CAS} +\[ \print{f} \mid \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + a = 4199 + f = a:findafactor() +\end{CAS} +\[ \print{f} \mid \print{a} \] +\end{codebox} + + +\coderef{function Integer:isprime()}{return bool} +\addcontentsline{toc}{subsubsection}{\ttfamily Integer:isprime} + +Uses Miller-Rabin to determine whether {\ttfamily Integer} is prime up to a very large number. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + p = 7038304939 + if p:isprime() then + tex.print(p:tolatex(), "is prime!") + end +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + p = 7038304939 + if p:isprime() then + tex.print(p:tolatex(), "is prime!") + end +\end{CAS} +\end{codebox} + +\coderef{function Rational:reduce()}{return Rational} +\addcontentsline{toc}{subsubsection}{\ttfamily Rational:reduce} + +Reduces a rational expression of integers to standard form. This method is called automatically when a new \texttt{Rational} expression is constructed: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = Rational(8,6) +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + a = Rational(8,6) +\end{CAS} +\[ \print{a} \] +\end{codebox} + + +\coderef{function Rational:tocompoundexpression()}{return BinaryOperation} +\addcontentsline{toc}{subsubsection}{\ttfamily Rational:tocompoundexpression} + +Converts a \texttt{Rational} expression into the corresponding \texttt{BinaryOperation} expression. + +\coderef{function Rational:asnumber()}{return number} +\addcontentsline{toc}{subsubsection}{\ttfamily Rational:asnumber} + +Returns the given rational as an approximate floating point number. Going the other way, the parser in \mintinline{latex}{\begin{CAS}..\end{CAS}} will convert decimals (as written) to fractions. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + a = 0.375 +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + a = 0.375 +\end{CAS} +\[ \print{a} \] +\end{codebox} + +\coderef{function SqrtExpression:topower()}{return BinaryOperation} +\addcontentsline{toc}{subsubsection}{\ttfamily SqrtExpression:topower} + +Converts a \texttt{SqrtExpression} to the appropriate \texttt{BinaryOperation}. For example, consider: +\begin{minted}{latex} +\begin{CAS} + a = sqrt(3) + b = a:topower() +\end{CAS} +\end{minted} +\begin{CAS} + a = sqrt(3) + b = a:topower() +\end{CAS} +Then: +\begin{multicols}{2} + \begin{center} + \underline{Expression shrub for \texttt{a}:} + + \parseshrub{a} + \bracketset{action character = @} + \begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners = 1pt, + fill = gray!20, + s sep = 1cm} + @\shrubresult + \end{forest} + + \columnbreak + + \underline{Expression shrub for \texttt{b}:} + + \parseshrub{b} + \bracketset{action character = @} + \begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners = 1pt, + fill = gray!20, + s sep = 1.75cm} + @\shrubresult + \end{forest} + \end{center} +\end{multicols} + +\newcoderef{function Equation:solvefor(var)}{return Equation}{var SymbolExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily Equation:solvefor} + +Attempts to solve the equation for a particular variable. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars("x", "y", "z") + lhs = e ^ (x^2 * y) + rhs = z + 1 + eq = Equation(lhs, rhs):autosimplify() + eqx = eq:solvefor(x) +\end{CAS} +\[ \print{eq} \to \print{eqx} \] +\end{minted} +\tcblower +\begin{CAS} + vars("x", "y", "z") + lhs = e ^ (x^2 * y) + rhs = z + 1 + eq = Equation(lhs, rhs):autosimplify() + eqx = eq:solvefor(x) +\end{CAS} +\[ \print{eq} \to \print{eqx} \] +\end{codebox} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus.tex b/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus.tex new file mode 100644 index 0000000000..31d52ab868 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus.tex @@ -0,0 +1,225 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden, + size=small, + bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white} + }, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\usetikzlibrary{shapes.multipart} +\useforestlibrary{edges} + +\def\error{\color{red}} +\def\self{\color{gray}} +\def\call{$\star$ } + +\begin{document} +\thispagestyle{empty} + +\section{Calculus} + This section contains reference materials for the calculus functionality of \texttt{luacas}. The classes in this module are diagramed below according to inheritance along with the methods/functions one can call upon them. + \begin{itemize} + \item {\error\ttfamily\itshape method}: an abstract method; + \item {\self\ttfamily\itshape method}: a method that returns the expression unchanged; + \item {\ttfamily\itshape method}: method that is either unique, implements an abstract method, or overrides an abstract +method; + \item {\tikz[baseline=-0.5ex]\node[fill=roseorange!30] {\ttfamily\bfseries Class};}: a concrete class. + \end{itemize} +Here is an inheritance diagram of the classes in the calculus module; all these classes inherit from the \texttt{CompoundExpression} branch of the inheritance tree. Most methods are stated, but some were omitted (because they inherit in the obvious way, they are auxiliary and not likely to be interesting to the end-user, etc). + \vfill +\forestset{ +rectcore/.style = {rectangle split, + rectangle split parts=2, + draw = {rosenavy,thick}, + rounded corners = 1pt, + font = \ttfamily\bfseries, + fill = roseblue!#1 + }, +rectalg/.style = {rectangle split, + rectangle split parts=2, + draw = {rose,thick}, + rounded corners = 1pt, + font = \ttfamily\bfseries, + fill = rose!#1 + }, +rectcalc/.style = {rectangle split, + rectangle split parts=2, + draw = {roseorange,thick}, + rounded corners = 1pt, + font = \ttfamily\bfseries, + fill = roseorange!#1 + } +} +\forestset{ + multiple directions/.style={ + for tree={#1}, + phantom, + for relative level=1{ + no edge, + delay={ + !c.content/.pgfmath=content("!u")}, + before computing xy={l=0,s=0} + } + }, + multiple directions/.default={}, + grow subtree/.style={for tree={grow=#1}}, + grow' subtree/.style={for tree={grow'=#1}}} +\tikzset{ + every two node part/.style={font=\ttfamily\itshape\footnotesize} +} +\begin{center} + \begin{forest} + for tree = {node options={align=left}, + edge = {-stealth} + }, + forked edges + [Expression\nodepart{two}$\cdots$,rectcore={0} + [CompoundExpressions\nodepart{two}$\cdots$,rectcore={0} + [DerivativeExpression\nodepart{two} + :new() \\ + :evaluate()\\ + :autosimplify() + ,rectcalc=30] + [DiffExpression\nodepart{two} + :new()\\ + :evaluate()\\ + :autosimplify() + ,rectcalc=30] + [IntegralExpression\nodepart{two} + :new()\\ + :isdefinite()\\ + .table()\\ + .linearproperties()\\ + .substitutionmethod()\\ + .enhancedsubstitutionmethod()\\ + .rationalfunction()\\ + .partsmethod()\\ + .eulersformula()\\ + .integrate() + ,rectcalc=30] + ] + ] + \end{forest} +\end{center} +\vfill + +\end{document} + diff --git a/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_classes/ref_calculus_classes.tex b/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_classes/ref_calculus_classes.tex new file mode 100644 index 0000000000..b08a190625 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_classes/ref_calculus_classes.tex @@ -0,0 +1,277 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} +\usepackage{fontawesome} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\begin{document} +\setdescription{style=multiline, + topsep=10pt, + leftmargin=5cm, + } + +\subsection{Calculus Classes} + +There are only a few classes (currently) in the calculus module all of which are concrete: +\begin{itemize} + \item {\ttfamily DerivativeExpression} + \item {\ttfamily DiffExpression} + \item {\ttfamily IntegralExpression} +\end{itemize} + +\newcoderef{function DerivativeExpression:new(expression, symbol)}{return DerivativeExpression}{expression Expression, symbol SymbolExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily DerivativeExpression} + +Creates a new single-variable derivative operation of the given \texttt{expression} with respect to the given \texttt{symbol}. If \texttt{symbol} is omitted, then \texttt{symbol} takes the default value of \mintinline{lua}{SymbolExpression("x")}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('x') +f = DerivativeExpression(sin(x)/x) +tex.print('\\[', f:tolatex(), '\\]') +\end{minted} +\tcblower +\luaexec{ + vars('x') + f = DerivativeExpression(sin(x)/x) + tex.print('\\[', f:tolatex(), '\\]') +} +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{DD()} shortcuts \mintinline{lua}{DerivativeExpression()}. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = DD(sin(x)/x) +\end{CAS} +\[ \print{f} \] +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = DD(sin(x)/x) +\end{CAS} +\[ \print{f} \] +\end{codebox} +Alternatively, one could also use \mintinline{lua}{diff()} (see below). + +\newcoderef{function DiffExpression:new(expression, symbols)}{return DiffExpression}{expression Expression, symbols table} +\addcontentsline{toc}{subsubsection}{\ttfamily DiffExpression} + +Creates a new multi-variable higher-order derivative operation of the given \texttt{expression} with respect to the given \texttt{symbols}. As opposed to \texttt{DerivativeExpression}, the argument \texttt{symbols} cannot be omitted. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('x','y') +f = DiffExpression(sin(x*y)/y,{x,y}) +tex.print('\\[', f:tolatex(), '\\]') +\end{minted} +\tcblower +\luaexec{ + vars('x','y') + f = DiffExpression(sin(x*y)/y,{x,y}) + tex.print('\\[', f:tolatex(), '\\]') +} +\end{codebox} +We can also use \texttt{DiffExpression} to create higher-order single variable derivatives: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('x') +f = DiffExpression(sin(x)/x,{x,x}) +tex.print('\\[', f:tolatex(), '\\]') +\end{minted} +\tcblower +\luaexec{ + vars('x') + f = DiffExpression(sin(x)/x,{x,x}) + tex.print('\\[', f:tolatex(), '\\]') +} +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{diff()} shortcuts \mintinline{lua}{DiffExpression()}. The arguments of \mintinline{lua}{diff()} can also be given in a more user-friendly, compact form. For example: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','y') + f = diff(sin(x)/x, {x,2}) + g = diff(sin(x*y)/y,x,{y,2}) +\end{CAS} +\[ \print{f} = \print*{f} \qquad \print{g} = \print*{g} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y') + f = diff(sin(x)/x, {x,2}) + g = diff(sin(x*y)/y,x,{y,2}) +\end{CAS} +\[ \print{f} = \print*{f} \qquad \print{g} = \print*{g} \] +\end{codebox} + +\newcoderef{function IntegralExpression:new(expression,symbol,lower,upper)}{return IntegralExpression}{expression Expression, symbol SymbolExpression, lower Expression, upper Expression} +\addcontentsline{toc}{subsubsection}{\ttfamily IntegralExpression} + +Creates a new integral operation of the given \texttt{expression} with respect to the given \texttt{symbol} over the given \texttt{lower} and \texttt{upper} bounds. If \texttt{lower} and \texttt{upper} are omitted, then an \emph{indefinite} \texttt{IntegralExpression} is constructed. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small,breaklines]{lua} +vars('x') +f = IntegralExpression(sin(sqrt(x)), x) +g = IntegralExpression(sin(sqrt(x)), x, Integer.zero(), pi) +tex.print('\\[', f:tolatex(), '\\]') +tex.print('\\[', g:tolatex(), '\\]') +\end{minted} +\tcblower +\luaexec{ + vars('x') + f = IntegralExpression(sin(sqrt(x)),x) + g = IntegralExpression(sin(sqrt(x)),x,Integer.zero(),pi) + tex.print('\\[', f:tolatex(), '\\]') + tex.print('\\[', g:tolatex(), '\\]') +} +\end{codebox} + +\subsubsection*{Parsing} + +The function \mintinline{lua}{int()} shortcuts \mintinline{lua}{IntegralExpression()}. For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + g = int(sin(sqrt(x)),x,0,pi) +\end{CAS} +\[ \print{g} = \print*{g} \] +\end{minted} +\tcblower +\begin{CAS} + g = int(sin(sqrt(x)),x,0,pi) +\end{CAS} +\[ \print{g} = \print*{g} \] +\end{codebox} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_methods/ref_calculus_methods.tex b/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_methods/ref_calculus_methods.tex new file mode 100644 index 0000000000..c64baa5934 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_calculus/ref_calculus_methods/ref_calculus_methods.tex @@ -0,0 +1,473 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} +\usepackage{fontawesome5} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\DeclareTotalTCBox{\lilcoderef}{O{} m m}{ + enhanced, + frame hidden, + colback=rosegray!15, + enhanced, + nobeforeafter, + tcbox raise base, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=1mm, + right=1mm, + top=1mm, + bottom=1mm, + oversize, + #1 +}{\mintinline{lua}{#2} \mintinline{lua}{#3}} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} +\usepackage{marginnote} + +\begin{document} +\setdescription{style=multiline, + topsep=10pt, + leftmargin=6.5cm, + } + +\subsection{Calculus Methods} + +\newcoderef{function IntegralExpression.table(integral)}{return Expression|nil}{integral IntegralExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily IntegralExpression.table} + +Attempts to integrate \texttt{integral.expression} with respect to \texttt{integral.symbol} by checking a table of basic integrals; returns nil if the integrand isn't in the table. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = int(cos(x),x) + f = f:table() + g = int(x*cos(x),x) + g = g:table() +\end{CAS} +\[ f = \print{f} \qquad g = \print{g} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = int(cos(x),x) + f = f:table() + g = int(x*cos(x),x) + g = g:table() + \end{CAS} +\[ f = \print{f} \qquad g = \print{g} \] +\end{codebox} +The table of integrals consists of power functions, exponentials, logarithms, trigonometric, and inverse trigonometric functions. + +\newcoderef{function IntegralExpression.linearproperties(integral)}{return Expression|nil}{integral IntegralExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily IntegralExpression.linearproperties} + +Attempts to integrate \texttt{integral.expression} with respect to \texttt{integral.symbol} by using linearity properties (e.g. the integral of a sum/difference is the sum/difference of integrals); returns nil if any individual component cannot be integrated using \mintinline{lua}{IntegralExpression:integrate()}. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = int(sin(x) + e^x,x) + g = f:table() + f = f:linearproperties() +\end{CAS} +\[ f = \print*{f} \qquad g = \print*{g} \] +\end{minted} +\tcblower +\begin{CAS} + f = int(sin(x) + e^x,x) + g = f:table() + f = f:linearproperties() +\end{CAS} +\[ f = \print*{f} \qquad g = \print*{g} \] +\end{codebox} + +\newcoderef{function IntegralExpression.substitutionmethod(integral)}{return Expression|nil}{integral IntegralExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily IntegralExpression.substitutionmethod} + +Attempts to integrate \texttt{integral.expression} with respect to \texttt{integral.symbol} via $u$-substitution; returns nil if no suitable substitution is found to be successful. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = int(x*e^(x^2),x) + g = int(x*e^x,x) + f = f:substitutionmethod() + g = g:substitutionmethod() +\end{CAS} +\[ f = \print*{f} \qquad g = \print*{g}.\] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = int(x*e^(x^2),x) + g = int(x*e^x,x) + f = f:substitutionmethod() + g = g:substitutionmethod() +\end{CAS} +\[ f = \print*{f} \qquad g = \print*{g}.\] +\end{codebox} + +\newcoderef{function IntegralExpression.enhancedsubstitutionmethod(integral)}{return Expression|nil}{integral IntegralExpression} +\addcontentsline{toc}{subsubsection}{\ttfamily IntegralExpression.enhancedsubstitutionmethod} + +Attempts integrate \texttt{integral.expression} with respect to \texttt{integral.symbol} via $u$-substitutions. This method distinguishes itself from the \mintinline{lua}{.substitutionmethod} by attempted to solve $u= g(x)$ for the original variable and then substituting the result into the expression. This behavior is not included in \mintinline{lua}{.substitutionmethod} due to speed concerns. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = int(x^5*sqrt(x^3+1),x) + g = f:substitutionmethod() + h = f:enhancedsubstitutionmethod() +\end{CAS} +\[ g= \print*{g} \] +\[ h= \print*{h} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = int(x^5*sqrt(x^3+1),x) + g = f:substitutionmethod() + h = f:enhancedsubstitutionmethod() +\end{CAS} +\[ g= \print*{g} \] +\[ h= \print*{h} \] +\end{codebox} + + +\newcoderef{function IntegralExpression.trialsubstitutions(Expression)}{return table} +\index{Core!Classes!\texttt{BinaryOperation}} +\addcontentsline{toc}{subsubsection}{\ttfamily BinaryOperation} + +Creates a new \texttt{BinaryOperation} expression. For example: + +\begin{codebox} +\begin{minted}[fontsize=\small]{lua} +vars('x','y','z') +w = BinaryOperation( + BinaryOperation.ADD, + {BinaryOperation( + BinaryOperation.MUL, + {x,y} + ),y,z} +) +tex.print("\\[w=",w:tolatex(),"\\]") +\end{minted} +\tcblower +\directlua{ +vars('x','y','z') +w = BinaryOperation( + BinaryOperation.ADD, + {BinaryOperation( + BinaryOperation.MUL, + {x,y} + ),y,z} +) +tex.print("\\[w=",w:tolatex(),"\\]") +} +\end{codebox} +The variable \texttt{operation} must be a function \mintinline{lua}{function f(a,b)} assigned to one of the following types: +\bgroup +\setdescription{style=multiline, + topsep=10pt, + leftmargin=4.5cm, + font=\ttfamily + } +\begin{description} + \item[BinaryOperation.ADD:] \mintinline{lua}{return a + b} + \item[BinaryOperation.SUB:] \mintinline{lua}{return a - b} + \item[BinaryOperation.MUL:] \mintinline{lua}{return a * b} + \item[BinaryOperation.DIV:] \mintinline{lua}{return a / b} + \item[BinaryOperation.IDIV:] \mintinline{lua}{return a // b} + \item[BinaryOperation.MOD:] \mintinline{lua}{return a % b} + \item[BinaryOperation.POW:] \mintinline{lua}{return a ^ b} +\end{description} +\egroup +The variable \texttt{expressions} must be a table of \texttt{Expression}s index by Lua numbers. + +\subsubsection*{Fields} + +\texttt{BinaryOperation}s have the following fields: \texttt{name}, \texttt{operation}, and \texttt{expressions}. In the example above, we have: +\begin{itemize} + \item the variable \texttt{expressions} is stored in \mintinline{lua}{w.expressions}; + \item \mintinline{lua}{w.name} stores the string \mintinline{lua}{"+"}; and + \item \mintinline{lua}{w.operation} stores the function: + \begin{minted}{lua} +BinaryOperation.ADD = function(a, b) + return a + b +end + \end{minted} +\end{itemize} + +\begin{multicols}{2} +The entries of \texttt{w.expressions} can be used/fetched in a reasonable way: +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +$\print{w.expressions[1]} \quad + \print{w.expressions[2]} \quad + \print{w.expressions[3]}$ + \end{minted} + \tcblower +$\print{w.expressions[1]} \quad + \print{w.expressions[2]} \quad + \print{w.expressions[3]}$ +\end{codebox} + +\begin{center} + \bracketset{action character = @} + \parseshrub{w} + \begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners = 1pt, + fill = gray!20, + l sep = 1.5cm, + s sep = 2cm} + @\shrubresult + \end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +Thank goodness for this. Creating new \texttt{BinaryOperation}s isn't nearly as cumbersome as the above would indicate. Using Lua's powerful metamethods, we can parse expressions easily. For example, the construction of \texttt{w} given above can be done much more naturally using: +\begin{codebox} +\begin{minted}[fontsize=\small]{lua} +vars('x','y','z') +w = x*y+y+z +tex.print("\\[w=", w:tolatex(), "\\]") +\end{minted} +\tcblower +\directlua{ + vars('x','y','z') + w = x*y+y+z + tex.print("\\[w=", w:tolatex(), "\\]") +} +\end{codebox} +\reversemarginpar +{\bf Warning:}\marginnote{\color{rose}\faExclamationTriangle} There are escape issues to be aware of with the operator \mintinline{latex}{%}. If you're writing custom \texttt{luacas} functions in a separate \texttt{.lua} file, then there are no issues; use \mintinline{latex}{%} with reckless abandon. But when using the operator \mintinline{latex}{%} within, say \mintinline{latex}{\begin{CAS}..\end{CAS}}, then one should write \mintinline{latex}{\%} in place of \mintinline{latex}{%}: + +\begin{codebox} +\begin{minted}[breaklines,fontsize=\small]{latex} +\begin{CAS} + a = 17 + b = 5 + c = a \% b +\end{CAS} +\[ \print{c} \equiv \print{a} \bmod{\print{b}} \] +\end{minted} +\tcblower +\begin{CAS} + a = 17 + b = 5 + c = a \% b +\end{CAS} +\[ \print{c} \equiv + \print{a} \bmod{\print{b}} \] +\end{codebox} +The above escape will {\bf not} work with \mintinline{latex}{\directlua}, but it will work for \mintinline{latex}{\luaexec} from the \texttt{luacode} package. Indeed, the \texttt{luacode} package was designed (in part) to make escapes like this more manageable. Here is the equivalent code using \mintinline{latex}{\luaexec}: +\begin{codebox}[] +\begin{minted}[fontsize=\small]{lua} +a = Integer(17) +b = Integer(5) +c = a \% b +tex.print("\\[",c:tolatex(),"\\equiv",a:tolatex(), "\\bmod{",b:tolatex(),"} \\]") +\end{minted} +\tcblower +\luaexec{ +a = Integer(17) +b = Integer(5) +c = a \% b +tex.print("\\[", c:tolatex(), "\\equiv", a:tolatex(), "\\bmod{", b:tolatex(), "} \\]") +} +\end{codebox} + +\newcoderef{function FunctionExpression:new(name,expressions)}{return FunctionExpression}{name string|SymbolExpression, expressions table} +\index{Core!Classes!\texttt{FunctionExpression}} +\addcontentsline{toc}{subsubsection}{\ttfamily FunctionExpression} + +Creates a generic function. For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('x','y') +f = FunctionExpression('f',{x,y}) +tex.print("\\[",f:tolatex(),"\\]") + \end{minted} + \tcblower + \luaexec{ + vars('x','y') + f = FunctionExpression('f',{x,y}) + tex.print("\\[",f:tolatex(),"\\]") + } +\end{codebox} +The variable \texttt{name} can be a string (like above), or another \texttt{SymbolExpression}. But in this case, the variable \texttt{name} just takes the value of the string \mintinline{lua}{SymbolExpression.symbol}. The variable \texttt{expressions} must be a table of \texttt{Expression}s indexed by Lua numbers. + +\subsubsection*{Fields} +\texttt{FunctionExpression}s have the following fields: \texttt{name}, \texttt{expressions}, \texttt{variables}, \texttt{derivatives}. In the example above, we have: +\begin{itemize} + \item the variable \texttt{name}, i.e. the string \mintinline{lua}{'f'}, is stored in \mintinline{lua}{f.name}; and + \item the variable \texttt{expressions}, i.e. the table \mintinline{lua}{{x,y}} is stored in \mintinline{lua}{f.expressions}. +\end{itemize} + +Wait a minute, what about \texttt{variables} and \texttt{derivatives}!? The field \texttt{variables} essentially stores a copy of the variable \texttt{expressions} \textit{as long as} the entries in that table are atomic. If they aren't, then \texttt{variables} will default to $x,y,z$, or $x_1,x_2,\ldots$ if the number of variables exceeds $3$. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('s','t') +f = FunctionExpression('f',{s*s,s+t+t}) +tex.print("The variables of f are:") +for _,symbol in ipairs(f.variables) do + tex.print(symbol:tolatex()) +end + \end{minted} + \tcblower +\luaexec{ + vars('s','t') + f = FunctionExpression('f',{s*s,s+t+t}) + tex.print("The variables of f are:") + for _,symbol in ipairs(f.variables) do + tex.print(symbol:tolatex()) + end +} +\end{codebox} +The field \texttt{derivatives} is a table of \texttt{Integer}s indexed by Lua numbers whose length equals \mintinline{lua}{#o.variables}. The default value for this table is a table of (\texttt{Integer}) zeros. So for the example above, we have: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +for _,integer in ipairs(f.derivatives) do + if integer == Integer.zero() then + tex.print("I'm a zero.\\newline") + end +end +\end{minted} +\tcblower +\luaexec{ + for _,integer in ipairs(f.derivatives) do + if integer == Integer.zero() then + tex.print("I'm a zero.\\newline") + end + end +} +\end{codebox} +We can change the values of \texttt{variables} and \texttt{derivatives} manually (or more naturally by other gizmos found in \texttt{luacas}). For example, keeping the variables from above, we have: +\begin{multicols}{2} +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +f.derivatives = {Integer.one(), + Integer.one()} +tex.print("\\[", + f:simplify():tolatex(), + "\\]") +\end{minted} +\tcblower +\luaexec{ + f.derivatives = {Integer.one(),Integer.one()} + tex.print("\\[", f:simplify():tolatex(), "\\]") +} +\end{codebox} + +\begin{center} +\parseshrub{f} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily, + draw, + rounded corners = 1pt, + fill = gray!20, + l sep = 1.5cm, + s sep = 0.75cm} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} + +\subsubsection*{Parsing} + +Thank goodness for this too. The parser nested within the \LaTeX{} environment \mintinline{latex}{\begin{CAS}..\end{CAS}} allows for fairly natural function assignment; the name of the function must be declared in \mintinline{lua}{vars(...)} (or rather, as a \texttt{SymbolExpression}) beforehand: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('s','t','f') + f = f(s^2,s+2*t) + f.derivatives = {1,1} +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\begin{CAS} + vars('s','t','f') + f = f(s^2,s+2*t) + f.derivatives = {1,1} +\end{CAS} +\[ \print{f} \] +\end{codebox} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/reference/ref_core/ref_core_methods/ref_core_methods.tex b/macros/luatex/latex/luacas/doc/reference/ref_core/ref_core_methods/ref_core_methods.tex new file mode 100644 index 0000000000..a88b5a15d8 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/reference/ref_core/ref_core_methods/ref_core_methods.tex @@ -0,0 +1,1155 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse,documentation} +\usepackage{microtype} +\usepackage{makeidx} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\definecolor{codegreen}{HTML}{49BE25} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\newtcolorbox{codehead}[1][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1 +} + +\usepackage{varwidth} + +\newtcolorbox{newcodehead}[2][]{ + enhanced, + frame hidden, + colback=rosegray!15, + boxrule=0mm, + leftrule=5mm, + rightrule=5mm, + boxsep=0mm, + arc=0mm, + outer arc=0mm, + left=3mm, + right=3mm, + top=1mm, + bottom=1mm, + toptitle=1mm, + bottomtitle=1mm, + oversize, + #1, + fonttitle=\bfseries\ttfamily\footnotesize, + coltitle=rosegray, + attach boxed title to top text right, + boxed title style={frame hidden,size=small,bottom=-1mm, + interior style={fill=none, + top color=white, + bottom color=white}}, + title={#2} +} + + +\makeindex + +\newcommand{\coderef}[2]{% +\begin{codehead}[sidebyside,segmentation hidden]% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{codehead}% +} + +\newcommand{\newcoderef}[3]{% +\begin{newcodehead}[sidebyside,segmentation hidden]{#3}% + \mintinline{lua}{#1}% + \tcblower% + \begin{flushright}% + \mintinline{lua}{#2}% + \end{flushright}% +\end{newcodehead}% +} + +\begin{document} + +\subsection{Core Methods} + +Any of the methods below can be used within \mintinline{latex}{\begin{CAS}..\end{CAS}}. There are times when the parser or \LaTeX{} front-end allows for simpler syntax or usability. + +\coderef{function Expression:autosimplify()}{return Expression|table} +\index{Core!Methods!\texttt{autosimplify}} +\addcontentsline{toc}{subsubsection}{\ttfamily autosimplify} + +Performs fast simplification techniques on an expression. The return depends on the type of input \texttt{Expression}. Generally, one should call {\ttfamily autosimplify()} on expressions before applying other core methods to them. + +Consider the code: + +\begin{minted}{latex} +\begin{CAS} + vars('x','y','z') + w = x/y + y/z + z/x +\end{CAS} +\[ \print{w} = \print{w:autosimplify()} \] +\end{minted} +\begin{CAS} + vars('x','y','z') + w = x/y + y/z + z/x +\end{CAS} +The output is as follows: +\[ \print{w} = \print{w:autosimplify()} \] +It seems that \texttt{autosimplify()} did nothing; but there are significant structural differences between \texttt{w} and \texttt{w:autosimplify()}: + +\begin{multicols}{2} + \begin{center} + \underline{Expression tree for \texttt{w}} + + \parseforest{w} + \bracketset{action character = @} + \begin{forest} + for tree = {font = \ttfamily} + @\forestresult + \end{forest} + + \underline{Expression tree for \texttt{w:autosimplify()}} + + \parseforest{w:autosimplify()} + \bracketset{action character = @} + \begin{forest} + for tree = {font = \ttfamily} + @\forestresult + \end{forest} +\end{center} +\end{multicols} + +Ironically, the \emph{autosimplified} expression tree on the right looks more complicated than the one on the left! But one of the primary functions of \texttt{autosimplify()} is to take an expression (that truly could be input in a myriad of ways) and convert that expression into something \emph{anticipatable}. + +For example, suppose the user inputs: +\begin{minted}{latex} +\begin{CAS} + w = x/y + (z/x+y/z) +\end{CAS} +\end{minted} +\begin{CAS} + w = x/y + (z/x+y/z) +\end{CAS} +In this case, the expression trees for \texttt{w} and \texttt{w:autosimplify()}, respectively, look as follows: + +\begin{multicols}{2} +\begin{center} +\parseforest{w} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} + +\parseforest{w:autosimplify()} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} +\end{center} +\end{multicols} +{\bf Note:} \texttt{w:autosimplify()} is exactly the same as it was before despite the different starting point. This is an essential function of \texttt{autosimplify()}. + +\subsubsection*{Parsing} + +The starred variant of the \LaTeX{} command \mintinline{latex}{\print} will automatically apply the method \texttt{autosimplify()} to its argument: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + a = x+x/2 +\end{CAS} +\[ \print{a} = \print*{a} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + a = x+x/2 +\end{CAS} +\[ \print{a} = \print*{a} \] +\end{codebox} +Alternatively, you can call \texttt{autosimplify()} directly within \mintinline{latex}{\begin{CAS}..\end{CAS}}: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + a = (x+x/2):autosimplify() +\end{CAS} +\[ \print{a} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + a = (x+x/2):autosimplify() +\end{CAS} +\[ \print{a} \] +\end{codebox} + +\coderef{function Expression:evaluate()}{return Expression} +\index{Core!Methods!\texttt{evaluate}} +\addcontentsline{toc}{subsubsection}{\ttfamily evaluate} + +Attempts to apply operations found in the expression tree of \texttt{Expression}. For instance, evaluating a {\ttfamily DerivativeExpression} applies the derivative operator with respect to the {\ttfamily symbol} field to its {\ttfamily expression} field. Evaluating a {\ttfamily BinaryOperation} with its {\ttfamily operation} field set to {\ttfamily ADD} returns the sum of the numbers in the {\ttfamily expressions} field, if possible. If the expression does not represent an operation or is unable to be evaluated, calling {\ttfamily evaluate()} on an expression returns itself. + +For example, the code: +\begin{minted}{latex} +\directlua{ + x = Integer(1)/Integer(2) + y = Integer(2)/Integer(3) + z = BinaryOperation(BinaryOperation.ADD,{x,y}) +} +\[ \print{z} = \print{z:evaluate()}.\] +\end{minted} +produces: +\directlua{ + x = Integer(1)/Integer(2) + y = Integer(2)/Integer(3) + z = BinaryOperation(BinaryOperation.ADD,{x,y}) +} +\[ \print{z} = \print{z:evaluate()}.\] + +\subsubsection*{Parsing} + +Arithmetic like above is actually done automatically (via the \texttt{Ring} interface): +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + x = 1/2 + y = 2/3 + z = x+y +\end{CAS} +\[ z = \print{z} \] +\end{minted} +\tcblower +\begin{CAS} + x = 1/2 + y = 2/3 + z = x+y +\end{CAS} +\[ z = \print{z} \] +\end{codebox} + +Otherwise, the \texttt{evaluate()} method will attempt to evaluate all subexpressions, and then stop there: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + y = diff(x^2+x,x)+diff(2*x,x) + y = y:evaluate() +\end{CAS} +\[ \print{y} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x') + y = diff(x^2+x,x)+diff(2*x,x) + y = y:evaluate() +\end{CAS} +\[ \print{y} \] +\end{codebox} +Whereas \texttt{autosimplify()} will return $3+2x$; indeed, the \texttt{autosimplify()} method (usually) begins by applying \texttt{evaluate()} first. + +\coderef{function Expression:expand()}{return Expression} +\index{Core!Methods!\texttt{expand}} +\addcontentsline{toc}{subsubsection}{\ttfamily expand} + +Expands an expression, turning products of sums into sums of products. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','y','z','w') + a = x+y + b = z+w + c = a*b +\end{CAS} +\[ \print{c} = \print{c:expand()} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y','z','w') + a = x+y + b = z+w + c = a*b +\end{CAS} +\[ \print{c} = \print{c:expand()} \] +\end{codebox} + +\subsubsection*{Parsing} + +There is an \texttt{expand()} function in the parser; though it calls the \texttt{autosimplify()} method first. So, for example, \mintinline{lua}{expand(c)} is equivalent to \mintinline{lua}{c:autosimplify():expand()}. + +\coderef{function Expression:factor()}{return Expression} +\index{Core!Methods!\texttt{factor}} +\addcontentsline{toc}{subsubsection}{\ttfamily factor} + +Factors an expression, turning sums of products into products of sums. For general \texttt{Expressions} this functionality is somewhat limited. For example: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + a = x-1 + b = a*x+a +\end{CAS} +\[ \print{b} = \print{b:factor()} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y') + a = x-y + b = a*x+a*y +\end{CAS} +\[ \print{b} = \print{b:factor()} \] +\end{codebox} +On the other hand: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','y') + a = x^2-y^2 +\end{CAS} +\[ \print{a} = \print{a:factor()} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y') + a = x^2-y^2 +\end{CAS} +\[ \print{a} = \print{a:factor()} \] +\end{codebox} + +\subsubsection*{Parsing} + +There is a \texttt{factor()} function in the parser that is more class-aware than the basic \texttt{:factor()} method mentioned here. For example: +\begin{codebox} + \begin{minted}[fontsize = \small]{latex} +\begin{CAS} + x = 12512 +\end{CAS} +\[ \print{x:factor()} = \print{factor(x)} \] +\end{minted} +\tcblower +\begin{CAS} + x = 12512 +\end{CAS} +\[ \print{x:factor()} = \print{factor(x)} \] +\end{codebox} + +\coderef{function Expression:freeof(symbol)}{return bool} +\index{Core!Methods!\texttt{freeof}} +\addcontentsline{toc}{subsubsection}{\ttfamily freeof} + +Determines whether or not {\ttfamily Expression} contains a particular {\ttfamily symbol} somewhere in its expression tree. + +The method \texttt{freeof()} is quite literal. For example: +\begin{codebox} +\begin{minted}[fontsize=\small]{lua} +vars('foo','bar') +baz = foo+bar +if baz:freeof(foo) then + tex.sprint(baz:tolatex(), " is free of ", + foo:tolatex(),"!") +else + tex.sprint(baz:tolatex(), " is bound by ", + foo:tolatex(),".") +end +\end{minted} +\tcblower +\directlua{ +vars('foo','bar') +baz = foo+bar +if baz:freeof(foo) then + tex.sprint(baz:tolatex(), " is free of ", + foo:tolatex(),"!") +else + tex.sprint(baz:tolatex(), " is bound by ", + foo:tolatex(),".") +end +} +\end{codebox} +On the other hand, the expression tree for \mintinline{lua}{SymbolExpression("foo")} contains a single node with no edges. With nary a \mintinline{lua}{SymbolExpression("fo")} to find in such an expression tree, we have: +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +vars('foo','fo') +if foo:freeof(fo) then + tex.sprint(foo:tolatex()," is free of ", + fo:tolatex(),"!") +else + tex.sprint(foo:tolatex()," is bound by ", + fo:tolatex(),'.') +end + \end{minted} + \tcblower + \directlua{ +vars('foo','fo') +if foo:freeof(fo) then + tex.sprint(foo:tolatex()," is free of ", + fo:tolatex(),"!") +else + tex.sprint(foo:tolatex()," is bound by ", + fo:tolatex(),'.') +end +} + \end{codebox} + +\coderef{function Expression:isatomic()}{return bool} +\index{Core!Methods!\texttt{isatomic}} +\addcontentsline{toc}{subsubsection}{\ttfamily isatomic} + +Determines whether an expression is \emph{atomic}. Typically, atomicity is measured by whether the \texttt{Expression} has any subexpression fields. So, for example, \texttt{Integer(5)} and \texttt{Integer(15)} are atomic, and so is \texttt{Integer(20)}. But: +\begin{minted}{lua} +BinaryOperation(BinaryOperation.ADD, + {Integer(5),Integer(15)}) +\end{minted} +is non-atomic. + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +x = SymbolExpression("x") +y = x*x+x +if x:isatomic() then + tex.print(tostring(x),"is atomic;") +end +if not y:isatomic() then + tex.print(tostring(y),"is compound.") +end +\end{minted} +\tcblower +\directlua{ +x = SymbolExpression("x") +y = x*x+x +if x:isatomic() then + tex.print(tostring(x),"is atomic;") +end +if not y:isatomic() then + tex.print(tostring(y),"is compound.") +end +} +\end{codebox} +Since \texttt{SymbolExpression} inherits from \texttt{AtomicExpression}, we have that \texttt{isatomic()} is taken literally as well. For example: +\begin{codebox} + \begin{minted}[fontsize=\small,breaklines]{lua} +y = SymbolExpression("x*x+x") +if not y:isatomic() then + tex.print(tostring(y),"is compound.") +else + tex.print("But",tostring(y),"is atomic, + from a certain point of view.") +end +\end{minted} +\tcblower +\directlua{ + y = SymbolExpression("x*x+x") +if not y:isatomic() then + tex.print(tostring(y),"is compound.") +else + tex.print("But",tostring(y),"is atomic, + from a certain point of view.") +end +} +\end{codebox} +\vskip 0.2cm + +\coderef{function Expression:iscomplexconstant()}{return bool} +\index{Core!Methods!\texttt{iscomplexconstant}} +\addcontentsline{toc}{subsubsection}{\ttfamily iscomplexconstant} + +Determines whether an expression is a complex number in the mathematical sense, such as $3 + \sqrt{2}i$. It's helpful to keep in mind that, oftentimes, content needs to be simplified/evaluated in order to obtain the intended results: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +a = (Integer.one() + I) ^ Integer(2) +if a:iscomplexconstant() then + tex.print("$",a:tolatex(),"$ is a complex constant.") +else + tex.print("$",a:tolatex(),"$ is not a complex constant.") +end +\end{minted} +\tcblower +\begin{center} +\luaexec{ + a = (Integer.one() + I) ^ Integer(2) + if a:iscomplexconstant() then + tex.print("$",a:tolatex(),"$ is a complex constant.") + else + tex.print("$",a:tolatex(),"$ is not a complex constant.") + end +} +\end{center} +\end{codebox} +While: +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +a = (Integer.one()+I) ^ Integer(2) +a = a:expand():simplify() +if a:iscomplexconstant() then + tex.print("$",a:tolatex(),"$ is a complex constant.") +else + tex.print("$",a:tolatex(),"$ is not a complex constant.") +end +\end{minted} +\tcblower +\begin{center} +\luaexec{ + a = (Integer.one()+I) ^ Integer(2) + a = a:expand():simplify() + if a:iscomplexconstant() then + tex.print("$",a:tolatex(),"$ is a complex constant.") + else + tex.print("$",a:tolatex(),"$ is not a complex constant.") + end +} +\end{center} +\end{codebox} + +\coderef{function Expression:isconstant()}{return bool} +\index{Core!Methods!\texttt{isconstant}} +\addcontentsline{toc}{subsubsection}{\ttfamily isconstant} + +Determines whether an expression is atomic and contains no variables. This method is counterintuitive in some cases. For instance: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +if not pi:isconstant() then + tex.print("$\\pi$ is not constant.") +end +\end{minted} +\tcblower +\luaexec{ +if not pi:isconstant() then + tex.print("$\\pi$ is not constant.") +end +} +\end{codebox} +This is because {\ttfamily isconstant()} is meant to check for certain autosimplification transformations that can be performed on arbitrary {\ttfamily Ring} elements but not on those constants. Use {\ttfamily isrealconstant()} for what mathematicians think of as constants. + +\coderef{function Expression:isrealconstant()}{return bool} +\index{Core!Methods!\texttt{isrealconstant}} +\addcontentsline{toc}{subsubsection}{\ttfamily isrealconstant} + +Determines whether an expression is a real number in the mathematical sense, such as $2$, $\sqrt{5}$, or $\sin(3)$. For example: + +\begin{codebox} + \begin{minted}[fontsize=\small]{lua} +if pi:isrealconstant() then + tex.print("$\\pi$ is a real constant.") +end +\end{minted} +\tcblower +\luaexec{ + if pi:isrealconstant() then + tex.print("$\\pi$ is a real constant.") + end +} +\end{codebox} + +\coderef{function Expression:order(Expression)}{return boolean} +\index{Core!Methods!\texttt{order}} +\addcontentsline{toc}{subsubsection}{\ttfamily order} + +For the goals of autosimplification, \texttt{Expression}s must be ordered. \texttt{Expression:order(other)} method returns \mintinline{lua}{true} if \texttt{Expression} is ``less than'' \texttt{other} according to this ordering. + +\begin{multicols}{2} +On certain classes, the ordering is intuitive: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +a = 4 +b = 3 +if a:order(2) then + tex.print(a:tolatex(), + "is less than", + b:tolatex()) +else + tex.print(b:tolatex(), + "is less than", + a:tolatex()) +end +\end{minted} +\tcblower +\begin{CAS} + a = 4 + b = 3 + if a:order(2) then + tex.print(a:tolatex(), + "is less than", + b:tolatex()) + else + tex.print(b:tolatex(), + "is less than", + a:tolatex()) + end +\end{CAS} +\end{codebox} + +\columnbreak + +On \texttt{SymbolExpression}s, the ordering is lexigraphic: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{lua} +vars('a') +vars('b') +if b:order(a) then + tex.print(b:tolatex(), + "is less than", + a:tolatex()) +else + tex.print(a:tolatex(), + "is less than", + b:tolatex()) +end +\end{minted} +\tcblower +\begin{CAS} + vars('a','b') + if b:order(a) then + tex.print(b:tolatex(), + "is less than", + a:tolatex()) + else + tex.print(a:tolatex(), + "is less than", + b:tolatex()) + end +\end{CAS} +\end{codebox} +\end{multicols} + +Of course, inter-class comparisons can be made as well -- but these are predominantly dictated by typesetting conventions. + +\coderef{function Expression:setsubexpressions(subexpressions)}{return Expression} +\index{Core!Methods!\texttt{setsubexpressions}} +\addcontentsline{toc}{subsubsection}{\ttfamily setsubexpressions} + +Creates a copy of an expression with the list of subexpressions as its new subexpressions. This can reduce code duplication in other methods. + + +\coderef{function Expression:simplify()}{return Expression} +\index{Core!Methods!\texttt{simplify}} +\addcontentsline{toc}{subsubsection}{\ttfamily simplify} + +Performs more extensive simplification of an expression. This may be slow, so this function is separate from autosimplification and is not called unless the user specifically directs the CAS to do so. The method aims to find an expression tree equivalent to the one given that is ``smaller'' in size as measured by the number of nodes in the expression tree. + +The \texttt{simplify()} method does call the \texttt{autosimplify()} method first. Here's an example of where the results of \texttt{autosimplify()} and \texttt{simplify()} differ: + +\begin{minted}[breaklines]{latex} +\begin{CAS} + vars('x') + a = 1-x+0*x + b = 1+1*x + c = a*b +\end{CAS} +\[ \print{c} = \print{c:autosimplify()} = \print{c:simplify()}. \] +\end{minted} +The code above produces: +\begin{CAS} + vars('x') + a = 1-x+0*x + b = 1+1*x + c = a*b +\end{CAS} +\[ \print{c} = \print{c:autosimplify()} = \print{c:simplify()}. \] + +\subsubsection*{Parsing} + +There is a \texttt{simplify()} function for those unfamiliar with Lua methods. So, for example, \mintinline{lua}{c:simplify()} is equivalent to \mintinline{lua}{simplify(c)}. + +\coderef{function Expression:size()}{return Integer} +\index{Core!Methods!\texttt{size}} +\addcontentsline{toc}{subsubsection}{\ttfamily size} + +Returns the number of nodes of the tree that constitutes an expression, or roughly the total number of expression objects that make up the expression. + +For example, consider: +\begin{minted}{latex} +\begin{CAS} + vars('x') + a = (1-x+0*x) + b = (1+1*x) + c = a*b +\end{CAS} +\end{minted} +\begin{CAS} + vars('x') + a = (1-x+0*x) + b = (1+1*x) + c = a*b +\end{CAS} +Then the expression trees for \texttt{c}, \texttt{c:autosimplify()}, and \texttt{c:simplify()} are as follows: + +\begin{multicols}{3} + \begin{center} + \underline{Tree for \texttt{c}:} +\parseforest{c} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} + +\columnbreak + +\underline{Tree for \texttt{c:autosimplify()}:} +\parseforest{c:autosimplify()} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} + +\columnbreak + +\underline{Tree for \texttt{c:simplify()}:} +\parseforest{c:simplify()} +\bracketset{action character = @} +\begin{forest} + for tree = {font = \ttfamily} + @\forestresult +\end{forest} +\end{center} +\end{multicols} +Accordingly, we have: +\begin{codebox} + \begin{minted}[breaklines,fontsize=\small]{lua} +tex.print("The size of \\texttt{c} is", + tostring(c:size()),"\\newline") +tex.print("The size of + \\texttt{c:autosimplify()} is", + tostring(c:autosimplify():size()), "\\newline") +tex.print("The size of + \\texttt{c:simplify()} is", + tostring(c:simplify():size())) + \end{minted} + \tcblower + \luaexec{ + tex.print("The size of \\texttt{c} is", tostring(c:size()),"\\newline") + tex.print("The size of \\texttt{c:autosimplify()} is", tostring(c:autosimplify():size()), "\\newline") + tex.print("The size of \\texttt{c:simplfy()} is", tostring(c:simplify():size())) +} +\end{codebox} +\vskip 0.2cm + +\coderef{function Expression:subexpressions()}{return table} +\index{Core!Methods!\texttt{subexpressions}} +\addcontentsline{toc}{subsubsection}{\ttfamily subexpressions} + +Returns a list of all subexpressions of an expression. This gives a unified interface to the instance variables for subexpressions, which have different names across classes. For example, consider: +\begin{codebox} +\begin{minted}[breaklines,fontsize=\footnotesize]{latex} +\begin{CAS} + vars('x','y','z') + a = x*y+y*z + b = int(sin(x),x,0,pi/2) +\end{CAS} +\[ a = \print{a} \quad \text{and} \quad b=\print{b}.\] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y','z') + a = x*y+y*z + b = int(sin(x),x,0,pi/2) +\end{CAS} +\[ a = \print{a} \quad \text{and} \quad b=\print{b}.\] +\end{codebox} +Here are the expression shrubs for \texttt{a} and \texttt{b}: + +\begin{multicols}{2} +\begin{center} + \underline{Expression shrub for \texttt{a}} + + \parseshrub{a} + \bracketset{action character = @} + \begin{forest} + for tree = {draw, + rectangle, + rounded corners=1pt, + fill=gray!10, + s sep = 2cm, + font=\ttfamily} + @\shrubresult + \end{forest} + + \underline{Expression shrub for \texttt{b}} + +\parseshrub{b} +\bracketset{action character = @} +\begin{forest} + for tree = {draw, + rectangle, + rounded corners=1pt, + fill=gray!10, + s sep = 1cm, + font=\ttfamily} + @\shrubresult +\end{forest} +\end{center} +\end{multicols} +On the other hand: +\begin{codebox} +\begin{minted}[breaklines,fontsize=\small]{lua} +for _,expr in ipairs(a:subexpressions()) do + tex.print("$", expr:tolatex(), "$\\quad") +end +\end{minted} +\tcblower +\luaexec{ + for _,expr in ipairs(a:subexpressions()) do + tex.print("$", expr:tolatex(), "$\\quad") + end +} +\end{codebox} +while: +\begin{codebox} +\begin{minted}[breaklines,fontsize=\small]{lua} +for _,expr in ipairs(b:subexpressions()) do + tex.print("$", expr:tolatex(), "$\\quad") +end +\end{minted} +\tcblower +\luaexec{ + for _,expr in ipairs(b:subexpressions()) do + tex.print("$",expr:tolatex(),"$\\quad") + end +} +\end{codebox} +\vskip 0.2cm + +\coderef{function Expression:substitute(map)}{return Expression} +\index{Core!Methods!\texttt{substitute}} +\addcontentsline{toc}{subsubsection}{\ttfamily substitute} + +The input \texttt{map} is a table that maps expressions to expressions; the method then recursively maps each instance of an expression with its corresponding table expression. One should take care when replacing multiple compound expressions in a single command, since there is no guarantee as to the order in which subexpressions in the table are replaced. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('foo','bar','baz') + qux = (foo/bar) + qux = qux:substitute({[foo]=bar,[bar]=baz}) +\end{CAS} +\[ \print{qux} \] +\end{minted} +\tcblower +\begin{CAS} + vars('foo','bar','baz') + qux = (foo/bar) + qux = qux:substitute({[foo]=bar,[bar]=baz}) + \end{CAS} + \[ \print{qux} \] +\end{codebox} + +\subsubsection*{Parsing} + +There is a \texttt{substitute()} function with a slightly more user-friendly syntax. In particular, + +\mintinline{lua}{(foo/bar):substitute({[foo]=bar,[bar]=baz})} + +is equivalent to + +\mintinline{lua}{substitute({[foo]=bar,[bar]=baz}, foo/bar)} + +\coderef{function Expression:tolatex()}{return string} +\index{Core!Methods!\texttt{tolatex}} +\addcontentsline{toc}{subsubsection}{\ttfamily tolatex} + +Converts an expression to \LaTeX{} code. Some folks have strong feelings about how certain things are typeset. Case and point, which of these is your favorite: +\[ \int \sin(\frac{y}{2})dy \qquad \int \sin\left( \frac{y}{2} \right)dy \qquad \int \sin\left( \frac{y}{2} \right)\, dy \qquad \int \sin\!\left( \frac{y}{2} \right)\, dy \qquad \int \sin\!\left( \frac{y}{2} \right) \mathop{\mathrm{d}y} \qquad \int \sin\mathopen{}\left( \frac{y}{2} \right) \mathop{dy} \quad ?\] +We've tried to remain neutral: + +\begin{codebox} +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('y') + f = diff(int(sin(y/2),y),y) +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\begin{CAS} + vars('y') + f = diff(int(sin(y/2),y),y) +\end{CAS} +\[ \print{f} \] +\end{codebox} +With any luck, we've pleased at least as many people as we've offended. In desperate times, one could rewrite the \texttt{tolatex()} method for any given class. Here, for example, is the \texttt{tolatex()} method as written for the \texttt{DerivativeExpression} class: +\begin{minted}[breaklines]{lua} +function DerivativeExpression:tolatex() + return '\\frac{d}{d' .. self.symbol:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)' +end +\end{minted} +But there are heathens that live among us who might prefer: +\begin{minted}[breaklines]{lua} +function DerivativeExpression:tolatex() + return '\\frac{\\mathrm{d}}{\\mathrm{d}' .. self.symbol:tolatex() .. '}\\left(' .. self.expression:tolatex() .. '\\right)' +end +\end{minted} +If we include the above function in a separate file, say \texttt{mytex.lua}, and use: + +\mintinline{latex}{\directlua{dofile('mytex.lua')}} + +or include the above function directly into the document via \mintinline{latex}{\directlua} or \mintinline{latex}{\luaexec}, then we would get: +\begin{codebox} +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = DerivativeExpression(y+sin(y),y) +\end{CAS} +\[ \print{f} \] +\end{minted} +\tcblower +\[ \frac{\mathrm{d}}{\mathrm{d}y} \left( y + \sin\mathopen{}\left(y\right) \right).\] +\end{codebox} + +\subsubsection*{Parsing} + +The \LaTeX{} command \mintinline{latex}{\print} calls the method \mintinline{lua}{tolatex()} unto its argument and uses \mintinline{lua}{tex.print()} to display the results. The starred variant \mintinline{latex}{\print*} applies the \texttt{autosimplify()} method before applying \mintinline{lua}{tolatex()}. + +Additionally, one can use the \texttt{disp()} function within \mintinline{latex}{\begin{CAS}..\end{CAS}}. + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + f = DerivativeExpression(y+sin(y),y) + disp(f) +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + f = DerivativeExpression(y+sin(y),y) + disp(f) +\end{CAS} +\end{codebox} + +The function \texttt{disp} takes two optional boolean arguments both are set to \texttt{false} by default. The first optional boolean controls \emph{inline} vs \emph{display} mode; the second optional boolean controls whether the method \texttt{autosimplify()} is called before printing: + +\begin{multicols}{3} + \begin{codebox}[] +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + disp(f,true) +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + disp(f,true) +\end{CAS} +\end{codebox} + \begin{codebox}[] +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + disp(f,true,true) +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + disp(f,true,true) +\end{CAS} +\end{codebox} + \begin{codebox}[] +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + disp(f,false,true) +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + disp(f,false,true) +\end{CAS} +\end{codebox} +\end{multicols} + + +\coderef{function Expression:topolynomial()}{return Expression | bool} +\index{Core!Methods!\texttt{topolynomial}} +\addcontentsline{toc}{subsubsection}{\ttfamily topolynomial} + +Attempts to cast \texttt{Expression} into a polynomial type ({\ttfamily PolynomialRing}); there are multiple outputs. The first output is \texttt{self} or \texttt{PolynomialRing}; the second output is \mintinline{lua}{false} or \mintinline{lua}{true}, respectively. \texttt{PolynomialRing} is the name of the class that codifies univariate polynomials proper. + +Polynomial computations tend to be significantly faster when those polynomials are stored as arrays of coefficients (as opposed to, say, when they are stored as generic \texttt{BinaryOperation}s). Hence the need for a method like \texttt{topolynomial()}. + +{\bf Warning:} the \texttt{topolynomial()} method expects the input to be autosimplified. For example: + +\begin{multicols}{2} +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = 3+2*x+x^2 + f,b = f:topolynomial() + if b then + tex.print("\\[",f:tolatex(),"\\]") + else + tex.print("womp womp") + end +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = 3+2*x+x^2 + f,b = f:topolynomial() + if b then + tex.print("\\[",f:tolatex(),"\\]") + else + tex.print("\\[ \\text{womp womp} \\]") + end +\end{CAS} +\end{codebox} +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = 3+2*x+x^2 + f,b = f:autosimplify():topolynomial() + if b then + tex.print("\\[",f:tolatex(),"\\]") + else + tex.print("womp womp") + end +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = 3+2*x+x^2 + f,b = f:autosimplify():topolynomial() + if b then + tex.print("\\[",f:tolatex(),"\\]") + else + tex.print("\\[ \\text{womp womp} \\]") + end +\end{CAS} +\end{codebox} +\end{multicols} + +\subsubsection*{Parsing} + +There is a \mintinline{lua}{topoly()} function that applies \mintinline{lua}{:autosimplify()} automatically to the input. For example: +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x') + f = 3+2*x+x^2 + f = topoly(f) +\end{CAS} +The Lua variable \texttt{f} is the \whatis{f}: $\print{f}$. +\end{minted} +\tcblower +\begin{CAS} + vars('x') + f = 3+2*x+x^2 + f = topoly(f) +\end{CAS} +The Lua variable \texttt{f} is the \whatis{f}: $\print{f}$. +\end{codebox} + + +\coderef{function Expression:type()}{return Expression | bool} +\index{Core!Methods!\texttt{type}} +\addcontentsline{toc}{subsubsection}{\ttfamily type} + +Returns the \mintinline{lua}{__index} field in the metatable for \texttt{Expression}. In other words, this function returns the type of \texttt{Expression}. Here's typical usage: + +\begin{codebox} + \begin{minted}[breaklines,fontsize=\small]{latex} +\begin{CAS} + vars('x') + if x:type() == SymbolExpression then + tex.print(x:tolatex(), "is a SymbolExpression.") + end +\end{CAS} +\end{minted} +\tcblower +\begin{CAS} + vars('x') + if x:type() == SymbolExpression then + tex.print(x:tolatex(), "is a SymbolExpression.") + end +\end{CAS} +\end{codebox} + +\subsubsection*{Parsing} + +The \LaTeX{} command \mintinline{latex}{\whatis} can be used to print the type of \texttt{Expression}: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +x is a \whatis{x} + \end{minted} + \tcblower + x is a \whatis{x} +\end{codebox} + +Alternatively, there's a \mintinline{lua}{whatis()} function and a \mintinline{lua}{longwhatis()} function that can be called within a Lua environment (like \mintinline{latex}{\directlua} or \mintinline{latex}{\luaexec}): + +\begin{codebox} +\begin{minted}[fontsize=\small]{lua} +tex.print(whatis(x), '\\newline') +tex.print(longwhatis(x)) +\end{minted} +\tcblower +\directlua{ + tex.print(whatis(x), '\\newline') + tex.print(longwhatis(x)) +} +\end{codebox} + + + + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/tutorial/tut.tex b/macros/luatex/latex/luacas/doc/tutorial/tut.tex new file mode 100644 index 0000000000..3e12ec919d --- /dev/null +++ b/macros/luatex/latex/luacas/doc/tutorial/tut.tex @@ -0,0 +1,217 @@ +\documentclass{article} + +\usepackage{standalone} +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\begin{document} + +\section{Tutorials} + +Taking a cue from the phenomenal TikZ documentation, we introduce basic usage of the \texttt{luacas} package through a few informal tutorials. In the subsections that follow, we'll walk through how each of the outputs below are made using \texttt{luacas}. {\bf Crucially}, none of the computations below are ``hardcoded''; all computations are performed and printed using \texttt{luacas} to maximize portability and code reuse. + +\begin{tcolorbox}[colback=rose!10, + colframe=rose, + arc=1pt, + frame hidden] +{\bf Tutorial 1:} {\itshape A limit definition of the derivative for Alice.}\vskip0.2cm +\small +\begin{CAS} + vars('x','h') + f = 2*x^3-x +\end{CAS} +Let $f(x) = \print{f}$. We wish to compute the derivative of $f(x)$ at $x$ using the limit definition of the derivative. Toward that end, we start with the appropriate difference quotient: +\begin{CAS} + subs = {[x] = x+h} + q = (f:substitute(subs) - f)/h +\end{CAS} +\[ \begin{aligned} + \print{q} &= + \begin{CAS} + q = expand(q) + \end{CAS} + \print{q}& &\text{expand/simplify} \\ + \begin{CAS} + subs = {[h]=0} + q = q:substitute(subs) + \end{CAS} + &\xrightarrow{h\to 0} \print{q}& &\text{take limit} \\ + &= + \begin{CAS} + q = simplify(q) + \end{CAS} + \print{q}& &\text{simplify.} +\end{aligned} \] +%So $\print{diff(f,x)} = \print*{diff(f,x)}$. +\end{tcolorbox} +\vfill + +\begin{tcolorbox}[colback=rosenavy!10, + colframe=rosenavy, + arc=1pt, + frame hidden] + \small + {\bf Tutorial 2:} {\itshape A local max/min diagram for Bob}. + \vskip 0.2cm + Consider the function $f(x)$ defined by: + \begin{CAS} + vars('x') + f = x^2+2*x-2 + g = x^2-1 + subs = {[x] = f} + dh = expand(substitute(subs,g)) + h = simplify(int(dh,x)+10) + \end{CAS} + $\displaystyle f(x) = \print{h}$. +\begin{multicols}{2} + Note that: + \[ f'(x) = \print{dh}.\] + The roots to $f'(x)=0$ equation are: + \begin{CAS} + r = roots(dh) + \end{CAS} + \[ \print{r[1]}, \quad \print{r[2]}, \quad \print{r[3]}, \quad \print{r[4]}.\] + Recall: $f'(x_0)$ measures the slope of the tangent line to $y=f(x)$ at $x=x_0$. The values $r$ where $f'(r)=0$ correspond to places where the slope of the tangent line to $y=f(x)$ is horizontal (see the illustration). This gives us a method for identifying locations where the graph $y=f(x)$ attains a peak (local maximum) or a valley (local minimum). + \directlua{ + v = {} + for i=1,4 do + table.insert(v,simplify(substitute({[x]=r[i]},h))) + end + } + \columnbreak + \store{h}\store{dh} + \begin{tikzpicture}[scale=0.95] + \begin{axis}[legend pos = north west] + \addplot + [domain=-3.5:1.5,samples=100] {\h}; + \addlegendentry{$f$}; + \addplot[densely dashed] + [domain=-3.25:1.25,samples=100] {\dh}; + \addlegendentry{$df/dx$}; + \addplot[gray,dashed,thick] + [domain=-3.5:1.5] {0}; + \luaexec{for i=1,4 do + tex.print("\\draw[fill=purple,purple]", + "(axis cs:{", tostring(r[i]) ,"},0) circle (1.5pt)", + "(axis cs:{", tostring(r[i]) ,"},{", tostring(v[i]), "}) circle (1.5pt)", + "(axis cs:{", tostring(r[i]) ,"},{", tostring(v[i]), "}) edge[dashed] (axis cs:{", tostring(r[i]) ,"},0);") + end} + \end{axis} + \end{tikzpicture} +\end{multicols} +\end{tcolorbox} + +\vfill + +\begin{tcolorbox}[colback=roseorange!10, + colframe=roseorange, + arc=1pt, + frame hidden] + \small +{\bf Tutorial 3:} {\itshape A limit definition of the derivative for Charlie.}\vskip 0.2cm + +\begin{CAS} + vars('x','h') + f = x/(x^2+1) +\end{CAS} +Let $f(x) = \print{f}$. We wish to compute the derivative of $f(x)$ at $x$ using the limit definition of the derivative. Toward that end, we start with the appropriate difference quotient: +\begin{CAS} + subs = {[x] = x+h} + q = (f:substitute(subs) - f)/h +\end{CAS} +\directlua{ + function Expression:mycombine() + local a = self.expressions[1].expressions[1].expressions[1] + local b = self.expressions[1].expressions[1].expressions[2] + local c = self.expressions[1].expressions[2].expressions[1] + local d = self.expressions[1].expressions[2].expressions[2] + local numerator = a*d-b*c + local denominator = self.expressions[2]*b*d + return numerator/denominator + end + function Expression:mysimplify() + local a = self.expressions[1] + local b = self.expressions[2] + a = simplify(a) + return a/b + end + function Expression:myfactor() + local a = self.expressions[1] + local b = self.expressions[2] + a = factor(a) + return a/b + end +} +\[ \begin{aligned} + \print{q} &= + \begin{CAS} + q = q:mycombine() + \end{CAS} + \print{q}& &\text{get a common denominator} \\ + &= + \begin{CAS} + q = q:mysimplify() + \end{CAS} + \print{q}& &\text{simplify the numerator} \\ + &= + \begin{CAS} + q = q:myfactor() + \end{CAS} + \print{q} & &\text{factor numerator} \\ + &= + \begin{CAS} + q = simplify(q) + \end{CAS} + \print{q}& &\text{cancel the $h$s} \\ + &\xrightarrow{h\to 0} + \begin{CAS} + subs = {[h] = 0} + q = substitute(subs,q):autosimplify() + \end{CAS} + \print{q}& &\text{take limit.} +\end{aligned} \] +\end{tcolorbox} +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/tutorial/tut1/tut1.tex b/macros/luatex/latex/luacas/doc/tutorial/tut1/tut1.tex new file mode 100644 index 0000000000..1151d3e80b --- /dev/null +++ b/macros/luatex/latex/luacas/doc/tutorial/tut1/tut1.tex @@ -0,0 +1,238 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\begin{document} + +\subsection{Tutorial 1: Limit Definition of the Derivative} + +Alice is teaching calculus, and she wants to give her students many examples of the dreaded \emph{limit definition of the derivative}. On the other hand, she'd like to avoid working out many examples by-hand. She decides to give \texttt{luacas} a try. + +Alice can access the \texttt{luacas} program using a custom environment: \mintinline{latex}{\begin{CAS}..\end{CAS}}. The first thing Alice must do is declare variables that will be used going forward: +\begin{minted}{latex} +\begin{CAS} + vars('x','h') +\end{CAS} +\end{minted} +Alice decides that $f$, the function to be differentiated, should be $x^2$. So Alice makes this assignment with: +\begin{minted}{latex} +\begin{CAS} + vars('x','h') + f = x^2 +\end{CAS} +\end{minted} +Now, Alice wants to use the variable $q$ to store the appropriate \emph{difference quotient} of $f$. Alice could hardcode this into $q$, but that seems to defeat the oft sought after goal of reusable code. So Alice decides to use the \texttt{substitute} command of \texttt{luacas}: +\begin{minted}{latex} +\begin{CAS} + vars('x','h') + f = x^2 + subs = {[x]=x+h} + q = (substitute(subs,f) - f)/h +\end{CAS} +\end{minted} +Alice is curious to know if $q$ is what she thinks it is. So Alice decides to have \LaTeX{} print out the contents of $q$ within her document. For this, she uses the \mintinline{latex}{\print} command. +\begin{CAS} + vars('x','h') + f = x^2 + subs = {[x]=x+h} + q = (substitute(subs,f)- f)/h +\end{CAS} +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\[ \print{q} \] + \end{minted} + \tcblower + \[ \print{q} \] +\end{codebox} +So far so good! Alice wants to expand the numerator of $q$; she finds the aptly named \texttt{expand} method helpful in this regard. Alice redefines \mintinline{lua}{q} to be \mintinline{lua}{q=expand(q)}, and prints the result to see if things worked as expected: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','h') + f = x^2 + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h + q = expand(q) +\end{CAS} +\[ \print{q} \] + \end{minted} + \tcblower + \begin{CAS} + q = expand(q) + \end{CAS} + \[ \print*{q} \] +\end{codebox} +Alice is pleasantly surprised that the result of the expansion has been \emph{simplified}, i.e., the factors of $x^2$ and $-x^2$ cancelled each other out, and the resulting extra factor of $h$ has been cancelled out of the numerator and denominator. + +Finally, Alice wants to take the limit as $h\to 0$. Now that our difference quotient has been expanded and simplified, this amounts to another substitution: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','h') + f = x^2 + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h + q = expand(q) + subs = {[h] = 0} + q = substitute(subs,q) +\end{CAS} +\[ \print{q} \] + \end{minted} + \tcblower + \begin{CAS} + subs = {[h]=0} + q = q:substitute(subs) + \end{CAS} + \[ \print{q} \] +\end{codebox} +Alice is slightly disappointed that $0+2x$ is returned and not $2x$. Alice takes a guess that there's a \mintinline{lua}{simplify} command. This does the trick: adding the line \mintinline{lua}{q = simplify(q)} before leaving the \texttt{CAS} environment returns the expected $2x$: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','h') + f = x^2 + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h + q = expand(q) + subs = {[h] = 0} + q = substitute(subs,q) + q = simplify(q) +\end{CAS} +\[ \print{q} \] + \end{minted} + \tcblower + \begin{CAS} + q=simplify(q) + \end{CAS} + \[ \print{q} \] +\end{codebox} + +Alternatively, Alice could have used the \mintinline{latex}{\print*} command instead of \mintinline{latex}{\print} -- the essential difference is that \mintinline{latex}{\print*}, unlike \mintinline{latex}{\print}, automatically simplifies the content of the argument. + +Alice is pretty happy with how everything is working, but she wants to be able to typeset the individual steps of this process. Alice is therefore thrilled to learn that the \mintinline{latex}{\begin{CAS}..\end{CAS}} environment is very robust -- it can: +\begin{itemize} + \item Be entered into and exited out of essentially anywhere within her \LaTeX{} document, for example, within \mintinline{latex}{\begin{aligned}..\end{aligned}}; and + \item CAS variables persist -- if Alice assigns \mintinline{lua}{f = x^2} within \mintinline{latex}{\begin{CAS}..\end{CAS}}, then the CAS remembers that \mintinline{lua}{f = x^2} the next time Alice enters the CAS environment. +\end{itemize} +Here's Alice's completed code: +\begin{codebox}[frame hidden,breakable] +\begin{minted}[breaklines,fontsize=\small]{latex} +\begin{CAS} + vars('x','h') + f = x^2 +\end{CAS} +Let $f(x) = \print{f}$. We wish to compute the derivative of $f(x)$ at $x$ using thelimit definition of the derivative. Toward that end, we start with the appopriatedifference quotient: +\begin{CAS} + subs = {[x]=x+h} + q = (substitute(subs,f) - f)/h +\end{CAS} +\[ \begin{aligned} + \print{q} &= + \begin{CAS} + q = expand(q) + \end{CAS} + \print{q}& &\text{expand/simplify} \\ + \begin{CAS} + subs = {[h]=0} + q = substitute(subs,q) + \end{CAS} + &\xrightarrow{h\to 0} \print{q}& &\text{take limit}\\ + &= + \begin{CAS} + q = simplify(q) + \end{CAS} + \print{q} & &\text{simplify.} +\end{aligned} \] +So $\print{diff(f,x)} = \print*{diff(f,x)}$. +\end{minted} +\end{codebox} + +Alice can produce another example merely by changing the definition of $f$ on the third line to another polynomial: + +\begin{minted}{latex} +\begin{CAS} + vars('x','h') + f = 2*x^3-x +\end{CAS} +\end{minted} +And here is Alice's completed project: +\begin{tcolorbox}[colback=rose!10, + colframe=rose, + arc=1pt, + frame hidden] +{\bf Tutorial 1:} {\itshape A limit definition of the derivative for Alice.}\vskip0.2cm + +\begin{CAS} +vars('x','h') +f = 2*x^3-x +\end{CAS} +Let $f(x) = \print{f}$. We wish to compute the derivative of $f(x)$ at $x$ using the limit definition of the derivative. Toward that end, we start with the appropriate difference quotient: +\begin{CAS} +subs = {[x] = x+h} +q = (f:substitute(subs) - f)/h +\end{CAS} +\[ \begin{aligned} +\print{q} &= +\begin{CAS} + q = expand(q) +\end{CAS} +\print{q}& &\text{expand/simplify} \\ +\begin{CAS} + subs = {[h]=0} + q = q:substitute(subs) +\end{CAS} +&\xrightarrow{h\to 0} \print{q}& &\text{take limit} \\ +&= +\begin{CAS} + q = simplify(q) +\end{CAS} +\print{q}& &\text{simplify.} +\end{aligned} \] +%So $\print{diff(f,x)} = \print*{diff(f,x)}$. +\end{tcolorbox} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/tutorial/tut2/tut2.tex b/macros/luatex/latex/luacas/doc/tutorial/tut2/tut2.tex new file mode 100644 index 0000000000..a80e285782 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/tutorial/tut2/tut2.tex @@ -0,0 +1,476 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\begin{document} + +\subsection{Tutorial 2: Finding Maxima/Minima} + +Bob is teaching calculus too, and he wants to give his students many examples of the process of \emph{finding the local max/min of a given function}. But, like Alice, Bob doesn't want to work out a bunch of examples by-hand. Bob decides to try his hand with \texttt{luacas} after having been taught the basics by Alice. + +Bob decides to stick with polynomials for these examples; if anything because those functions are in the wheel-house of \texttt{luacas}. In particular, Bob decides that the \emph{derivative} of the function he wants to use should be a composition of quadratics. This ought to ensure that the roots of that derivative are expressible in a nice way. + +Accordingly, Bob declares variables and chooses two quadratic polynomials to compose, say $f$ and $g$, and sets $dh = g \circ f$: + +\begin{minted}{latex} +\begin{CAS} + vars('x') + f = x^2+2*x-2 + g = x^2-1 + subs = {[x] = f} + dh = substitute(subs,g) +\end{CAS} +\end{minted} +\begin{CAS} + vars('x') + f = x^2+2*x-2 + g = x^2-1 + subs = {[x] = f} + dh = substitute(subs,g) +\end{CAS} + +Bob wants to compute $h$, the integral of $dh$. Bob could certainly compute this quantity by-hand, but why hardcode that information into the document when \texttt{luacas} can do this for you? So Bob uses the \texttt{int} command and shifts the result (with some malice aforethought): + +\begin{minted}{latex} +\begin{CAS} + h = int(dh,x) + 10 +\end{CAS} +\end{minted} +\begin{CAS} + h = int(dh,x) + 10 +\end{CAS} + +Bob is curious to know the value of $h$. So he uses \mintinline{latex}{\print{h}} to produce: +\begin{codebox} +\begin{minted}[fontsize=\small]{latex} +\[ \print{h} \] +\end{minted} + \tcblower +\[\print{h} \] +\end{codebox} +This isn't exactly what Bob had in mind. It occurs to Bob that he may need to simplify the expression $h$, so he tries: + +\begin{codebox} +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + h = simplify(int(dh,x)+10) +\end{CAS} +\[ \print{h} \] +\end{minted} + \tcblower +\begin{CAS} + h = simplify(h) +\end{CAS} +\[\print{h} \] +\end{codebox} + +That's more like it! Now, Bob wants to find the roots to $dh$. Bob uses the \texttt{roots} command to do this: + +\begin{minted}{latex} +\begin{CAS} + r = roots(dh) +\end{CAS} +\end{minted} +\begin{CAS} + r = roots(dh) +\end{CAS} + +But then Bob wonders to himself, ``How do I actually retrieve the roots of $dh$ from \texttt{luacas}?'' The assignment \mintinline{lua}{r = roots(dh)} stores the roots of the polynomial $dh$ in a table named \texttt{r}: + +\begin{codebox}[] +\begin{minted}[fontsize=\small]{latex} +\[ \print{r[1]}, \quad \print{r[2]}, \quad \print{r[3]}, \quad\print{r[4]} \] +\end{minted} + \tcblower + \begin{CAS} + r = roots(dh) + \end{CAS} + \[ \print{r[1]}, \quad \print{r[2]}, \quad \print{r[3]}, \quad \print{r[4]} \] +\end{codebox} +If Bob truly wants to print the entire list \texttt{r}, Bob can use the \mintinline{latex}{\lprint} (\textbf{l}ist \textbf{print}) command: +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\[ \left\{ \lprint{r} \right\} \] + \end{minted} + \tcblower + \[ \left\{ \lprint{r} \right\} \] +\end{codebox} +Splendid! Bob would now like to evaluate the function $h$ at these roots (for these are the local max/min values of $h$). Here's Bob's first thought: + +\begin{codebox} +\begin{minted}[fontsize=\small]{latex} +\begin{CAS} + v = simplify(substitute({[x]=r[1]},h)) +\end{CAS} +\[ \print{v} \] +\end{minted} + \tcblower + \begin{CAS} + v = simplify(substitute({[x]=r[1]},h)) + \end{CAS} + \[ \print{v} \] +\end{codebox} + +What the heck?! Bob is (understandably) confused. But here's where Bob learns a valuable lesson\dots + +\subsubsection{A brief interlude: Lua numbers vs \texttt{luacas Integers}} + +The \LaTeX{} environment \mintinline{latex}{\begin{CAS}..\end{CAS}} is really a glorified Lua environment. The ``glory'' comes in how the contents of the environment are parsed in a special manner to make interacting with the CAS (mostly) easy. Bob has encountered a situation where that interaction is not as easy as we'd like. + +For comparison, consider the following: +\begin{multicols}{2} +Here's some code using the \mintinline{latex}{\begin{CAS}..\end{CAS}}: +\begin{codebox}\small + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('y') + a = 1 + b = y+a +\end{CAS} +\[ \print{b} \] +\end{minted} + \tcblower + \begin{CAS} + vars('y') + a = 1 + b = y+a + \end{CAS} + \[ \print{b} \] +\end{codebox} +Here's that same code but using \mintinline{latex}{\directlua} instead: +\begin{codebox}\small +\begin{minted}[fontsize=\small]{latex} +\directlua{ + vars('y') + a = Integer(1) + b = y+a +} +\[ \print{b} \] +\end{minted} + \tcblower + \directlua{ + vars('y') + a = Integer(1) + b = y+a +} +\[ \print{b} \] +\end{codebox} +\end{multicols} +The essential difference being: +\begin{itemize} + \item Using \mintinline{latex}{\begin{CAS}..\end{CAS}}, a parser automatically interprets any digit strings as an \texttt{Integer}; this is a special class defined within the bowels of \texttt{luacas}. Ultimately, it allows for us to define things like the addition of an \texttt{Integer} and an \texttt{Expression} (in this case, the result is a new \texttt{Expression}) as well as arbitrary precision arithmetic. + \item Using \mintinline{latex}{\directlua}, there is no parsing, so the user (aka Bob) is responsible for telling \texttt{luacas} what to interpret as an \texttt{Integer} versus what to interpret as a normal Lua \texttt{number}. +\end{itemize} +Generally speaking, we like what the parser in \mintinline{latex}{\begin{CAS}..\end{CAS}} does: it keeps us from having to wrap all integers in \texttt{Integer(..)} (among other things). But the price we pay is that the parser indiscriminately wraps \emph{all} (or rather, most) digit strings in \texttt{Integer(..)}. This causes a problem in the following line in Bob's code: +\begin{minted}{lua} +v = simplify(substitute({[x]=r[1]},h)) +\end{minted} +The parser sees \mintinline{lua}{r[1]} and interprets \texttt{1} as \texttt{Integer(1)} -- but \mintinline{lua}{r[Integer(1)]} is \texttt{nil}, so no substitution is performed. + +The good news is that, excluding the annoyance between \texttt{Integer} and Lua number, interacting with the CAS via \mintinline{latex}{\directlua} is not much different than interacting with it via \mintinline{latex}{\begin{CAS}..\end{CAS}}. + +\subsubsection*{Back to the tutorial...} + +After that enlightening interlude, Bob realizes that some care needs to be taken when constructing tables. Here's a solution from within \mintinline{latex}{\begin{CAS}..\end{CAS}}: + +\begin{codebox}[] + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + r = ZTable(r) + v = ZTable() + for i in range(1, 4) do + v[i] = simplify(substitute({[x]=r[i]},h)) + end +\end{CAS} +\[ \left\{ \lprint{v} \right\} \] +\end{minted} +\tcblower +\begin{CAS} + r = ZTable(r) + v = ZTable() + for i in range(1, 4) do + v[i] = simplify(substitute({[x]=r[i]},h)) + end + \end{CAS} + \[ \left\{ \lprint{v} \right\} \] +\end{codebox} + +The function \mintinline{lua}{ZTable()} sets indices appropriately for use within \mintinline{latex}{\begin{CAS}..\end{CAS}} while the function \mintinline{lua}{range()} protects the bounds of the for-loop. Alternatively, Bob can make tables directly within \mintinline{latex}{\directlua} (or \mintinline{latex}{\luaexec} from the \texttt{luacode} package) using whatever Lua syntax pleases him: + +\begin{codebox}[] +\begin{minted}[fontsize=\small]{latex} +\directlua{ + v = {} + for i=1,4 do + table.insert(v,simplify(substitute({[x]=r[i]},h))) + end} +\[ \left\{ \lprint{v} \right\} \] +\end{minted} + \tcblower + \directlua{ + v = {} + for i=1,4 do + table.insert(v,simplify(substitute({[x]=r[i]},h))) + end +} +\[ \left\{ \lprint{v} \right\} \] +\end{codebox} +Great! But still; Bob doesn't want to just pretty-print the roots of $dh$ (or the values that $h$ takes at those roots). Bob is determined to plot the results -- he wants to hammer home the point that the roots of $dh$ point to the local extrema of $h$. + +Luckily, Bob is familiar with some of the fantastic graphics tools in the \LaTeX{} ecosystem, like \texttt{pgfplots} and \texttt{asymptote}. But then Bob begins to wonder, ``How can I yoink results out of \texttt{luacas} so that I may yeet them into something like \texttt{pgfplots}?'' +Bob is delighted to find the following commands: \mintinline{latex}{\fetch} and \mintinline{latex}{\store}. + +Whereas the \mintinline{latex}{\print} command relies on the \texttt{luacas} method \mintinline{lua}{tolatex()}, the commands \mintinline{latex}{\fetch} and \mintinline{latex}{\store} rely on the \texttt{luacas} function \mintinline{lua}{tostring()}. Bob can view the output of \mintinline{lua}{tostring()} using the \mintinline{latex}{\vprint} command ({\bf v}erbatim {\bf print}). For example, \mintinline{latex}{\vprint{h}} produces: +\vprint{h} +This is more-or-less what Bob wants -- but he doesn't want the verbatim output printed to his document, Bob just wants the contents of \mintinline{lua}{tostring(h)}. Here's where \mintinline{latex}{\fetch} comes in. The command \mintinline{latex}{\fetch{h}} is equivalent to: +\begin{minted}{latex} + \directlua{ + tex.print(tostring(h)) + } +\end{minted} +For comparison, the command \mintinline{latex}{\print{h}} is equivalent to: +\begin{minted}{latex} + \directlua{ + tex.print(h:tolatex()) + } +\end{minted} +For Bob's purposes, \mintinline{latex}{\fetch{h}} is exactly what he needs: + +\begin{codebox}\small +\begin{minted}[breaklines,fontsize=\small]{latex} +\begin{tikzpicture}[scale=0.9] + \begin{axis}[legend pos = north west] + \addplot [domain=-3.5:1.5,samples=100] + {\fetch{h}}; + \addlegendentry{$f$}; + \addplot[densely dashed] + [domain=-3.25:1.25,samples=100] + {\fetch{dh}}; + \addlegendentry{$df/dx$}; + \addplot[gray,dashed,thick] + [domain=-3.5:1.5] {0}; + \end{axis} +\end{tikzpicture} +\end{minted} +\tcblower +\begin{tikzpicture}[scale=0.9] + \begin{axis}[legend pos = north west] + \addplot [domain=-3.5:1.5,samples=100] {\fetch{h}}; + \addlegendentry{$f$}; + \addplot[densely dashed] + [domain=-3.25:1.25,samples=100] {\fetch{dh}}; + \addlegendentry{$df/dx$}; + \addplot[gray,dashed,thick] + [domain=-3.5:1.5] {0}; + \end{axis} +\end{tikzpicture} +\end{codebox} +Alternatively, Bob could use \mintinline{latex}{\store}. The \mintinline{latex}{\store} command will \emph{fetch} the contents of its mandatory argument and store it in a macro of the same name. +\begin{minted}{latex} +\store{h} +\store{dh} +\end{minted} +Now the macros \mintinline{latex}{\h} and \mintinline{latex}{\dh} can be used in place of \mintinline{latex}{\fetch{h}} and \mintinline{latex}{\fetch{dh}}, respectively. An optional argument can be used to store contents in a macro under a different name. This is useful for situations like the following: +\begin{minted}{latex} +\store{r[1]}[rootone] +\end{minted} +Now \mintinline{latex}{\rootone} can be used in place of \mintinline{latex}{\fetch{r[1]}}. But Bob wants to fetch all the values stored in \texttt{r} (and \texttt{v}, for that matter). In this case, Bob can use: +\begin{minted}{latex} +\store{r} +\store{v} +\end{minted} +The command \mintinline{latex}{\store{r}} is equivalent to: +\begin{minted}{latex} +\def\r{{ \fetch{r[1]}, \fetch{r[2]}, \fetch{r[3]}, \fetch{r[4]} }} +\end{minted} +The contents of the \LaTeX{} macro \mintinline{latex}{\r} can be accessed with \mintinline{latex}{\pgfmathsetmacro}. For example: + +\begin{codebox} +\begin{minted}[fontsize=\small,numbersep=6pt,linenos]{latex} +\begin{tikzpicture}[scale=0.6] + \draw [dashed,latex-latex] + (-7,0) -- (4,0); + \foreach \k in {0,1,2,3}{ + \pgfmathsetmacro\a{\r[\k]} + \draw (\a,0) circle (\a); + } + \foreach \x in {-6,...,3}{ + \draw[fill,orange] + (\x,0) circle (2pt) + node[below] {\footnotesize$\x$}; + } +\end{tikzpicture} +\end{minted} + \tcblower + \store{r} + \begin{center} + \begin{tikzpicture}[scale=0.65] + \draw [dashed,latex-latex] (-7,0) -- (4,0); + \foreach \k in {0,1,2,3}{ + \pgfmathsetmacro\a{\r[\k]} + \draw (\a,0) circle (\a); + } + \foreach \x in {-6,...,3}{ + \draw[fill,orange] (\x,0) circle (2pt) + node[below] {\footnotesize$\x$}; + } + \end{tikzpicture} +\end{center} +\end{codebox} + +Alternatively, Bob could avoid the call to \mintinline{latex}{\pgfmathsetmacro} by replacing lines 5-6 in the above code with the slightly more verbose: + +\begin{minted}{latex} + \draw ({\fetch{r[\k]}},0) circle (\fetch{r[\k]}); +\end{minted} + +Alternatively still, Bob could appeal directly to the \mintinline{lua}{tostring()} function in \texttt{luacas} and iterate over tables like \texttt{r} using Lua itself. This can often be a simpler solution (particularly when working within \mintinline{latex}{\begin{axis}..\end{axis}}), and it is exactly what Bob does in his complete project shared below: +\begin{codebox}[frame hidden,breakable] +\begin{minted}[breaklines,fontsize=\small]{latex} +Consider the function $f(x)$ defined by: +\begin{CAS} + vars('x') + f = x^2+2*x-2 + g = x^2-1 + subs = {[x] = f} + dh = expand(substitute(subs,g)) + h = simplify(int(dh,x)+10) +\end{CAS} +$\displaystyle f(x) = \print{h}$. +\begin{multicols}{2} + Note that: + \[ f'(x) = \print{dh}.\] + The roots to $f'(x)=0$ equation are: + \begin{CAS} + r = roots(dh) + \end{CAS} + \[ \left\{ \lprint{r} \right\} \] + Recall: $f'(x_0)$ measures the slope of the tangent line to $y= (x)$ at $x=x_0$. The values $r$ where $f'(r)=0$ correspond to places where the slope of the tangent line to $y=f(x)$ is horizontal (see the illustration). This gives us a method for identifying locations where the graph $y=f(x)$ attains a peak (local maximum) or a valley (local minimum). + \begin{CAS} + r = ZTable(r) + v = ZTable() + for i in range(1, 4) do + v[i] = simplify(substitute({[x]=r[i]},h)) + end + \end{CAS} + \columnbreak + \store{h}\store{dh} + \begin{tikzpicture}[scale=0.95] + \begin{axis}[legend pos = north west] + \addplot [domain=-3.5:1.5,samples=100] {\h}; + \addlegendentry{$f$}; + \addplot[densely dashed] [domain=-3.25:1.25,samples=100] {\dh}; + \addlegendentry{$df/dx$}; + \addplot[gray,dashed,thick] [domain=-3.5:1.5] {0}; + \luaexec{for i=1,4 do + tex.print("\\draw[fill=purple,purple]", + "(axis cs:{",tostring(r[i]),"},0) circle (1.5pt)", + "(axis cs:{",tostring(r[i]),"},{",tostring(v[i]),"}) circle (1.5pt)", + "(axis cs:{",tostring(r[i]),"},{",tostring(v[i]),"}) edge[dashed] (axis cs:{",tostring(r[i]),"},0);") + end} + \end{axis} + \end{tikzpicture} +\end{multicols} +\end{minted} +\end{codebox} +And here is Bob's completed project: +\begin{tcolorbox}[colback=rosenavy!10, + colframe=rosenavy, + arc=1pt, + frame hidden] + {\bf Tutorial 2:} {\itshape A local max/min diagram for Bob}. + \vskip 0.2cm + Consider the function $f(x)$ defined by: + \begin{CAS} + vars('x') + f = x^2+2*x-2 + g = x^2-1 + subs = {[x] = f} + dh = expand(substitute(subs,g)) + h = simplify(int(dh,x)+10) + \end{CAS} + $\displaystyle f(x) = \print{h}$. +\begin{multicols}{2} + Note that: + \[ f'(x) = \print{dh}.\] + The roots to $f'(x)=0$ equation are: + \begin{CAS} + r = roots(dh) + \end{CAS} + \[ \left\{ \lprint{r} \right\} \] + Recall: $f'(x_0)$ measures the slope of the tangent line to $y=f(x)$ at $x=x_0$. The values $r$ where $f'(r)=0$ correspond to places where the slope of the tangent line to $y=f(x)$ is horizontal (see the illustration). This gives us a method for identifying locations where the graph $y=f(x)$ attains a peak (local maximum) or a valley (local minimum). + \begin{CAS} + r = ZTable(r) + v = ZTable() + for i in range(1, 4) do + v[i] = simplify(substitute({[x]=r[i]},h)) + end + \end{CAS} + \columnbreak + \store{h}\store{dh} + \begin{tikzpicture}[scale=0.95] + \begin{axis}[legend pos = north west] + \addplot + [domain=-3.5:1.5,samples=100] {\h}; + \addlegendentry{$f$}; + \addplot[densely dashed] + [domain=-3.25:1.25,samples=100] {\dh}; + \addlegendentry{$df/dx$}; + \addplot[gray,dashed,thick] + [domain=-3.5:1.5] {0}; + \luaexec{for i=1,4 do + tex.print("\\draw[fill=purple,purple]", + "(axis cs:{", tostring(r[i]) ,"},0) circle (1.5pt)", + "(axis cs:{", tostring(r[i]) ,"},{", tostring(v[i]), "}) circle (1.5pt)", + "(axis cs:{", tostring(r[i]) ,"},{", tostring(v[i]), "}) edge[dashed] (axis cs:{", tostring(r[i]) ,"},0);") + end} + \end{axis} + \end{tikzpicture} +\end{multicols} +\end{tcolorbox} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/tutorial/tut3/demotut3.dat b/macros/luatex/latex/luacas/doc/tutorial/tut3/demotut3.dat new file mode 100644 index 0000000000..95139afaf2 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/tutorial/tut3/demotut3.dat @@ -0,0 +1,46 @@ +\parseforest{q} +\bracketset{action character = @} +\begin{forest} + for tree = { + font=\ttfamily, + rectangle, + rounded corners=1pt + }, + where level=0{% + fill=orange!25 + }{}, + @\forestresult +\end{forest} + +\begin{CAS} + r = diff(q,x,h) +\end{CAS} +\whatis{q} vs \whatis{r} + +\luaexec{if q.operation == BinaryOperation.ADD then + tex.print("I'm an \\texttt{ADD}") +end} + +\luaexec{tex.print("I'm an order", r.degree, "derivative.")} + +\parseshrub{q} +\begin{forest} + for tree = {draw,rectangle,rounded corners=1pt,fill=lightgray!20 font=\ttfamily} + @\shrubresult +\end{forest} + +\parseshrub{q.expressions[1]} +\begin{forest} + for tree = {draw,rectangle, + rounded corners=1pt,fill=lightgray!20, + font=\ttfamily, s sep=2cm} + @\shrubresult +\end{forest} + +\parseshrub{r} +\begin{forest} + for tree = {draw,rectangle, + rounded corners=1pt,fill=lightgray!20, + font=\ttfamily, s sep=1cm} + @\shrubresult +\end{forest} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/doc/tutorial/tut3/tut3.tex b/macros/luatex/latex/luacas/doc/tutorial/tut3/tut3.tex new file mode 100644 index 0000000000..1fda71a5a0 --- /dev/null +++ b/macros/luatex/latex/luacas/doc/tutorial/tut3/tut3.tex @@ -0,0 +1,575 @@ +\documentclass{article} + +\usepackage{luacas} +\usepackage{amsmath} +\usepackage{amssymb} + +\usepackage[margin=1in]{geometry} +\usepackage[shortlabels]{enumitem} + +\usepackage{pgfplots} +\pgfplotsset{compat=1.18} +\usetikzlibrary{positioning,calc} +\usepackage{forest} +\usepackage{minted} +\usemintedstyle{pastie} +\usepackage[hidelinks]{hyperref} +\usepackage{parskip} +\usepackage{multicol} +\usepackage[most]{tcolorbox} + \tcbuselibrary{xparse} +\usepackage{microtype} + +\definecolor{rose}{RGB}{128,0,0} +\definecolor{roseyellow}{RGB}{222,205,99} +\definecolor{roseblue}{RGB}{167,188,214} +\definecolor{rosenavy}{RGB}{79,117,139} +\definecolor{roseorange}{RGB}{232,119,34} +\definecolor{rosegreen}{RGB}{61,68,30} +\definecolor{rosewhite}{RGB}{223,209,167} +\definecolor{rosebrown}{RGB}{108,87,27} +\definecolor{rosegray}{RGB}{84,88,90} + +\usepackage[ +backend=biber, +style=numeric, +]{biblatex} +\addbibresource{sources.bib} + +\newtcolorbox{codebox}[1][sidebyside]{ + enhanced,skin=bicolor, + #1, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle +} + +\begin{document} + +\subsection{Tutorial 3: Adding Functionality} + +Charlie, like Alice and Bob, is also teaching calculus. Charlie likes Alice's examples and wants to try something similar. But Charlie would like to do more involved examples using rational functions. Accordingly, Charlie copy-and-pastes Alice's code: +\begin{CAS} + vars('x','h') + f = 1/(x^2+1) + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h + q = expand(q) +\end{CAS} +\begin{minted}{latex} +\begin{CAS} + vars('x','h') + f = 1/(x^2+1) + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h + q = expand(q) +\end{CAS} +\end{minted} +Unfortunately, \mintinline{latex}{\[ q=\print{q} \]} produces: +\[ q = \print{q} \] +The \mintinline{lua}{simplify()} command doesn't seem to help either! What Charlie truly needs is to combine terms, i.e., Charlie needs to find a \emph{common denominator}. They're horrified to learn that no such functionality exists in this burgeoning package. + +So what's Charlie to do? They could put a feature request in, but they're concerned that the schlubs in charge of managing the package won't get around to it until who-knows-when. So Charlie decides to take matters into their own hands. Besides, looking for that silver lining, they'll learn a little bit about how \texttt{luacas} is structured. + +At the heart of any CAS is the idea of an \texttt{Expression}. Mathematically speaking, an \texttt{Expression} is a rooted tree. Luckily, this tree can be drawn using the (wonderful) \texttt{forest} package. In particular, the command \mintinline{latex}{\parseforest{q}} will scan the contents of the expression \texttt{q} and parse the results into a form compatible with the \texttt{forest} package; those results are saved in a macro named \mintinline{latex}{\forestresult}. + +\tcbsidebyside[ + sidebyside adapt=right, + enhanced,skin=bicolor, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle, + frame hidden + ]{ + \inputminted[ + firstline = 1, + lastline = 13, + breaklines, + fontsize=\small + ] + {latex} + {demotut3.dat}} + {\parseforest{q} + \bracketset{action character = @} + \begin{forest} + for tree = { + font=\ttfamily, + rectangle, + rounded corners=1pt + }, + where level=0{% + fill=orange!25 + }{}, + @\forestresult + \end{forest}} + +The root of the tree above is \texttt{ADD} since $q$ is, at its heart, the addition of two other expressions. Charlie wonders how they might check to see if a mystery \texttt{Expression} is an \texttt{ADD}? But this is putting the cart before the horse; Charlie should truly wonder how to check for the \emph{type} of \texttt{Expression} -- then they can worry about other attributes. + +Charlie can print the \texttt{Expression} type directly into their document using the \mintinline{latex}{\whatis} command: + +\begin{codebox} + \inputminted[ + firstline = 15, + lastline = 18, + breaklines, + fontsize=\small + ] + {latex} + {demotut3.dat} + \tcblower + \begin{CAS} + r = diff(q,x,h) + \end{CAS} + \whatis{q} vs \whatis{r} +\end{codebox} + +So \texttt{q} is a \texttt{BinaryOperation}? This strikes Charlie as a little strange. On the other hand, \texttt{q} is the result of a binary operation applied to two other expressions; so perhaps this makes a modicum of sense. + +At any rate, Charlie now knows, according to \texttt{luacas}, that \texttt{q} is of the \texttt{Expression}-type \whatis{q}. The actual operator that's used to form \texttt{q} is stored in the attribute \mintinline{lua}{q.operation}: + +\tcbsidebyside[ + sidebyside adapt=right, + enhanced,skin=bicolor, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle, + frame hidden + ]{ + \inputminted[ + firstline = 20, + lastline = 22, + breaklines, + fontsize=\small + ] + {latex} + {demotut3.dat}} + { + \luaexec{ + if q.operation == BinaryOperation.ADD then + tex.sprint("I'm an \\texttt{ADD}") + end + }} + +Of course, different \texttt{Expression} types have different attributes. For example, being a \texttt{DiffExpression}, \texttt{r} has the attribute \texttt{r.degree}: + +\tcbsidebyside[ + sidebyside adapt=right, + enhanced,skin=bicolor, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle, + frame hidden + ]{ + \inputminted[ + firstline = 24, + lastline = 24, + breaklines, + fontsize=\small + ] + {latex} + {demotut3.dat}} + { + \luaexec{ + tex.print("I'm an order", r.degree, "derivative.") + }} + +\texttt{BinaryOperation}s have several attributes, but the most important attribute for Charlie's purposes is \texttt{q.expressions}. In this case, \texttt{q.expressions} is a table with two entries; those two entries are precisely the \texttt{Expressions} whose sum forms \texttt{q}. In particular, + +\mintinline{latex}{\[ \print{q.expressions[1]} \qquad \text{and} \qquad \print{q.expressions[2]} \]} + +produces: +\[ \print{q.expressions[1]} \qquad\text{and} \qquad \print{q.expressions[2]} \] + +The expression \texttt{q.expressions[1]} is another \texttt{BinaryOperation}. Instead of printing the entire expression tree (as we've done above), Charlie might be interested in the commands \mintinline{latex}{\parseshrub} and \mintinline{latex}{\shrubresult}: + +\tcbsidebyside[ + sidebyside adapt=right, + enhanced,skin=bicolor, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle, + frame hidden + ]{\small + \inputminted[ + firstline = 26, + lastline = 30, + breaklines + ] + {latex} + {demotut3.dat}} + {\parseshrub{q.expressions[1]} + \bracketset{action character = @} + \begin{forest} + for tree = {draw,rectangle,rounded corners=1pt,fill=lightgray!20,font=\ttfamily, s sep=1.5cm} + @\shrubresult + \end{forest} +} + +The ``shrub'' is essentially the first level of the ``forest'', but with some extra information concerning attributes. For contrast, here's the result of \mintinline{latex}{\parseshrub} and \mintinline{latex}{\shrubresult} applied to \texttt{r}, the \texttt{DiffExpression} defined above. + +\tcbsidebyside[ + sidebyside adapt=right, + enhanced,skin=bicolor, + arc=1pt, + colframe=brown, + colback=brown!15,colbacklower=white, + boxrule=1pt, + notitle, + frame hidden + ]{\small + \inputminted[ + firstline = 40, + lastline = 46, + breaklines + ] + {latex} + {demotut3.dat}} + {\parseshrub{r} + \bracketset{action character = @} + \begin{forest} + for tree = {draw,rectangle,rounded corners=1pt, + fill=lightgray!20,font=\ttfamily, s sep=1.5cm} + @\shrubresult + \end{forest} +} +The attribute \mintinline{lua}{r.degree} returns the size of the table stored in \mintinline{lua}{r.symbols} which, in turn, records the variables (and order from left-to-right) with which to differentiate the expression stored in \mintinline{lua}{r.expression}. + +Now that Charlie knows the basics of how \texttt{luacas} is structured, they're ready to try their hand at adding some functionality. + + +\begin{multicols}{2} +First, Charlie decides to up the complexity of their expression {\ttfamily f} so that they have something more general to work with: +\begin{CAS} + vars('x','h') + f = x/(x^2+1) + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h +\end{CAS} +\begin{minted}{latex} +\begin{CAS} + vars('x','h') + f = x/(x^2+1) + subs = {[x]=x+h} + q = (substitute(subs,f)-f)/h +\end{CAS} +\end{minted} +Next, Charlie decides to print the un\texttt{expand}ed expression tree for $q$ to help give them a clear view (see right). + +\begin{center} +\parseforest{q} +\bracketset{action character = @} +\begin{forest} + for tree = { + font=\ttfamily} + @\forestresult +\end{forest} +\end{center} +\end{multicols} + +Charlie now wants to write their own function for combining expressions like this into a single denominator. It's probably best that Charlie writes this function in a separate file, say \texttt{myfile.lua}. Like most functions in \texttt{luacas}, Charlie defines this function as a \emph{method} applied to an \texttt{Expression}: + +\begin{minted}[linenos]{lua} +function Expression:mycombine() +\end{minted} + +Next, Charlie declares some local variables to identify appropriate numerators and denominators: +\begin{minted}[linenos,firstnumber=2]{lua} + local a = self.expressions[1].expressions[1].expressions[1] + local b = self.expressions[1].expressions[1].expressions[2] + local c = self.expressions[1].expressions[2].expressions[1] + local d = self.expressions[1].expressions[2].expressions[2] +\end{minted} + +So, for example, $a = x+h$, $b = (x+h)^2+1$, and so on. Charlie now forms the numerator and denominator, and returns the function: + +\begin{minted}[linenos,firstnumber=6]{lua} + local numerator = a*d-b*c + local denominator = self.expressions[2]*b*d + return numerator/denominator +end +\end{minted} + +\luaexec{ + function Expression:mycombine() + local a = self.expressions[1].expressions[1].expressions[1] + local b = self.expressions[1].expressions[1].expressions[2] + local c = self.expressions[1].expressions[2].expressions[1] + local d = self.expressions[1].expressions[2].expressions[2] + local numerator = a*d-b*c + local denominator = self.expressions[2]*b*d + return numerator/denominator + end +} +Now Charlie only needs to ensure that \texttt{myfile.lua} is in a location visible to their TeX installation (e.g. in the current working folder). Charlie can then produce the following: + +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\directlua{dofile('myfile.lua')} +\begin{CAS} + q = q:mycombine() +\end{CAS} +\[ \print{q} \] + \end{minted} + \tcblower + \begin{CAS} + q = q:mycombine() + \end{CAS} + \[ \print{q}\] +\end{codebox} + +Charlie wants to simplify the numerator (but not the denominator). So they decide to write another function in \texttt{myfile.lua} that does precisely this: + +\begin{minted}[linenos,firstnumber = 11]{lua} +function Expression:mysimplify() + local a = self.expressions[1] + local b = self.expressions[2] + a = simplify(a) + return a/b +end +\end{minted} +\luaexec{ +function Expression:simplifynum() + local a = self.expressions[1] + local b = self.expressions[2] + a = simplify(a) + return a/b +end +} +Now Charlie has: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + q = q:mysimplify() +\end{CAS} +\[ \print{q} \] +\end{minted} +\tcblower +\begin{CAS} + q = q:simplifynum() +\end{CAS} +\[ \print{q} \] +\end{codebox} +Finally, Charlie wants to factor the numerator. So Charlie writes the following final function to \texttt{myfile.lua}: +\begin{minted}[linenos,firstnumber = 18]{lua} +function Expression:myfactor() + local a = self.expressions[1] + local b = self.expressions[2] + a = factor(a) + return a/b +end +\end{minted} + +\luaexec{ +function Expression:factornum() + local a = self.expressions[1] + local b = self.expressions[2] + a = factor(a) + return a/b +end +} +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +After factoring the numerator: +\begin{CAS} + q = q:myfactor() +\end{CAS} +\[ \print{q} \] +And then simplifying: +\begin{CAS} + q = simplify(q) +\end{CAS} +\[ \print{q} \] +\end{minted} +\tcblower +After factoring the numerator: +\begin{CAS} + q = q:factornum() +\end{CAS} +\[ \print{q} \] +And then simplifying: +\begin{CAS} + q = simplify(q) +\end{CAS} +\[ \print{q} \] +\end{codebox} +Armed with their custom functions \texttt{mycombine}, \texttt{mysimplify}, and \texttt{myfactor}, Charlie can write examples just like Alice's examples, but using rational functions instead. + +Of course, the schlubs that manage this package feel for Charlie, and recognize that there are other situations in which folks may want to combine a sum of rational expressions into a single rational expression. Accordingly, there is indeed a \texttt{combine} command included in \texttt{luacas} that performs this task: +\begin{codebox} + \begin{minted}[fontsize=\small]{latex} +\begin{CAS} + vars('x','y','z') + a = y/z + b = z/x + c = x/y + d = combine(a+b+c) +\end{CAS} +\[ \print{a+b+c} = \print{d} \] +\end{minted} +\tcblower +\begin{CAS} + vars('x','y','z') + a = y/z + b = z/x + c = x/y + d = combine(a+b+c) +\end{CAS} +\[ \print{a+b+c} = \print{d} \] +\end{codebox} + +Here's Charlie's complete code (but using \mintinline{latex}{\directlua}) instead: + +\begin{codebox}[frame hidden,breakable] +\begin{minted}[breaklines,fontsize=\small]{latex} +\begin{CAS} + vars('x','h') + f = x/(x^2+1) +\end{CAS} +Let $f(x) = \print{f}$. We wish to compute the derivative of $f(x)$ at $x$ using the limit definition of the derivative. Toward that end, we start with the appropriate difference quotient: +\begin{CAS} + subs = {[x] = x+h} + q = (f:substitute(subs) - f)/h +\end{CAS} +\directlua{ +\end{minted} +\end{codebox} +And now the Lua code: +\begin{codebox}[frame hidden, breakable] +\begin{minted}[breaklines,fontsize=\small]{lua} + function Expression:mycombine() + local a = self.expressions[1].expressions[1].expressions[1] + local b = self.expressions[1].expressions[1].expressions[2] + local c = self.expressions[1].expressions[2].expressions[1] + local d = self.expressions[1].expressions[2].expressions[2] + local numerator = a*d-b*c + local denominator = self.expressions[2]*b*d + return numerator/denominator + end + function Expression:mysimplify() + local a = self.expressions[1] + local b = self.expressions[2] + a = simplify(a) + return a/b + end + function Expression:myfactor() + local a = self.expressions[1] + local b = self.expressions[2] + a = factor(a) + return a/b + end +\end{minted} +\end{codebox} +And now back to the \LaTeX{} code: +\begin{codebox}[frame hidden,breakable] +\begin{minted}[breaklines,fontsize=\small]{latex} +} +\[ \begin{aligned} + \print{q} &= + \begin{CAS} + q = q:mycombine() + \end{CAS} + \print{q}& &\text{get a common denominator} \\ + &= + \begin{CAS} + q = q:mysimplify() + \end{CAS} + \print{q}& &\text{simplify the numerator} \\ + &= + \begin{CAS} + q = q:myfactor() + \end{CAS} + \print{q} & &\text{factor numerator} \\ + &= + \begin{CAS} + q = simplify(q) + \end{CAS} + \print{q}& &\text{cancel the $h$s} \\ + &\xrightarrow{h\to 0} + \begin{CAS} + subs = {[h] = 0} + q = substitute(subs,q):autosimplify() + \end{CAS} + \print{q}& &\text{take limit.} +\end{aligned} \] +\end{minted} +\end{codebox} +And here is Charlie's completed project: +\begin{tcolorbox}[colback=roseorange!10, + colframe=roseorange, + arc=1pt, + frame hidden] +{\bf Tutorial 3:} {\itshape A limit definition of the derivative for Charlie.}\vskip 0.2cm + +\begin{CAS} + vars('x','h') + f = x/(x^2+1) +\end{CAS} +Let $f(x) = \print{f}$. We wish to compute the derivative of $f(x)$ at $x$ using the limit definition of the derivative. Toward that end, we start with the appropriate difference quotient: +\begin{CAS} + subs = {[x] = x+h} + q = (f:substitute(subs) - f)/h +\end{CAS} +\directlua{ + function Expression:mycombine() + local a = self.expressions[1].expressions[1].expressions[1] + local b = self.expressions[1].expressions[1].expressions[2] + local c = self.expressions[1].expressions[2].expressions[1] + local d = self.expressions[1].expressions[2].expressions[2] + local numerator = a*d-b*c + local denominator = self.expressions[2]*b*d + return numerator/denominator + end + function Expression:mysimplify() + local a = self.expressions[1] + local b = self.expressions[2] + a = simplify(a) + return a/b + end + function Expression:myfactor() + local a = self.expressions[1] + local b = self.expressions[2] + a = factor(a) + return a/b + end +} +\[ \begin{aligned} + \print{q} &= + \begin{CAS} + q = q:mycombine() + \end{CAS} + \print{q}& &\text{get a common denominator} \\ + &= + \begin{CAS} + q = q:mysimplify() + \end{CAS} + \print{q}& &\text{simplify the numerator} \\ + &= + \begin{CAS} + q = q:myfactor() + \end{CAS} + \print{q} & &\text{factor numerator} \\ + &= + \begin{CAS} + q = simplify(q) + \end{CAS} + \print{q}& &\text{cancel the $h$s} \\ + &\xrightarrow{h\to 0} + \begin{CAS} + subs = {[h] = 0} + q = substitute(subs,q):autosimplify() + \end{CAS} + \print{q}& &\text{take limit.} +\end{aligned} \] +\end{tcolorbox} + +\end{document} \ No newline at end of file diff --git a/macros/luatex/latex/luacas/tex/_lib/inspect.lua b/macros/luatex/latex/luacas/tex/_lib/inspect.lua new file mode 100644 index 0000000000..dedd91f248 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/_lib/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/pepperfish.lua b/macros/luatex/latex/luacas/tex/_lib/pepperfish.lua new file mode 100644 index 0000000000..4717543c4b --- /dev/null +++ b/macros/luatex/latex/luacas/tex/_lib/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/table.lua b/macros/luatex/latex/luacas/tex/_lib/table.lua new file mode 100644 index 0000000000..70495d1479 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/_lib/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/algebra/_init.lua b/macros/luatex/latex/luacas/tex/algebra/_init.lua new file mode 100644 index 0000000000..0a9b114cb9 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/_init.lua @@ -0,0 +1,24 @@ +-- 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 new file mode 100644 index 0000000000..2bf0b6a7ef --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/equation.lua b/macros/luatex/latex/luacas/tex/algebra/equation.lua new file mode 100644 index 0000000000..4e81d5ca95 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/euclideandomain.lua b/macros/luatex/latex/luacas/tex/algebra/euclideandomain.lua new file mode 100644 index 0000000000..fab2c5c7c2 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/factorialexpression.lua b/macros/luatex/latex/luacas/tex/algebra/factorialexpression.lua new file mode 100644 index 0000000000..1c5b824ff7 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/field.lua b/macros/luatex/latex/luacas/tex/algebra/field.lua new file mode 100644 index 0000000000..c845e94c55 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/integer.lua b/macros/luatex/latex/luacas/tex/algebra/integer.lua new file mode 100644 index 0000000000..a3c54f498c --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/integerquotientring.lua b/macros/luatex/latex/luacas/tex/algebra/integerquotientring.lua new file mode 100644 index 0000000000..4c2188c9e5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/logarithm.lua b/macros/luatex/latex/luacas/tex/algebra/logarithm.lua new file mode 100644 index 0000000000..07a9db84d1 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/polynomialring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring.lua new file mode 100644 index 0000000000..568c21c921 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/polynomialring/berlekampfactoring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/berlekampfactoring.lua new file mode 100644 index 0000000000..feabb61a3f --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/polynomialring/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/decomposition.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/decomposition.lua new file mode 100644 index 0000000000..84f2af1efc --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/polynomialring/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/zassenhausfactoring.lua b/macros/luatex/latex/luacas/tex/algebra/polynomialring/zassenhausfactoring.lua new file mode 100644 index 0000000000..be19298e64 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/polynomialring/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/rational.lua b/macros/luatex/latex/luacas/tex/algebra/rational.lua new file mode 100644 index 0000000000..811909a948 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/ring.lua b/macros/luatex/latex/luacas/tex/algebra/ring.lua new file mode 100644 index 0000000000..4903b9c912 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/rootexpression.lua b/macros/luatex/latex/luacas/tex/algebra/rootexpression.lua new file mode 100644 index 0000000000..a66182579a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/sqrtexpression.lua b/macros/luatex/latex/luacas/tex/algebra/sqrtexpression.lua new file mode 100644 index 0000000000..bc96f4e1d8 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/trigexpression.lua b/macros/luatex/latex/luacas/tex/algebra/trigexpression.lua new file mode 100644 index 0000000000..307bfe1737 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/algebra/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/calculus/_init.lua b/macros/luatex/latex/luacas/tex/calculus/_init.lua new file mode 100644 index 0000000000..59ba1bbcc8 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/_init.lua @@ -0,0 +1,6 @@ +-- 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 new file mode 100644 index 0000000000..b757759c4c --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/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/diffexpression.lua b/macros/luatex/latex/luacas/tex/calculus/diffexpression.lua new file mode 100644 index 0000000000..df1dc90f33 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/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/integralexpression.lua b/macros/luatex/latex/luacas/tex/calculus/integralexpression.lua new file mode 100644 index 0000000000..5267127264 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/calculus/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 new file mode 100644 index 0000000000..cfd640a214 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/_init.lua @@ -0,0 +1,16 @@ +-- 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 new file mode 100644 index 0000000000..6cc6e4f58e --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/binaryoperation.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation.lua new file mode 100644 index 0000000000..708a6c1540 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/binaryoperation/difference.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/difference.lua new file mode 100644 index 0000000000..2cc56b6600 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/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/power.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/power.lua new file mode 100644 index 0000000000..35df72c440 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/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/product.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/product.lua new file mode 100644 index 0000000000..b104cb5e8a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/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/quotient.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/quotient.lua new file mode 100644 index 0000000000..4dac489bf5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/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/sum.lua b/macros/luatex/latex/luacas/tex/core/binaryoperation/sum.lua new file mode 100644 index 0000000000..5bb204066f --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/binaryoperation/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/compoundexpression.lua b/macros/luatex/latex/luacas/tex/core/compoundexpression.lua new file mode 100644 index 0000000000..792c36cfc2 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/constantexpression.lua b/macros/luatex/latex/luacas/tex/core/constantexpression.lua new file mode 100644 index 0000000000..0f91a037a9 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/expression.lua b/macros/luatex/latex/luacas/tex/core/expression.lua new file mode 100644 index 0000000000..8ff4659f79 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/functionexpression.lua b/macros/luatex/latex/luacas/tex/core/functionexpression.lua new file mode 100644 index 0000000000..dc94790b81 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/symbolexpression.lua b/macros/luatex/latex/luacas/tex/core/symbolexpression.lua new file mode 100644 index 0000000000..0d9192c084 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/core/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/luacas.sty b/macros/luatex/latex/luacas/tex/luacas.sty new file mode 100644 index 0000000000..461fae6efa --- /dev/null +++ b/macros/luatex/latex/luacas/tex/luacas.sty @@ -0,0 +1,312 @@ +% Permission is granted to copy, distribute and/or modify this +% software under the terms of the LaTeX Project Public License +% (LPPL), version 1.3c or any later version. +% +% This software is provided 'as is', without warranty of any kind, +% either expressed or implied, including, but not limited to, the +% implied warranties of merchantability and fitness for a +% particular purpose. + +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{luacas} + [2022/05/07 v1.0 CAS written in Lua for LaTeX] + +\RequirePackage{iftex} +\ifluatex + \RequirePackage{luacode} +\else + {\PackageError{luacas} + {Not running under LuaLaTeX} + {This package requires LuaLaTeX. Try compiling this document with\MessageBreak 'lualatex' instead of 'latex'. This is a fatal error; I'm aborting now.}% + }\stop +\fi + +%Required Packages +\RequirePackage{xparse} +\RequirePackage{pgfkeys} +\RequirePackage{verbatim} +\RequirePackage{tikz} +\RequirePackage{xcolor} +\RequirePackage{mathtools} + +%These files contain Lua code for parsing luacas output; they also initialize the CAS itself +\directlua{require('test.parser') + require('test.helper') +} + +\NewDocumentEnvironment{CAS}% + {+b}% + {\luaexec{CASparse([[#1]])}}% + {} + +\newcommand{\get}% + [2][true]% + {\directlua{disp(#2, #1)}} + +\newcommand{\fetch}[1]{ + \directlua{tex.print(tostring(#1))} +} + +\NewDocumentCommand{\store}{m O{#1}}{ + \expandafter\def\csname #2\endcsname{% + \directlua{ + input = #1 + if not input[1] then + tex.sprint{tostring(input)} + else + tex.sprint("{") + for _,entry in ipairs(input) do + tex.sprint(tostring(entry),",") + end + tex.sprint("}") + end + }% + }% +}% + +\NewDocumentCommand{\yoink}{m O{#1}}{% +\expandafter\def\csname #2\endcsname{% + \directlua{ + tex.print(tostring(#1)) + }% + }% +} + +%\newcommand{\eval}[1]{\luaexec{tex.print(parse('#1'):tolatex())}} + +%%%%%%%%%% +%% Core %% +%%%%%%%%%% + +%pretty print + +\NewDocumentCommand{\print}{s m}{% + \IfBooleanTF{#1}{% + \directlua{ + local sym = #2 + if sym then + tex.print(sym:autosimplify():tolatex()) + else + tex.print('nil') + end + }% + }{% + \directlua{ + local sym = #2 + if sym then + tex.print(sym:tolatex()) + else + tex.print('nil') + end + }% + }% +} + +%verbatim print + +\NewDocumentCommand{\vprint}{s m}{% + \IfBooleanTF{#1}{% + \directlua{ + local sym = #2 + tex.sprint([[\unexpanded{\begin{verbatim}]]..tostring(sym)..[[\end{verbatim}}]]) + }% + }{% + \directlua{ + local sym = #2 + tex.sprint([[\unexpanded{\begin{verbatim}]]..tostring(sym:autosimplify())..[[\end{verbatim}}]]) + }% + }% +} + +\NewDocumentCommand{\lprint}{m O{nil,nil}}{% + \luaexec{ + local tbl = #1 + local low,upp = #2 + local tmp =0 + if tbl[0] == nil then + tmp = 1 + end + upp = upp or \#tbl + low = low or tmp + for i=low,upp do + tex.print(tbl[i]:tolatex()) + if tbl[i+1] then + tex.print(",") + end + end + } +} + +%prints the first level of an expression tree; for use within a tikzpicture environment + +\NewDocumentCommand{\printshrub}{s m}{% + \IfBooleanTF{#1}{% + \directlua{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\node [label=90:",whatis(sym),"] {",nameof(sym),"}") + tex.print(sym:gettheshrub()) + tex.print(";") + }% + }{% + \directlua{ + local sym = #2 + tex.print("\\node [label=90:",whatis(sym),"] {",nameof(sym),"}") + tex.print(sym:gettheshrub()) + tex.print(";") + }% + } +} + +%prints the full expression tree; for use within a tikzpicture environment + +\NewDocumentCommand{\printtree}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\node {",nameof(sym),"}") + tex.print(sym:getthetree()) + tex.print(";") + }% + }{% + \luaexec{ + local sym = #2 + tex.print("\\node {",nameof(sym),"}") + tex.print(sym:getthetree()) + tex.print(";") + }% + } +} + +%parses an expression tree for use within the forest environment; result is stored in \forestresult + +\NewDocumentCommand{\parseforest}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\def\\forestresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(sym:gettheforest()) + tex.print("]") + tex.print("}") + }% + }{% + \luaexec { + local sym = #2 + tex.print("\\def\\forestresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(sym:gettheforest()) + tex.print("]") + tex.print("}") + }% + } +} + +\NewDocumentCommand{\parseshrub}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + tex.print("\\def\\shrubresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(", tikz+={\\node[anchor=south] at (.north) {test};}") + tex.print(sym:getthefancyshrub()) + tex.print("]") + tex.print("}") + }% + }{% + \luaexec{ + local sym = #2 + tex.print("\\def\\shrubresult{") + tex.print("[") + tex.print(nameof(sym)) + tex.print(", tikz+={\\node[anchor=south,font=\\ttfamily\\footnotesize,gray] at (.north) {",longwhatis(sym),"};}") + tex.print(sym:getthefancyshrub()) + tex.print("]") + tex.print("}") + }% + } +} + +\NewDocumentCommand{\whatis}{m}{% + \luaexec{ + tex.sprint("{\\ttfamily",longwhatis(#1),"}") + }% +} + +\NewDocumentCommand{\freeof}{s m m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym1 = #2 + local sym2 = #3 + if sym1:freeof(sym2) then + tex.print(1) + else + tex.print(0) + end + } + }{% + \luaexec{ + local sym1 = #2 + local sym2 = #3 + sym1 = sym1:autosimplify() + sym2 = sym2:autosimplify() + if sym1:freeof(sym2) then + tex.print(1) + else + tex.print(0) + end + }% + }% +} + +\NewDocumentCommand{\isatomic}{s m}{% + \IfBooleanTF{#1}{ + \luaexec{ + local sym = #2 + if sym:isatomic() then + tex.print(1) + else + tex.print(0) + end + } + }{% + \luaexec{ + local sym = #2 + sym = sym:autosimplify() + if sym:isatomic() then + tex.print(1) + else + tex.print(0) + end + }% + }% +} + +\NewDocumentCommand{\isconstant}{s m}{% + \IfBooleanTF{#1}{% + \luaexec{ + local sym = #2 + if sym:isconstant() then + tex.print(1) + else + tex.print(0) + end + }% + }{% + \luaexec{% + local sym = #2 + sym = sym:autosimplify() + if sym:isconstant() then + tex.print(1) + else + tex.print(0) + end + }% + }% +} diff --git a/macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua b/macros/luatex/latex/luacas/tex/test/calculus/derivatives.lua new file mode 100644 index 0000000000..5d5429f59b --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/calculus/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/integrals.lua b/macros/luatex/latex/luacas/tex/test/calculus/integrals.lua new file mode 100644 index 0000000000..0c12f7a787 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/calculus/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 new file mode 100644 index 0000000000..09ad9874cd --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/collect.lua b/macros/luatex/latex/luacas/tex/test/expressions/collect.lua new file mode 100644 index 0000000000..b40e8e3d09 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/equations.lua b/macros/luatex/latex/luacas/tex/test/expressions/equations.lua new file mode 100644 index 0000000000..3d6aa131fc --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/functions.lua b/macros/luatex/latex/luacas/tex/test/expressions/functions.lua new file mode 100644 index 0000000000..e2c15096d6 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/logarithms.lua b/macros/luatex/latex/luacas/tex/test/expressions/logarithms.lua new file mode 100644 index 0000000000..2813a6b8f5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/rationalexponent.lua b/macros/luatex/latex/luacas/tex/test/expressions/rationalexponent.lua new file mode 100644 index 0000000000..85690f8c66 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/simplify.lua b/macros/luatex/latex/luacas/tex/test/expressions/simplify.lua new file mode 100644 index 0000000000..ac5582901d --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/substitute.lua b/macros/luatex/latex/luacas/tex/test/expressions/substitute.lua new file mode 100644 index 0000000000..4d1f3496bf --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/expressions/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/helper.lua b/macros/luatex/latex/luacas/tex/test/helper.lua new file mode 100644 index 0000000000..b550967da9 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/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/main.lua b/macros/luatex/latex/luacas/tex/test/main.lua new file mode 100644 index 0000000000..638f7734b5 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/main.lua @@ -0,0 +1,154 @@ +---@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 new file mode 100644 index 0000000000..6cb22a1bdb --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/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._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/partialfractions.lua b/macros/luatex/latex/luacas/tex/test/polynomials/partialfractions.lua new file mode 100644 index 0000000000..280d4d56f3 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/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/polynomial.lua b/macros/luatex/latex/luacas/tex/test/polynomials/polynomial.lua new file mode 100644 index 0000000000..545538c764 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/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/polynomialmod.lua b/macros/luatex/latex/luacas/tex/test/polynomials/polynomialmod.lua new file mode 100644 index 0000000000..1906ad2d72 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/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/roots.lua b/macros/luatex/latex/luacas/tex/test/polynomials/roots.lua new file mode 100644 index 0000000000..01975ab1ec --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/polynomials/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/rings/conversion.lua b/macros/luatex/latex/luacas/tex/test/rings/conversion.lua new file mode 100644 index 0000000000..b9eb523c7a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/rings/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/modulararithmetic.lua b/macros/luatex/latex/luacas/tex/test/rings/modulararithmetic.lua new file mode 100644 index 0000000000..1bd315595a --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/rings/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/number.lua b/macros/luatex/latex/luacas/tex/test/rings/number.lua new file mode 100644 index 0000000000..9afea0d364 --- /dev/null +++ b/macros/luatex/latex/luacas/tex/test/rings/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