/** * Generate LaTeX code for an ExpressionNode * * Create a string giving LaTeX code for an ExpressionNode `(x op y)` * where `op` is one of `+`, `-`, `*`, `/` or `^` * * ### Typesetting rules: * * - Adds parantheses around each operand, if needed. (I.e. if their precedence * lower than that of the current Node.) For example, the AST `(^ (+ 1 2) 3)` * generates `(1+2)^3` but `(+ (^ 1 2) 3)` generates `1^2+3` as expected. * - Multiplications are typeset implicitly `(* x y)` returns `xy` or using * `\cdot` if the first factor is a FunctionNode or the (left operand) in the * second factor is a NumberNode, so `(* x 2)` return `x \cdot 2` and `(* (sin x) x)` * return `\sin x \cdot x` (but `(* x (sin x))` returns `x\sin x`) * - Divisions are typeset using `\frac` * - Exponentiation adds braces around the power when needed. * * @param ExpressionNode $node AST to be typeset * @retval string */ public function visitExpressionNode(ExpressionNode $node) { $left = $node->getLeft(); $leftValue = $this->parenthesize($left, $node); $operator = $node->getOperator(); $right = $node->getRight(); if ($operator == '*') { $operator = ''; if ($left instanceof FunctionNode || $right instanceof NumberNode || $right instanceof IntegerNode || $right instanceof RationalNode || $right instanceof ExpressionNode && $right->getLeft() instanceof NumberNode) { $operator = '\\cdot '; } } if ($right) { $rightValue = $this->parenthesize($right, $node); switch ($operator) { case '/': // No parantheses needed return '\\frac{' . $left->accept($this) . '}{' . $right->accept($this) . '}'; case '^': return $leftValue . '^' . $this->bracesNeeded($right); default: return "{$leftValue}{$operator}{$rightValue}"; } } return "{$operator}{$leftValue}"; }
/** * Print an ExpressionNode. * * @param ExpressionNode $node */ public function visitExpressionNode(ExpressionNode $node) { $leftValue = $node->getLeft()->accept($this); $operator = $node->getOperator(); // The operator and the right side are optional, remember? if (!$operator) { return "{$leftValue}"; } $right = $node->getRight(); if ($right) { $rightValue = $node->getRight()->accept($this); return "({$operator}, {$leftValue}, {$rightValue})"; } else { return "({$operator}, {$leftValue})"; } }
/** * Evaluate an ExpressionNode * * Computes the value of an ExpressionNode `x op y` * where `op` is one of `+`, `-`, `*`, `/` or `^` * * @throws UnknownOperatorException if the operator is something other than * `+`, `-`, `*`, `/` or `^` * * @param ExpressionNode $node AST to be evaluated * @retval float */ public function visitExpressionNode(ExpressionNode $node) { $operator = $node->getOperator(); $leftValue = $node->getLeft()->accept($this); if ($node->getRight()) { $rightValue = $node->getRight()->accept($this); } else { $rightValue = null; } // Perform the right operation based on the operator switch ($operator) { case '+': return $leftValue + $rightValue; case '-': return $rightValue === null ? -$leftValue : $leftValue - $rightValue; case '*': return $rightValue * $leftValue; case '/': if ($rightValue == 0) { throw new DivisionByZeroException(); } return $leftValue / $rightValue; case '^': return pow($leftValue, $rightValue); default: throw new UnknownOperatorException($operator); } }
/** * Generate ASCII output code for an ExpressionNode * * Create a string giving ASCII output representing an ExpressionNode `(x op y)` * where `op` is one of `+`, `-`, `*`, `/` or `^` * * * @param ExpressionNode $node AST to be typeset * @retval string */ public function visitExpressionNode(ExpressionNode $node) { $left = $node->getLeft(); $leftValue = $this->parenthesize($left, $node); $operator = $node->getOperator(); $right = $node->getRight(); if ($right) { $rightValue = $this->parenthesize($right, $node); switch ($operator) { case '^': return $leftValue . '^' . $this->bracesNeeded($right); default: return "{$leftValue}{$operator}{$rightValue}"; } } return "{$operator}{$leftValue}"; }
public function testCannotEvaluateUnknownOperator() { $node = new ExpressionNode(new RationalNode(1, 1), '+', new VariableNode('x')); // We need to cheat here, since the ExpressionNode contructor already // throws an UnknownOperatorException when called with, say '%' $node->setOperator('%'); $this->setExpectedException(UnknownOperatorException::class); $this->evaluate($node); }
/** * Evaluate an ExpressionNode * * Computes the value of an ExpressionNode `x op y` * where `op` is one of `+`, `-`, `*`, `/` or `^` * * @throws UnknownOperatorException if the operator is something other than * `+`, `-`, `*`, `/` or `^` * * @param ExpressionNode $node AST to be evaluated * @retval float */ public function visitExpressionNode(ExpressionNode $node) { $operator = $node->getOperator(); $a = $node->getLeft()->accept($this); if ($node->getRight()) { $b = $node->getRight()->accept($this); } else { $b = null; } // Perform the right operation based on the operator switch ($operator) { case '+': return Complex::add($a, $b); case '-': if ($b === null) { return Complex::mul($a, -1); } return Complex::sub($a, $b); case '*': return Complex::mul($a, $b); case '/': return Complex::div($a, $b); case '^': // This needs to be improved. return Complex::pow($a, $b); default: throw new UnknownOperatorException($operator); } }
/** * Differentiate an ExpressionNode * * Using the usual rules for differentiating, create an ExpressionNode * giving an AST correctly representing the derivative `(x op y)'` * where `op` is one of `+`, `-`, `*`, `/` or `^` * * ### Differentiation rules: * * - \\( (f+g)' = f' + g' \\) * - \\( (f-g) ' = f' - g' \\) * - \\( (-f)' = -f' \\) * - \\( (f*g)' = f'g + f g' \\) * - \\( (f/g)' = (f' g - f g')/g^2 \\) * - \\( (f^g)' = f^g (g' \\log(f) + g/f) \\) with a simpler expression when g is a NumberNode * * @throws UnknownOperatorException if the operator is something other than * `+`, `-`, `*`, `/` or `^` * * @param ExpressionNode $node AST to be differentiated * @retval Node */ public function visitExpressionNode(ExpressionNode $node) { $operator = $node->getOperator(); $leftValue = $node->getLeft()->accept($this); if ($node->getRight()) { $rightValue = $node->getRight()->accept($this); } else { $rightValue = null; } // Perform the right operation based on the operator switch ($operator) { case '+': return $this->nodeFactory->addition($leftValue, $rightValue); case '-': return $this->nodeFactory->subtraction($leftValue, $rightValue); // Product rule (fg)' = fg' + f'g // Product rule (fg)' = fg' + f'g case '*': return $this->nodeFactory->addition($this->nodeFactory->multiplication($node->getLeft(), $rightValue), $this->nodeFactory->multiplication($leftValue, $node->getRight())); // Quotient rule (f/g)' = (f'g - fg')/g^2 // Quotient rule (f/g)' = (f'g - fg')/g^2 case '/': $term1 = $this->nodeFactory->multiplication($leftValue, $node->getRight()); $term2 = $this->nodeFactory->multiplication($node->getLeft(), $rightValue); $numerator = $this->nodeFactory->subtraction($term1, $term2); $denominator = $this->nodeFactory->exponentiation($node->getRight(), new IntegerNode(2)); return $this->nodeFactory->division($numerator, $denominator); // f^g = exp(g log(f)), so (f^g)' = f^g (g'log(f) + g/f) // f^g = exp(g log(f)), so (f^g)' = f^g (g'log(f) + g/f) case '^': $base = $node->getLeft(); $exponent = $node->getRight(); if ($exponent instanceof IntegerNode || $exponent instanceof NumberNode) { $power = $exponent->getValue(); $fpow = $this->nodeFactory->exponentiation($base, $power - 1); return $this->nodeFactory->multiplication($power, $this->nodeFactory->multiplication($fpow, $leftValue)); } elseif ($exponent instanceof RationalNode) { $newPower = new RationalNode($exponent->getNumerator() - $exponent->getDenominator(), $exponent->getDenominator()); $fpow = $this->nodeFactory->exponentiation($base, $newPower); return $this->nodeFactory->multiplication($exponent, $this->nodeFactory->multiplication($fpow, $leftValue)); } else { $term1 = $this->nodeFactory->multiplication($rightValue, new FunctionNode('log', $node->getLeft())); $term2 = $this->nodeFactory->division($node->getRight(), $node->getLeft()); $factor2 = $this->nodeFactory->addition($term1, $term2); return $this->nodeFactory->multiplication($node, $factor2); } default: throw new UnknownOperatorException($operator); } }
public function parenthesize(Node $node, ExpressionNode $cutoff, $prepend = '', $conservative = false) { $text = $node->accept($this); if ($node instanceof ExpressionNode) { // Second term is a unary minus if ($node->getOperator() == '-' && $node->getRight() == null) { return "({$text})"; } if ($cutoff->getOperator() == '-' && $node->lowerPrecedenceThan($cutoff)) { return "({$text})"; } if ($conservative) { // Add parentheses more liberally for / and ^ operators, // so that e.g. x/(y*z) is printed correctly if ($cutoff->getOperator() == '/' && $node->lowerPrecedenceThan($cutoff)) { return "({$text})"; } if ($cutoff->getOperator() == '^' && $node->getOperator() == '^') { return "({$text})"; } } if ($node->strictlyLowerPrecedenceThan($cutoff)) { return "({$text})"; } } if (($node instanceof NumberNode || $node instanceof IntegerNode || $node instanceof RationalNode) && $node->getValue() < 0) { return "({$text})"; } // Treat rational numbers as divisions on printing if ($node instanceof RationalNode && $node->getDenominator() != 1) { $fakeNode = new ExpressionNode($node->getNumerator(), '/', $node->getDenominator()); if ($fakeNode->lowerPrecedenceThan($cutoff)) { return "({$text})"; } } return "{$prepend}{$text}"; }
/** * Evaluate an ExpressionNode * * Computes the value of an ExpressionNode `x op y` * where `op` is one of `+`, `-`, `*`, `/` or `^` * * @throws UnknownOperatorException if the operator is something other than * `+`, `-`, `*`, `/` or `^` * * @param ExpressionNode $node AST to be evaluated * @retval float */ public function visitExpressionNode(ExpressionNode $node) { $operator = $node->getOperator(); $a = $node->getLeft()->accept($this); if ($node->getRight()) { $b = $node->getRight()->accept($this); } else { $b = null; } // Perform the right operation based on the operator switch ($operator) { case '+': $p = $a->getNumerator() * $b->getDenominator() + $a->getDenominator() * $b->getNumerator(); $q = $a->getDenominator() * $b->getDenominator(); return new RationalNode($p, $q); case '-': if ($b === null) { return new RationalNode(-$a->getNumerator(), $a->getDenominator()); } $p = $a->getNumerator() * $b->getDenominator() - $a->getDenominator() * $b->getNumerator(); $q = $a->getDenominator() * $b->getDenominator(); return new RationalNode($p, $q); case '*': $p = $a->getNumerator() * $b->getNumerator(); $q = $a->getDenominator() * $b->getDenominator(); return new RationalNode($p, $q); case '/': if ($b->getNumerator() == 0) { throw new DivisionByZeroException(); } $p = $a->getNumerator() * $b->getDenominator(); $q = $a->getDenominator() * $b->getNumerator(); return new RationalNode($p, $q); case '^': return $this->rpow($a, $b); default: throw new UnknownOperatorException($operator); } }
public function testCanCompareExpressionNodes() { $node = new ExpressionNode(new VariableNode('x'), '+', new VariableNode('y')); $node2 = new ExpressionNode(new VariableNode('x'), '-', new VariableNode('y')); $node3 = new ExpressionNode(new VariableNode('x'), '-', null); $node4 = new ExpressionNode(null, '-', new VariableNode('y')); $other = new VariableNode('x'); $this->assertFalse($node->compareTo(null)); $this->assertFalse($node->compareTo($other)); $this->assertTrue($node->compareTo($node)); $this->assertTrue($node2->compareTo($node2)); $this->assertTrue($node3->compareTo($node3)); $this->assertTrue($node4->compareTo($node4)); $this->assertFalse($node->compareTo($node2)); $this->assertFalse($node->compareTo($node3)); $this->assertFalse($node->compareTo($node4)); $this->assertFalse($node2->compareTo($node3)); $this->assertFalse($node2->compareTo($node4)); $this->assertFalse($node3->compareTo($node4)); $this->assertFalse($node2->compareTo($node4)); }
/** * Simplify the given ExpressionNode, using the appropriate factory. * * @param ExpressionNode $node * @retval Node Simplified version of the input */ public function simplify(ExpressionNode $node) { switch ($node->getOperator()) { case '+': return $this->addition($node->getLeft(), $node->getRight()); case '-': return $this->subtraction($node->getLeft(), $node->getRight()); case '*': return $this->multiplication($node->getLeft(), $node->getRight()); case '/': return $this->division($node->getLeft(), $node->getRight()); case '^': return $this->exponentiation($node->getLeft(), $node->getRight()); } }
public function testCanCreateTemporaryUnaryMinusNode() { $node = new ExpressionNode(null, '~', null); $this->assertEquals($node->getOperator(), '~'); $this->assertNull($node->getRight()); $this->assertNull($node->getLeft()); $this->assertEquals($node->getPrecedence(), 25); }