m-polynomial.mkxl /size: 23 Kb    last modification: 2024-01-16 10:22
1%D \module
2%D   [       file=m-polynomial,
3%D        version=2023.8.18,
4%D          title=\CONTEXT\ Math Module,
5%D       subtitle=Polynomials,
6%D         author={Mikael Sundqvist & Hans Hagen},
7%D           date=\currentdate,
8%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
9%C
10%C This module is part of the \CONTEXT\ macro||package and is
11%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
12%C details.
13
14% maybe just m-polynomial like m-matrix
15
16% built-in
17%
18% alternative=align:normal % uses align mechanism (carry over spacing)
19% alternative=align:split  % uses align mechanism
20% alternative=text:normal  % not aligned
21% alternative=text:align   % uses alignhere etc
22% alternative=default      % same as text:align (can be normal instead)
23% alternative=none         % only processing
24%
25% user:
26%
27% alternative=somename     % \startsetups[math:polynomial:somename]
28
29\startluacode
30if not tex.issetup then -- will become a faster helper
31
32    function tex.issetup(n) -- will become a faster helper
33        return tex.isdefined(tokens.getters.macro("??setup") .. ":" .. n)
34    end
35
36end
37
38local tonumber = tonumber
39
40local context   = context
41local copytable = table.copy
42local round     = math.round
43
44local shows   = { }
45local helpers = { }
46
47local implement = interfaces.implement
48
49local v_none = interfaces.variables.none
50
51local ctx_NC         = context.NC
52local ctx_NR         = context.NR
53local ctx_startcolor = context.startcolor
54local ctx_stopcolor  = context.stopcolor
55
56local result = {
57    n           = 0,
58    numerator   = { },
59    denominator = { },
60    steps       = { },
61}
62
63do
64
65    local function show(t,split,symbol,mark)
66        local n = #t
67        local done = false
68        for i=n,1,-1 do
69            local tn = t[i]
70            ctx_NC()
71            if not tn or tn == 0 then
72                if split then
73                    ctx_NC()
74                end
75            else
76                if mark then ctx_startcolor { mark } end
77                if done and tn > 0 then
78                    context("+")
79                end
80                if tn < 0 then
81                    context("-")
82                    tn = - tn
83                end
84                if split then
85                    if mark then ctx_stopcolor() end
86                    ctx_NC()
87                    if mark then ctx_startcolor { mark } end
88                end
89                if i > 1 then
90                    if tn ~= 1 then
91                        context("%.3N",tn)
92                    end
93                    context(symbol)
94                else
95                   context("%.3N",tn)
96                end
97                if i > 2 then
98                    context("^{%s}",i-1)
99                end
100                if mark then ctx_stopcolor() end
101                done = true
102            end
103        end
104        ctx_NR()
105    end
106
107    local function default(settings)
108        local split  = settings.split
109        local symbol = settings.symbol or "x"
110        context.startalign {
111            n     = (split and 2 or 1) * result.n,
112            align = "all:right",
113        }
114            local steps    = result.steps
115            local previous = result.numerator
116            show(result.denominator,split,symbol,"blue")
117            show(result.numerator,split,symbol)
118            for i=1,#steps do
119                local current = steps[i].numerator
120                if previous then
121                    show(helpers.subtract(previous,current),split,symbol,"red")
122                end
123                show(current,split,symbol)
124                previous = current
125            end
126            show(result.quotient,split,symbol,"green")
127         -- inspect(result)
128        context.stopalign()
129    end
130
131    shows["align:normal"] = function(settings) default(settings,false) end
132    shows["align:split"]  = function(settings) default(settings,true)  end
133
134end
135
136do
137
138    local function show(t,symbol)
139        local n = #t
140        local done = false
141        for i=n,1,-1 do
142            local tn = t[i]
143            if not tn or tn == 0 then
144                -- ignore
145            else
146                if done and tn > 0 then
147                    context("+")
148                end
149                if tn < 0 then
150                    context("-")
151                    tn = - tn
152                end
153                if i > 1 then
154                    if tn ~= 1 then
155                        context("%.3N",tn)
156                    end
157                    context(symbol)
158                else
159                   context("%.3N",tn)
160                end
161                if i > 2 then
162                    context("^{%s}",i-1)
163                end
164                done = true
165            end
166        end
167    end
168
169    helpers.show = show
170
171    local function show_n_over_d(n,d,index,symbol)
172        local colors = result.colors
173        local c  = colors and colors[index]
174        local cn = c and (c.ns or c.n)
175        local cd = c and (c.ds or c.d)
176        context.frac(
177            function()
178                if cn then
179                    ctx_startcolor { cn }
180                        show(n,symbol)
181                    ctx_stopcolor()
182                else
183                    show(n,symbol)
184                end
185            end,
186            function()
187                if cd then
188                    ctx_startcolor { cd }
189                        show(d,symbol)
190                    ctx_stopcolor()
191                else
192                    show(d,symbol)
193                end
194            end
195        )
196    end
197
198    local function show_what(w,what,index,symbol)
199        local colors = result.colors
200        local c = colors and colors[index]
201        local cw = c and c[what]
202        if cw then
203            ctx_startcolor { cw }
204                show(w,symbol)
205            ctx_stopcolor()
206        else
207            show(w,symbol)
208        end
209    end
210
211    local function show_n(n,index,symbol) show_what(n,"n",index,symbol) end
212    local function show_d(d,index,symbol) show_what(d,"d",index,symbol) end
213    local function show_q(q,index,symbol) show_what(q,"q",index,symbol) end
214
215    helpers.show_n        = show_n
216    helpers.show_d        = show_d
217    helpers.show_q        = show_q
218    helpers.show_n_over_d = show_n_over_d
219
220    local function lines(settings,align)
221        local steps    = result.steps
222        local nofsteps = #steps
223        local colors   = result.colors
224        local symbol   = result.symbol or "x"
225        show_n_over_d(result.numerator,result.denominator,0,symbol)
226        if nofsteps > 0 then
227            if align then
228                context.alignhere()
229            end
230            context(steps[1].clipped and "" or "=")
231            for i=1,nofsteps do
232                local step = steps[i]
233                show_q(step.quotient,i,symbol)
234                if not helpers.iszero(step.numerator) then
235                    context("+")
236                    show_n_over_d(step.numerator,result.denominator,i,symbol)
237                end
238                if i < nofsteps then
239                    if align then
240                        context.breakhere()
241                    end
242                    context(step.clipped and "" or "=",symbol)
243                end
244            end
245        else
246         -- context.quad()
247         -- context.mtext("(no quotient)")
248        end
249    end
250
251    shows["text:normal"] = function(settings) lines(settings,false) end
252    shows["text:align"]  = function(settings) lines(settings,true)  end
253
254end
255
256do
257
258    local function subtract(n,d)
259        local t = { }
260        for i=1,#d do
261             t[i] = n[i] - d[i]
262        end
263        return t
264    end
265
266    local function negate(v)
267        local t = { }
268        for i=1,#v do
269            t[i] = -v[i]
270        end
271        return t
272    end
273
274    local function iszero(t)
275        for i=1,#t do
276            if t[i] ~= 0 then
277                return false
278            end
279        end
280        return true
281    end
282
283    local function last(t)
284        for i=#t,1,-1 do
285            local ti = t[i]
286            if ti ~= 0 then
287                return i
288            end
289        end
290        return false
291    end
292
293    local meps <const> = -0.000001
294    local peps <const> =  0.000001
295
296    local function solve(settings)
297        local numerator   = settings.numerator
298        local denominator = settings.denominator
299        if type(numerator) == "string" then
300            numerator          = utilities.parsers.settings_to_array(numerator)
301            settings.numerator = numerator
302        end
303        if type(denominator) == "string" then
304            denominator          = utilities.parsers.settings_to_array(denominator)
305            settings.denominator = denominator
306        end
307        local n = #numerator
308        local d = #denominator
309        for i=1,n do numerator  [i] = tonumber(numerator  [i]) or 0 end
310        for i=1,d do denominator[i] = tonumber(denominator[i]) or 0 end -- proper numbers
311        for i=1,n do denominator[i] = tonumber(denominator[i]) or 0 end
312        --
313        if d > n then
314            logs.report("polynomial","denominator has a higher degree")
315            -- kind of fatal error
316        end
317        --
318        local nlast    = last(numerator)
319        local dlast    = last(denominator)
320        local steps    = { }
321        local quotient = { }
322        result   = {
323            n           = n,
324            numerator   = numerator,
325            denominator = denominator,
326            steps       = steps,
327            colors      = settings.colors,
328            symbol      = settings.symbol or "x",
329        }
330        for i=1,n do
331            quotient[i] = 0
332        end
333        while nlast and dlast and nlast >= dlast do
334            numerator = copytable(numerator)
335            local shift  = nlast - dlast
336            local nvalue = numerator[nlast]
337            local dvalue = denominator[dlast]
338         -- local factor = nvalue // dvalue
339            local factor = nvalue / dvalue
340            local clipped = false
341            for i=1,n do
342                 local ni = numerator[i]
343                 local di = denominator[i-shift] or 0
344                 local ni = ni - factor * di
345                 if ni ~= 0 and ni > meps and ni < peps then
346                     ni = 0
347                     clipped = true
348                 end
349                 numerator[i] = ni
350            end
351            quotient[shift+1] = factor
352            steps[#steps+1]  = {
353                numerator = numerator,
354                factor   = factor,
355                nvalue   = nvalue,
356                dvalue   = dvalue,
357                shift    = shift,
358                quotient = copytable(quotient),
359                clipped  = clipped
360            }
361            local l = last(numerator)
362            if l == nlast then
363                break
364            end
365            nlast = l
366        end
367        return result
368    end
369
370    helpers.last     = last
371    helpers.subtract = subtract
372    helpers.negate   = negate
373    helpers.iszero   = iszero
374    helpers.solve    = solve
375
376    shows[v_none] = function(settings)
377        --
378    end
379
380    shows.default = shows["text:normal"]
381
382    local function tocolors(colors)
383        if type(colors) == "string" then
384            local list = utilities.parsers.settings_to_hash(colors)
385            local done = { }
386            for k, v in next, list do
387                done[tonumber(k) or 0] = utilities.parsers.settings_to_hash(v)
388            end
389            return done
390        end
391    end
392
393    implement {
394        name      = "polynomial",
395        arguments =  {
396            {
397                { "alternative" },
398                { "numerator" },
399                { "denominator" },
400                { "split", "boolean" },
401                { "colors" },
402                { "symbol" },
403            },
404        },
405        actions   = function(settings)
406            settings.colors = tocolors(settings.colors)
407            result = solve(settings)
408            local alternative = settings.alternative or "default"
409            if shows[alternative] then
410                shows[alternative](settings)
411            else
412                local s = "math:polynomial:" .. alternative
413                if tex.issetup(s) then
414                    context.formatted.directsetup(s)
415                else
416                    shows.default(settings)
417                end
418            end
419        end
420    }
421
422    local show = helpers.show
423
424    local function getcolor(n,what)
425        local colors = result.colors
426        local c = colors and colors[n]
427        return c and c[what]
428    end
429
430    implement {
431        name      = "polynomialsteps",
432        usage     = "value",
433        actions   = function()
434            return tokens.values.integer, #result.steps
435        end
436    }
437
438    implement {
439        name      = "polynomialfactor",
440        arguments = "integer",
441        actions   = function(n)
442            local s = result.steps[n]
443            if s and s.factor then
444                context(round(s.factor))
445            else
446                context(0)
447            end
448        end,
449    }
450
451    implement {
452        name      = "polynomialshift",
453        arguments = "integer",
454        actions   = function(n)
455            local s = result.steps[n]
456            if s and s.shift then
457                context(round(s.shift))
458            else
459                context(0)
460            end
461        end,
462    }
463
464
465-- tokens.converters = { }
466--
467-- local getexpansion = token.getexpansion
468--
469-- function tokens.converters.tonumber(s)
470--     return tonumber(s and getexpansion([[\noexpand\the\numexpr ]] .. s)) or 0
471-- end
472--
473-- interfaces.implement {
474--     name      = "demo",
475--     public    = true,
476--     arguments = "optional",
477--     actions = function(s)
478--         context(tokens.converters.tonumber(s))
479--     end
480-- }
481
482
483    local function whatever(kind,s)
484        if kind == "-" then
485            s = helpers.negate(s)
486        end
487        if kind == "+" or kind == '-' then
488            for i=#s,1,-1 do
489                if s[i] < 0 then
490                    break
491                elseif s[i] > 0 then
492                    context("+")
493                    break
494                end
495            end
496        end
497        helpers.show(s,result.symbol)
498    end
499
500    implement {
501        name      = "polynomialnumerator",
502        arguments = { "string", "integer" },
503        actions   = function(kind,n)
504            if n > 0 then
505                local s = result.steps[n]
506                if s then
507                    whatever(kind,s.numerator)
508                end
509            else
510                whatever(kind,result.numerator)
511            end
512        end
513    }
514
515    implement {
516        name      = "polynomialdenominator",
517        arguments = { "string", "integer" },
518        actions   = function(kind,n)
519            whatever(kind,result.denominator)
520        end,
521    }
522
523    implement {
524        name      = "polynomialquotient",
525        arguments = { "string", "integer" },
526        actions   = function(kind,n)
527            if n > 0 then
528                local s = result.steps[n]
529                if s then
530                    whatever(kind,s.quotient)
531                end
532            end
533        end,
534    }
535
536    implement {
537        name      = "polynomialstep",
538        arguments = { "string", "integer" },
539        actions   = function(kind,n)
540            local s = n > 0 and result.steps[n] or result
541            if s then
542                helpers.show_n_over_d(s.numerator,result.denominator,n,result.symbol)
543            end
544        end
545    }
546
547    implement {
548        name      = "polynomialquotientstep",
549        arguments = { "string", "integer" },
550        actions   = function(kind,n)
551            local s = result.steps[n]
552            if s then
553                local f = s.factor
554                if n > 1 then
555                    local p = result.steps[n-1]
556                    s = helpers.subtract(s.quotient,p.quotient)
557                else
558                    s = s.quotient
559                end
560                if kind == "-" then
561                    if f > 0 then
562                        context("-")
563                    else
564                        s = helpers.negate(s)
565                        context("+")
566                    end
567                elseif kind == "+" then
568                    if f > 0 then
569                        context("+")
570                    end
571                end
572                helpers.show_q(s,n,result.symbol)
573            end
574        end,
575    }
576
577    implement {
578        name      = "ifpolynomialclipped",
579        arguments = "integer",
580        usage     = "condition",
581        public    = true,
582        actions   = function(n)
583            local s = result.steps[n]
584            return tokens.values.boolean, s and s.quotient
585        end,
586    }
587
588    implement {
589        name      = "ifpolynomialnumerator",
590        arguments = "integer",
591        usage     = "condition",
592        public    = true,
593        actions   = function(n)
594            local s
595            if n > 0 then
596                s = result.steps[n]
597            else
598                s = result
599            end
600            return tokens.values.boolean, s and not helpers.iszero(s.numerator)
601        end
602    }
603
604    implement {
605        name      = "polynomialsymbol",
606        public    = true,
607        protected = true,
608        actions   = function()
609            context(result.symbol)
610        end,
611    }
612
613end
614
615\stopluacode
616
617\unprotect
618
619\installcorenamespace {polynomial}
620
621\installparameterhandler\??polynomial {polynomial}
622\installsetuphandler    \??polynomial {polynomial}
623
624\setuppolynomial
625  [\c!split=\v!no,
626   \c!alternative=text:align,
627   \c!symbol=x]
628
629\tolerant\protected\def\polynomial[#S#1]#*[#2]#*[#3]%
630  {\begingroup
631   \ifhastok={#1}%
632     \setupcurrentpolynomial[#1]%
633     \donetrue
634   \else
635     \donefalse
636   \fi
637   \clf_polynomial {
638      numerator   \ifdone{#2}\else{#1}\fi
639      denominator \ifdone{#3}\else{#2}\fi
640      alternative {\polynomialparameter\c!alternative}
641      colors      {\polynomialparameter\c!color}
642      symbol      {\polynomialparameter\c!symbol}
643      split       \ifcstok{\polynomialparameter\c!split}\v!yes true \else false\fi
644   }%
645   \endgroup}
646
647\tolerant\protected\def\polynomialsteps       {\the\clf_polynomialsteps}
648\tolerant\protected\def\polynomialfactor      [#1]{\clf_polynomialfactor      \numexpr\ifparameter#1\or#1\else\zerocount\fi\relax}
649\tolerant\protected\def\polynomialshift       [#1]{\clf_polynomialshift       \numexpr\ifparameter#1\or#1\else\zerocount\fi\relax}
650
651\tolerant\def\module_polynomial_component[#1]#*[#2]#*[#3]%
652  {\ifarguments
653   \or
654     #1{}\zerocount
655   \or
656     #1{}\numexpr\ifparameter#2\or#2\else\zerocount\fi\relax
657   \else
658     #1{#2}\numexpr\ifparameter#3\or#3\else\zerocount\fi\relax
659   \fi}
660
661\protected\def\polynomialnumerator   {\module_polynomial_component[\clf_polynomialnumerator   ]}
662\protected\def\polynomialdenominator {\module_polynomial_component[\clf_polynomialdenominator ]}
663\protected\def\polynomialquotient    {\module_polynomial_component[\clf_polynomialquotient    ]}
664\protected\def\polynomialquotientstep{\module_polynomial_component[\clf_polynomialquotientstep]}
665\protected\def\polynomialstep        {\module_polynomial_component[\clf_polynomialstep        ]}
666
667\startsetups math:polynomial:complete
668    \frac {
669        \polynomialnumerator
670    } {
671        \polynomialdenominator
672    }
673    \alignhere
674    \localcontrolledrepeat \polynomialsteps {
675        =
676        \ifnum\currentloopiterator>\plusone
677            \polynomialquotient[\currentloopiterator - 1] +
678        \fi
679        \frac {
680            \polynomialquotientstep[\currentloopiterator]
681            (\polynomialdenominator)
682            \polynomialnumerator[+][\currentloopiterator - 1]
683% \polynomialnumerator[-][\currentloopiterator - 1]
684            \polynomialquotientstep[-][\currentloopiterator]
685% \polynomialquotientstep[+][\currentloopiterator]
686            (\polynomialdenominator)
687        } {
688            \polynomialdenominator
689        }
690        \breakhere
691        =
692        \polynomialquotient[\currentloopiterator]
693        \ifpolynomialnumerator\currentloopiterator
694            +
695            \frac {
696                \polynomialnumerator[\currentloopiterator]
697            } {
698                \polynomialdenominator
699            }
700        \fi
701        \ifnum\currentloopiterator<\polynomialsteps
702            \breakhere
703        \fi
704    }
705\stopsetups
706
707\protect
708
709\continueifinputfile{m-polynomial.mkxl}
710
711\setuplayout[tight]
712
713\setuppapersize[A4,landscape][A4,landscape]
714
715\starttext
716
717\setuppolynomial[symbol=z]
718
719% \def\TempHack
720%   {\scratchcounter\setmathoptions\mathbinarycode
721%    \bitwiseflip\scratchcounter-\lookaheadforendclassoptioncode
722%    \setmathoptions\mathbinarycode\scratchcounter}
723%
724% \startformula
725% \TempHack
726% \polynomial
727%     [-5, -3,  3, -1, 1,  0, 2, -3, -2, -1, 4]
728%     [-8, -7, -5,  2, 8, -9, 1]
729% \stopformula
730
731% \startformula
732% \TempHack
733% \polynomial
734%     [split=yes]
735%     [-5, -3,  3, -1, 1,  0, 2, -3, -2, -1, 4]
736%     [-8, -7, -5,  2, 8, -9, 1]
737% \stopformula
738
739\def\TestPolynomial#1#2#3%
740  {\startformula
741   \polynomial
742      [alternative=#1]
743      [#2]
744      [#3]
745   \stopformula}
746
747% \page
748
749% \def\TestPolynomials#1#2%
750%   {\page
751%    \TestPolynomial{text:normal} {#1}{#2}
752%    \TestPolynomial{text:align}  {#1}{#2}
753%    \TestPolynomial{align:normal}{#1}{#2}
754%    \TestPolynomial{align:split} {#1}{#2}
755%    \page
756%    }
757%
758% \TestPolynomials
759%     {7, -5,  2, 3}
760%     {3, 0, 1}
761%
762% \TestPolynomials
763%     {7, -5,  2, 3}
764%     {3,  0,  2.7}
765%
766% \TestPolynomials
767%     {7, -5,  2, 3}
768%     {3,  0,  1}
769
770\startformula
771\polynomial
772  [color={1={n=red,d=green,q=blue},2={n=cyan,d=magenta,q=orange}}]
773  [7, -5,  2, 3, 2]
774  [3,  0,  1]
775\stopformula
776
777\polynomial
778  [alternative=none,
779   color={1={n=red,d=green,q=blue},2={n=cyan,d=magenta,q=orange,ds=darkgray}}]
780  [7, -5,  2, 3, 2]
781  [3,  0,  1]
782
783numerator
784
785\startformula
786    \polynomialnumerator
787\stopformula
788
789numerator 2
790
791\startformula
792    \polynomialnumerator[2]
793\stopformula
794
795denominator
796
797\startformula
798    \polynomialdenominator
799\stopformula
800
801quotient 1/2/3
802
803\startformula
804    \polynomialquotient[1]/
805    \polynomialquotient[2]/
806    \polynomialquotient[3]
807\stopformula
808
809quotientstep 1/2/3
810
811\startformula
812    \polynomialquotientstep[1]/
813    \polynomialquotientstep[2]/
814    \polynomialquotientstep[3]
815\stopformula
816
817steps
818
819\startformula
820    \polynomialsteps
821\stopformula
822
823factor
824
825\startformula
826    \polynomialfactor[2]
827\stopformula
828a
829shift
830
831\startformula
832    \polynomialshift[2]
833\stopformula
834
835
836\startformula
837    \frac {\polynomialnumerator}{\polynomialdenominator}
838    =
839    \polynomialquotient[1]
840    +
841    \frac {\polynomialnumerator[1]}{\polynomialdenominator}
842\stopformula
843
844\startformula
845\polynomial
846  [alternative=complete,
847   color={1={n=red,d=green,q=blue},2={n=cyan,d=magenta,q=orange,ds=darkgray}}]
848  [7, -5,  2, 3, 2]
849  [3,  0,  1]
850\stopformula
851
852% \ifpolynomialclipped1 YES \else NOP \fi
853
854\dorecurse\polynomialsteps{
855    \startformula
856        \polynomialstep[#1]
857    \stopformula
858}
859
860% \input tufte
861
862% \startformula
863%     \denominator
864%     \numerator[2]
865%     \frac{3x^3+2x^2-5x+7}{x^2+3}
866%     \alignhere
867%     = 3x + \frac{2x^2 - 14x + 7}{x^2+3}
868%     \breakhere
869%     = 3x + 2 + \frac{-14x+1}{x^2+3}
870% \stopformula
871
872% \input tufte
873
874% \startformula
875%     \frac{3x^3+2x^2-5x+7}{x^2+3}
876%     \alignhere
877%     = 3x + \frac{2x^2 - 14x + 7}{x^2+3}
878%     \breakhere
879%     = 3x + 2 + \frac{-14x+1}{x^2+3}
880% \stopformula
881
882% \input tufte
883
884% \TestPolynomials
885%     {-5, -3,  3, -1, 1,  0, 2, -3, -2, -1, 4}
886%     {-8, -7, -5,  2, 8, -9, 1}
887
888% \TestPolynomials
889%     {-1, 0, 1}
890%     {-1, 1}
891
892% \input tufte
893
894% \TestPolynomials
895%     {-1, 1}
896%     {-1, 0, 1}
897
898% \input tufte
899
900\stoptext
901