/usr/share/yacas/plots.rep/plot2d.ys is in yacas 1.3.3-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | //////////////////////////////////////////////////
/// Plot2D --- adaptive two-dimensional plotting
//////////////////////////////////////////////////
/// definitions of backends
Use("plots.rep/backends-2d.ys");
/*
Plot2D is an interface for various backends (Plot2D'...). It calls
Plot2D'get'data to obtain the list of points and values, and then it calls
Plot2D'<backend> on that data.
Algorithm for Plot2D'get'data:
1) Split the given interval into Div(points+3, 4) subintervals, and split each subinterval into 4 parts.
2) For each of the parts: evaluate function values and call Plot2D'adaptive
3) concatenate resulting lists and return
*/
LocalSymbols(var, func, range, option, options'list, delta, options'hash, c, fc, all'values, dummy)
[
// declaration of Plot2D with variable number of arguments
Function() Plot2D(func);
Function() Plot2D(func, range);
Function() Plot2D(func, range, options, ...);
/// interface routines
1 # Plot2D(_func) <-- ("Plot2D" @ {func, -5:5});
2 # Plot2D(_func, _range) <-- ("Plot2D" @ {func, range, {}});
3 # Plot2D(_func, _range, option_IsFunction) _ (Type(option) = "=" Or Type(option) = "==") <-- ("Plot2D" @ {func, range, {option}});
/// Plot a single function
5 # Plot2D(_func, _range, options'list_IsList)_(Not IsList(func)) <-- ("Plot2D" @ {{func}, range, options'list});
/// Top-level 2D plotting routine:
/// plot several functions sharing the same xrange and other options
4 # Plot2D(func'list_IsList, _range, options'list_IsList) <--
[
Local(var, func, delta, options'hash, c, fc, all'values, dummy);
all'values := {};
options'hash := "ListToHash" @ {options'list};
// this will be a string - name of independent variable
options'hash["xname"] := "";
// this will be a list of strings - printed forms of functions being plotted
options'hash["yname"] := {};
// parse range
If (
Type(range) = "=" Or Type(range) = "==", // variable also specified -- ignore for now, store in options
[
// store alternative variable name
options'hash["xname"] := String(range[1]);
range := range[2];
]
);
If(
Type(range) = ":", // simple range
range := N(Eval({range[1], range[2]}))
);
// set default option values
If(
options'hash["points"] = Empty,
options'hash["points"] := 23
);
If(
options'hash["depth"] = Empty,
options'hash["depth"] := 5
);
If(
options'hash["precision"] = Empty,
options'hash["precision"] := 0.0001
);
If(
options'hash["output"] = Empty Or IsString(options'hash["output"]) And Plot2D'outputs()[options'hash["output"]] = Empty,
options'hash["output"] := Plot2D'outputs()["default"]
);
// a "filename" parameter is required when using data file
If(
options'hash["output"] = "datafile" And options'hash["filename"] = Empty,
options'hash["filename"] := "output.data"
);
// we will divide each subinterval in 4 parts, so divide number of points by 4 now
options'hash["points"] := N(Eval(Div(options'hash["points"]+3, 4)));
// in case it is not a simple number but an unevaluated expression
options'hash["precision"] := N(Eval(options'hash["precision"]));
// store range in options
options'hash["xrange"] := {range[1], range[2]};
// compute the separation between grid points
delta := N(Eval( (range[2] - range[1]) / (options'hash["points"]) ));
// check that the input parameters are valid (all numbers)
Check(IsNumber(range[1]) And IsNumber(range[2]) And IsNumber(options'hash["points"]) And IsNumber(options'hash["precision"]),
"Plot2D: Error: plotting range '"
:(ToString()Write(range))
:"' and/or the number of points '"
:(ToString()Write(options'hash["points"]))
:"' and/or precision '"
:(ToString()Write(options'hash["precision"]))
:"' is not numeric"
);
// loop over functions in the list
ForEach(func, func'list)
[
// obtain name of variable
var := VarList(func); // variable name in a one-element list
Check(Length(var)<=1,
"Plot2D: Error: expression is not a function of one variable: "
:(ToString()Write(func))
);
// Allow plotting of constant functions
If(Length(var)=0, var:={dummy});
// store variable name if not already done so
If(
options'hash["xname"] = "",
options'hash["xname"] := String(VarList(var)[1])
);
// store function name in options
DestructiveAppend(options'hash["yname"], ToString()Write(func));
// compute the first point to see if it's okay
c := range[1];
fc := N(Eval(Apply({var, func}, {c})));
Check(IsNumber(fc) Or fc=Infinity Or fc= -Infinity Or fc=Undefined,
"Plot2D: Error: cannot evaluate function '"
:(ToString()Write(func))
:"' at point '"
:(ToString()Write(c))
:"' to a number, instead got '"
:(ToString()Write(fc))
:"'"
);
// compute all other data points
DestructiveAppend(all'values, Plot2D'get'data(func, var, c, fc, delta, options'hash));
If(InVerboseMode(), Echo({"Plot2D: using ", Length(all'values[Length(all'values)]), " points for function ", func}), True);
];
// call the specified output backend
Plot2D'outputs()[options'hash["output"]] @ {all'values, options'hash};
];
//HoldArg("Plot2D", range);
//HoldArg("Plot2D", options);
HoldArgNr("Plot2D", 2, 2);
HoldArgNr("Plot2D", 3, 2);
HoldArgNr("Plot2D", 3, 3);
/// this is the middle-level plotting routine; it generates the initial
/// grid, calls the adaptive routine, and gathers data points.
/// func must be just one function (not a list)
Plot2D'get'data(_func, _var, _x'init, _y'init, _delta'x, _options'hash) <--
[
Local(i, a, fa, b, fb, c, fc, result);
// initialize list by first points (later will always use Tail() to exclude first points of subintervals)
result := { {c,fc} := {x'init, y'init} };
For(i:=0, i<options'hash["points"], i++)
[
{a,fa} := {c, fc}; // this is to save time but here a = x'init + i*delta'x
// build subintervals
{b, c} := N(Eval({x'init + (i+1/2)*delta'x, x'init + (i+1)*delta'x})); // this is not computed using "a" to reduce roundoff error
{fb, fc} := N(Eval(MapSingle({var, func}, {b, c})));
result := Concat(result,
Tail(Plot2D'adaptive(func, var, {a,b,c}, {fa, fb, fc}, options'hash["depth"],
// since we are dividing into "points" subintervals, we need to relax precision
options'hash["precision"]*options'hash["points"] )));
];
result;
];
//////////////////////////////////////////////////
/// Plot2D'adaptive --- core routine to collect data
//////////////////////////////////////////////////
/*
Plot2D'adaptive returns a list of pairs of coordinates { {x1,y1}, {x2,y2},...}
All arguments except f() and var must be numbers. var is a one-element list containing the independent variable. The "a,b,c" and "fa, fb, fc" arguments are values of the function that are already computed -- we don't want to recompute them once more.
See documentation (Algorithms.chapt.txt) for the description of the algorithm.
*/
Plot2D'adaptive(_func, _var, {_a,_b,_c}, {_fa, _fb, _fc}, _depth, _epsilon) <--
[
Local(a1, b1, fa1, fb1);
a1 := N(Eval((a+b)/2));
b1 := N(Eval((b+c)/2));
{fa1, fb1} := N(Eval(MapSingle({var, func}, {a1, b1})));
If(
depth<=0 Or
(
// condition for the values not to oscillate too rapidly
sign'change(fa, fa1, fb) + sign'change(fa1, fb, fb1) + sign'change(fb, fb1, fc) <= 2
And
// condition for the values not to change too rapidly
N(Eval(Abs( (fa-5*fa1+9*fb-7*fb1+2*fc)/24 ) )) // this is the Simpson quadrature for the (fb,fb1) subinterval (using points b,b1,c), subtracted from the 4-point Newton-Cotes quadrature for the (fb,fb1) subinterval (using points a, a1, b, b1)
<= N(Eval( epsilon*( // the expression here will be nonnegative because we subtract the minimum value
//(fa+fc+2*fb+4*(fa1+fb1))/12 // this is 1/4 of the Simpson quadrature on the whole interval
(5*fb+8*fb1-fc)/12 // this is the Simpson quadrature for the (fb,f1) subinterval
- Min({fa,fa1,fb,fb1,fc}) ) ) )
),
// okay, do not refine any more
{{a,fa}, {a1,fa1}, {b,fb}, {b1,fb1}, {c,fc}},
// not okay, need to refine more
Concat(
// recursive call on two halves of the interval; relax precision by factor of 2
Plot2D'adaptive(func, var, {a, a1, b}, {fa, fa1, fb}, depth-1, epsilon*2), // Tail() omits the pair {b, fb}
Tail(Plot2D'adaptive(func, var, {b, b1, c}, {fb, fb1, fc}, depth-1, epsilon*2))
)
);
];
]; // LocalSymbols()
|