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