      1 /*
      2     Copyright 2008-2022
      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