Skip to content
Snippets Groups Projects
Select Git revision
  • 1d6bceadb1c167d3e055ff0887da2e9acaeaef20
  • master default protected
  • hsh_v3.11
  • hsh_v3.10-r6
  • hsh_v3.10-r3
  • v3.9-r9
  • v3.10-r6
  • v3.9-r8
  • v3.10-r5
  • v3.9-r7
  • v3.10-r4
  • v3.9-r6
  • v3.10-r3
  • v3.9-r5
  • v3.10-r2
  • v3.9-r4
  • v3.10-r1
  • v3.9-r3
  • v3.9-r2
  • v3.9-r1
  • v3.8-r5
  • v3.8-r4
  • v3.8-r3
  • v3.8-r2
  • v3.8-r1
25 results

config.php

  • intervals.mac 33.11 KiB
    /*  Author Chris Sangwin
        University of Edinburgh
        Copyright (C) 2020 Chris Sangwin
    
        This program is free software: you can redistribute it or modify
        it under the terms of the GNU General Public License version two.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
        GNU General Public License for details.
    
        You should have received a copy of the GNU General Public License
        along with this program. If not, see <http://www.gnu.org/licenses/>. */
    
    
    /********************************************************************/
    /*  A package for manipulating intervals in Maxima.                 */
    /*  Based on code by Matthew James Read, 2012.                      */
    /*  Re-written, May 2020. Chris Sangwin, <C.J.Sangwin@ed.ac.uk>     */
    /*                                                                  */
    /*  V1.0 May 2020                                                   */
    /*                                                                  */
    /********************************************************************/
    
    /* Deal with unions. */
    
    unionp(ex) := if safe_op(ex)="%union" or safe_op(ex)="union" then true else false;
    
    intersectionp(ex) := if safe_op(ex)="%intersection" then true else false;
    
    /* Define simple intervals. */
    
    /* Defines the check functions for when intervals are entered: */
    cc_num(x,y) := block([Ans],
        Ans: 'cc(x,y),                                  /* Makes Ans equal to the original interval. Note the ' to stop evaluation or else it would create an infinite loop. */
        if not ev(real_numberp(x), simp) then           /* Checks x is a real number. */
            error("intervals: ",x," should be a real number"),
        if not ev(real_numberp(y), simp) then               /* Checks y is a real number. */
            error("intervals: ",y," should be a real number"),
        if y<x then Ans:{},                             /* Our interval is the empty set if y<x. */
        if x=y then Ans:{x},                            /* Simply the set {x} is x=y. */
        Ans
    )$
    
    oo_num(x,y) := block([Ans],
        Ans: 'oo(x,y),
        if ev(not real_numberp(x) and not(x=inf or x=-inf ), simp) then
            error("intervals: ",x," should be a real number"),
        if ev(not real_numberp(y) and not(y=inf or y=-inf ), simp) then
            error("intervals: ",y," should be a real number"),
        if y<x then Ans:{},
        if x=y then Ans:{},
        Ans
    )$
    
    co_num(x,y) := block([Ans],
        Ans: 'co(x,y),
        if ev(not real_numberp(x), simp) then
            error("intervals: ",x," should be a real number"),
        if ev((not real_numberp(y) and not(y=inf or y=-inf)), simp) then
            error("intervals: ",y," should be a real number"),
        if y<x then Ans:{},
        if x=y then Ans:{},
        Ans
    )$
    
    oc_num(x,y) := block([Ans],
        Ans: 'oc(x,y),
        if ev(not real_numberp(x) and not(x=inf or x=-inf), simp) then
            error("intervals: ",x," should be a real number"),
        if ev(not real_numberp(y), simp) then
            error("intervals: ",y," should be a real number"),
        if y<x then Ans:{},
        if x=y then Ans:{},
        Ans
    )$
    
    /* Validate student's input. */
    
    /* Return a list of errors for a single connected component. */ 
    interval_validate_single_interval(ex) := block([ret, iop, il, ir],
        ret:"",
        if trivialintervalp(ex) then return(""),
        if not(intervalp(ex)) then
            return(StackAddFeedback("", "Interval_notinterval", stack_disp(ex, "i"))),
        if not(is(length(args(ex))=2)) then
            /* The tex functions only cope with two arguments, so we have to use a string here! */
            return(StackAddFeedback("", "Interval_wrongnumargs", stack_disp(string(ex), "i"))),
        iop:op(ex),
        il:first(args(ex)),
        ir:second(args(ex)),
        if real_numberp(il) and real_numberp(ir) and is(ir<il) then       
            ret:StackAddFeedback(ret, "Interval_backwards", stack_disp(ex, "i"), stack_disp(apply(iop,[ir, il]), "i")),
        return(ret)  
    )$
    
    /* Validate a realset, mostly for student feedback, so no errors thrown. */
    interval_validate_realset(ex) := block(
        if trivialintervalp(ex) then return(""),
        if setp(ex) then return(""),
        if intervalp(ex) then return(interval_validate_single_interval(ex)),
        if safe_op(ex)="%union" then return(apply(sconcat, maplist(interval_validate_realset, args(ex)))),
        if safe_op(ex)="%intersection" then return(apply(sconcat, maplist(interval_validate_realset, args(ex)))),
        return(StackAddFeedback("", "Interval_illegal_entries", stack_disp(ex, "i")))
    )$
    
    cc_interval_tex(ex) := block([a, b],
        a:first(args(ex)),
        b:second(args(ex)),
        concat("\\left[ ", tex1(a),",\\, ",tex1(b), "\\right]")
    )$
    texput(cc, cc_interval_tex)$
    
    /* Note, the mismatching square brackets play havoc with the PHP interface. */
    co_interval_tex(ex) := block([a, b],
        a:first(args(ex)),
        b:second(args(ex)),
        /*concat("\\left[ ", tex1(a),",\\, ",tex1(b), "\\right)")*/
        concat("!LEFTSQ! ", tex1(a),",\\, ",tex1(b), "!RIGHTR!")
    )$
    texput(co, co_interval_tex)$
    
    oc_interval_tex(ex) := block([a, b],
        a:first(args(ex)),
        b:second(args(ex)),
        /*concat("\\left( ", tex1(a),",\\, ",tex1(b), "\\right]")*/
        concat("!LEFTR! ", tex1(a),",\\, ",tex1(b), "!RIGHTSQ!")
    )$
    texput(oc, oc_interval_tex)$
    
    oo_interval_tex(ex) := block([a, b],
        a:first(args(ex)),
        b:second(args(ex)),
        concat("\\left( ", tex1(a),",\\, ",tex1(b), "\\right)")
    )$
    texput(oo, oo_interval_tex)$
    
    realset_tex(ex) := block([a, b, c],
        if not(length(args(ex))=2) then error("realset: this function must have two arguments."),
        a:first(args(ex)),
        b:second(args(ex)),
        c:ev(interval_complement(b), simp),
        if safe_setp(c) then
            concat("{", tex1(a), " \\not\\in {",tex1(c), "}}")
        else
            concat("{", tex1(a), " \\in {",tex1(b), "}}")
    )$
    texput(realset, realset_tex)$
    
    /* Returns True if p is an element of A. False, otherwise: */
    
    inintervalp(p, A) := block ([Ans, Args, x, y, Atemp, cc:cc(0,1), oo:oo(0,1), co:co(0,1), oc:oc(0,1), i:1, j:1, n],
            cc:op(cc), oo:op(oo), co:op(co), oc:op(oc),
    
            Ans:false,
            if not ev(real_numberp(p), simp) then
            error("intervals: ",p," should be a real number"),
    
            if atom(A) then Ans:false
            elseif op(A)=set then
                (
                Atemp:listify(A),
                n:length(Atemp),
                while i<(n+1) do
                    (
                    if p=Atemp[i] then Ans:true,
                    i:i+1 )
                )
            elseif not( op(A)="[" ) then
                (
                Args:args(A),
                x:first(Args),
                y:last(Args),
                if op(A)=cc then
                    (
                    if (p>=x and p<=y) then Ans:true
                    ),
                if op(A)=oo then
                    (
                    if (p>x and p<y) then Ans:true
                    ),
                if op(A)=co then
                    (
                    if (p>=x and p<y) then Ans:true
                    ),
                if op(A)=oc then
                    (
                    if (p>x and p<=y) then Ans:true
                    )
                )
            elseif op(A)="[" then
                (
                n:length(A),
                while j<n+1 do
                    (
                    Atemp:A[j],
                    Ans:inintervalp(p,Atemp),
                    if Ans=false then j:j+1 else j:n+1
                    )
                )
            else error("intervals: the interval, ",A,", is not of a recognised form"),
            Ans
    )$
    
    intervalp(X) := if (safe_op(X)="cc" or safe_op(X)="oo" or safe_op(X)="oc" or safe_op(X)="co") then true else false$
    
    realsetp(ex) := block(
        if is(ex=all) then return(true),
        if is(ex=none) then return(true),
        if atom(ex) then return(false),
        if safe_setp(ex) then return(all_listp(real_numberp, args(ex))),
        if intervalp(ex) then return(all_listp(extended_real_numberp, args(ex))),
        if op(ex)=%union then return(all_listp(realsetp, args(ex))),
        if op(ex)=%intersection then return(all_listp(realsetp, args(ex))),
        return(false)
    )$
    
    /* Does not require all numbers to be actual real numbers. */
    realset_soft_p(ex) := block(
        if is(ex=all) then return(true),
        if is(ex=none) then return(true),
        if atom(ex) then return(false),
        if safe_setp(ex) then return(true),
        if intervalp(ex) then return(true),
        if op(ex)=realset then return(true),
        if op(ex)=%union then return(all_listp(realset_soft_p, args(ex))),
        if op(ex)=%intersection then return(all_listp(realset_soft_p, args(ex))),
        return(false)
    )$
    
    /* Only looks at the very top level, used for validation */
    realset_surface_p(ex) := block(
        if is(ex=all) then return(true),
        if is(ex=none) then return(true),
        if atom(ex) then return(false),
        if safe_setp(ex) then return(true),
        if intervalp(ex) then return(true),
        if op(ex)=%union then return(true),
        if op(ex)=%intersection then return(true),
        return(false)
    )$
    
    /* Make a real set, taking edge cases into account.  This is also a top level function to convert true/false into all/none. */
    realsetmake(v, ex) := block(
        if is(ex=false) then return(none),
        if is(ex={}) then return(none),
        if is(ex=%union()) then return(none),
        if is(ex=%intersection()) then return(none),
        if is(ex=true) then return(all),
        if is(ex=all) or is(ex=none) or is(ex=unknown) then return(ex),
        if atom(ex) then return(ex),
        if is(safe_op(ex)="realset") then return(ex),
        if not(realset_soft_p(ex)) then error("realsetmake: second argument must appear to be a real set."), 
        return(realset(v, ex))
    )$
    
    /* Predicate to remove trivial cases like oo(a,a) and co(-inf, -inf). */
    trivialintervalp(ex) := block(
        if is(ex=all) or is(ex=none) then return(true),
        if safe_setp(ex) and ex={} then return(true),
        if not(intervalp(ex)) then return(false),
        if safe_op(ex)="oo" and first(ex)=second(ex) then return(true),
        if safe_op(ex)="oc" and first(ex)=second(ex) then return(true),
        if safe_op(ex)="co" and first(ex)=second(ex) then return(true),
        if first(ex)=inf then return(true),
        if second(ex)=-inf then return(true),
        return(false)
    )$
    
    /* Return the number of separate connected components. */ 
    interval_count_components(ex) := block(
        if not(realsetp(ex)) then error("interval_count_components"),
        if ex=all then return(1),
        if trivialintervalp(ex) then return(0),
        if intervalp(ex) then return(1),
        if setp(ex) then return(cardinality(ex)),
        ev(apply("+", map(interval_count_components, args(ex))), simp)
    )$
    
    interval_simple_union(X,Y) := block([A:X, B:Y, Ans, x1, x2, y1, y2, Args1, Args2, Aset, swap:false, setAns:[], cc:cc(0,1), oo:oo(0,1), co:co(0,1), oc:oc(0,1), i:1, j:1, n],
            cc:op(cc), oo:op(oo), co:op(co), oc:op(oc),
    
            if A=all then return(all),
            if B=all then return(all),
            if A=none or A={} then return(B),
            if B=none or B={} then return(A),
    
            if atom(A) then error("interval_simple_union: invalid first argument"),
            if atom(B) then error("interval_simple_union: invalid second argument"),
    
            if safe_setp(A) then (
                if safe_setp(B) then
                    Ans:union(A,B)
                else (
                    Args1:args(B),
                    x1:first(Args1),
                    y1:last(Args1),
                    Aset:listify(A),
                    n:length(Aset),
                    while i<(n+1) do (
                        if (Aset[i]<x1 or Aset[i]>y1) then
                            setAns:cons(Aset[i],setAns)
                        elseif Aset[i]=x1 then (
                            if op(B)=oc then B:cc(x1,y1),
                            if op(B)=oo then B:co(x1,y1)
                        )
                        elseif Aset[i]=y1 then (
                            if op(B)=co then B:cc(x1,y1),
                            if op(B)=oo then B:oc(x1,y1)
                        ),
                        i:i+1
                    ),
                    if length(setAns)>0 then (setAns:setify(setAns), Ans: [B,setAns] ) else Ans:B
                    )
                )
                elseif safe_setp(B) then (
                    Args1:args(A),
                    x1:first(Args1), y1:last(Args1),
                    Aset:listify(B),
                    n:length(Aset),
                    while i<(n+1) do (
                        if (Aset[i]<x1 or Aset[i]>y1) then
                            setAns:cons(Aset[i],setAns)
                        elseif Aset[i]=x1 then (
                            if op(A)=oc then A:cc(x1,y1),
                            if op(A)=oo then A:co(x1,y1)
                        )
                        elseif Aset[i]=y1 then (
                            if op(A)=co then A:cc(x1,y1),
                            if op(A)=oo then A:oc(x1,y1)
                        ),
                        i:i+1
                    ),
                    if length(setAns)>0 then (setAns:setify(setAns), Ans: [A,setAns] ) else Ans:A
                ),
    
            if ( not atom(A) and not atom(B) ) then (
                Args1:args(A),
                Args2:args(B),
    
                if not(atom(A) or safe_setp(A) or atom(B) or safe_setp(B)) then (
                    if first(Args1)<first(Args2) then
                        swap:false,
                    if first(Args1)=first(Args2) then (
                        if ( op(A)=co or op(A)=cc ) then
                            swap:false
                        elseif ( op(B)=co or op(B)=cc ) then
                            swap:true
                        else swap:false
                    ),
                    if first(Args1)>first(Args2) then swap:true,
                    if swap=false then (
                        x1:first(Args1),
                        y1:last(Args1),
                        x2:first(Args2),
                        y2:last(Args2)
                    ) else (
                        Atemp:A,
                        A:B,
                        B:Atemp,
                        x2:first(Args1),
                        y2:last(Args1),
                        x1:first(Args2),
                        y1:last(Args2)
                    ),
                    if x2>y1 then
                        Ans:[A,B],
                    if (x2<y1 and y2>y1) then (
                        if (op(A)=cc or op(A)=co) then (
                            if (op(B)=oc or op(B)=cc) then
                                Ans:cc(x1,y2)
                            elseif (op(B)=oo or op(B)=co) then
                                Ans:co(x1,y2)
                            )
                        elseif (op(A)=oc or op(A)=oo) then (
                            if (op(B)=oc or op(B)=cc) then
                                Ans:oc(x1,y2)
                            elseif (op(B)=oo or op(B)=co) then
                                Ans:oo(x1,y2)
                            )
                        ),
                    if (x2<y1 and y2=y1) then (
                        if (op(B)=oc or op(B)=cc) then
                            Ans:interval_simple_union( A , {y2} )
                        else
                            Ans:A
                        ),
                    if (x2<y1 and y2<y1) then
                        Ans:A,
                    if x2=y1 then (
                        if ( (op(A)=co or op(A)=oo) and (op(B)=oo or op(B)=oc) ) then
                            Ans:[A,B]
                        else (
                            if (op(A)=cc or op(A)=co) then (
                                if (op(B)=oc or op(B)=cc) then
                                    Ans:cc(x1,y2)
                                elseif (op(B)=oo or op(B)=co) then
                                    Ans:co(x1,y2)
                                )
                            elseif (op(A)=oc or op(A)=oo) then (
                                if (op(B)=oc or op(B)=cc) then
                                    Ans:oc(x1,y2)
                                elseif (op(B)=oo or op(B)=co) then
                                    Ans:oo(x1,y2)
                                )
                            )
                        )
                    )
                ),
            Ans
    )$
    
    
    /* Finds the intersection of two "simple" real sets. */
    interval_simple_intersect(X,Y) := block([A:X, B:Y, Ans, x1, x2, y1, y2, Args1, Args2, Aset, swap:false, lopen:false, ropen:false, setAns:[], cc:cc(0,1), oo:oo(0,1), co:co(0,1), oc:oc(0,1), i:1, n],
            cc:op(cc), oo:op(oo), co:op(co), oc:op(oc),
    
            if not(realsetp(X)) then error("interval_simple_intersect expects its first argument to be a real set."),
            if not(realsetp(Y)) then error("interval_simple_intersect expects its second argument to be a real set."),
    
            if safe_setp(A) and safe_setp(B) then return(intersect(A,B)),
            /* A & B are not both sets. */
            if safe_setp(B) then (
                A:Y,
                B:X
            ),
            if safe_setp(A) then (
                Args1:args(B),
                x1:first(Args1), y1:last(Args1),
                Aset:listify(A),
                n:length(Aset),
                while i<(n+1) do (
                    if inintervalp(Aset[i],B) then setAns:cons(Aset[i],setAns),
                    i:i+1
                ),
                if length(setAns)>0 then (
                    setAns:setify(setAns),
                    Ans:setAns
                ) else (
                    Ans:{}
                ),
                return(Ans)
            ),
            /* At this point we have both A & B not sets. */
            if not(intervalp(A) and intervalp(B)) then error("interval_simple_intersect expects its arguments to be sets or simple intervals."),
    
            Args1:args(A),
            Args2:args(B),
    
            if first(Args1)<first(Args2) then
                swap:false,
            if first(Args1)=first(Args2) then (
                if (op(A)=co or op(A)=cc) then (
                    swap:false 
                ) elseif (op(B)=co or op(B)=cc ) then (
                    swap:true 
                ) else (
                    swap:false
                )
            ),
            if is(first(Args1)>first(Args2)) then (
                swap:true
            ),
    
            if swap=false then (
                x1:first(Args1),
                y1:last(Args1),
                x2:first(Args2),
                y2:last(Args2)
            ) else (
                Atemp:A,
                A:B,
                B:Atemp,
                x2:first(Args1),
                y2:last(Args1),
                x1:first(Args2),
                y1:last(Args2)
            ),
            if x2>y1 then (
                Ans:{}
            ),
            if (x2<y1 and y2>y1) then (
                if (op(A)=cc or op(A)=oc) then (
                    if (op(B)=cc or op(B)=co) then
                        Ans:cc(x2,y1)
                    elseif (op(B)=oo or op(B)=oc) then
                        Ans:oc(x2,y1)
                ) elseif (op(A)=co or op(A)=oo) then (
                    if (op(B)=co or op(B)=cc) then
                        Ans:co(x2,y1)
                    elseif (op(B)=oo or op(B)=oc) then (
                        Ans:oo(x2,y1)
                    )
                )
            ),
            if (x2<y1 and y2<y1) then
                Ans:B,
            if (x2<y1 and y2=y1) then (
                if (op(B)=oc or op(B)=oo) then lopen:true,
                if (op(B)=oo or op(B)=co or op(A)=oo or op(A)=co) then ropen:true,
                if (lopen and ropen) then Ans:oo(x2,y1),
                if (lopen and not ropen) then Ans:oc(x2,y1),
                if (not lopen and ropen) then Ans:co(x2,y1),
                if (not lopen and not ropen) then Ans:cc(x2,y1)
            ),
            if x2=y1 then (
                if ((op(A)=cc or op(A)=oc) and (op(B)=co or op(B)=cc)) then
                    Ans:{x2}
                else
                    Ans:{}
            ),
        Ans
    )$
    
    interval_disjointp(A, B) := if interval_simple_intersect(A, B)={} then true else false$
    
    /* Is the ex1 contained within the real set ex2? */ 
    interval_subsetp(ex1, ex2) := block(
        if not(realsetp(ex1)) then error("interval_subsetp expects its first argument to be a real set."),
        if not(realsetp(ex2)) then error("interval_subsetp expects its second argument to be a real set."),
        if interval_intersect(ex1, ex2) = ex1 then true else false
    )$
    
    /* Is the simple interval ex a explicitly a subinterval of EX? */ 
    interval_containsp(ex, EX) := block(
        if not(intervalp(ex)) then error("interval_containsp expects its first argument to be a simple interval."),
        if not(realsetp(EX)) then error("interval_containsp expects its second argument to be a real set."),
        if is(ex=EX) then return(true),
        if not(safe_op(EX)="%union" or safe_op(EX)="%intersection") then return(false),
        if elementp(ex,setify(args(EX))) then return(true),
        return(false)
    )$
    
    /* Top level intersection function which takes real sets, such as %unions. */
    interval_intersect(X,Y) := block([A, B, Ans:[], temp, m, n, i:1, j:1],
        A:X,
        B:Y,
    
        if safe_op(A)="%intersection" then A:interval_intersect_list(args(A)),
        if safe_op(B)="%intersection" then B:interval_intersect_list(args(B)),
    
        if is(A=all) then return(B),
        if is(B=all) then return(A),
        if atom(A) then return({}),
        if atom(B) then return({}),
        if is(A={}) then return({}),
        if is(B={}) then return({}),
    
        if op(A)=%union then A:args(A),
        if op(B)=%union then B:args(B),
        if not(listp(A)) and not(listp(B)) then return(interval_simple_intersect(A,B)),
    
        /* Ensure we have lists to deal with, by making them lists of one element if needed. */
        if not(listp(A)) then (temp:[], A:cons(A,temp) ),
        if not(listp(B)) then (temp:[], B:cons(B,temp) ),
    
        m:length(A),
        n:length(B),
        if (m=1 and n=1) then (
            A:A[1],
            B:B[1],
            return(interval_simple_intersect(A,B))
        ) else (
            while i<m+1 do (
                while j<n+1 do (
                    temp:interval_simple_intersect(A[i], B[j]),
                    if not atom(temp) then (
                    Ans:append(Ans, [temp])
                        ),
                    j:j+1
                ),
                j:1,
                i:i+1
            )
        ),
        if listp(Ans) then (
            if length(Ans)=1 then Ans:Ans[1],
            if length(Ans)=0 then Ans:{}
        ),
        interval_tidy(Ans)
    )$
    
    /* Given a *list* of intervals, returns the intersection of all of them. */
    interval_intersect_list(X) :=
        block ( [A:X, Ans, n, i, simp],
            simp:true,
            if X=[] then return({}),
            n:length(A),
            if n=1 then return(first(A)),
            Ans:A[1],
            i:2,
            while i<n+1 do
                (
                Ans:interval_intersect(Ans, A[i]),
                i:i+1
                ),
            Ans
            );
    
    interval_intersect_nary([X]) := interval_intersect_list(X)$
    
    /* Given intervals, returns the same intervals but in ascending order of the first element in the interval. */
    interval_sort(X) := block([A:X, Ans:[], x, n, i],
        if safe_op(X) = "%union" then A:args(X),
    
        n:length(A),
        while n>0 do
            (
            x:A[1],
            i:2,
            while i<n+1 do block(
                if is(first(A[i]) < first(x)) then x:A[i],
                i:ev(i+1,simp)
                ),
            Ans:append(Ans,[x]),
            A:delete(x, A, 1),
            n:ev(n-1, simp)
            ),
        /* %union does things to its arguments like moving -inf to the right with simp:true. */
        /* Return a list to avoid killing the order here. */
        Ans
    );
    
    /* Given a union of disjoint intervals,
       checks whether any intervals are connected, and if so, joins them up and returns the ammended union. */
    interval_connect(X) := block([Ans, n, x, y, i:1],
        if not(op(X)=%union or listp(X)) then error("interval_connect requires a %union or list of intervals"),
        Ans:args(X),
        n:length(Ans),
        while i<n do
            (
            i:ev(i,simp),
            if last( Ans[i] ) >= first( Ans[ev(i+1, simp)] ) then
                (
                x:interval_simple_union( Ans[i], Ans[ev(i+1, simp)] ),
                if ( not op(x) = "[" ) then
                    (
                    Ans:delete( Ans[ev(i+1, simp)], Ans, 1 ),
                    Ans:delete( Ans[i], Ans, 1 ),
                    Ans:append( Ans, [x] ),
                    i:ev(i-1, simp),
                    n:ev(n-1, simp)
                    )
                ),
            i:i+1
            ),
        if length(Ans) = 1 then return(Ans[1]),
        Ans:apply(%union, Ans),
        Ans
    );
    
    /* Given a union of disjoint sets, returns the "canonical form" of this union: */
    interval_tidy(X) := block([A, Ans:[], n, setpart:{}, x, y, i:1],
        if X=all then return(all),
        if atom(X) then return(Ans:phi),
        if listp(X) then X:apply(%union, X),
        X:ev(X, %intersection=interval_intersect_nary),
    
        if not(op(X)=%union or listp(X)) then (
            Ans:X
        ) else (
            A:args(X),
            i:1,
            n:length(A),
            while i<ev(n+1, simp) do (
                i:ev(i,simp),
                if safe_setp(A[i]) then (
                    setpart:union(setpart, A[i] ),
                    A:delete( A[i], A, 1 ),
                    i:ev(i-1, simp),
                    n:ev(n-1, simp)
                ) else if trivialintervalp(A[i]) then (
                    A:delete( A[i], A, 1 ),
                    i:ev(i-1, simp),
                    n:ev(n-1, simp)
                    ),
                i:ev(i+1, simp)
                ),
                A:interval_sort(A),
                if is(length(A)>1) then
                    A:interval_connect(A),
                if length(setpart)>0 then A:append( args(A), [setpart] ),
                if is(A=[]) then
                    A:{}
                elseif is(length(A)=1) then
                    A:first(A),
                Ans:A
            ),
            if Ans=oo(-inf,inf) then return(all),
            Ans
    )$
    
    interval_complement_order_points(X):=
        block( [A:X, Ans:[], setpart, n, i:1],
            A:interval_tidy(A),
            if safe_setp(last(A)) then (
                setpart:listify(last(A)),
                A:delete(last(A), A, 1),
                n:length(A) + length(setpart),
    
                while i<n+1 do
                    (
                    if length(setpart)>0 then
                        (
                        if length(A)=0 then
                            (
                            Ans:append( Ans, [ { setpart[1] } ] ),
                            setpart:delete( setpart[1], setpart, 1 )
                            )
                        else
                            (
                            if setpart[1] < first( A[1] ) then
                                (
                                Ans:append( Ans, [ { setpart[1] } ] ),
                                setpart:delete( setpart[1], setpart, 1 )
                                )
                            else
                                (
                                Ans:append( Ans, [ A[1] ] ),
                                A:delete( A[1], A, 1 )
                                )
                            )
                        ),
                    i:i+1
                    )
                )
            else Ans:A,
            Ans
    )$
    
    /* Return the set complement of a real set. */
    interval_complement(X):= block([A:X, Ans:[], x, y, cc:cc(0,1), oo:oo(0,1), co:co(0,1), oc:oc(0,1), n, i:1],
        cc:op(cc), oo:op(oo), co:op(co), oc:op(oc),
    
        if atom(A) then return(oo(-inf,inf)),
        if not (op(A) = "[" or op(A)=%union) then (
            if safe_setp(A) then Ans:interval_set_complement(A)
            elseif intervalp(A) then (
                if op(A)=co then
                    (
                    Ans:append( Ans, [ oo(-inf, first(A) ) ] ),
                    Ans:append( Ans, [ co( last(A), inf) ] )
                    ),
                if op(A)=cc then
                    (
                    Ans:append( Ans, [ oo(-inf, first(A) ) ] ),
                    Ans:append( Ans, [ oo( last(A), inf) ] )
                    ),
                if op(A)=oc then
                   (
                   Ans:append( Ans, [ oc(-inf, first(A) ) ] ),
                   Ans:append( Ans, [ oo( last(A), inf) ] )
                   ),
                if op(A)=oo then
                   (
                   Ans:append( Ans, [ oc(-inf, first(A) ) ] ),
                   Ans:append( Ans, [ co( last(A), inf) ] )
                   )
                )
            ) else (
                A:interval_complement_order_points(A),
                A:args(A),
    
                /* Just use DeMorgan's laws. */
                Ans:ev(interval_intersect_list(maplist(lambda([ex2], interval_tidy(interval_complement(ex2))), A)), simp),
    
                if listp(Ans) and length(Ans)=1 then
                    Ans:Ans[1]
            ),
        if listp(Ans) then
           Ans:apply(%union, Ans),
        Ans
    )$
    
    /* Take a set of real numbers, and return the %union of intervals not containing these numbers. */
    interval_set_complement(X):= block([A:X, Ans:[], temp, n, i:1],
        if not(setp(X)) then error("interval_set_complement requires a set."),
        A:listify(A),
        n:length(A),
        temp:oo(-inf, A[1]),
        Ans:[temp],
        while i<n do (
            temp:oo( A[i], A[i+1] ),
            temp:[temp],
            Ans:append(Ans, temp),
            i:i+1
            ),
        temp:oo(A[n], inf),
        temp:[temp],
        Ans:append(Ans, temp),
        apply(%union, Ans)
    )$
    
    
    
    /* Turns a single variable system over the reals into a set of real numbers, together with insoluable bits (if any). */
    single_variable_solver_real(ex) := block([v, rs1, rs2],
        if is(ex=false) then return(none),
        if is(ex=true) then return(all),
        if atom(ex) then return(ex),
        v:listofvars(ex),
        if is(length(v)=0) then block
            (
            if is(ratsimp(lhs(ex)-rhs(ex))=0) then
                ex:all
            else
                ex:none
            ),
        if not(length(v)=1) then return(ex),
        v:first(v),
        ex:abs_replace_eq(ex),
        ex:stack_noteq_single_remove(ex),
        ex:subst("%and", "nounand", ex),
        ex:subst("%or", "nounor", ex),
        /* %not is not an infix operator... */
        ex:subst(%not, "not", ex),
        ex:subst(%not, "nounnot", ex),
        ex:subst("%and", "and", ex),
        ex:subst("%or", "or", ex),
    
        /* Notes,
           (1) assume_pos automatically removes terms like v>=0 in the simplifier.
           (2) we do need simplification here to reduce execution time.
        */
    
        if assume_pos then
            ex:block([assume_pos:false], ev(single_variable_solver_real_rec(ex %and (v>=0), v), simp))
        else
            ex:ev(single_variable_solver_real_rec(ex, v), simp),
    
        if ((safe_op(ex)="[" or safe_op(ex)="%union") and is(length(args(ex))=1)) then ex:first(ex),
    
        if is(ex={}) then return(none),
        if is(ex={v}) then return(all),
        if logic_edgep(ex) then return(ex),
        if is(equal(ex,oo(-inf,inf))) then return(all),
    
        rs1:ex,
        rs2:false,
        if safe_op(ex)="%or" then block(
            rs1:ev(sublist(args(ex), realset_soft_p), simp),
            rs2:ev(sublist(args(ex), lambda([ex2], not realset_soft_p(ex2))), simp),
            if is(length(rs1)=1) then rs1:first(rs1),
            if is(rs1=none) then
                ex:apply("%or", rs2)
            else if is(rs1=all) then
                ex:all
            else
                ex:(if realset_soft_p(rs1) then realsetmake(v, rs1) else rs1) %or apply("%or", rs2)
        ),
        if safe_op(ex)="%union" or safe_setp(ex) then
            ex:realsetmake(v, ex),
    
        return(ex)
    )$
    
    single_variable_solver_real_rec(ex, v) := block([r0, r1, r2],
        if atom(ex) then return(ex),
        if intervalp(ex) then return(ex),
        /* Equations should look real. */
        if not(freeof(%i,ex)) then return(ex),
    
        if equationp(ex) then ex:subst("%or", "nounor", pm_replace(ex)),
        if equationp(ex) then return(ev(equation_to_intervals(ex, v), simp)),
        if linear_inequalityp(ex) then return(ev(linear_inequality_to_interval(ex), simp)),
    
        /* Possible recursion from here. */
        if inequalityp(ex) then ex:ev(inequality_factor_solve(ex), simp),
    
        if safe_op(ex)="%or" or safe_op(ex)="%and" then block(
            r0:maplist(lambda([ex2], single_variable_solver_real_rec(ex2, v)), args(ex)),
            r1:ev(sublist(r0, realset_soft_p), simp),
            r2:ev(sublist(r0, lambda([ex2], not(realset_soft_p(ex2)))), simp)
            ),
        if safe_op(ex)="%or" then return(ev(apply("%or", append([interval_tidy(r1)], r2)), simp)),
        if safe_op(ex)="%and" then return(ev(apply("%and", append([interval_intersect_list(r1)], r2)), simp)),
    
        return(ex)
    )$
    
    equation_to_intervals(ex, v) := block([sol0, sol1, sol2],
        sol0:radcan(solve(ex, v)),
        if sol0=[] then return({}),
        if logic_edgep(sol0) then return(sol0),
        /* We need the "freeof" clause to catch rearrangements of equations. */
        sol1:sublist(sol0, lambda([ex2], is(lhs(ex2)=v) and freeof(v, rhs(ex2)))),
        sol2:sublist(sol0, lambda([ex2], not(is(lhs(ex2)=v) and freeof(v, rhs(ex2))))),
        sol1:maplist(rhs,sol1),
        sol1:flatten(setify(sol1)),
        if is(length(sol2)=1) then
            sol2:first(sol2)
        else
            sol2:apply("%or", sol2),
        if emptyp(sol1) then
            return(sol2),
        return(sol1 %or sol2)
    )$
    
    /* Calculate the natural domain of a single-variable term.  */
    natural_domain(ex) := block([v, ex2],
        if atom(ex) then return(all),
        v:listofvars(ex),
        if is(v=[]) then return(all),
        if ev(not(is(length(v)=1)), simp) then return(unknown),
        /* We only work over real expressions. */
        if not(is(freeof(%i, ex))) then return(unknown),
        /* We only calculate domains of some things. */
        if not(is(freeof(sum, ex))) then return(unknown),
        if not(is(freeof(int, ex))) then return(unknown),
        v:first(v),
        /* Recurse using true/false instead of all/none, then convert. */
        ex2:natural_domain_rec(ex),
        if realset_soft_p(ex2) then ex2:realsetmake(v, ex2),
        ex2
    )$
    
    /* Calculate the natural domain of a single-variable term.  */
    natural_domain_rec(ex) := block([v, ex2],
        if atom(ex) then return(all),
        v:listofvars(ex),
        if is(v=[]) then return(all),
        if not(is(length(v)=1)) then return(unknown),
        v:first(v),
    
        if safe_op(ex)="sqrt" then
            return(single_variable_solver_real(first(args(ex))>=0)),
        if safe_op(ex)="ln" or safe_op(ex)="log" or safe_op(ex)="lg" then
            return(single_variable_solver_real(first(args(ex))>0)),
        if safe_op(ex)="/" then
            ex2:[natural_domain_rec(first(args(ex))), single_variable_solver_real((second(args(ex))>0) %or (second(args(ex))<0))]
        else
            ex2:map(natural_domain_rec, args(ex)),
        /* We have to strip of the realset bit before intersecting. */
        ex2:map(lambda([ex3], if is(safe_op(ex3)="realset") then second(ex3) else ex3), ex2),
        /* Only return a define value if we really have one. */
        if any_listp(lambda([ex3], is(ex3=unknown) or not(realset_soft_p(ex3) or is(ex3=true) or is(ex3=false))), ex2) then
            ex2:unknown
        else
            ex2:interval_intersect_list(ex2),
        ex2
    )$