Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

First release

git-svn-id: http://svn.php.net/repository/pear/packages/Math_Derivative/trunk@207265 c90b9560-bf6c-de11-be94-00142212c4b1
  • Loading branch information...
commit 8d425f44fe0cf33e045f4a222b4884d23f6989de 1 parent d0b9d01
Etienne Kneuss colder authored

Showing 2 changed files with 754 additions and 0 deletions. Show diff stats Hide diff stats

  1. +723 0 Derivative.php
  2. +31 0 package.xml
723 Derivative.php
... ... @@ -0,0 +1,723 @@
  1 +<?php
  2 +
  3 +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4 +
  5 +/**
  6 + * Main file of the Math_Derivative PEAR Package
  7 + *
  8 + * PHP version 5
  9 + *
  10 + * LICENSE: This source file is subject to version 3.0 of the PHP license
  11 + * that is available through the world-wide-web at the following URI:
  12 + * http://www.php.net/license/3_0.txt. If you did not receive a copy of
  13 + * the PHP License and are unable to obtain it through the web, please
  14 + * send a note to license@php.net so we can mail you a copy immediately.
  15 + *
  16 + * @category Math
  17 + * @package Math_Derivative
  18 + * @author Etienne Kneuss <colder@php.net>
  19 + * @copyright 1997-2005 The PHP Group
  20 + * @license http://www.php.net/license/3_0.txt PHP License 3.0
  21 + * @version CVS: $Id$
  22 + * @link http://pear.php.net/package/Math_Derivative
  23 + */
  24 +
  25 +// {{{ Math_Derivative
  26 +
  27 +/**
  28 + * This class allows you to calculate the derivative of a mathematical expression.
  29 + *
  30 + * Notice that expressions that are passed to it won't always be valid php syntax :
  31 + * "a^b" means "a raised to the power of b" for Math_Derivative and not the usual
  32 + * bitwise XOR.
  33 + *
  34 + * That's is important to know, especially if you plan to evaluate the expression
  35 + * returned using php's own eval() e.g eval($myobject->getDerivative(...));
  36 + *
  37 + * Math_Derivative::getDerivative() will also work on literal expressions.
  38 + *
  39 + * Some functions are already implemented, so you can use them in the input:
  40 + * sin(), cos(), tan(), ln(), log(), e().
  41 + * Use Math_Derivative::registerFunction() to define your own functions.
  42 + *
  43 + * A cache management is already bundled to save intermediary results of the whole
  44 + * object, it provides a significant speed boost, especially when used with
  45 + * repetitive expressions. It is also allowed to save the cache and reuse it in
  46 + * another instance of the class. Its use is optional though.
  47 + *
  48 + *
  49 + * @category Math
  50 + * @package Math_Derivative
  51 + * @author Etienne Kneuss <colder@php.net>
  52 + * @copyright 1997-2005 The PHP Group
  53 + * @license http://www.php.net/license/3_0.txt PHP License 3.0
  54 + * @version Release: @package_version@
  55 + * @link http://pear.php.net/package/Math_Derivative
  56 + */
  57 +
  58 +class Math_Derivative {
  59 + // {{{ properties
  60 +
  61 + /**
  62 + * The variable on which the expression depends
  63 + *
  64 + * @var string
  65 + * @access protected
  66 + * @see Math_Derivative::definedx()
  67 + */
  68 + protected $_d = 'x';
  69 +
  70 + /**
  71 + * The operators' precedences table. It's not ment to change.
  72 + *
  73 + * @var array
  74 + * @access protected
  75 + */
  76 + protected $_operatorsPrecedences = array('+' => 1, '-' => 1, '*' => 2, '/' => 3, '^' => 4);
  77 +
  78 + /**
  79 + * The array that contains already calculated intermediary expressions
  80 + *
  81 + * @var array
  82 + * @access protected
  83 + */
  84 + protected $_cache = array();
  85 +
  86 + /**
  87 + * Whether to use or not the cache
  88 + *
  89 + * @var boolean
  90 + * @access protected
  91 + */
  92 + protected $_useCache = true;
  93 +
  94 + /**
  95 + * Contains derivative forms of functions
  96 + * that could appear in expressions
  97 + *
  98 + * @var array
  99 + * @access protected
  100 + */
  101 + protected $_registeredFunctions = array('sin' => 'cos(arg)*d(arg)',
  102 + 'cos' => '-sin(arg)*d(arg)',
  103 + 'tan' => '1/cos(arg)^2*d(arg)',
  104 + 'ln' => 'd(arg)/(arg)',
  105 + 'log' => 'd(arg)/(arg)',
  106 + 'e' => 'd(arg)*e(arg)');
  107 +
  108 + // }}}
  109 + // {{{ definedx([(string) $value])
  110 +
  111 + /**
  112 + * Defines the variable on which the expression depends.
  113 + *
  114 + * Only alphabetical characters are allowed. It haven't to be
  115 + * a single char.
  116 + *
  117 + * @param string $value the string to quote
  118 + *
  119 + * @return boolean Whether the change was effective
  120 + *
  121 + * @access protected
  122 + * @see Math_Derivative::getDerivative()
  123 + */
  124 +
  125 + protected function definedx($value = 'x')
  126 + {
  127 + if (!ctype_alpha($value) && !empty($value)) {
  128 + return false;
  129 + }
  130 +
  131 + if ($this->_useCache && !empty($this->_cache) && $value !== $this->_d) {
  132 + // restore the cache to avoid cache corruption
  133 + $this->resetCache();
  134 + }
  135 +
  136 + $this->_d = $value;
  137 + return true;
  138 + }
  139 +
  140 + // }}}
  141 + // {{{ useCache((bool) $flag)
  142 +
  143 + /**
  144 + * Sets if Math_Derivative have to use the caching system.
  145 + *
  146 + *
  147 + * @param boolean $flag
  148 + *
  149 + * @return boolean true
  150 + *
  151 + * @access public
  152 + */
  153 +
  154 + public function useCache($flag)
  155 + {
  156 + $this->_useCache = (bool)$flag;
  157 +
  158 + return true;
  159 + }
  160 +
  161 + // }}}
  162 + // {{{ resetCache([(array) $cache])
  163 +
  164 + /**
  165 + * Restores/resets the cache.
  166 + *
  167 + *
  168 + * @param array $cache Old cache to restore.
  169 + *
  170 + * @return boolean Whether the old cache was accepted
  171 + *
  172 + * @access public
  173 + */
  174 +
  175 + public function resetCache($cache = array())
  176 + {
  177 + if (!is_array($cache)) {
  178 + return false;
  179 + }
  180 +
  181 + $this->_cache = $cache;
  182 + return true;
  183 + }
  184 +
  185 + // }}}
  186 + // {{{ getCache()
  187 +
  188 + /**
  189 + * Returns the cache
  190 + *
  191 + *
  192 + * @return array the current cache
  193 + *
  194 + * @access public
  195 + */
  196 +
  197 + public function getCache()
  198 + {
  199 + return $this->_cache;
  200 + }
  201 +
  202 + // }}}
  203 + // {{{ getDerivative((string)$expression, (string) $d [, (int) $level = 1])
  204 +
  205 + /**
  206 + * Calculates the derivative of $expression with respect to $d, taken $level times
  207 + *
  208 + *
  209 + * @param string $expression expression of which you want to get the derivative
  210 + * @param string $d variable on which the expression depends
  211 + * @param int $level level of the derivative you want.
  212 + *
  213 + * @return string the derivative
  214 + *
  215 + * @access public
  216 + */
  217 +
  218 + public function getDerivative ($expression, $d, $level = 1)
  219 + {
  220 +
  221 + $this->definedx($d);
  222 +
  223 +
  224 + for($i = 0; $i < $level; $i++) {
  225 + $expression = $this->parse($expression);
  226 + }
  227 + return $this->cleanExpression($expression);
  228 + }
  229 +
  230 + // }}}
  231 + // {{{ cleanExpression((string)$expression)
  232 +
  233 + /**
  234 + * Cleans the expression to make the parser's job easier.
  235 + *
  236 + *
  237 + * @param string $expression expression you want to clean
  238 + *
  239 + * @return string cleaned expression
  240 + *
  241 + * @access protected
  242 + */
  243 +
  244 + public function cleanExpression($expression)
  245 + {
  246 +
  247 + // clean surrounding whitespaces
  248 + $expression = trim($expression);
  249 +
  250 + $deep = 0;
  251 + $deeps = array();
  252 +
  253 + // clean surounding parenthesis
  254 +
  255 + for ($i = 0; $i < strlen($expression); $i ++) {
  256 + $char = $expression[$i];
  257 +
  258 + if ($char === '(') {
  259 + $deep++;
  260 + continue;
  261 + }
  262 + if ($char === ')') {
  263 + $deep--;
  264 + continue;
  265 + }
  266 + if (!empty($deeps[$deep])) {
  267 + continue;
  268 + }
  269 +
  270 + $deeps[$deep] = true;
  271 +
  272 + }
  273 +
  274 + $num_to_delete = min(array_keys($deeps));
  275 +
  276 + if ($num_to_delete > 0 ) {
  277 + $expression = substr($expression, $num_to_delete, -$num_to_delete);
  278 + }
  279 +
  280 + return $expression;
  281 +
  282 + }
  283 +
  284 + // }}}
  285 + // {{{ parse((string)$expression)
  286 +
  287 + /**
  288 + * Parses the expression and recursively calculates its derivative
  289 + *
  290 + *
  291 + * @param string $expression expressionyou want to parse
  292 + *
  293 + * @return string the derivative
  294 + *
  295 + * @access protected
  296 + */
  297 + protected function parse($expression)
  298 + {
  299 +
  300 + $expression = $this->cleanExpression($expression);
  301 +
  302 + $initial_expression = $expression;
  303 +
  304 + // if the expression doesn't rely on dx -> 0
  305 + if (!$this->reliesOndx($expression)) {
  306 + return '0';
  307 + }
  308 +
  309 + // checks if it already exists in the cache
  310 + if ($this->_useCache && isset($this->_cache[$initial_expression])) {
  311 + return $this->_cache[$initial_expression];
  312 + }
  313 +
  314 + // begins parsing
  315 + $deepness = 0;
  316 +
  317 + // array containing the positions of the main operators
  318 + $main_operators = array('precedence' => 0, 'positions' => array());
  319 +
  320 + for ($i=0; $i < strlen($expression); $i++) {
  321 + $char = $expression[$i];
  322 +
  323 + if ($char == '(') {
  324 + $deepness++;
  325 + continue;
  326 + }
  327 +
  328 + if ($char == ')') {
  329 + $deepness--;
  330 + continue;
  331 + }
  332 +
  333 + if ($deepness) {
  334 + continue;
  335 + }
  336 +
  337 +
  338 + if (isset($this->_operatorsPrecedences[$char])) {
  339 + // current char is an operator
  340 + if ($main_operators['precedence'] == $this->_operatorsPrecedences[$char]) {
  341 + // same type that the main operators : add its position to the list.
  342 + $main_operators['positions'][] = array($char, $i);
  343 + } else if ($main_operators['precedence'] > $this->_operatorsPrecedences[$char] || !$main_operators['precedence']) {
  344 + // lower precedence than the list : this operator becomes a main operator
  345 + $main_operators = array('precedence' => $this->_operatorsPrecedences[$char],
  346 + 'positions' => array(array($char, $i)));
  347 + }
  348 +
  349 + }
  350 + }
  351 + unset($deepness);
  352 +
  353 + // splits the expression using operators' positions
  354 + $pos = 0;
  355 + $expression_parts = array();
  356 + foreach ($main_operators['positions'] as $operator) {
  357 + $expression_parts[] = substr($expression, $pos, $operator[1]-$pos);
  358 + $expression_parts[] = $operator[0];
  359 + $pos = $operator[1]+1;
  360 + }
  361 + $expression_parts[] = substr($expression, $pos);
  362 +
  363 + unset($pos);
  364 +
  365 + // dispatchs to the rule corresponding to the main operator
  366 + switch ($main_operators['precedence']) {
  367 +
  368 + case 1 : // + -
  369 + $expression = $this->ruleAddition($expression_parts);
  370 + break;
  371 +
  372 + case 2 : // *
  373 + $expression = $this->ruleMultiplication($expression_parts);
  374 + break;
  375 +
  376 + case 3: // /
  377 + $expression = $this->ruleDivision($expression_parts);
  378 + break;
  379 +
  380 + case 4: // ^
  381 + $expression = $this->rulePower($expression_parts);
  382 + break;
  383 +
  384 + default: // term
  385 + $expression = $this->ruleTerm($expression);
  386 +
  387 +
  388 + }
  389 +
  390 + // put in cache
  391 + if ($this->_useCache) {
  392 + $this->_cache[$initial_expression] = $expression;
  393 + }
  394 +
  395 + return $expression;
  396 + }
  397 +
  398 + // }}}
  399 + // {{{ reliesOndx((string)$expression)
  400 +
  401 + /**
  402 + * Checks whether the expression relies on d?
  403 + *
  404 + *
  405 + * @param string $expression expression you want to check
  406 + *
  407 + * @return boolean whether $expression relies on d?
  408 + *
  409 + * @access protected
  410 + */
  411 +
  412 + protected function reliesOndx($expression)
  413 + {
  414 + return (bool) preg_match('/\b'.$this->_d.'\b/', $expression);
  415 + }
  416 +
  417 + // }}}
  418 + // {{{ ruleAddition((array)$parts)
  419 +
  420 + /**
  421 + * Apply the rule of additions
  422 + *
  423 + * scheme : (a+b-c)' = a' + b' - c'
  424 + *
  425 + *
  426 + * @param array $parts parts of the expression
  427 + *
  428 + * @return string expression with the rule applied
  429 + *
  430 + * @access protected
  431 + */
  432 +
  433 + protected function ruleAddition($parts)
  434 + {
  435 +
  436 + $return_value = '';
  437 +
  438 + foreach ($parts as $i=>$part) {
  439 +
  440 + if ($i&1) {
  441 + if (strlen($return_value) || $part == '-') {
  442 + $return_value .= $part;
  443 + }
  444 + continue;
  445 + }
  446 +
  447 + $derivative = $this->parse($part);
  448 +
  449 + if (empty($derivative)) {
  450 + if (strlen($return_value)) {
  451 + $return_value = substr($return_value, 0, -1);
  452 + }
  453 + continue;
  454 + }
  455 +
  456 + $return_value .= $derivative;
  457 + }
  458 +
  459 + return '('.$return_value.')';
  460 +
  461 + }
  462 +
  463 + // }}}
  464 + // {{{ ruleMultiplication((array)$parts)
  465 +
  466 + /**
  467 + * Apply the rule of multiplications
  468 + *
  469 + * scheme : (a*b*c)' = a'*b*c + b'*a*c + c'*a*b
  470 + *
  471 + *
  472 + * @param array $parts parts of the expression
  473 + *
  474 + * @return string expression with the rule applied
  475 + *
  476 + * @access protected
  477 + */
  478 +
  479 + protected function ruleMultiplication($parts)
  480 + {
  481 +
  482 + $terms = array();
  483 +
  484 + foreach ($parts as $i=>$part) {
  485 + if ($i&1) {
  486 + continue;
  487 + }
  488 +
  489 + $pieces = array();
  490 + $numeric_factor = 1;
  491 +
  492 +
  493 +
  494 + foreach ($parts as $j=>$otherPart) {
  495 + if ($i == $j || $j&1 || $otherPart === '1') {
  496 + continue;
  497 + }
  498 +
  499 + if ($otherPart === '0') {
  500 + // a factor is = 0 -> whole term ignored
  501 + continue 2;
  502 + }
  503 +
  504 + if (is_numeric($otherPart)) {
  505 + $numeric_factor *= $otherPart;
  506 + } else {
  507 + $pieces[] = $otherPart;
  508 + }
  509 + }
  510 +
  511 +
  512 + $deriv = $this->parse($part);
  513 +
  514 + if ($deriv === '0') {
  515 + // deriv = 0 -> whole term ignored
  516 + continue 1;
  517 + }
  518 +
  519 + if (is_numeric($deriv)) {
  520 + $numeric_factor *= $deriv;
  521 + } else {
  522 + $pieces[] = $deriv;
  523 + }
  524 +
  525 +
  526 +
  527 + if ($numeric_factor != 1) {
  528 + array_unshift($pieces, $numeric_factor);
  529 + }
  530 +
  531 + $terms[] = implode('*', $pieces);
  532 + }
  533 +
  534 +
  535 + return '('.implode('+', $terms).')';
  536 +
  537 + }
  538 +
  539 + // }}}
  540 + // {{{ ruleDivision((array)$parts)
  541 +
  542 + /**
  543 + * Apply the rule of divisions
  544 + *
  545 + * scheme : (a/b/c)' = ((a/b)'/c)'
  546 + * (a/b)' = (a'*b - b'*a) / b*b
  547 + *
  548 + *
  549 + * @param array $parts parts of the expression
  550 + *
  551 + * @return string expression with the rule applied
  552 + *
  553 + * @access protected
  554 + */
  555 +
  556 + protected function ruleDivision($parts)
  557 + {
  558 +
  559 + if (count($parts) > 3) {
  560 +
  561 + $last_element = array_pop($parts); // save the last term
  562 + array_pop($parts); // delete the last /
  563 +
  564 + $parts = array(implode('', $parts), '/', $last_element);
  565 + return $this->ruleDivision($parts);
  566 + } else {
  567 + return '('.$this->parse($parts[0]).'*'.$parts[2].'-'.$this->parse($parts[2]).'*'.$parts[0].')/('.$parts[2].'*'.$parts[2].')';
  568 + }
  569 +
  570 + }
  571 +
  572 + // }}}
  573 + // {{{ rulePower((array)$parts)
  574 +
  575 + /**
  576 + * Apply the rule of powers
  577 + *
  578 + * scheme : (a^b^c) = ((a^b)^c)
  579 + * (a^b)' => 1) b doesn't rely on dx -> a' * b * a^(b-1)
  580 + * 2) a and b rely on dx -> a^b * ((a'*b)/a + b'*log(a))
  581 + *
  582 + *
  583 + * @param array $parts parts of the expression
  584 + *
  585 + * @return string expression with the rule applied
  586 + *
  587 + * @access protected
  588 + */
  589 +
  590 + protected function rulePower($parts)
  591 + {
  592 +
  593 + if (count($parts) > 3) {
  594 +
  595 + $last_element = array_pop($parts); // save the last term
  596 + array_pop($parts); // delete the last ^
  597 +
  598 + $parts = array(implode('', $parts), '^', $last_element);
  599 + return $this->rulePower($parts);
  600 +
  601 + } else {
  602 +
  603 + if (!$this->reliesOndx($parts[2])) {
  604 + // a' * b * a^(b-1)
  605 +
  606 + $lterm = '*'.$parts[0];
  607 +
  608 + if (is_numeric($parts[2])) {
  609 + $exp = ($parts[2]-1);
  610 + if ($exp == 0) {
  611 + $lterm = '';
  612 + } elseif ($exp > 1) {
  613 + $lterm .= '^'.$exp;
  614 + } else if ($exp < 0) {
  615 + $lterm .= '^('.$exp.')';
  616 + }
  617 +
  618 +
  619 + } else {
  620 + $lterm .= '^('.$parts[2].'-1)';
  621 + }
  622 + return $this->parse($parts[0]).'*'.$parts[2].$lterm;
  623 +
  624 + } else {
  625 + // a^b * ((a'*b)/a + b'*log(a))
  626 + return $parts[0].'^'.$parts[2].'* (('.$parts[2].'*'.$this->parse($parts[0]).')/'.$parts[0] .'+ '.$this->parse($parts[2]).'*log('.$parts[0].'))';
  627 + }
  628 +
  629 +
  630 + }
  631 +
  632 + }
  633 +
  634 + // }}}
  635 + // {{{ registerFunction((string)$name, (string)$derivative)
  636 +
  637 + /**
  638 + * Registers a function to be used in the input
  639 + * e.g. $object->registerFunction('test', 'd(arg)*arg')
  640 + * arg := the argument of the function
  641 + * d() := derivative
  642 + *
  643 + * @param string $name name of the function
  644 + * @param string $derivative derivative form of it
  645 + *
  646 + * @return bool true
  647 + *
  648 + * @access public
  649 + */
  650 +
  651 + public function registerFunction($name, $derivative)
  652 + {
  653 + $this->_registeredFunctions[$name] = $derivative;
  654 + return true;
  655 + }
  656 +
  657 + // }}}
  658 + // {{{ getDerivativeCallback((array)$match)
  659 +
  660 + /**
  661 + * Callback used in preg_replace_callback as a recursive way to derivate nested d()'s
  662 + *
  663 + *
  664 + * @param array $match parts of the expression
  665 + *
  666 + * @return string expression with the rule applied
  667 + *
  668 + * @access protected
  669 + */
  670 +
  671 + protected function getDerivativeCallback($match)
  672 + {
  673 + $expression = preg_replace_callback('/d\s*(\(((?:[^()]+|(?1))+)\))/', array(&$this, 'getDerivativeCallback'), $match[2]);
  674 +
  675 + return $this->parse($expression);
  676 +
  677 + }
  678 +
  679 + // }}}
  680 + // {{{ ruleTerm((string)$part)
  681 +
  682 + /**
  683 + * Checks is the term contains a function or if its the variable itself
  684 + *
  685 + *
  686 + * @param string $part expression
  687 + *
  688 + * @return string expression with the rule applied
  689 + *
  690 + * @access protected
  691 + */
  692 + protected function ruleTerm($part)
  693 + {
  694 +
  695 + if ($part == $this->_d) {
  696 + return 1;
  697 + } else if (preg_match('/^(\w+)\s*(\(((?:[^()]+|(?2))+)\))$/', $part, $match)) {
  698 + // function detected
  699 +
  700 + if (isset($this->_registeredFunctions[$match[1]])) {
  701 +
  702 + // use the derivative form of a registered function
  703 + $derivative = $this->_registeredFunctions[$match[1]];
  704 +
  705 + // push $match[3] on 'arg'
  706 + $derivative = preg_replace('/\barg\b/', $match[3], $derivative);
  707 +
  708 + // recursively evaluates d()
  709 + $derivative = preg_replace_callback('/d\s*(\(((?:[^()]+|(?1))+)\))/', array(&$this, 'getDerivativeCallback'), $derivative);
  710 +
  711 + return $derivative;
  712 + }
  713 +
  714 + }
  715 + return '('.$part.')\'';
  716 + }
  717 +
  718 + // }}}
  719 +}
  720 +
  721 +// }}}
  722 +?>
  723 +
31 package.xml
... ... @@ -0,0 +1,31 @@
  1 +<?xml version="1.0" encoding="ISO-8859-1" ?>
  2 +<!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0">
  3 +<package version="1.0">
  4 + <name>Math_Derivative</name>
  5 + <summary>Calculate the derivative of a mathematical expression</summary>
  6 + <description>This class allows you to calculate the derivative of an expression stored in a string.</description>
  7 + <maintainers>
  8 + <maintainer>
  9 + <user>colder</user>
  10 + <name>Etienne Kneuss</name>
  11 + <email>colder@php.net</email>
  12 + <role>lead</role>
  13 + </maintainer>
  14 + </maintainers>
  15 + <release>
  16 + <version>0.1</version>
  17 + <date>2006-02-15</date>
  18 + <license>PHP License</license>
  19 + <state>alpha</state>
  20 + <filelist>
  21 + <file role="php" baseinstalldir="Math" name="Derivative.php"/>
  22 + </filelist>
  23 + </release>
  24 + <changelog>
  25 + <release>
  26 + <version>0.1</version>
  27 + <date>2006-02-15</date>
  28 + <notes>First version</notes>
  29 + </release>
  30 + </changelog>
  31 +</package>

0 comments on commit 8d425f4

Please sign in to comment.
Something went wrong with that request. Please try again.