Sindbad~EG File Manager
/* M. Beeson, for MathXpert. Handle the Graph button on the Speedbar */
/*
Original date 7.20.95
modified 1.21.99
1.15.00 modified get_function on complex equation topics
3.8.00 modified get_function on limit topics
8.28.04 modified about line 870 to set an error message on illegal input.
9.11.07 moved initialization of p->brotherdoc a few lines higher, above fillgraphstructures.
5.20.13 removed include getprob.h
6.10.13 added code in graph_problemtype to make a Riemann-sums graph on topic _integral_test
added code at line 202 to get the eigenvariable right
12.8.14 moved call to init_graph_ranges to make sure it's also called on limit problems
3.4.24 adapted to WebMathXpert-- the original windows code is now in winfront/window_grbutton.c
This file is now part of the Engine; when the Graph button is pressed, the same document is
used, not a new one.
3.5.24 added p->function = t in grbutton
3.27.24 added t = p->function in grbutton (after enhance_problem has changed p->function).
6.22.24 commented out two lines (dated in the comment)
7.24.24 added call to fill_graph_structures, line 163 or so
8.19.24 added include "enhance.c"
12.10.24 modified show_area to allow hi to be an atom, and
modified modified get_function to make upper limit a parameter
12.31.24 made graphButton and get_function and graph_problemtype
treat _sum_series the same as integral_test
1.3.25 modified get_function on DIFFERENTIATE
1.6.25 removed code in graphButton that initialized a param in sum_series problems
2.24.25 graph_problemtype should set p->graphtopic also on DIFFERENTIATE_FROM_DEFN
2.25.25 put in code to check if this is the first click on graphButton in
get_function under DIFFERENTIATE_FROM_DEFN; and code to get the original eigenvariable
2.26.25 modified get_function under SERIES so we get m rectangles in the graph,
by setting hi = lo + m -1
3.1.25 added available_int_parameter and code that calls it
*/
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include "globals.h"
#include "graphstr.h"
#include "mpdoc.h"
#include "tdefn.h"
#include "heaps.h"
#include "probtype.h"
#include "grafinit.h" /* setupdata */
#include "activate.h" /* init_proverDLL */
#include "mathpert.h"
#include "grbutton.h"
#include "deval.h"
#include "prover.h" /* ISINFINITE */
#include "exec.h" /* erasecolors */
#include "pvalaux.h" /* strongnegate */
#include "solvelin.h" /* solve_linear_ineq_for */
#include "islinear.h" /* is_linear_system */
#include "ssolve.h" /* solved */
#include "grapher.h" /* graph_ineq */
#include "graphrng.h" /* init_graph_ranges */
#include "automode.h" /* get_activeline */
#include "mpmem.h"
#include "errbuf.h"
#include "activedoc.h" /* GetActiveDoc */
#include "getprob.h" /* enhance_problem */
#include "pstring.h" /* log_term, for debugging */
#include "mainchoi.h" /* COMPAREDERIVS */
#include "deriv.h" /* derivative */
#define LIMITAND(t) (ARITY(t)==2 ? ARG(1,t) : ARG(2,t))
static int graph_problemtype(int problemtype, int topic, term t,PDOCDATA p);
static term get_function(PDOCDATA pSymbolData, int *problemtype, term *a, term *b, int *whichline, const char **errmsg);
static term strip_eq(term);
static term strip_eq2(term,term);
static term available_int_parameter(PDOCDATA pDocData, term t);
/*___________________________________________________________________*/
static term available_int_parameter(PDOCDATA pDocData, term t)
// find a parameter of type INTEGER not occurring in t if there is one,
// and return it (as a term); or return zero if there isn't one.
{ parameter *params = pDocData->DocVarData.parameters;
int nparams = pDocData->DocVarData.nparameters;
int i;
term m;
unsigned f;
term *varlist = pDocData->DocVarData.varlist;
for(i=0;i<nparams;i++)
{ f = params[i].functor;
if(!contains(t,f))
{ m = varlist[params[i].index];
if(TYPE(m) == INTEGER)
return m;
}
}
return zero;
}
/*___________________________________________________________________*/
int graph_button(PDOCDATA p)
/* p is a pointer to the document data structure of the
document active when the Graph button was
pressed. Determine what problemtype and topic are needed for the
graph, and put them in pDocData->graphproblemtype and
pDocData->graphtopic. Fill in pDocData->function and other
data needed for the graph. Return 0 for success.
Return 1 if the current line is too complicated to graph
(I think it only happens in an inequality problem.)
*/
{
const char *errmsg;
term t,a,b,k,temp;
double z;
int i,err,problemtype,whichline,currenttopic;
int noldvars;
int newvarflag=0;
problemtype = p->problemtype;
currenttopic = p ->currenttopic;
SETFUNCTOR(a,ILLEGAL,0);
SETFUNCTOR(b,ILLEGAL,0); /* so we can tell if get_function sets them */
noldvars = get_nvariables();
int firstflag = (p->graphs[0] == NULL) ? 1 : 0; // set if this is the first press of GraphButton on this document
t = get_function(p,&problemtype,&a,&b,&whichline,&errmsg);
p->function = t;
if(noldvars < get_nvariables())
newvarflag = 1;
if(FUNCTOR(t) == ILLEGAL)
{ return 168; // MathXpert cannot make that graph
}
err = graph_problemtype(problemtype,currenttopic, t,p);
/* this fills in p->graphproblemtype, p->graphcurrenttopic,
p->mainchoice, and p->ngraphs. */
enhance_problem(&p->function, p->graphtopic, p->graphproblemtype);
/* change f to diff(f,x) etc. and set varinfo correctly */
if(err)
{ /* the error has already been reported to the user in graph_problemtype */
if(newvarflag)
set_nvariables(noldvars);
return 1;
}
t = p->function; // enhance_problem may have changed it
int saveEigenindex;
if(p->problemtype == DIFFERENTIATE_FROM_DEFN)
{ saveEigenindex = get_eigenindex();
set_eigenvariable(0);
}
setupdata(&p->ngraphs,&p->mainchoice,t,p->graphs);
if(p->problemtype == DIFFERENTIATE_FROM_DEFN)
{
set_eigenvariable(saveEigenindex);
}
#if 0
if(problemtype == SIGMA_NOTATION)
{ /* then we don't take the sigma term as the function to graph
but rather the summand. Since we are making a Riemann-sums
graph, we need to put into p->function an OR term of the form
or(summand,left,right,nintervals), which will get 'left' sums
used by default */
term nintervals;
term u = make_term(OR,4);
term v = make_term('+',3);
ARGREP(u,0,ARG(0,t));
ARGREP(u,1,ARG(2,t)); /* left */
ARGREP(u,2,sum(ARG(3,t),one)); /* right */
ARGREP(v,0,ARG(3,t));
ARGREP(v,1,tnegate(ARG(2,t)));
ARGREP(v,2,one);
polyval(v,&nintervals);
/* there are hi-lo + 1 intervals */
ARGREP(u,3,nintervals);
permcopy(u,&p->function);
}
else
#endif
permcopy(t,&p->function);
erasecolors(&p->function);
/* don't just call setup_varlist as it sometimes gets the variables in a
different order. We want them in the same order so as to get the
eigenvariable right. Copy the varlist and parameters of the old document */
//if(newvarflag) // removed 6.22.24
// p->DocVarData.nvariables = noldvars;
/* not set_nvariables(noldvars); since we've already called init_varDLL */
activate(p);
SetActiveDoc(p);
set_valuepointers(&p->function);
if(whichline)
swapvars(0,p->DocVarData.eigenvariable);
/* otherwise enhance_variable screws things up when it adds a new variable
in the varlist[1] position. */
if(p->problemtype == LINEAR_EQUATIONS)
{ /* Get the eigenvariable right. By now p->function contains
solved equations, so be sure the eigenvariable is the one
that occurs on the right, not the left. */
term vlist;
term *varlist = get_varlist();
int nvariables = get_nvariables();
is_linear_system(p->function,whichline,&vlist);
for(i=0;i<nvariables;i++)
{ if(equals(varlist[i],ARG(0,vlist)))
{ swapvars(i,0);
break;
}
}
}
p->DocVarData.eigenvariable = 0;
/* Infinite series cannot be graphed. They get replaced by a finite series
whose upper limit is a parameter. */
if(contains_series(p->function) && firstflag)
{ k = getnewintvar1(t,"knmjpqrsKNMJPQRS");
if(FUNCTOR(k) == ILLEGAL)
{ errbuf(0, english(1448));
/* Too many subscripted variables, can't make more. */
return 1;
}
subst(k,infinity,p->function,&temp);
p->function = temp;
/* the new variable k will automatically be made into a parameter
when init_params is called from fill_graph_structures, which
is called at the end of setupdata. */
}
if(problemtype == INTEGRATION && p->ngraphs==2)
p->graphs[0]->graphcolor = (~p->graphs[0]->background) & 0x00ffffffL;
/* this is the color used to outline the rectangles
in Simpson's rule. Setting it to the negation of the background
means they won't show, you'll just see the area, because the
rectangles are filled in with the negation of the background. */
p->initialized = 1; // superfluous, it was already done in init_doc_data
init_graph_ranges(p);
if(problemtype == LIMITS || problemtype == LHOPITAL)
{ /* adjust the x-range so the limit point is visible */
if(equals(a,infinity))
{ p->graphs[0]->xmin = -8.0;
p->graphs[0]->xmax = 100.0;
}
else if(equals(a,minusinfinity))
{ p->graphs[0]->xmin = -100.0;
p->graphs[0]->xmax = 8.0;
}
else
{ deval(a,&z);
if(z != BADVAL)
{ p->graphs[0]->xmin = z-2.0;
p->graphs[0]->xmax = z+2.0;
}
else
assert(0);
}
}
return 0;
}
/*______________________________________________________________*/
static int graph_problemtype(int problemtype, int topic, term t, PDOCDATA p)
/* p is a pointer to a new document data structure, intended
for a graph of t. Fill in
p->graphproblemtype, p->mainchoice, p->graphtopic, and p->ngraphs
as appropriate for graphing term t from a symbol document whose problemtype and topic are
given by the first two parameters. Return 0 for success, 1 for
failure. Report the reason to the user if the return value is 1;
this means the Graph Button isn't going to do anything.
*/
{ int nvariables,err;
unsigned f = FUNCTOR(t);
term vlist;
switch(problemtype)
/* set the problemtype, topic, and mainchoice for the graph */
{ case LINEAR_EQUATION: /* fall through */
case SOLVE_EQUATION:
if(f == AND && ! interval_as_and(t))
/* one equation is converted to an AND or a
mathematical term before getting here */
{ assert(!ZERO(ARG(0,t)) && !ZERO(ARG(1,t)));
p->graphproblemtype = COMPARE_GRAPHS;
p->graphtopic = _compare_same;
p->mainchoice = COMPARE_SAME;
p->ngraphs = 2;
return 0;
}
if(f == OR)
{ p->graphproblemtype = COMPARE_GRAPHS;
p->graphtopic = _compare_different;
p->mainchoice = COMPARE_DIFFERENT;
if(ARITY(t) > 6)
{ // const char *errmsg = english(760);
/* Too many equations */
// interface prevents entering more than 6 anyway,
// so no errmsg is needed.
return 1;
}
p->ngraphs = ARITY(t);
return 0;
}
if(f == '<' || f == '>' || f == LE || f == GE || interval_as_and(t))
/* inequalities can arise even if we started with an equation,
for example abs(x-1) + abs(x) = 1 */
{ p->graphproblemtype = GRAPH_INEQUALITY;
/* is this going to be graphed as a relation or
as one or two functions? */
if(graph_ineq(t))
{ p->graphtopic = _graph_ineq;
p->mainchoice = MC_INEQ;
p->ngraphs = f == AND ? 2 : 1;
}
else
{ p->graphtopic = _graph_set;
p->mainchoice = MC_SET;
p->ngraphs = 1;
}
return 0;
}
if(topic == _complex_quadratics ||
topic == _complex_cubics ||
topic == _complex_roots
)
{ p->graphproblemtype = POLYROOTS;
p->graphtopic = _polyroots;
p->mainchoice = POLYROOT;
}
else
{ p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
}
p->ngraphs = 1;
return 0;
case TRIG_IDENTITY:
if(f == AND) /* one equation is converted to an AND or a
mathematical term before getting here */
{ assert(!ZERO(ARG(0,t)) && !ZERO(ARG(1,t)));
p->graphproblemtype = COMPARE_GRAPHS;
p->graphtopic = _compare_different;
p->mainchoice = COMPARE_DIFFERENT;
/* For identities they should be on different axes,
otherwise they will be superimposed if the identity
is valid */
p->ngraphs = 2;
return 0;
}
p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
case LINEAR_EQUATIONS:
assert(FUNCTOR(t) == AND);
err = is_linear_system(t,0,&vlist);
if(err)
assert(0);
nvariables = ARITY(vlist);
RELEASE(vlist);
if(nvariables > 2)
{ // const char *errmsg = english(764);
/* MathXpert can't graph linear equations in more than two variables.*/
// But make sure instead that no graph button appears
// when nvariables > 2
return 1;
}
p->graphproblemtype = COMPARE_GRAPHS;
p->graphtopic = _compare_same;
p->mainchoice = COMPARE_SAME;
p->ngraphs = ARITY(t);
return 0;
case INEQUALITIES:
case ABSOLUTE_VALUE:
p->graphproblemtype = GRAPH_INEQUALITY;
/* is this going to be graphed as a relation or
as one or two functions? */
if(graph_ineq(t))
{ p->graphtopic = _graph_ineq;
p->mainchoice = MC_INEQ;
p->ngraphs = f == AND ? 2 : 1;
}
else
{ p->graphtopic = _graph_set;
p->mainchoice = MC_SET;
p->ngraphs = 1;
}
return 0;
case COMPLEX_NUMBERS:
{ // const char *errmsg = english(766);
/* Graphing not available for complex numbers */
// FINISH THIS
return 1;
}
case LIMITS: /* fall through */
case LHOPITAL:
p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
case DIFFERENTIATE_FROM_DEFN:
p->graphproblemtype = COMPARE_GRAPHS;
p->ngraphs = 3;
p->mainchoice = COMPARE_SAME;
p->graphtopic = _compare_same;
return 0;
case DIFFERENTIATE:
if(topic == _logarithmic_differentiation)
/* On this topic, the derivatives are so complicated
that MathXpert often either can't calculate their
singularities, or takes too long calculating their
singularities. So we graph only the function,
not the derivative. */
{ p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
}
p->graphproblemtype = COMPARE_GRAPHS;
// have we already taken the second or more derivatives?
int currentline = p->DocVarData.currentline;
term lastline = p->DocProverData.history[currentline];
if(
FUNCTOR(lastline)== '=' &&
FUNCTOR(ARG(0,lastline)) == DIFF &&
ARITY(ARG(0,lastline)) >= 2
)
{ // yes, at least two derivatives
p->graphtopic = _comparetwoderivs;
p->mainchoice = COMPAREDERIVS;
p->ngraphs = 3;
}
else // one derivative only
{ p->graphtopic = _comparefandfprime;
p->mainchoice = COMPAREDERIV;
p->ngraphs = 2;
}
return 0;
case IMPLICIT_DIFF:
p->graphproblemtype = GRAPH_RELATION;
p->graphtopic = _graph_relation;
p->mainchoice = RELATION;
p->ngraphs = 1;
return 0;
case IMPROPER_INTEGRATION: // fall through
case INTEGRATION:
if(FUNCTOR(t) == OR)
{ /* a simpson's-rule graph for a definite integral */
p->ngraphs = 2;
p->graphproblemtype = SIMPSONS_RULE;
p->graphtopic = _simpsons_rule;
p->mainchoice = SIMPSONSRULE;
return 0;
}
p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
case MINMAX:
p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
#if 0
case SIGMA_NOTATION:
/* Make a step-function graph showing the sum, by
setting the graph color equal to the background color */
p->graphproblemtype = RIEMANN_SUMS;
p->graphtopic = _riemann_sums;
p->mainchoice = RIEMANNSUMS;
p->ngraphs = 2;
return 0;
#endif
case ADDSERIES: /* fall through */
case TESTCONVERGENCE:
/* The original problem is a series. If there is a
variable other than the summation variable we'll make
a graph of the partial sums with a parameter as the upper
limit. If the series has been added up (no SUM in the
current line) we'll make a COMPARE_SAME graph showing the
current line versus the partial sums of the original series.
If there is no variable other than the summation
variable, we will make a Riemann-sums style graph of the
summand, with a parameter for the number of terms. This
parameter must also affect the x-range and y-range!
*/
// if(topic == _integral_test || topic == _sum_series)
// Now I do it for all series
{ p->graphproblemtype = RIEMANN_SUMS;
p->graphtopic = _riemann_sums;
p->mainchoice = RIEMANNSUMS;
p->ngraphs = 2;
return 0;
}
case POWERSERIES:
if(FUNCTOR(t) == AND)
{ /* t is the AND of the current line and original, one
of which is or contains a series and the other does not.
It will be ARG(1,t) that contains a series. */
term *atomlist;
int nvars = variablesin(ARG(0,t),&atomlist);
free2(atomlist);
if(nvars > 0)
{ p->graphproblemtype = COMPARE_GRAPHS;
p->graphtopic = _compare_same;
p->mainchoice = COMPARE_SAME;
p->ngraphs = 2;
return 0;
}
return 1;
}
// before a series has been produced, we come here:
p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
case RELATED_RATES:
// actually there is no graph button on RELATED_RATES
// so this code is never used
p->graphproblemtype = GRAPH_RELATION;
p->graphtopic = _graph_relation;
p->mainchoice = RELATION;
p->ngraphs = 1;
return 0;
case SYMBOLIC_ODE:
/* No message box needed as these problemtypes are disabled now
anyway. FINISH THIS */
return 1;
default: /* SIMPLIFY, COMMON_DENOMINATOR, ROOTS, etc. */
/* We will make a 2d graph regardless of the number of variables
present. */
p->graphproblemtype = ORDINARY_GRAPH;
p->graphtopic = _ordinary_graph;
p->mainchoice = ORDINARY;
p->ngraphs = 1;
return 0;
}
}
/*____________________________________________________________________*/
static int show_area(term lo, term hi)
/* lo and hi are the limits of integration of a definite integral.
Return nonzero, if we should make a simpson's-rule graph, 0 if not.
Originally this required lo and hi to be seminumerical and not
infinite. As of 12.20.24 the upper limit can be a variable,
which will become a parameter. The return value is 1 or -1;
if it is -1 it means that the upper limit is less than the lower
limit, so for the graph they should be switched. */
{ double z,w;
if(ISINFINITE(lo) || ISINFINITE(hi))
return 0;
if(seminumerical(lo) && ISATOM(hi))
{ // in that case we will make hi into a parameter
return 1;
}
if(!seminumerical(lo) || !seminumerical(hi))
return 0;
deval(lo,&z);
if(z == BADVAL)
return 0;
deval(hi,&w);
if(w == BADVAL)
return 0;
if(fabs(z-w) < VERYSMALL)
return 0;
return z < w ? 1 : -1;
}
/*____________________________________________________________________*/
static term soup_up(term t)
/* t is an inequality or an OR of inequalities. It can also be an
interval_as_and. Return a term to be
sent to the grapher when the graph button is pushed to graph the line
with t on it. Specifically,
In the case of an inequality (where say x is the eigenvariable):
If the inequality has the form f(x) < g(x), return f(x) < y < g(x),
where y is a new variable.
Example: abs(x-1) < 1, return ans(x-1) < y < 1.
x < 1, return x < y < 1
If there are two or more variables already, f(x,y) < g(x,y), just
return the unchanged inequality.
In the case of an OR of inequalities, just return the OR as a
relation to be graphed.
In the case of an interval_as_and, just return the input term t.
On illegal input returns an ILLEGAL atom.
*/
{ int nvars;
term *atomlist;
term x,y;
term left,right,ans;
unsigned short f = FUNCTOR(t);
if(interval_as_and(t))
return t;
if(f == OR)
return t;
if(get_nvariables() == 0)
return t;
x = get_eigenvariable();
nvars = variablesin(t,&atomlist);
if(nvars > 1)
{ free2(atomlist);
return t;
}
if(nvars == 1 && !equals(x,atomlist[0]))
x = atomlist[0];
free2(atomlist);
if(!INEQUALITY(f))
{ SETFUNCTOR(ans,ILLEGAL,0);
return ans;
/* You can get here if e.g. "use assumptions" has produced
a Boolean combination of inequalities. */
}
y = getnewvar(t,"yzwuvpq");
if(f == '<' || f == LE || f == '=')
{ left = ARG(0,t);
right = ARG(1,t);
}
else
{ left = ARG(1,t);
right = ARG(0,t);
}
if(f == '<' || f == '>')
return and(lessthan(left,y),lessthan(y,right));
else
return and(le(left,y),le(y,right));
}
/*____________________________________________________________________*/
static term get_function(PDOCDATA pSymbolData, int *problemtype, term *a, term *b, int *whichline, const char **errmsg)
/* return the term to be graphed from the given symbol document,
assuming the document has the specified problemtype. If the
problemtype is definite integration, return the limits of integration
in *a and *b; if it is LIMITS or LHOPITAL, return in *a the point that
the limit variable tends to. Otherwise *a and *b can be garbage.
Do not return a limit, integral, or derivative, or an '='.
In *whichline, return 0 if we're graphing the original problem, 1 if
graphing the current line. If *whichline is returned as 2, it means
that the return value is: and(original problem, current line).
In the case of one or several equations:
If the line of the solution to be graphed is a single
equation, return the nonzero side if one side is zero, or an AND of
the two sides. If the line of the solution to be graphed contains more
than one non-solved equation, return an OR of that dimension whose args
are the differences of the sides.
In the case of an inequality (where say x is the eigenvariable):
If the inequality has the form f(x) < g(x), return f(x) < y < g(x),
where y is a new variable.
Example: abs(x-1) < 1, return ans(x-1) < y < 1.
x < 1, return x < y < 1
If there are two or more variables already, f(x,y) < g(x,y), just
return the unchanged inequality.
In the case of a definite integral whose limits will both deval,
return a term as needed for a SIMPSONSRULE graph with 128 intervals.
If only one limit will deval (or neither) just return the integrand.
[well, as of 12.10.24, the upper limit can now be an atom]
In case of an inequality problemtype with a current line too complicated
to graph, an ILLEGAL term will be returned.
*/
{ int currentline = get_activeline();
unsigned short f;
int switchlimits;
term t,q,u,ans;
term lo,hi,vlist;
term *varlist;
int err,i,nvariables;
unsigned short n,k;
double z;
term x = get_eigenvariable();
term y;
switch(*problemtype)
{ case MINMAX:
t = pSymbolData->DocProverData.history[0];
if(FUNCTOR(t) == '=')
t = ARG(1,t);
*whichline = 0;
return t;
case LINEAR_EQUATION: /* fall through */
case SOLVE_EQUATION:
if(pSymbolData->currenttopic == _complex_roots ||
pSymbolData->currenttopic == _complex_quadratics ||
pSymbolData->currenttopic == _complex_cubics
)
{ t = pSymbolData->DocProverData.history[0];
if(FUNCTOR(t) != '=')
assert(0);
return sum(ARG(0,t), strongnegate(ARG(1,t)));
}
if(currentline == 0)
{ t = pSymbolData->DocProverData.history[0];
if(FUNCTOR(t) != '=')
assert(0);
return strip_eq(t);
}
/* Now the question is, do we use the current line or the
original equation? If the current line is already solved
there won't be any question about it, we'll use the
original equation. Let's see if that's the case. */
t = pSymbolData->DocProverData.history[currentline];
f = FUNCTOR(t);
if(f == MULTIPLICITY)
{ t = ARG(0,t);
f = FUNCTOR(t);
}
if(f == '=' && equals(ARG(0,t), x) && !contains(ARG(1,t),FUNCTOR(x)))
{ *whichline = 0;
return strip_eq(pSymbolData->DocProverData.history[0]);
}
/* use the original line, the current line is solved. */
if(equals(t,falseterm) || equals(t,trueterm))
{ *whichline = 0;
return strip_eq(pSymbolData->DocProverData.history[0]);
}
/* use the original line, the current line is solved. */
else if(f == '=')
/* The current line is not solved yet, ask the user. */
{ // *whichline = get_whichline(pSymbolData->hwnd,1);
// but for now, just use the current line
*whichline = pSymbolData->DocVarData.currentline;
return strip_eq(pSymbolData->DocProverData.history[*whichline ? currentline : 0]);
}
else if(INEQUALITY(f))
/* inequalities can arise while solving equations,
e.g. abs(x-1) + abs(x) = 1
*/
{ *problemtype = INEQUALITIES;
*whichline = 1;
ans = soup_up(t);
if(FUNCTOR(ans) == ILLEGAL)
{ *errmsg = english(1971);
/* This inequality is too complicated for MathXpert to graph. */
return ans;
}
}
else if(f == OR)
{ n = ARITY(t);
/* collect all the non-solved equations into a new term q,
stripping off MULTIPLICITY as you go */
q = make_term(OR,n);
k=0;
for(i=0;i<n;i++)
{ u = ARG(i,t);
if(FUNCTOR(u)== MULTIPLICITY)
u = ARG(0,u);
if(!equals(ARG(0,u),x) || contains(ARG(1,u),FUNCTOR(x)))
{ ARGREP(q,k,u);
++k;
}
}
if(k==0)
{ RELEASE(q); /* all current equations are solved */
*whichline = 0;
return strip_eq(pSymbolData->DocProverData.history[0]);
}
else if (k==1) /* just one current equation isn't solved */
{ t = ARG(0,q);
RELEASE(q);
// *whichline = get_whichline(pSymbolData->hwnd,1);
// if(*whichline == 0)
// return strip_eq(pSymbolData->DocProverData.history[0]);
// else
return strip_eq(t);
}
else /* at least two current equations are not solved */
SETFUNCTOR(q,OR,k);
/* Now ask the user */
*whichline = 0; // FINISH THIS //get_whichline(pSymbolData->hwnd,ARITY(t));
if(*whichline == 0)
return strip_eq(pSymbolData->DocProverData.history[0]);
/* MathXpert can't make, in one document, several graphs each
containing two graph lines. Therefore, if both sides are
nonzero, they get subtracted so each equation gets a
single graph line */
t = make_term(OR,k); /* eventually another function changes
this OR to an AND, but not till it
serves as a signal to use different
axes instead of the same axes */
for(i=0;i<k;i++)
{ u = ARG(i,q);
if(FUNCTOR(u) == MULTIPLICITY)
u = ARG(0,u);
assert(FUNCTOR(u) == '=');
if(ZERO(ARG(1,u)))
ARGREP(t,i,ARG(0,u));
else if(ZERO(ARG(0,u)))
ARGREP(t,i,ARG(1,u));
else
ARGREP(t,i,sum(ARG(0,u),strongnegate(ARG(1,u))));
}
*whichline = 1;
return t;
}
assert(0);
case LINEAR_EQUATIONS:
/* Do we use the current line or the
original equation? If the current line is already solved
there won't be any question about it, we'll use the
original equation. Let's see if that's the case. */
t = pSymbolData->DocProverData.history[currentline];
if(FUNCTOR(t) == AND)
{ err = is_linear_system(t,currentline,&vlist);
if(err)
assert(0);
x = ARG(0,vlist);
y = ARG(1,vlist);
n = ARITY(t);
/* collect all the non-solved equations into a new term q */
q = make_term(AND,n);
k=0;
for(i=0;i<n;i++)
{ u = ARG(i,t);
if(
!(
(equals(ARG(0,u),x) || equals(ARG(0,u),y)) &&
!contains(ARG(1,u),FUNCTOR(x)) &&
!contains(ARG(1,u),FUNCTOR(y))
)
)
{ ARGREP(q,k,u);
++k;
}
}
if(k==0)
{ RELEASE(q); /* all current equations are solved */
*whichline = 0;
return strip_eq2(pSymbolData->DocProverData.history[0],y);
}
else if (k==1) /* just one current equation isn't solved */
{ t = ARG(0,q);
RELEASE(q);
if(currentline ==0)
*whichline = 0;
else
// *whichline = get_whichline(pSymbolData->hwnd,1);
// if(*whichline == 0)
// return strip_eq2(pSymbolData->DocProverData.history[0],y);
// else
return strip_eq2(t,y);
}
else /* at least two current equations are not solved */
SETFUNCTOR(q,AND,k);
if (currentline==0)
*whichline = 0;
else
{ /* Here Windows MathXpert asked the user,
but WebMathXpert just uses the unsolved equations q. */
return strip_eq2(q,y);
}
}
else /* use the original problem */
{ t = pSymbolData->DocProverData.history[0];
*whichline = 0;
if(FUNCTOR(t) != AND)
assert(0);
err = is_linear_system(t,0,&vlist);
if(err)
assert(0);
if(FUNCTOR(vlist) == AND && ARITY(vlist) > 1)
{ x = ARG(0,vlist);
y = ARG(1,vlist);
return strip_eq2(t,y);
}
else
return zero; /* assert(0) */
}
case ABSOLUTE_VALUE:
case INEQUALITIES:
u = pSymbolData->DocProverData.history[0];
if(currentline == 0)
{ *whichline = 0;
if(FUNCTOR(u) == '=')
{ /* equations can be entered under ABSOLUTE_VALUE */
{ *problemtype = SOLVE_EQUATION;
return strip_eq(u);
}
}
ans = soup_up(u);
if(FUNCTOR(ans) == ILLEGAL)
*errmsg = english(38); /* Illegal symbol */
/* We get here if somehow a conjunction of inequalities arises */
return ans;
}
/* Now the question is, do we use the current line or the
original inequality? If the current line is already solved
there won't be any question about it, we'll use the
original equation. Let's see if that's the case.
*/
t = pSymbolData->DocProverData.history[currentline];
x = get_eigenvariable();
if(solved(t,x))
/* use the original line, the current line is solved. */
{ *whichline = 0;
if(FUNCTOR(u) == '=')
{ *problemtype = SOLVE_EQUATION;
return strip_eq(u);
}
return soup_up(u);
}
if(equals(t,falseterm) || equals(t,trueterm))
{ *whichline = 0;
return u;
}
/* The current line is not solved yet, ask the user. */
*whichline = currentline; // get_whichline(pSymbolData->hwnd,1);
t = pSymbolData->DocProverData.history[whichline ? currentline : 0];
if(FUNCTOR(t) == '=')
{ *problemtype = SOLVE_EQUATION;
return strip_eq(t);
}
return soup_up(t);
case TRIG_IDENTITY:
if(currentline == 0)
return strip_eq(pSymbolData->DocProverData.history[0]);
*whichline = 1; // get_whichline(pSymbolData->hwnd,2);
return strip_eq(pSymbolData->DocProverData.history[whichline ? currentline : 0]);
case LIMITS: /* fall through */
case LHOPITAL:
t = pSymbolData->DocProverData.history[0];
if(FUNCTOR(t) != LIMIT)
{ *errmsg = english(763); /* Limit expected */
SETFUNCTOR(q,ILLEGAL,0);
*whichline = 0;
return q;
}
lo = ARG(1,ARG(0,t));
if(!ISINFINITE(lo))
{ deval(lo,&z);
if(z==BADVAL)
{ *a = zero; /* we need to set it to SOMETHING or the graph button
will cause a crash */
*whichline = 0;
return LIMITAND(t);
}
}
*a = lo;
*whichline = 0;
return LIMITAND(t);
case DIFFERENTIATE:
t =pSymbolData->DocProverData.history[0];
*whichline = 0;
f = FUNCTOR(t);
if(f == '=' && /* dy/dx = d/dx u */
FUNCTOR(ARG(0,t)) == DIFF &&
ISATOM(ARG(0,ARG(0,t))) &&
FUNCTOR(ARG(1,t)) == DIFF
)
return ARG(0,ARG(1,t));
if(f == '=' &&
FUNCTOR(ARG(0,t)) == PR &&
(FUNCTOR(ARG(1,t)) == PR || FUNCTOR(ARG(1,t)) == DIFF)
)
return ARG(0,ARG(1,t));
if(f == DIFF || f == PR)
{ ans = ARG(0,t);
if(FUNCTOR(ans) == INTEGRAL)
ans = ARG(0,ans);
/* graph the function inside the integral */
return ans;
}
if(!contains(t,DIFF))
return t; // the first line can be just y = f(x)
break; /* assert(0); */
case DIFFERENTIATE_FROM_DEFN:
// we'll return an AND of arity 3, containing
// y = f(x), a linear function through (a,f(a)) and (a+h, f(a+h)), and
// a linear function through (a,f(a)) with slope f'(a)
{ int temp = DIFFERENTIATE; // need the braces to be able to declare here.
int temp2 = 0;
term t = get_function(pSymbolData,&temp,a,b,&temp2,errmsg);
term x = pSymbolData->DocVarData.varlist[0];
if(!contains(t, FUNCTOR(x)))
{ // this happens if we've already introduced a limit in
// the symbolic calculation, so now the limit variable is the
// eigenvariable. The original eigenvariable should now
// be the first variable that is not a parameter
term *varlist = pSymbolData->DocVarData.varlist;
int nvars = pSymbolData->DocVarData.nvariables;
int i;
for(i = 0;i<nvars; i++)
{ if (! isparameter(varlist[i]))
{ x = varlist[i];
break;
}
if (i== nvars)
assert(0); // you must find x
}
}
term deriv = derivative(t,x);
// Create h and A ('a' is already taken in this C code)
// But if they press GraphButton a second time from the
// symbolic view, we don't want to create these variables again.
int nparams = pSymbolData->DocVarData.nparameters;
parameter *params = pSymbolData->DocVarData.parameters;
// do there already exists parameters thata don't occur in
// the original problem?
term *varlist = pSymbolData->DocVarData.varlist;
term h,A;
int nvars = pSymbolData->DocVarData.nvariables;
for(i=0;i<nvars;i++)
{ if(!contains(t,FUNCTOR(varlist[i])) && isparameter(varlist[i]) >=0)
break;
}
if(i==nvars)
{ // this is the first click of the GraphButton
// so create the necessary new variables h and a
h = getnewvar(t,"huvstw");
A = getnewvar(sum(t,h),"abcpq");
int nvars = get_nvariables();
initialize_parameter(nvars-1,0,1.0);
initialize_parameter(nvars-2,0,1.0);
// we need to set a small increment on h
params[nparams-1].increment = 0.1; // this is h;
params[nparams-2].increment = 1.0; // this is a;
}
else
{ // not the first click, so h and A should already exist.
// They should be the last two parameters
assert(nparams >= 2);
term *varlist = pSymbolData->DocVarData.varlist;
h = varlist[params[nparams-1].index];
A = varlist[params[nparams-2].index];
}
term fofa, slope;
subst(A,x,t,&fofa);
subst(A,x,deriv,&slope);
term tangent = sum(fofa, product(slope,sum(x,tnegate(A))));
// Now for the third function
term a2 = sum(A,h);
term y2;
subst(a2,x,t,&y2);
term deltay = sum(y2,tnegate(fofa));
term slope2 = make_fraction(deltay,h);
term secant = sum(fofa, product(slope2, sum(x,tnegate(A))));
*whichline = 0;
return and3(t,secant,tangent);
}
case RELATED_RATES:
// this code is never hit because needsGraphButton says not to put a graph button
// in RELATED_RATES documents. But if you want to do so in the future, you must
// worry about getting the x and y variables right on the graph, as t is the eigenvariable.
*whichline = 0;
t = pSymbolData->DocProverData.history[0];
/* we're going to make a relation graph */
if(FUNCTOR(t) == AND)
t = ARG(0,t);
return t;
case IMPLICIT_DIFF:
*whichline = 0;
t = pSymbolData->DocProverData.history[0];
/* we're going to make a relation graph */
if(FUNCTOR(t) != '=')
assert(0);
if(FUNCTOR(ARG(0,t)) == DIFF &&
FUNCTOR(ARG(1,t)) != DIFF
)
return equation(ARG(0,ARG(0,t)),ARG(0,ARG(1,t)));
return t;
case IMPROPER_INTEGRATION:
*whichline = 0;
t =pSymbolData->DocProverData.history[0];
assert(FUNCTOR(t) == INTEGRAL && ARITY(t) == 4);
lo = ARG(2,t);
hi = ARG(3,t);
ans = make_term(OR,4);
ARGREP(ans,0,ARG(0,t));
ARGREP(ans,1,lo);
ARGREP(ans,2,hi);
ARGREP(ans,3,make_int(256)); /* number of intervals to use */
*a = lo;
*b = hi;
return ans;
case INTEGRATION:
*whichline = 0;
t =pSymbolData->DocProverData.history[0];
assert(FUNCTOR(t) == INTEGRAL);
n = ARITY(t);
if(n == 4 && !IMPROPER(t))
{ lo = ARG(2,t);
hi = ARG(3,t);
switchlimits = show_area(lo,hi);
if(switchlimits == -1)
{ term swap = lo;
lo = hi;
hi = swap;
}
if(switchlimits)
{ /* trapezoid-rule graph expects or(function,lo,hi,nintervals) */
if(!seminumerical(hi))
{ double upper_limit;
deval(sum(lo,five),&upper_limit); // upper_limit = lo + 5
term *atomlist; // variablesin will make space with callocate
if(ISATOM(hi)) // often the case
initialize_parameter(get_index(hi), get_currentline(),upper_limit);
else // for example, integral(sin x,x,0,a^2)
{ int nvars = variablesin(hi, &atomlist);
// how should we initialized the parameters in atomlist?
for(int k = 0; k< nvars; k++)
{ // Try initializing them all to 1
int j = get_index(atomlist[k]);
initialize_parameter(j,get_currentline(),1.0);
}
// check the resulting value
double z;
deval(sum(hi,tnegate(lo)),&z);
if(z < 0.0001)
{
for(int k = 0; k< nvars; k++)
{ // Try initializing them all to 1
int j = get_index(atomlist[k]);
initialize_parameter(j,get_currentline(),-1.0);
}
}
free2(atomlist);
}
}
ans = make_term(OR,4);
ARGREP(ans,0,ARG(0,t));
ARGREP(ans,1,lo);
ARGREP(ans,2,hi);
ARGREP(ans,3,make_int(128)); /* use 128 intervals */
*a = lo;
*b = hi;
return ans;
}
}
return ARG(0,t);
case ADDSERIES: /* fall through */
case TESTCONVERGENCE:
*whichline = 0;
// set up a Riemann-sums graph in graphs[0] and
// an ordinary graph of the integrand in graphs[1]
double z;
term t = pSymbolData->DocProverData.history[0]; // indexed sum
term u = ARG(0,t); // the summand
term n = ARG(1,t); // the index variable,
/* If this is not the first press of the GraphButton, then
we don't need to create a new parameter, indeed we should not!
So before doing so, look to see if we have one already.
*/
term m;
m = available_int_parameter(pSymbolData,t);
if(equals(m,zero))
{ // there wasn't one already, so make one
m = getnewintvar(t,"mkjMKJ"); // parameter to be, the number of terms
}
if(equals(ARG(3,t),infinity) && !equals(ARG(2,t),minusinfinity))
{ // color the m unit-width rectangles starting at lo blue
// by creating a new indexed sum.
term lo = ARG(2,t);
term hi = make_term('+',3);
term x = getnewvar(t,"xtuvzw");
set_eigenvariable(get_index(x));
term ans, integrand;
int nintervals = 6;
subst(x,n,u,&integrand);
SETVALUE(m,(double) nintervals);
ARGREP(hi,0,m);
ARGREP(hi,1,lo);
ARGREP(hi,2,minusone);
deval(lo,&z);
initialize_parameter(get_index(m),get_currentline(), nintervals);
// harmless if m already was initialized, will just do nothing in that case
ans = make_term(OR,5);
term parametrized_sum = indexedsum(u,n,lo,hi);
ARGREP(ans,0,parametrized_sum);
// left and right go in as args 1 and 2 of ans; we subtract 1 from lo and hi,
// corresponding to the use of 'right' Riemann sums
ARGREP(ans,1,lo); // sum(lo,minusone));
ARGREP(ans,2,hi); // sum(hi,minusone));
ARGREP(ans,3,m); // number of intervals is given by the value
ARGREP(ans,4,zero); /* 0 = left, 1 = centered, 2 = right */
return ans;
}
return pSymbolData->DocProverData.history[0];
case POWERSERIES:
*whichline = 2; /* return and(original, current) */
t = pSymbolData->DocProverData.history[0];
q = pSymbolData->DocProverData.history[currentline];
if(!contains(t,SUM) && contains(q,SUM) && !contains(q,INTEGRAL) && !contains(q,DIFF))
{ term tamedq;
// replace infinity in q by a new variable and make it a parameter
term m = getnewboundintvar(t, "mkjpqrs");
subst(m,infinity,q,&tamedq);
initialize_parameter(get_index(m), currentline, 3);
return and(t,tamedq);
}
if(!contains(t,SUM))
return t;
if(contains(t,SUM))
assert(0); // you can't enter a series in POWERSERIES,
// you're supposed to enter a function to expand in a series.
}
t = pSymbolData->DocProverData.history[currentline];
*whichline = 1;
/* for all other problem types use the current line */
if(FUNCTOR(t) == DIFF && FUNCTOR(ARG(0,t)) == INTEGRAL)
/* this happens under the topic _fundamental_theorem,
which has problemtype SIMPLIFY rather than DIFFERENTIATE */
{ x = ARG(1,ARG(0,t));
varlist = get_varlist();
nvariables = get_nvariables();
if(!ISATOM(x))
assert(0);
/* the variable of integration should be the independent variable
of the graph */
for(i=0;i<nvariables;i++)
{ if(equals(varlist[i],x))
{ if(i > 0)
swapvars(i,0);
}
set_eigenvariable(0);
}
return ARG(0,ARG(0,t)); /* graph the integrand */
}
if(FUNCTOR(t) == INTEGRAL && ARITY(t) == 2 && FUNCTOR(ARG(0,t)) == DIFF &&
equals(ARG(1,ARG(0,t)),ARG(1,t))
)
return ARG(0,ARG(0,t)); /* integral( du/dx,x), graph u */
if(contains(t,DIFF) || contains(t,INTEGRAL) || contains(t, LIMIT))
{ *errmsg = (char *) english(768); /* Cannot graph this expression */
SETFUNCTOR(q,ILLEGAL,0);
return q;
}
return t;
}
/*____________________________________________________________________*/
static term strip_eq(term t)
/* t is an equation, or a MULTIPLICITY of an equation;
return the non-zero side,
or the AND of the two sides if both are nonzero,
or zero if both sides are zero.
*/
{ if(FUNCTOR(t) == MULTIPLICITY)
t = ARG(0,t);
if(ZERO(ARG(1,t)))
return ARG(0,t);
if(ZERO(ARG(0,t)))
return ARG(1,t);
return and(ARG(0,t),ARG(1,t));
}
/*____________________________________________________________________*/
static term strip_eq2(term t, term x)
/* If t is an equation (it must be linear), solve it for x
and return the solved equation.
If t is an AND or an OR, return the AND or OR of the
result of applying strip_eq2 to the args.
*/
{ unsigned short f = FUNCTOR(t);
term ans;
int i,err;
unsigned short n;
if(f == '=')
{ err = solve_linear_ineq_for(t,x,&ans);
if(err)
{ err = ssolve(t,x,&ans); /* bring out the big guns */
/* Maybe solve_linear_ineq could fail if t isn't explicitly
linear but still might pass islinear */
if(err)
return t; /* this can happen for example if memory is low
and polyval therefore refuses to expand a sum. */
}
return ans;
}
assert( f == AND);
n = ARITY(t);
ans = make_term(f,n);
for(i=0;i<n;i++)
ARGREP(ans,i,strip_eq2(ARG(i,t),x));
return ans;
}
/*________________________________________________________________*/
int graph_ineq(term t)
/* t is an inequality or AND term.
Return 1 if t appears suitable for graphing as an inequality of
functions, 0 if it must be graphed as a set or relation.
(This doesn't take the difficulty of calculating singularities into
consideration; later in draw.c the problem may be graphed as a
set anyway if the singularities are too hard to compute.)
*/
{ term left, middle,right;
unsigned f = FUNCTOR(t);
if(interval_as_and(t))
{ left = ARG(0,ARG(0,t));
right = ARG(1,ARG(1,t));
middle = ARG(1,ARG(0,t));
if(!ISATOM(middle))
return 0;
if(equals(middle, get_eigenvariable()))
return 0;
if(contains(left,FUNCTOR(middle)))
return 0;
if(contains(right,FUNCTOR(middle)))
return 0;
return 1;
}
if(f == '<' || f == '>' || f == LE || f == GE)
{ if(ISATOM(ARG(0,t)) &&
!equals(ARG(0,t), get_eigenvariable()) &&
!contains(ARG(1,t),FUNCTOR(ARG(0,t)))
)
return 1;
if(ISATOM(ARG(1,t)) &&
!equals(ARG(1,t), get_eigenvariable()) &&
!contains(ARG(0,t),FUNCTOR(ARG(1,t)))
)
return 1;
}
return 0;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists