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