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