      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