1 /* 2 JessieCode Computer algebra algorithms 3 4 Copyright 2011-2021 5 Michael Gerhaeuser, 6 Alfred Wassermann 7 8 JessieCode is free software dual licensed under the GNU LGPL or MIT License. 9 10 You can redistribute it and/or modify it under the terms of the 11 12 * GNU Lesser General Public License as published by 13 the Free Software Foundation, either version 3 of the License, or 14 (at your option) any later version 15 OR 16 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 17 18 JessieCode is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 GNU Lesser General Public License for more details. 22 23 You should have received a copy of the GNU Lesser General Public License and 24 the MIT License along with JessieCode. If not, see <http://www.gnu.org/licenses/> 25 and <http://opensource.org/licenses/MIT/>. 26 */ 27 28 /*global JXG: true, define: true, window: true, console: true, self: true, document: true, parser: true*/ 29 /*jslint nomen: true, plusplus: true*/ 30 31 /* depends: 32 jxg 33 parser/geonext 34 base/constants 35 base/text 36 math/math 37 math/geometry 38 math/statistics 39 utils/type 40 utils/uuid 41 */ 42 43 /** 44 * @fileoverview Here, the computer algebra algorithms are implemented. 45 */ 46 47 define([ 48 'jxg', 'base/constants', 'base/text', 'math/math', 'math/geometry', 'math/statistics', 'utils/type', 'utils/env' 49 ], function (JXG, Const, Text, Mat, Geometry, Statistics, Type, Env) { 50 51 "use strict"; 52 53 /** 54 * A JessieCode object provides an interface to the parser and stores all variables and objects used within a JessieCode script. 55 * The optional argument <tt>code</tt> is interpreted after initializing. To evaluate more code after initializing a JessieCode instance 56 * please use {@link JXG.JessieCode#parse}. For code snippets like single expressions use {@link JXG.JessieCode#snippet}. 57 * @constructor 58 * @param {String} [code] Code to parse. 59 * @param {Boolean} [geonext=false] Geonext compatibility mode. 60 */ 61 JXG.CA = function (node, createNode, parser) { 62 this.node = node; 63 this.createNode = createNode; 64 this.parser = parser; 65 }; 66 67 JXG.extend(JXG.CA.prototype, /** @lends JXG.CA.prototype */ { 68 findMapNode: function(mapname, node) { 69 var i, len, ret; 70 71 //console.log("FINDMAP", node); 72 if (node.value === 'op_assign' && node.children[0].value === mapname) { 73 return node.children[1]; 74 } else if (node.children) { 75 len = node.children.length; 76 for (i = 0; i < len; ++i) { 77 ret = this.findMapNode(mapname, node.children[i]); 78 if (ret !== null) { 79 return ret; 80 } 81 } 82 } 83 return null; 84 }, 85 86 /** 87 * Declare all subnodes as math nodes, 88 * i.e recursively set node.isMath = true; 89 */ 90 setMath: function(node) { 91 var i, len; 92 93 if ((node.type == 'node_op' && ( 94 node.value == 'op_add' || node.value == 'op_sub' || 95 node.value == 'op_mul' || node.value == 'op_div' || 96 node.value == 'op_neg' || node.value == 'op_execfun' || 97 node.value == 'op_exp')) || 98 node.type == 'node_var' || node.type == 'node_const') { 99 100 node.isMath = true; 101 } 102 if (node.children) { 103 len = node.children.length; 104 for (i = 0; i < len; ++i) { 105 this.setMath(node.children[i]); 106 } 107 } 108 }, 109 110 deriveElementary: function(node, varname) { 111 var fun = node.children[0].value, 112 arg = node.children[1], 113 newNode; 114 115 116 switch (fun) { 117 case 'abs': 118 // x / sqrt(x * x) 119 newNode = this.createNode('node_op', 'op_div', 120 arg[0], 121 this.createNode('node_op', 'op_execfun', 122 this.createNode('node_var', 'sqrt'), 123 [this.createNode('node_op', 'op_mul', 124 Type.deepCopy(arg[0]), 125 Type.deepCopy(arg[0]) 126 )] 127 ) 128 ); 129 break; 130 131 case 'sqrt': 132 newNode = this.createNode('node_op', 'op_div', 133 this.createNode('node_const', 1.0), 134 this.createNode('node_op', 'op_mul', 135 this.createNode('node_const', 2.0), 136 this.createNode(node.type, node.value, 137 Type.deepCopy(node.children[0]), 138 Type.deepCopy(node.children[1]) 139 ) 140 ) 141 ); 142 break; 143 144 case 'sin': 145 newNode = this.createNode('node_op', 'op_execfun', 146 this.createNode('node_var', 'cos'), 147 Type.deepCopy(arg) 148 ); 149 break; 150 151 case 'cos': 152 newNode = this.createNode('node_op', 'op_neg', 153 this.createNode('node_op', 'op_execfun', 154 this.createNode('node_var', 'sin'), 155 Type.deepCopy(arg) 156 ) 157 ); 158 break; 159 160 case 'tan': 161 newNode = this.createNode('node_op', 'op_div', 162 this.createNode('node_const', 1.0), 163 this.createNode('node_op', 'op_exp', 164 this.createNode('node_op', 'op_execfun', 165 this.createNode('node_var', 'cos'), 166 Type.deepCopy(arg) 167 ), 168 this.createNode('node_const', 2) 169 ) 170 ); 171 break; 172 173 case 'cot': 174 newNode = this.createNode('node_op', 'op_neg', 175 this.createNode('node_op', 'op_div', 176 this.createNode('node_const', 1.0), 177 this.createNode('node_op', 'op_exp', 178 this.createNode('node_op', 'op_execfun', 179 this.createNode('node_var', 'sin'), 180 Type.deepCopy(arg) 181 ), 182 this.createNode('node_const', 2) 183 ) 184 ) 185 ); 186 break; 187 188 case 'exp': 189 newNode = this.createNode(node.type, node.value, 190 Type.deepCopy(node.children[0]), 191 Type.deepCopy(node.children[1]) 192 ); 193 break; 194 195 case 'pow': 196 // (f^g)' = f^g*(f'g/f + g' log(f)) 197 newNode = this.createNode('node_op', 'op_mul', 198 this.createNode('node_op', 'op_execfun', 199 Type.deepCopy(node.children[0]), 200 Type.deepCopy(node.children[1]) 201 ), 202 this.createNode('node_op', 'op_add', 203 this.createNode('node_op', 'op_mul', 204 this.derivative(node.children[1][0], varname), 205 this.createNode('node_op', 'op_div', 206 Type.deepCopy(node.children[1][1]), 207 Type.deepCopy(node.children[1][0]) 208 ) 209 ), 210 this.createNode('node_op', 'op_mul', 211 this.derivative(node.children[1][1], varname), 212 this.createNode('node_op', 'op_execfun', 213 this.createNode('node_var', 'log'), 214 [Type.deepCopy(node.children[1][0])] 215 ) 216 ) 217 ) 218 ); 219 break; 220 221 case 'log': 222 case 'ln': 223 newNode = this.createNode('node_op', 'op_div', 224 this.createNode('node_const', 1.0), 225 // Attention: single variable mode 226 Type.deepCopy(arg[0]) 227 ); 228 break; 229 230 case 'log2': 231 case 'lb': 232 case 'ld': 233 newNode = this.createNode('node_op', 'op_mul', 234 this.createNode('node_op', 'op_div', 235 this.createNode('node_const', 1.0), 236 // Attention: single variable mode 237 Type.deepCopy(arg[0]) 238 ), 239 this.createNode('node_const', 1.4426950408889634) // 1/log(2) 240 ); 241 break; 242 243 case 'log10': 244 case 'lg': 245 newNode = this.createNode('node_op', 'op_mul', 246 this.createNode('node_op', 'op_div', 247 this.createNode('node_const', 1.0), 248 // Attention: single variable mode 249 Type.deepCopy(arg[0]) 250 ), 251 this.createNode('node_const', 0.43429448190325176) // 1/log(10) 252 ); 253 break; 254 255 case 'asin': 256 newNode = this.createNode('node_op', 'op_div', 257 this.createNode('node_const', 1.0), 258 this.createNode('node_op', 'op_execfun', 259 this.createNode('node_var', 'sqrt'), 260 [ 261 this.createNode('node_op', 'op_sub', 262 this.createNode('node_const', 1.0), 263 this.createNode('node_op', 'op_mul', 264 Type.deepCopy(arg[0]), 265 Type.deepCopy(arg[0]) 266 ) 267 ) 268 ] 269 ) 270 ); 271 break; 272 273 case 'acos': 274 newNode = this.createNode('node_op', 'op_neg', 275 this.createNode('node_op', 'op_div', 276 this.createNode('node_const', 1.0), 277 this.createNode('node_op', 'op_execfun', 278 this.createNode('node_var', 'sqrt'), 279 [ 280 this.createNode('node_op', 'op_sub', 281 this.createNode('node_const', 1.0), 282 this.createNode('node_op', 'op_mul', 283 Type.deepCopy(arg[0]), 284 Type.deepCopy(arg[0]) 285 ) 286 ) 287 ] 288 ) 289 ) 290 ); 291 break; 292 293 //case 'atan2': 294 295 case 'atan': 296 newNode = this.createNode('node_op', 'op_div', 297 this.createNode('node_const', 1.0), 298 this.createNode('node_op', 'op_add', 299 this.createNode('node_const', 1.0), 300 this.createNode('node_op', 'op_mul', 301 Type.deepCopy(arg[0]), 302 Type.deepCopy(arg[0]) 303 ) 304 ) 305 ); 306 break; 307 308 case 'acot': 309 newNode = this.createNode('node_op', 'op_neg', 310 this.createNode('node_op', 'op_div', 311 this.createNode('node_const', 1.0), 312 this.createNode('node_op', 'op_add', 313 this.createNode('node_const', 1.0), 314 this.createNode('node_op', 'op_mul', 315 Type.deepCopy(arg[0]), 316 Type.deepCopy(arg[0]) 317 ) 318 ) 319 ) 320 ); 321 break; 322 323 case 'sinh': 324 newNode = this.createNode('node_op', 'op_execfun', 325 this.createNode('node_var', 'cosh'), 326 [Type.deepCopy(arg[0])] 327 ); 328 break; 329 330 case 'cosh': 331 newNode = this.createNode('node_op', 'op_execfun', 332 this.createNode('node_var', 'sinh'), 333 [Type.deepCopy(arg[0])] 334 ); 335 break; 336 337 case 'tanh': 338 newNode = this.createNode('node_op', 'op_sub', 339 this.createNode('node_const', 1.0), 340 this.createNode('node_op', 'op_exp', 341 this.createNode('node_op', 'op_execfun', 342 this.createNode('node_var', 'tanh'), 343 [Type.deepCopy(arg[0])] 344 ), 345 this.createNode('node_const', 2.0) 346 ) 347 ); 348 break; 349 350 case 'asinh': 351 newNode = this.createNode('node_op', 'op_div', 352 this.createNode('node_const', 1.0), 353 this.createNode('node_op', 'op_execfun', 354 this.createNode('node_var', 'sqrt'), 355 [ 356 this.createNode('node_op', 'op_add', 357 this.createNode('node_op', 'op_mul', 358 Type.deepCopy(arg[0]), 359 Type.deepCopy(arg[0]) 360 ), 361 this.createNode('node_const', 1.0) 362 ) 363 ] 364 ) 365 ); 366 break; 367 368 case 'acosh': 369 newNode = this.createNode('node_op', 'op_div', 370 this.createNode('node_const', 1.0), 371 this.createNode('node_op', 'op_execfun', 372 this.createNode('node_var', 'sqrt'), 373 [ 374 this.createNode('node_op', 'op_sub', 375 this.createNode('node_op', 'op_mul', 376 Type.deepCopy(arg[0]), 377 Type.deepCopy(arg[0]) 378 ), 379 this.createNode('node_const', 1.0) 380 ) 381 ] 382 ) 383 ); 384 break; 385 386 case 'atanh': 387 newNode = this.createNode('node_op', 'op_div', 388 this.createNode('node_const', 1.0), 389 this.createNode('node_op', 'op_sub', 390 this.createNode('node_const', 1.0), 391 this.createNode('node_op', 'op_mul', 392 Type.deepCopy(arg[0]), 393 Type.deepCopy(arg[0]) 394 ) 395 ) 396 ); 397 break; 398 399 default: 400 newNode = this.createNode('node_const', 0.0); 401 this._error('Derivative of "' + fun + '" not yet implemented'); 402 } 403 404 return newNode; 405 }, 406 407 derivative: function(node, varname) { 408 var i, len, newNode; 409 410 switch (node.type) { 411 case 'node_op': 412 switch (node.value) { 413 /* 414 case 'op_map': 415 if (true) { 416 newNode = this.createNode('node_op', 'op_map', 417 Type.deepCopy(node.children[0]), 418 this.derivative(node.children[1], varname) 419 ); 420 } else { 421 newNode = this.derivative(node.children[1], varname); 422 } 423 break; 424 */ 425 case 'op_execfun': 426 // f'(g(x))g'(x) 427 if (node.children[0].value == 'pow') { 428 newNode = this.deriveElementary(node, varname); 429 } else { 430 newNode = this.createNode('node_op', 'op_mul', 431 this.deriveElementary(node, varname), 432 // Warning: single variable mode 433 this.derivative(node.children[1][0], varname) 434 ); 435 436 } 437 break; 438 439 case 'op_div': 440 // (f'g − g'f )/(g*g) 441 newNode = this.createNode('node_op', 'op_div', 442 this.createNode('node_op', 'op_sub', 443 this.createNode('node_op', 'op_mul', 444 this.derivative(node.children[0], varname), 445 Type.deepCopy(node.children[1]) 446 ), 447 this.createNode('node_op', 'op_mul', 448 Type.deepCopy(node.children[0]), 449 this.derivative(node.children[1], varname) 450 ) 451 ), 452 this.createNode('node_op', 'op_mul', 453 Type.deepCopy(node.children[1]), 454 Type.deepCopy(node.children[1]) 455 ) 456 ); 457 break; 458 459 case 'op_mul': 460 // fg' + f'g 461 newNode = this.createNode('node_op', 'op_add', 462 this.createNode('node_op', 'op_mul', 463 Type.deepCopy(node.children[0]), 464 this.derivative(node.children[1], varname)), 465 this.createNode('node_op', 'op_mul', 466 this.derivative(node.children[0], varname), 467 Type.deepCopy(node.children[1])) 468 ); 469 break; 470 471 case 'op_neg': 472 newNode = this.createNode('node_op', 'op_neg', 473 this.derivative(node.children[0], varname) 474 ); 475 break; 476 477 case 'op_add': 478 case 'op_sub': 479 newNode = this.createNode('node_op', node.value, 480 this.derivative(node.children[0], varname), 481 this.derivative(node.children[1], varname) 482 ); 483 break; 484 485 case 'op_exp': 486 // (f^g)' = f^g*(f'g/f + g' log(f)) 487 newNode = this.createNode('node_op', 'op_mul', 488 Type.deepCopy(node), 489 this.createNode('node_op', 'op_add', 490 this.createNode('node_op', 'op_mul', 491 this.derivative(node.children[0], varname), 492 this.createNode('node_op', 'op_div', 493 Type.deepCopy(node.children[1]), 494 Type.deepCopy(node.children[0]) 495 ) 496 ), 497 this.createNode('node_op', 'op_mul', 498 this.derivative(node.children[1], varname), 499 this.createNode('node_op', 'op_execfun', 500 this.createNode('node_var', 'log'), 501 [Type.deepCopy(node.children[0])] 502 ) 503 ) 504 ) 505 ); 506 break; 507 } 508 break; 509 510 case 'node_var': 511 //console.log('node_var', node); 512 if (node.value === varname) { 513 newNode = this.createNode('node_const', 1.0); 514 } else { 515 newNode = this.createNode('node_const', 0.0); 516 } 517 break; 518 519 case 'node_const': 520 newNode = this.createNode('node_const', 0.0); 521 break; 522 523 case 'node_const_bool': 524 break; 525 526 case 'node_str': 527 break; 528 529 } 530 531 return newNode; 532 }, 533 534 /** 535 * f = map (x) -> x*sin(x); 536 * Usages: 537 * h = D(f, x); 538 * h = map (x) -> D(f, x); 539 * 540 */ 541 expandDerivatives: function(node, parent, ast) { 542 var len, i, j, mapNode, codeNode, ret, node2, newNode, 543 mapName, varname, vArray, order; 544 545 ret = 0; 546 if (!node) { 547 return ret; 548 } 549 550 this.line = node.line; 551 this.col = node.col; 552 553 // First we have to go down in the tree. 554 // This ensures that in cases like D(D(f,x),x) the inner D is expanded first. 555 len = node.children.length; 556 for (i = 0; i < len; ++i) { 557 if (node.children[i] && node.children[i].type) { 558 node.children[i] = this.expandDerivatives(node.children[i], node, ast); 559 } else if (Type.isArray(node.children[i])) { 560 for (j = 0; j < node.children[i].length; ++j) { 561 if (node.children[i][j] && node.children[i][j].type) { 562 node.children[i][j] = this.expandDerivatives(node.children[i][j], node, ast); 563 } 564 } 565 } 566 } 567 568 switch (node.type) { 569 case 'node_op': 570 switch (node.value) { 571 case 'op_execfun': 572 if (node.children[0] && node.children[0].value === 'D') { 573 if (node.children[1][0].type == 'node_var') { 574 /* 575 * Derive map, that is compute D(f,x) 576 * where e.g. f = map (x) -> x^2 577 * 578 * First step: find node where the map is defined 579 */ 580 mapName = node.children[1][0].value; 581 mapNode = this.findMapNode(mapName, ast); 582 vArray = mapNode.children[0]; 583 584 // Variable name for differentiation 585 if (node.children[1].length >= 2) { 586 varname = node.children[1][1].value; 587 } else { 588 varname = mapNode.children[0][0]; // Usually it's 'x' 589 } 590 codeNode = mapNode.children[1]; 591 } else { 592 /* 593 * Derive expression, e.g. 594 * D(2*x, x) 595 */ 596 codeNode = node.children[1][0]; 597 vArray = ['x']; 598 599 // Variable name for differentiation and order 600 if (node.children[1].length >= 2) { 601 varname = node.children[1][1].value; 602 } else { 603 varname = 'x'; 604 } 605 } 606 607 // Differentiation order 608 if (node.children[1].length >= 3) { 609 order = node.children[1][2].value; 610 } else { 611 order = 1; 612 } 613 614 // Create node which contains the derivative 615 newNode = codeNode; 616 //newNode = this.removeTrivialNodes(newNode); 617 if (order >= 1) { 618 while (order >= 1) { 619 newNode = this.derivative(newNode, varname); 620 newNode = this.removeTrivialNodes(newNode); 621 order--; 622 } 623 } 624 625 // Replace the node containing e.g. D(f,x) by the derivative. 626 if (parent.type == 'node_op' && parent.value == 'op_assign') { 627 // If D is an assignment it has to be replaced by a map 628 // h = D(f, x) 629 node2 = this.createNode('node_op', 'op_map', 630 vArray, 631 newNode 632 ); 633 } else { 634 node2 = newNode; 635 } 636 637 this.setMath(node2); 638 node.type = node2.type; 639 node.value = node2.value; 640 node.children[0] = node2.children[0]; 641 node.children[1] = node2.children[1]; 642 } 643 } 644 break; 645 646 case 'node_var': 647 case 'node_const': 648 case 'node_const_bool': 649 case 'node_str': 650 break; 651 } 652 653 return node; 654 }, 655 656 removeTrivialNodes: function(node) { 657 var i, len, n0, n1, swap; 658 659 // In case of 'op_execfun' the children[1] node is an array. 660 if (Type.isArray(node)) { 661 len = node.length; 662 for (i = 0; i < len; ++i) { 663 node[i] = this.removeTrivialNodes(node[i]); 664 } 665 } 666 if (node.type != 'node_op' || !node.children) { 667 return node; 668 } 669 670 len = node.children.length; 671 for (i = 0; i < len; ++i) { 672 this.mayNotBeSimplified = false; 673 do { 674 node.children[i] = this.removeTrivialNodes(node.children[i]); 675 } while (this.mayNotBeSimplified); 676 677 } 678 679 switch (node.value) { 680 // Allow maps of the form 681 // map (x) -> x; 682 case 'op_map': 683 n0 = node.children[0]; 684 n1 = node.children[1]; 685 if (n1.type == 'node_var') { 686 for (i = 0; i < n0.length; ++i) { 687 // Allow maps of the form map(x) -> x 688 if (n0[i] == n1.value) { 689 n1.isMath = true; 690 break; 691 } 692 } 693 } 694 break; 695 696 // a + 0 -> a 697 // 0 + a -> a 698 case 'op_add': 699 n0 = node.children[0]; 700 n1 = node.children[1]; 701 if (n0.type == 'node_const' && n0.value === 0.0) { 702 return n1; 703 } 704 if (n1.type == 'node_const' && n1.value === 0.0) { 705 return n0; 706 } 707 708 // const + const -> const 709 if (n0.type == 'node_const' && n1.type == 'node_const') { 710 n0.value += n1.value; 711 return n0; 712 } 713 break; 714 715 // 1 * a = a 716 // a * 1 = a 717 // a * 0 = 0 718 // 0 * a = 0 719 // - * - = + 720 // Order children 721 case 'op_mul': 722 n0 = node.children[0]; 723 n1 = node.children[1]; 724 if (n0.type == 'node_const' && n0.value == 1.0) { 725 return n1; 726 } 727 if (n1.type == 'node_const' && n1.value == 1.0) { 728 return n0; 729 } 730 if (n0.type == 'node_const' && n0.value === 0.0) { 731 return n0; 732 } 733 if (n1.type == 'node_const' && n1.value === 0.0) { 734 return n1; 735 } 736 if (n1.type == 'node_const' && n1.value === 0.0) { 737 return n1; 738 } 739 740 // (-a) * (-b) -> a*b 741 if (n0.type == 'node_op' && n0.value == 'op_neg' && 742 n1.type == 'node_op' && n1.value == 'op_neg' ) { 743 node.children = [n0.children[0], n1.children[0]]; 744 this.mayNotBeSimplified = true; 745 return node; 746 } 747 // (-a) * b -> -(a*b) 748 if (n0.value == 'op_neg' && n1.value != 'op_neg' ) { 749 node.type = 'node_op'; 750 node.value = 'op_neg'; 751 node.children = [this.createNode('node_op', 'op_mul', n0.children[0], n1)]; 752 this.mayNotBeSimplified = true; 753 return node; 754 } 755 // a * (-b) -> -(a*b) 756 if (n0.value != 'op_neg' && n1.value == 'op_neg' ) { 757 node.type = 'node_op'; 758 node.value = 'op_neg'; 759 node.children = [this.createNode('node_op', 'op_mul', n0, n1.children[0])]; 760 this.mayNotBeSimplified = true; 761 return node; 762 } 763 // (1 / a) * b -> a / b 764 if (n0.value == 'op_div' && 765 n0.children[0].type == 'node_const' && n0.children[0].value == 1.0) { 766 node.type = 'node_op'; 767 node.value = 'op_div'; 768 node.children = [n1, n0.children[1]]; 769 this.mayNotBeSimplified = true; 770 return node; 771 } 772 // a * (1 / b) -> a / b 773 if (n1.value == 'op_div' && 774 n1.children[0].type == 'node_const' && n1.children[0].value == 1.0) { 775 node.type = 'node_op'; 776 node.value = 'op_div'; 777 node.children = [n0, n1.children[1]]; 778 this.mayNotBeSimplified = true; 779 return node; 780 } 781 782 // Order children 783 // a * const -> const * a 784 if (n0.type != 'node_const' && n1.type == 'node_const') { 785 node.children = [n1, n0]; 786 this.mayNotBeSimplified = true; 787 return node; 788 } 789 // a + (-const) -> -const * a 790 if (n0.type != 'node_const' && n1.type == 'node_op' && 791 n1.value == 'op_neg' && n1.children[0].type == 'node_const') { 792 node.children = [n1, n0]; 793 this.mayNotBeSimplified = true; 794 return node; 795 } 796 797 // a * var -> var * a 798 // a * fun -> fun * a 799 if (n0.type == 'node_op' && n0.value != 'op_execfun' && 800 (n1.type == 'node_var' || (n1.type == 'node_op' && n1.value == 'op_execfun'))) { 801 node.children = [n1, n0]; 802 this.mayNotBeSimplified = true; 803 return node; 804 } 805 806 // a + (-var) -> -var * a 807 if (n0.type != 'node_op' && n1.type == 'node_op' && 808 n1.value == 'op_neg' && n1.children[0].type == 'node_var') { 809 node.children = [n1, n0]; 810 this.mayNotBeSimplified = true; 811 return node; 812 } 813 // a * (const * b) -> const * (a*b) 814 // a * (const / b) -> const * (a/b) 815 if (n0.type != 'node_const' && n1.type == 'node_op' && 816 (n1.value == 'op_mul' || n1.value == 'op_div') && 817 n1.children[0].type == 'node_const') { 818 swap = n1.children[0]; 819 n1.children[0] = n0; 820 node.children = [swap, n1]; 821 this.mayNotBeSimplified = true; 822 return node; 823 } 824 825 // (const * a) * b -> const * (a * b) 826 if (n1.type != 'node_const' && n0.type == 'node_op' && 827 n0.value == 'op_mul' && 828 n0.children[0].type == 'node_const') { 829 node.children = [ 830 n0.children[0], 831 this.createNode('node_op', 'op_mul', n0.children[1], n1) 832 ]; 833 this.mayNotBeSimplified = true; 834 return node; 835 } 836 837 // const * const -> const 838 if (n0.type == 'node_const' && n1.type == 'node_const') { 839 n0.value *= n1.value; 840 return n0; 841 } 842 843 // const * (const * a) -> const * a 844 // const * (const / a) -> const / a 845 if (n0.type == 'node_const' && n1.type == 'node_op' && 846 (n1.value == 'op_mul' || n1.value == 'op_div') && 847 n1.children[0].type == 'node_const') { 848 n1.children[0].value *= n0.value; 849 return n1; 850 } 851 852 // a * a-> a^2 853 n0.hash = this.parser.compile(n0); 854 n1.hash = this.parser.compile(n1); 855 if (n0.hash === n1.hash) { 856 node.value = 'op_exp'; 857 node.children[1] = this.createNode('node_const', 2.0); 858 return node; 859 } 860 861 if (n0.type == 'node_const' && n1.type == 'node_op' && 862 (n1.value == 'op_mul' || n1.value == 'op_div') && 863 n1.children[0].type == 'node_const') { 864 n1.children[0].value *= n0.value; 865 return n1; 866 } 867 868 // a * a^b -> a^(b+1) 869 if (n1.type == 'node_op' && n1.value == 'op_exp') { 870 if (!n0.hash) { 871 n0.hash = this.parser.compile(n0); 872 } 873 if (!n1.children[0].hash) { 874 n1.children[0].hash = this.parser.compile(n1.children[0]); 875 } 876 if (n0.hash === n1.children[0].hash) { 877 n1.children[1] = this.createNode('node_op', 'op_add', 878 n1.children[1], 879 this.createNode('node_const', 1.0) 880 ); 881 this.mayNotBeSimplified = true; 882 return n1; 883 } 884 } 885 886 // a^b * a^c -> a^(b+c) 887 if (n0.type == 'node_op' && n0.value == 'op_exp' && 888 n1.type == 'node_op' && n1.value == 'op_exp') { 889 n0.children[0].hash = this.parser.compile(n0.children[0]); 890 n1.children[0].hash = this.parser.compile(n1.children[0]); 891 if (n0.children[0].hash === n1.children[0].hash) { 892 n0.children[1] = this.createNode('node_op', 'op_add', 893 n0.children[1], 894 n1.children[1] 895 ); 896 this.mayNotBeSimplified = true; 897 return n0; 898 } 899 } 900 901 break; 902 903 // 0 - a -> -a 904 // a - 0 -> a 905 // a - a -> 0 906 case 'op_sub': 907 n0 = node.children[0]; 908 n1 = node.children[1]; 909 if (n0.type == 'node_const' && n0.value === 0.0) { 910 node.value = 'op_neg'; 911 node.children[0] = n1; 912 return node; 913 } 914 if (n1.type == 'node_const' && n1.value === 0.0) { 915 return n0; 916 } 917 if (n0.type == 'node_const' && n1.type == 'node_const' && 918 n0.value == n1.value) { 919 return this.createNode('node_const', 0.0); 920 } 921 if (n0.type == 'node_var' && n1.type == 'node_var' && 922 n0.value == n1.value) { 923 return this.createNode('node_const', 0.0); 924 } 925 926 // const - const -> const 927 if (n0.type == 'node_const' && n1.type == 'node_const') { 928 n0.value -= n1.value; 929 return n0; 930 } 931 932 // const * a - const * a -> const * a 933 if (n0.type == 'node_op' && n0.value == 'op_mul' && 934 n1.type == 'node_op' && n1.value == 'op_mul') { 935 936 n0.children[1].hash = this.parser.compile(n0.children[1]); 937 n1.children[1].hash = this.parser.compile(n1.children[1]); 938 if (n0.children[1].hash === n1.children[1].hash) { 939 940 node.value = 'op_mul'; 941 node.children = [ 942 this.createNode('node_op', 'op_sub', 943 n0.children[0], 944 n1.children[0]), 945 n0.children[1] 946 ]; 947 this.mayNotBeSimplified = true; 948 return node; 949 } 950 } 951 // const * a - a -> (const - 1) * a 952 if (n0.type == 'node_op' && n0.value == 'op_mul') { 953 954 n0.children[1].hash = this.parser.compile(n0.children[1]); 955 n1.hash = this.parser.compile(n1); 956 if (n0.children[1].hash === n1.hash) { 957 958 node.value = 'op_mul'; 959 node.children = [ 960 this.createNode('node_op', 'op_sub', 961 n0.children[0], 962 this.createNode('node_const', 1.0)), 963 n1 964 ]; 965 this.mayNotBeSimplified = true; 966 return node; 967 } 968 } 969 // a - const*a -> (const - 1) * a 970 if (n1.type == 'node_op' && n1.value == 'op_mul') { 971 972 n1.children[1].hash = this.parser.compile(n1.children[1]); 973 n0.hash = this.parser.compile(n0); 974 if (n1.children[1].hash === n0.hash) { 975 976 node.value = 'op_mul'; 977 node.children = [ 978 this.createNode('node_op', 'op_sub', 979 this.createNode('node_const', 1.0), 980 n1.children[0]), 981 n0 982 ]; 983 this.mayNotBeSimplified = true; 984 return node; 985 } 986 } 987 988 break; 989 990 // -0 -> 0 991 // -(-b) = b 992 case 'op_neg': 993 n0 = node.children[0]; 994 if (n0.type == 'node_const' && n0.value === 0.0) { 995 return n0; 996 } 997 if (n0.type == 'node_op' && n0.value == 'op_neg') { 998 return n0.children[0]; 999 } 1000 break; 1001 1002 // a / a -> 1, a != 0 1003 // 0 / a -> 0, a != 0 1004 // a / 0 -> Infinity, a != 0 1005 // 0 / 0 -> NaN, a == 0 1006 case 'op_div': 1007 n0 = node.children[0]; 1008 n1 = node.children[1]; 1009 if (n0.type == 'node_const' && n1.type == 'node_const' && 1010 n0.value == n1.value && n0.value !== 0) { 1011 n0.value = 1.0; 1012 return n0; 1013 } 1014 if (n0.type == 'node_const' && n0.value === 0 && 1015 n1.type == 'node_const' && n1.value !== 0) { 1016 n0.value = 0.0; 1017 return n0; 1018 } 1019 1020 // Risky: 0 / (something != 0) -> 0.0 1021 if (n0.type == 'node_const' && n0.value === 0 && 1022 (n1.type == 'node_op' || n1.type == 'node_var')) { 1023 node.type = 'node_const'; 1024 node.value = 0.0; 1025 return node; 1026 } 1027 1028 if (n0.type == 'node_var' && n1.type == 'node_var' && 1029 n0.value == n1.value) { 1030 return this.createNode('node_const', 1.0); 1031 } 1032 if (n0.type == 'node_const' && n0.value !== 0 && 1033 n1.type == 'node_const' && n1.value === 0) { 1034 if (n0.value > 0.0) { 1035 n0.value = Infinity; 1036 } else { 1037 n0.value = -Infinity; // Do we ever need this? 1038 } 1039 return n0; 1040 } 1041 1042 // (-a) / (-b) -> a/b 1043 if (n0.type == 'node_op' && n0.value == 'op_neg' && 1044 n1.type == 'node_op' && n1.value == 'op_neg' ) { 1045 node.children = [n0.children[0], n1.children[0]]; 1046 this.mayNotBeSimplified = true; 1047 return node; 1048 } 1049 // (-a) / b -> -(a/b) 1050 if (n0.value == 'op_neg' && n1.value != 'op_neg' ) { 1051 node.type = 'node_op'; 1052 node.value = 'op_neg'; 1053 node.children = [this.createNode('node_op', 'op_div', n0.children[0], n1)]; 1054 this.mayNotBeSimplified = true; 1055 return node; 1056 } 1057 // a / (-b) -> -(a/b) 1058 if (n0.value != 'op_neg' && n1.value == 'op_neg' ) { 1059 node.type = 'node_op'; 1060 node.value = 'op_neg'; 1061 node.children = [this.createNode('node_op', 'op_div', n0, n1.children[0])]; 1062 this.mayNotBeSimplified = true; 1063 return node; 1064 } 1065 1066 // a^b / a -> a^(b-1) 1067 if (n0.type == 'node_op' && n0.value == 'op_exp') { 1068 if (!n1.hash) { 1069 n1.hash = this.parser.compile(n1); 1070 } 1071 if (!n0.children[0].hash) { 1072 n0.children[0].hash = this.parser.compile(n0.children[0]); 1073 } 1074 if (n1.hash === n0.children[0].hash) { 1075 n0.children[1] = this.createNode('node_op', 'op_sub', 1076 n0.children[1], 1077 this.createNode('node_const', 1.0) 1078 ); 1079 this.mayNotBeSimplified = true; 1080 return n0; 1081 } 1082 } 1083 1084 // (const * a) / b -> const * (a / b) 1085 if (n1.type != 'node_const' && n0.type == 'node_op' && 1086 n0.value == 'op_mul' && 1087 n0.children[0].type == 'node_const') { 1088 node.value = 'op_mul'; 1089 node.children = [ 1090 n0.children[0], 1091 this.createNode('node_op', 'op_div', n0.children[1], n1) 1092 ]; 1093 this.mayNotBeSimplified = true; 1094 return node; 1095 } 1096 1097 // a^b / a^c -> a^(b-c) 1098 if (n0.type == 'node_op' && n0.value == 'op_exp' && 1099 n1.type == 'node_op' && n1.value == 'op_exp') { 1100 n0.children[0].hash = this.parser.compile(n0.children[0]); 1101 n1.children[0].hash = this.parser.compile(n1.children[0]); 1102 if (n0.children[0].hash === n1.children[0].hash) { 1103 n0.children[1] = this.createNode('node_op', 'op_sub', 1104 n0.children[1], 1105 n1.children[1] 1106 ); 1107 this.mayNotBeSimplified = true; 1108 return n0; 1109 } 1110 } 1111 1112 1113 break; 1114 1115 // a^0 = 1 1116 // a^1 -> a 1117 // 1^a -> 1 1118 // 0^a -> 0: a const != 0 1119 case 'op_exp': 1120 n0 = node.children[0]; 1121 n1 = node.children[1]; 1122 if (n1.type == 'node_const' && n1.value === 0.0) { 1123 n1.value = 1.0; 1124 return n1; 1125 } 1126 if (n1.type == 'node_const' && n1.value == 1.0) { 1127 return n0; 1128 } 1129 if (n0.type == 'node_const' && n0.value == 1.0) { 1130 return n0; 1131 } 1132 if (n0.type == 'node_const' && n0.value === 0.0 && 1133 n1.type == 'node_const' && n1.value !== 0.0) { 1134 return n0; 1135 } 1136 1137 // (a^b)^c -> a^(b*c) 1138 if (n0.type == 'node_op' && n0.value == 'op_exp') { 1139 node.children = [ 1140 n0.children[0], 1141 this.createNode('node_op', 'op_mul', 1142 n0.children[1], 1143 n1) 1144 ]; 1145 return node; 1146 } 1147 break; 1148 } 1149 1150 switch (node.value) { 1151 // const_1 + const_2 -> (const_1 + const_2) 1152 // a + a -> 2*a 1153 // a + (-b) = a - b 1154 case 'op_add': 1155 n0 = node.children[0]; 1156 n1 = node.children[1]; 1157 if (n0.type == 'node_const' && n1.type == 'node_const' && 1158 n0.value == n1.value) { 1159 n0.value += n1.value; 1160 return n0; 1161 } 1162 1163 if (n0.type == 'node_var' && n1.type == 'node_var' && 1164 n0.value == n1.value) { 1165 node.children[0] = this.createNode('node_const', 2.0); 1166 node.value = 'op_mul'; 1167 return node; 1168 } 1169 1170 if (n0.type == 'node_op' && n0.value == 'op_neg') { 1171 node.value = 'op_sub'; 1172 node.children[0] = n1; 1173 node.children[1] = n0.children[0]; 1174 this.mayNotBeSimplified = true; 1175 return node; 1176 } 1177 1178 if (n1.type == 'node_op' && n1.value == 'op_neg') { 1179 node.value = 'op_sub'; 1180 node.children[1] = n1.children[0]; 1181 this.mayNotBeSimplified = true; 1182 return node; 1183 } 1184 1185 // const * a + const * a -> const * a 1186 if (n0.type == 'node_op' && n0.value == 'op_mul' && 1187 n1.type == 'node_op' && n1.value == 'op_mul') { 1188 1189 n0.children[1].hash = this.parser.compile(n0.children[1]); 1190 n1.children[1].hash = this.parser.compile(n1.children[1]); 1191 if (n0.children[1].hash === n1.children[1].hash) { 1192 1193 node.value = 'op_mul'; 1194 node.children = [ 1195 this.createNode('node_op', 'op_add', 1196 n0.children[0], 1197 n1.children[0]), 1198 n0.children[1] 1199 ]; 1200 this.mayNotBeSimplified = true; 1201 return node; 1202 } 1203 } 1204 // const * a + a -> (const + 1) * a 1205 if (n0.type == 'node_op' && n0.value == 'op_mul') { 1206 1207 n0.children[1].hash = this.parser.compile(n0.children[1]); 1208 n1.hash = this.parser.compile(n1); 1209 if (n0.children[1].hash === n1.hash) { 1210 1211 node.value = 'op_mul'; 1212 node.children = [ 1213 this.createNode('node_op', 'op_add', 1214 n0.children[0], 1215 this.createNode('node_const', 1.0)), 1216 n1 1217 ]; 1218 this.mayNotBeSimplified = true; 1219 return node; 1220 } 1221 } 1222 // a + const*a -> (const + 1) * a 1223 if (n1.type == 'node_op' && n1.value == 'op_mul') { 1224 1225 n1.children[1].hash = this.parser.compile(n1.children[1]); 1226 n0.hash = this.parser.compile(n0); 1227 if (n1.children[1].hash === n0.hash) { 1228 1229 node.value = 'op_mul'; 1230 node.children = [ 1231 this.createNode('node_op', 'op_add', 1232 this.createNode('node_const', 1.0), 1233 n1.children[0]), 1234 n0 1235 ]; 1236 this.mayNotBeSimplified = true; 1237 return node; 1238 } 1239 } 1240 1241 break; 1242 1243 // a - (-b) = a + b 1244 case 'op_sub': 1245 n0 = node.children[0]; 1246 n1 = node.children[1]; 1247 if (n1.type == 'node_op' && n1.value == 'op_neg') { 1248 node.value = 'op_add'; 1249 node.children[1] = n1.children[0]; 1250 this.mayNotBeSimplified = true; 1251 return node; 1252 } 1253 break; 1254 1255 case 'op_execfun': 1256 return this.simplifyElementary(node); 1257 } 1258 1259 return node; 1260 }, 1261 1262 simplifyElementary: function(node) { 1263 var fun = node.children[0].value, 1264 arg = node.children[1], 1265 newNode; 1266 1267 // Catch errors of the form sin() 1268 if (arg.length == 0) { 1269 return node; 1270 } 1271 1272 switch (fun) { 1273 // sin(0) -> 0 1274 // sin(PI) -> 0 1275 // sin (int * PI) -> 0 1276 // sin (PI * int) -> 0 1277 // Same for tan() 1278 case 'sin': 1279 case 'tan': 1280 if (arg[0].type == 'node_const' && arg[0].value === 0) { 1281 node.type = 'node_const'; 1282 node.value = 0.0; 1283 return node; 1284 } 1285 if (arg[0].type == 'node_var' && arg[0].value == 'PI') { 1286 node.type = 'node_const'; 1287 node.value = 0.0; 1288 return node; 1289 } 1290 if (arg[0].type == 'node_op' && arg[0].value == 'op_mul' && 1291 arg[0].children[0].type == 'node_const' && arg[0].children[0].value % 1 === 0 && 1292 arg[0].children[1].type == 'node_var' && arg[0].children[1].value == 'PI') { 1293 node.type = 'node_const'; 1294 node.value = 0.0; 1295 return node; 1296 } 1297 break; 1298 1299 // cos(0) -> 1.0 1300 // cos(PI) -> -1.0 1301 // cos(int * PI) -> +/- 1.0 1302 // cos(PI * int) -> +/- 1.0 1303 case 'cos': 1304 if (arg[0].type == 'node_const' && arg[0].value === 0) { 1305 node.type = 'node_const'; 1306 node.value = 1.0; 1307 return node; 1308 } 1309 if (arg[0].type == 'node_var' && arg[0].value == 'PI') { 1310 node.type = 'node_op'; 1311 node.value = 'op_neg'; 1312 node.children = [this.createNode('node_const', 1.0)]; 1313 return node; 1314 } 1315 /* 1316 if (arg[0].type == 'node_op' && arg[0].value == 'op_mul' && 1317 ((arg[0].children[0].type == 'node_const' && arg[0].children[0].value % 1 === 0 && 1318 arg[0].children[1].type == 'node_var' && arg[0].children[1].value == 'PI') || 1319 (arg[0].children[1].type == 'node_const' && arg[0].children[1].value % 1 === 0 && 1320 arg[0].children[0].type == 'node_var' && arg[0].children[0].value == 'PI'))) { 1321 node.type = 'node_const'; 1322 node.value = 1.0; 1323 return node; 1324 } 1325 */ 1326 break; 1327 1328 // exp(0) -> 1 1329 case 'exp': 1330 if (arg[0].type == 'node_const' && arg[0].value === 0) { 1331 node.type = 'node_const'; 1332 node.value = 1.0; 1333 return node; 1334 } 1335 break; 1336 1337 // pow(a, 0) -> 1 1338 case 'pow': 1339 if (arg[1].type == 'node_const' && arg[1].value === 0) { 1340 node.type = 'node_const'; 1341 node.value = 1.0; 1342 return node; 1343 } 1344 break; 1345 1346 } 1347 1348 return node; 1349 } 1350 1351 }); 1352 1353 return JXG.CA; 1354 }); 1355