1 /*
  2     Copyright 2008-2023
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 
 35 /**
 36  * @fileoverview In this file the namespace Math.Symbolic is defined, which holds methods
 37  * and algorithms for symbolic computations.
 38  * @author graphjs
 39  */
 40 
 41 import Const from "../base/constants";
 42 import Coords from "../base/coords";
 43 import Mat from "./math";
 44 import Geometry from "./geometry";
 45 import Server from "../server/server";
 46 import Type from "../utils/type";
 47 
 48 var undef;
 49 
 50 /**
 51  * The JXG.Math.Symbolic namespace holds algorithms for symbolic computations.
 52  * @name JXG.Math.Symbolic
 53  * @exports Mat.Symbolic as JXG.Math.Symbolic
 54  * @namespace
 55  */
 56 Mat.Symbolic = {
 57     /**
 58      * Generates symbolic coordinates for the part of a construction including all the elements from that
 59      * a specific element depends of. These coordinates will be stored in GeometryElement.symbolic.
 60      * @param {JXG.Board} board The board that's element get some symbolic coordinates.
 61      * @param {JXG.GeometryElement} element All ancestor of this element get symbolic coordinates.
 62      * @param {String} variable Name for the coordinates, e.g. x or u.
 63      * @param {String} append Method for how to append the number of the coordinates. Possible values are
 64      *                        'underscore' (e.g. x_2), 'none' (e.g. x2), 'brace' (e.g. x[2]).
 65      * @returns {Number} Number of coordinates given.
 66      * @memberof JXG.Math.Symbolic
 67      */
 68     generateSymbolicCoordinatesPartial: function (board, element, variable, append) {
 69         var t_num,
 70             t,
 71             k,
 72             list = element.ancestors,
 73             count = 0,
 74             makeCoords = function (num) {
 75                 var r;
 76 
 77                 if (append === "underscore") {
 78                     r = variable + "_{" + num + "}";
 79                 } else if (append === "brace") {
 80                     r = variable + "[" + num + "]";
 81                 } else {
 82                     r = variable + num;
 83                 }
 84 
 85                 return r;
 86             };
 87 
 88         board.listOfFreePoints = [];
 89         board.listOfDependantPoints = [];
 90 
 91         for (t in list) {
 92             if (list.hasOwnProperty(t)) {
 93                 t_num = 0;
 94 
 95                 if (Type.isPoint(list[t])) {
 96                     for (k in list[t].ancestors) {
 97                         if (list[t].ancestors.hasOwnProperty(k)) {
 98                             t_num++;
 99                         }
100                     }
101 
102                     if (t_num === 0) {
103                         list[t].symbolic.x = list[t].coords.usrCoords[1];
104                         list[t].symbolic.y = list[t].coords.usrCoords[2];
105                         board.listOfFreePoints.push(list[t]);
106                     } else {
107                         count += 1;
108                         list[t].symbolic.x = makeCoords(count);
109                         count += 1;
110                         list[t].symbolic.y = makeCoords(count);
111                         board.listOfDependantPoints.push(list[t]);
112                     }
113                 }
114             }
115         }
116 
117         if (Type.isPoint(element)) {
118             element.symbolic.x = "x";
119             element.symbolic.y = "y";
120         }
121 
122         return count;
123     },
124 
125     /**
126      * Clears all .symbolic.x and .symbolic.y members on every point of a given board.
127      * @param {JXG.Board} board The board that's points get cleared their symbolic coordinates.
128      * @memberof JXG.Math.Symbolic
129      */
130     clearSymbolicCoordinates: function (board) {
131         var clear = function (list) {
132             var t,
133                 l = (list && list.length) || 0;
134 
135             for (t = 0; t < l; t++) {
136                 if (Type.isPoint(list[t])) {
137                     list[t].symbolic.x = "";
138                     list[t].symbolic.y = "";
139                 }
140             }
141         };
142 
143         clear(board.listOfFreePoints);
144         clear(board.listOfDependantPoints);
145 
146         delete board.listOfFreePoints;
147         delete board.listOfDependantPoints;
148     },
149 
150     /**
151      * Generates polynomials for a part of the construction including all the points from that
152      * a specific element depends of.
153      * @param {JXG.Board} board The board that's points polynomials will be generated.
154      * @param {JXG.GeometryElement} element All points in the set of ancestors of this element are used to generate the set of polynomials.
155      * @param {Boolean} generateCoords
156      * @returns {Array} An array of polynomials as strings.
157      * @memberof JXG.Math.Symbolic
158      */
159     generatePolynomials: function (board, element, generateCoords) {
160         var t,
161             k,
162             i,
163             list = element.ancestors,
164             number_of_ancestors,
165             pgs = [],
166             result = [];
167 
168         if (generateCoords) {
169             this.generateSymbolicCoordinatesPartial(board, element, "u", "brace");
170         }
171 
172         list[element.id] = element;
173 
174         for (t in list) {
175             if (list.hasOwnProperty(t)) {
176                 number_of_ancestors = 0;
177                 pgs = [];
178 
179                 if (Type.isPoint(list[t])) {
180                     for (k in list[t].ancestors) {
181                         if (list[t].ancestors.hasOwnProperty(k)) {
182                             number_of_ancestors++;
183                         }
184                     }
185                     if (number_of_ancestors > 0) {
186                         pgs = list[t].generatePolynomial();
187 
188                         for (i = 0; i < pgs.length; i++) {
189                             result.push(pgs[i]);
190                         }
191                     }
192                 }
193             }
194         }
195 
196         if (generateCoords) {
197             this.clearSymbolicCoordinates(board);
198         }
199 
200         return result;
201     },
202 
203     /**
204      * Calculate geometric locus of a point given on a board. Invokes python script on server.
205      * @param {JXG.Board} board The board on which the point lies.
206      * @param {JXG.Point} point The point that will be traced.
207      * @returns {Array} An array of points.
208      * @memberof JXG.Math.Symbolic
209      */
210     geometricLocusByGroebnerBase: function (board, point) {
211         var poly,
212             polyStr,
213             result,
214             P1,
215             P2,
216             i,
217             xs,
218             xe,
219             ys,
220             ye,
221             c,
222             s,
223             tx,
224             bol = board.options.locus,
225             oldRadius = {},
226             numDependent = this.generateSymbolicCoordinatesPartial(board, point, "u", "brace"),
227             xsye = new Coords(Const.COORDS_BY_USR, [0, 0], board),
228             xeys = new Coords(
229                 Const.COORDS_BY_USR,
230                 [board.canvasWidth, board.canvasHeight],
231                 board
232             ),
233             sf = 1,
234             transx = 0,
235             transy = 0,
236             rot = 0;
237 
238         if (Server.modules.geoloci === undef) {
239             Server.loadModule("geoloci");
240         }
241 
242         if (Server.modules.geoloci === undef) {
243             throw new Error("JSXGraph: Unable to load JXG.Server module 'geoloci.py'.");
244         }
245 
246         xs = xsye.usrCoords[1];
247         xe = xeys.usrCoords[1];
248         ys = xeys.usrCoords[2];
249         ye = xsye.usrCoords[2];
250 
251         // Optimizations - but only if the user wants to
252         //   Step 1: Translate all related points, such that one point P1 (board.options.locus.toOrigin if set
253         //     or a random point otherwise) is moved to (0, 0)
254         //   Step 2: Rotate the construction around the new P1, such that another point P2 (board.options.locus.to10 if set
255         //     or a random point \neq P1 otherwise) is moved onto the positive x-axis
256         //  Step 3: Dilate the construction, such that P2 is moved to (1, 0)
257         //  Step 4: Give the scale factor (sf), the rotation (rot) and the translation vector (transx, transy) to
258         //    the server, which retransforms the plot (if any).
259 
260         // Step 1
261         if (bol.translateToOrigin && board.listOfFreePoints.length > 0) {
262             if (
263                 bol.toOrigin !== undef &&
264                 bol.toOrigin !== null &&
265                 Type.isInArray(board.listOfFreePoints, bol.toOrigin.id)
266             ) {
267                 P1 = bol.toOrigin;
268             } else {
269                 P1 = board.listOfFreePoints[0];
270             }
271 
272             transx = P1.symbolic.x;
273             transy = P1.symbolic.y;
274             // translate the whole construction
275             for (i = 0; i < board.listOfFreePoints.length; i++) {
276                 board.listOfFreePoints[i].symbolic.x -= transx;
277                 board.listOfFreePoints[i].symbolic.y -= transy;
278             }
279 
280             xs -= transx;
281             xe -= transx;
282             ys -= transy;
283             ye -= transy;
284 
285             // Step 2
286             if (bol.translateTo10 && board.listOfFreePoints.length > 1) {
287                 if (
288                     bol.to10 !== undef &&
289                     bol.to10 !== null &&
290                     bol.to10.id !== bol.toOrigin.id &&
291                     Type.isInArray(board.listOfFreePoints, bol.to10.id)
292                 ) {
293                     P2 = bol.to10;
294                 } else {
295                     if (board.listOfFreePoints[0].id === P1.id) {
296                         P2 = board.listOfFreePoints[1];
297                     } else {
298                         P2 = board.listOfFreePoints[0];
299                     }
300                 }
301 
302                 rot = Geometry.rad([1, 0], [0, 0], [P2.symbolic.x, P2.symbolic.y]);
303                 c = Math.cos(-rot);
304                 s = Math.sin(-rot);
305 
306                 for (i = 0; i < board.listOfFreePoints.length; i++) {
307                     tx = board.listOfFreePoints[i].symbolic.x;
308                     board.listOfFreePoints[i].symbolic.x =
309                         c * board.listOfFreePoints[i].symbolic.x -
310                         s * board.listOfFreePoints[i].symbolic.y;
311                     board.listOfFreePoints[i].symbolic.y =
312                         s * tx + c * board.listOfFreePoints[i].symbolic.y;
313                 }
314 
315                 // thanks to the rotation this is zero
316                 P2.symbolic.y = 0;
317 
318                 tx = xs;
319                 xs = c * xs - s * ys;
320                 ys = s * tx + c * ys;
321                 tx = xe;
322                 xe = c * xe - s * ye;
323                 ye = s * tx + c * ye;
324 
325                 // Step 3
326                 if (bol.stretch && Math.abs(P2.symbolic.x) > Mat.eps) {
327                     sf = P2.symbolic.x;
328 
329                     for (i = 0; i < board.listOfFreePoints.length; i++) {
330                         board.listOfFreePoints[i].symbolic.x /= sf;
331                         board.listOfFreePoints[i].symbolic.y /= sf;
332                     }
333 
334                     for (i = 0; i < board.objectsList.length; i++) {
335                         if (
336                             board.objectsList[i].elementClass === Const.OBJECT_CLASS_CIRCLE &&
337                             board.objectsList[i].method === "pointRadius"
338                         ) {
339                             oldRadius[i] = board.objectsList[i].radius;
340                             board.objectsList[i].radius /= sf;
341                         }
342                     }
343 
344                     xs /= sf;
345                     xe /= sf;
346                     ys /= sf;
347                     ye /= sf;
348 
349                     // this is now 1
350                     P2.symbolic.x = 1;
351                 }
352             }
353 
354             // make the coordinates "as rational as possible"
355             for (i = 0; i < board.listOfFreePoints.length; i++) {
356                 tx = board.listOfFreePoints[i].symbolic.x;
357 
358                 if (Math.abs(tx) < Mat.eps) {
359                     board.listOfFreePoints[i].symbolic.x = 0;
360                 }
361 
362                 if (Math.abs(tx - Math.round(tx)) < Mat.eps) {
363                     board.listOfFreePoints[i].symbolic.x = Math.round(tx);
364                 }
365 
366                 tx = board.listOfFreePoints[i].symbolic.y;
367 
368                 if (Math.abs(tx) < Mat.eps) {
369                     board.listOfFreePoints[i].symbolic.y = 0;
370                 }
371 
372                 if (Math.abs(tx - Math.round(tx)) < Mat.eps) {
373                     board.listOfFreePoints[i].symbolic.y = Math.round(tx);
374                 }
375             }
376         }
377 
378         // end of optimizations
379 
380         poly = this.generatePolynomials(board, point);
381         polyStr = poly.join(",");
382 
383         this.cbp = function (data) {
384             result = data;
385         };
386 
387         this.cb = Type.bind(this.cbp, this);
388 
389         Server.modules.geoloci.lociCoCoA(
390             xs,
391             xe,
392             ys,
393             ye,
394             numDependent,
395             polyStr,
396             sf,
397             rot,
398             transx,
399             transy,
400             this.cb,
401             true
402         );
403 
404         this.clearSymbolicCoordinates(board);
405 
406         for (i in oldRadius) {
407             if (oldRadius.hasOwnProperty(i)) {
408                 board.objects[i].radius = oldRadius[i];
409             }
410         }
411 
412         return result;
413     }
414 };
415 
416 export default Mat.Symbolic;
417