1 /* 2 Copyright 2008-2024 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.js"; 42 import Coords from "../base/coords.js"; 43 import Mat from "./math.js"; 44 import Geometry from "./geometry.js"; 45 import Server from "../server/server.js"; 46 import Type from "../utils/type.js"; 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