New Controllers added. Added Charts

This commit is contained in:
2017-09-19 22:01:00 +02:00
parent ccc5b07ddf
commit 930311b550
400 changed files with 30686 additions and 8 deletions

168
Laravel/vendor/jlawrence/eos/README.md vendored Normal file
View File

@@ -0,0 +1,168 @@
# EOS
[![Build Status](https://travis-ci.org/jlawrence11/eos.svg?branch=master)](https://travis-ci.org/jlawrence11/eos)
[![Latest Stable Version](https://poser.pugx.org/jlawrence/eos/v/stable.svg)](https://packagist.org/packages/jlawrence/eos)
[![Latest Unstable Version](https://poser.pugx.org/jlawrence/eos/v/unstable.svg)](https://packagist.org/packages/jlawrence/eos)
[![Total Downloads](https://poser.pugx.org/jlawrence/eos/downloads.svg)](https://packagist.org/packages/jlawrence/eos)
[![License](https://poser.pugx.org/jlawrence/eos/license.svg)](https://packagist.org/packages/jlawrence/eos)
[![Code Climate](https://codeclimate.com/github/jlawrence11/eos/badges/gpa.svg)](https://codeclimate.com/github/jlawrence11/eos)
[![Test Coverage](https://codeclimate.com/github/jlawrence11/eos/badges/coverage.svg)](https://codeclimate.com/github/jlawrence11/eos)
## Installation
Install EOS with [Composer](https://getcomposer.org/)
Add the dependency:
```json
"require": {
"jlawrence/eos": "3.*"
}
```
Run `composer update` and you're done.
## Equation Operating System
### jlawrence\eos\
This class makes it incredibly easy to use and parse/solve equations in
your own applications. __NOTE__ ALL of the functions within
these classes are static. It is also important to note that these
classes throw exceptions if running in to errors, please read the beginning
of the `Math.php` file for the defines of the exceptions thrown. Exceptions
includes a descriptive message of the error encountered and within `Parser` will
also typically include the full equation used.
#### Parser
This class has one important function, `Parser::solve()` which does all the legwork,
so we'll start there and end with examples.
use jlawrence\eos\Parser;
##### solve($infix, $variables)
To use this function:
$value = Parser::solve($eq, $vars);
###### _$infix_
Is simply a standard equation with variable support.
Example Equations:
2(4x)
5+((1+2)*4)+3
5+4(1+2)+3
10*sin(x)
10*cos(x)
The parser has good implied multiplication.
###### _$variables_
The variables are fairly simple to understand. If it contains a scalar (ie
a non-array value) _every_ variable within the equation will be replaced with
that number. If it contains an array, there will be a by-variable replacement -
note that the array MUST be in the format of `'variable' => value`
Such as:
array(
'x' => 2,
'y' => 3
);
Given the equation:
5x^y
If this is called by:
Parser::solveIF('5x^y', 2);
It will equal '20', as every variable is replaced by 2. However, if called like:
Parser::solveIF('5x^y', array(
'x' => 2,
'y' => 3));
You will get the result of '40' as it would equate to `5*2^3`, as expected.
#### jlawrence\eos\Graph
To use:
use jlawrence\eos\Graph;
This is the fun class that can create graphs.
The image will default to 640x480, to initialize a different size use:
Graph::init($width, $height);
The `$width` and `$height` are the values used for the image size.
##### graph($eq, $xLow, $xHigh, [$xStep, $xyGrid, $yGuess, ...])
This method will generate the graph for the equation (`$eq`) with a min and max
`x` range that it will parse through. All Variables explained:
* `$eq`
The Standard Equation to use. _Must_ have a variable in it. (ie `x`)
* `$xLow`
The starting point for the calculations - the left side of the graph.
* `$xHigh`
The last point calculated for the variable - the right side of the graph.
* `$xStep`
Stepping point for the variable. Set to null/false to use the smart xStep feature within the graph class.
* `$xyGrid = false`
Show `x/y` gridlines on the graph. Defaults to false. Each grid line is set at an integer, with a max of 30 lines, so it will calculate the stepping for it. When the grid is show, the lines are labeled along the top and left side of the image.
* `$yGuess = true`
Guess the Lower and Upper `y-bounds` (The bottom and top of the image
respectively.) This will set the the bounds to the lowest `y` value
encountered for the `$yLow`, and the largest `y` value for `$yHigh`.
* `$yLow = null`
Lower bound for `y`. Will be reset if a lower value for `y` is found if `$yGuess` is true.
* `$yHigh = null`
Upper bound for `y`. Will be reset if a larger `y` value is found if `$yGuess` is true.
If you don't want the axis' labeled with their numbers, you can turn off the default behavior with:
Graph::$labelAxis = false;
TODO:
* Allow user-defined colors for all aspects of the graph.
To set up a graph with a `21x21` window (ie `-10 to 10`) for the equation
`sin(x)` and output as PNG, would use as:
Graph::graph('sin(x)', -10, 10, 0.01, true, false, -10, 10);
Graph::outPNG();
It would look like:
![Sin(x)](http://s6.postimg.org/nm7tcj8lt/eos3.png)
## Development
### Testing
Run the unit tests by first installing phpunit with (from the repository root)
```
composer update
```
Then run the tests with
```
phpunit
```
---
When creating classes for adding functions to the package, make sure to call
`Parser::solveIF()` instead of `Parser::solve()` so that the class retains
the full original equation used by the user.
---

View File

@@ -0,0 +1,22 @@
{
"name": "jlawrence/eos",
"description": "Parse and solve math equations without using 'eval()'.",
"license": "LGPL-2.1+",
"keywords": ["EOS","equations","math","solve"],
"authors": [
{
"name": "Jon Lawrence",
"email": "jon@jon-lawrence.com"
}
],
"require": {},
"require-dev": {
"phpunit/phpunit": "4.*",
"codeclimate/php-test-reporter": "dev-master"
},
"autoload": {
"psr-4": {
"jlawrence\\eos\\": "src/"
}
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Created by: Jon Lawrence on 2015-07-13 8:42 AM
*/
namespace jlawrence\eos;
class AdvancedFunctions
{
/**
* Create a list for the parser of 'expressiveFunction' => 'class::function'
*
* @return array
*/
public static function map()
{
$ret = array(
'sum' => __NAMESPACE__ .'\AdvancedFunctions::sum',
'log' => __NAMESPACE__ .'\AdvancedFunctions::log'
);
return $ret;
}
/**
* Summation function
*
* Will take an equation and run it through a summation algorithm. All parts
* of the input can be in equation form, so the start and stops can have
* equations to determine what they should be using the globally inputted
* variables from the user.
*
* @param string $input String in the form of "equation, start, stop"
* @param array $vars Array of variables used for solving the current equation.
* @return float The summation of the equation
*/
public static function sum($input, $vars)
{
//remove whitespace
$input = preg_replace("/\s/", "", $input);
//split in to parts
list($eq, $start, $stop) = explode(",", $input);
$ret = 0;
//make sure there's a variable, or return equation as-is
if((Parser::solveIF($eq,0)) == preg_replace("/[\(\)]/", "", $eq)) {
return $eq;
}
$start = Parser::solveIF($start, $vars);
$stop = Parser::solveIF($stop, $vars);
for($i=$start; $i <= $stop; $i++) {
$ret += Parser::solveIF($eq, $i);
}
return $ret;
}
/**
* Log function for all non-natural logs. Defaults to base 10
*
* @param $input
* @param array $vars Variable replacement
* @return float
* @throws \Exception
*/
public static function log($input, $vars)
{
$base = 10;
if(stripos($input, ",")) {
list($eq, $base) = explode(",", $input);
} else {
$eq = $input;
}
//Make sure no functions or operators are hidden inside
$sc = Parser::solveIF($eq, $vars);
if(10 != $base) {
$base = Parser::solveIF($base, $vars);
}
$ans = log($sc, $base);
if(is_nan($ans) || is_infinite($ans)) {
throw new \Exception("Result of 'log({$eq}, {$base}) = {$ans}' is either infinite or a non-number in ". Parser::$inFix, Math::E_NAN);
}
return $ans;
}
}

View File

@@ -0,0 +1,281 @@
<?php
namespace jlawrence\eos;
// fun class that requires the GD libraries to give visual output to the user
/**
* Equation Graph
*
* Fun class that requires the GD libraries to give visual output of an
* equation to the user. Extends the Parser class.
*
* @author Jon Lawrence <jlawrence11@gmail.com>
* @copyright Copyright ©2005-2013 Jon Lawrence
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @package Math
* @subpackage EOS
* @version 3.x
*/
class Graph
{
private static $width = 640;
private static $height = 480;
/**
* @var resource
*/
private static $image;
public static $labelAxis = true;
public static $backgroundColor = array(255, 255, 255);
public static $gridColor = array(150, 150, 150);
public static $axisColor = array(0, 0, 0);
public static $lineColor = array(0, 0, 0);
/**
* Initializer
*
* Sets up the Graph class with an image width and height defaults to
* 640x480
*
* @param int $width Image width
* @param int $height Image height
*/
public static function init($width = 640, $height = 480)
{
// default width and height equal to that of a poor monitor (in early 2000s)
self::$width = $width;
self::$height = $height;
// initialize main class
Parser::init();
//can't really mess this up, return true
return true;
}
/**
* Create GD Graph Image
*
* Creates a GD image based on the equation given with the parameters that are set
*
* @param string $eq Equation to use. Needs variable in equation to create graph, all variables are interpreted as 'x'
* @param integer $xLow Lower x-bound for graph
* @param integer $xHigh Upper x-bound for graph
* @param float $xStep Stepping points while solving, the lower, the better precision. Slow if lower than .01
* @param bool $xyGrid Draw grid-lines?
* @param bool $yGuess Guess the upper/lower yBounds?
* @param int $yLow Lower y-bound
* @param int $yHigh Upper y-bound
* @return null
*/
public static function graph($eq, $xLow, $xHigh, $xStep = null, $xyGrid = false, $yGuess = true, $yLow = null, $yHigh = null)
{
//create our image and allocate the two colors
$img = ImageCreate(self::$width, self::$height);
//The following noinspection needed because the first color allocated is the background, but not used for anything else.
/** @noinspection PhpUnusedLocalVariableInspection */
$bgColor = ImageColorAllocate($img, self::$backgroundColor[0], self::$backgroundColor[1], self::$backgroundColor[2]);
$aColor = ImageColorAllocate($img, self::$axisColor[0], self::$axisColor[1], self::$axisColor[2]);
$lColor = ImageColorAllocate($img, self::$lineColor[0], self::$lineColor[1], self::$lineColor[2]);
$gColor = ImageColorAllocate($img, self::$gridColor[0], self::$gridColor[1], self::$gridColor[2]);
//$black = ImageColorAllocate($img, 0, 0, 0);
//$grey = ImageColorAllocate($img, 150, 150, 150);
//$darkGrey = ImageColorAllocate($img, 50, 50, 50);
if ($xLow > $xHigh)
list($xLow, $xHigh) = array($xHigh, $xLow); //swap function
//Smart xStep calc
if ($xStep == false) {
$xStep = ($xHigh - $xLow) / self::$width;
}
$xStep = abs($xStep);
$hand = null;
$xVars = array();
//If yGuess is true, make sure yLow and yHigh are not set
if ($yGuess) {
$yLow = null;
$yHigh = null;
}
//We want to limit the number of lines/ticks/etc so graph remains readable, set max now
$xMaxLines = 30;
$yMaxLines = 30;
//DEVELOPER, UNCOMMENT NEXT LINE IF WANTING TO PREVENT SLOW GRAPHS
//$xStep = ($xStep < .01) ? $xStep : 0.01;
$xScale = self::$width / ($xHigh - $xLow);
$counter = 0;
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
$hand = fopen("Graph.txt", "w");
fwrite($hand, "$eq\n");
}
// @codeCoverageIgnoreEnd
for ($i = $xLow; $i <= $xHigh; $i += $xStep) {
$tester = sprintf("%10.3f", $i);
if ($tester == "-0.000") $i = 0;
$y = Parser::solve($eq, $i);
//eval('$y='. str_replace('&x', $i, $eq).";"); /* used to debug my Parser class results */
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
$tmp1 = sprintf("y(%5.3f) = %10.3f\n", $i, $y);
fwrite($hand, $tmp1);
}
// @codeCoverageIgnoreEnd
// If developer asked us to find the upper and lower bounds for y...
if ($yGuess == true) {
$yLow = ($yLow === null || ($y < $yLow)) ? $y : $yLow;
$yHigh = ($yHigh === null || $y > $yHigh) ? $y : $yHigh;
}
$xVars[$counter] = $y;
$counter++;
}
//Now that we have all the variables stored...find the yScale
$yScale = self::$height / (($yHigh) - ($yLow));
// @codeCoverageIgnoreStart
//Calculate the stepping points for lines now
if ($yHigh - $yLow > $yMaxLines) {
$yJump = ceil(($yHigh - $yLow) / $yMaxLines);
} else {
$yJump = 1;
}
if ($xHigh - $xLow > $xMaxLines) {
$xJump = ceil(($xHigh - $xLow) / $xMaxLines);
} else {
$xJump = 1;
}
// @codeCoverageIgnoreEnd
// add 0.01 to each side so that if y is from 1 to 5, the lines at 1 and 5 are seen
$yLow -= 0.01;
$yHigh += 0.01;
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
fwrite($hand, $yLow . " -- " . $yHigh . "\n");
}
// @codeCoverageIgnoreEnd
// if developer wanted a grid on the graph, add it now
if ($xyGrid == true) {
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
fwrite($hand, "Drawing Grid\n");
}
// @codeCoverageIgnoreEnd
for ($i = ceil($yLow); $i <= floor($yHigh); $i += $yJump) {
$i0 = abs($yHigh - $i);
ImageLine($img, 0, $i0 * $yScale, self::$width, $i0 * $yScale, $gColor);
imagestring($img, 1, 2, $i0 * $yScale + 2, $i, $gColor);
}
for ($i = ceil($xLow); $i <= floor($xHigh); $i += $xJump) {
$i0 = abs($xLow - $i);
ImageLine($img, $i0 * $xScale, 0, $i0 * $xScale, self::$height, $gColor);
imagestring($img, 1, $i0 * $xScale + 2, 2, $i, $gColor);
}
}
//Now that we have the scales, let's see if we can draw an x/y-axis
if ($xLow <= 0 && $xHigh >= 0) {
//the y-axis is within our range - draw it.
$x0 = abs($xLow) * $xScale;
ImageLine($img, $x0, 0, $x0, self::$height, $aColor);
for ($i = ceil($yLow); $i <= floor($yHigh); $i += $yJump) {
$i0 = abs($yHigh - $i);
ImageLine($img, $x0 - 3, $i0 * $yScale, $x0 + 3, $i0 * $yScale, $aColor);
//If we want the axis labeled... (call in the allies?)
if (self::$labelAxis) {
imagestring($img, 1, $x0 + 2, $i0 * $yScale + 1, $i, $aColor);
}
}
}
if ($yLow <= 0 && $yHigh >= 0) {
//the x-axis is within our range - draw it.
$y0 = abs($yHigh) * $yScale;
ImageLine($img, 0, $y0, self::$width, $y0, $aColor);
//Create ticks for y
for ($i = ceil($xLow); $i <= floor($xHigh); $i += $xJump) {
$i0 = abs($xLow - $i);
ImageLine($img, $i0 * $xScale, $y0 - 3, $i0 * $xScale, $y0 + 3, $aColor);
//If we want the axis labeled....
if (self::$labelAxis) {
imagestring($img, 1, $i0 * $xScale + 2, $y0 + 1, $i, $aColor);
}
}
}
$counter = 1;
//now graph it all ;]
for ($i = $xLow + $xStep; $i <= $xHigh; $i += $xStep) {
$x1 = (abs($xLow - ($i - $xStep))) * $xScale;
$y1 = (($xVars[$counter - 1] < $yLow) || ($xVars[$counter - 1] > $yHigh)) ? -1 : (abs($yHigh - $xVars[$counter - 1])) * $yScale;
$x2 = (abs($xLow - $i)) * $xScale;
$y2 = (($xVars[$counter] < $yLow) || ($xVars[$counter] > $yHigh)) ? -1 : (abs($yHigh - $xVars[$counter])) * $yScale;
// if any of the y values were found to be off of the y-bounds, don't graph those connecting lines
if ($y1 != -1 && $y2 != -1)
ImageLine($img, $x1, $y1, $x2, $y2, $lColor);
$counter++;
}
// @codeCoverageIgnoreStart
if (Math::$DEBUG) {
fclose($hand);
}
// @codeCoverageIgnoreEnd
self::$image = $img;
}
/**
* Sends JPG to browser
*
* Sends a JPG image with proper header to output
*
* @codeCoverageIgnore
*/
public static function outJPG()
{
header("Content-type: image/jpeg");
ImageJpeg(self::$image);
}
/**
* Sends PNG to browser
*
* Sends a PNG image with proper header to output
*
* @codeCoverageIgnore
*/
public static function outPNG()
{
header("Content-type: image/png");
ImagePng(self::$image);
}
/**
* Output GD Image
*
* Will give the developer the GD resource for the graph that
* can be used to store the graph to the FS or other media
*
* @return Resource GD Image Resource
*/
public static function getImage()
{
return self::$image;
}
/**
* Output GD Image
*
* Alias for eqGraph::getImage()
*
* @return Resource GD Image resource
*/
public static function outGD()
{
return self::getImage();
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* Created by: Jon Lawrence on 2015-07-02 2:49 PM
*/
namespace jlawrence\eos;
/**
* Class Math
* @package jlawrence\eos
*
* Will be a holder for constants, variables, and other things commonly needed by the
* rest of the package.
*/
class Math
{
/**
* No matching open/close pair in equation
*/
const E_NO_SET = 5500;
/**
* Division by zero
*/
const E_DIV_ZERO = 5501;
/**
* No equation present
*/
const E_NO_EQ = 5502;
/**
* No variable replacements available
*/
const E_NO_VAR = 5503;
/**
* Not A Number (NAN)
*/
const E_NAN = 5504;
/**
* @var bool Use debug features
*/
public static $DEBUG = false;
}

View File

@@ -0,0 +1,656 @@
<?php
/**
* matrix.class.php
*
* Will set up the defines for error checking as well as provide
* the Matrix class for include. As this is made to be modular,
* only the class (and possibly helper classes) along with their
* defines will be found in this file.
* @package Math
* @subpackage Matrix
*/
namespace jlawrence\eos;
/**
* Matrix Class
*
* This class will allow you to create and use Matrices
* as well as providing common Matrix operations. It
* uses PHP5 for OOP and Error Throwing and is commented
* for the PHPDoc Parser for documentation creation.
*
* @version $Id: matrix.class.php 10 2012-08-06 23:41:36Z jlawrence11 $
* @author Jon Lawrence <JLawrence11@gmail.com>
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @copyright Copyright <20>2012, Jon Lawrence
* @package Math
* @subpackage Matrix
*/
class Matrix {
/**
* Invalid String input type
*/
const E_INVALID_INPUT = 5001;
/**
* Matrix needed to be a square matrix for the operation
*/
const E_NOT_SQUARE = 5002;
/**
* Matrix was undefined
*/
const E_NO_MATRIX = 5003;
/**
* Matrix had varying column lengths
*/
const E_INVALID_MATRIX = 5004;
/**
* Matrix operation required rows/cols to be even, they were not
*/
const E_NOT_EQUAL = 5005;
/**
* Determinate was '0' while preforming another operation
*/
const E_NO_INVERSE = 5006;
private $matrix;
/**
* Construct method
*
* For format of input string, see the see tag below
*
* @see Matrix::_assign()
* @param string $mText Matrix text input
*/
public function __construct($mText="")
{
if ($mText) $this->_assign($mText);
}
/**
* Create a matrix based on string input similar to the TI Calculators
* input string "[1,2,3;4,5,6;7,8,9]" is the equivalent of the matrix:
* <pre>
* | 1 2 3 |
* | 4 5 6 |
* | 7 8 9 |
* </pre>
*
* @param String $mText The matrix in string format to assign to the current object
* @return Boolean True if is passes verification after being converted
* @throws \Exception If the input text is not in a valid format
*/
public function _assign($mText)
{
if(trim($mText)=="")
return false;
$mText = preg_replace("/\s/", "", $mText);
if(!preg_match("/^\[(([\-]*[0-9\. ]+[,]{0,1})+[;]{0,1})*\]$/", $mText)) {
throw new \Exception("'{$mText}' is not a valid input", Matrix::E_INVALID_INPUT);
}
$mText = preg_replace("/(\[|\])/", "", $mText);
$rows = explode(";", $mText);
$i=0;$j=0;
foreach($rows as $row)
{
$cols = explode(",", $row);
foreach($cols as $value)
{
$this->matrix[$i][$j] = $value;
$j++;
}
$i++;
$j = 0;
}
return $this->_verify();
}
/**
* Private function that will verify all the columns have the same
* number of items, ensuring it is a valid matrix
*
* @access private
* @param array|bool $mArray
* @return bool True if it passes, false if not a valid matrix
*/
private function _verify($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
$nSet = false;
if(is_array($mArray))
{
foreach($mArray as $row)
{
$cols = count($row);
if($nSet===false) {
$nSet = $cols;
}
if($cols != $nSet) {
return false;
}
}
} else {
return false;
}
return true;
}
/**
* Is it a valid matrix?
*
* Public function to tell the class user whether or not the passed
* array is valid, if no array is passed, it will tell the user whether
* the matrix of the current instance is valid. Valid is denoted by all
* rows have the same number of columns.
*
* @param array|bool $mArray Array to be used, if not assigned will default to $this->matrix
* @return bool True/False depending on if array is a valid matrix
*/
public function isValid($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
return $this->_verify($mArray);
}
/**
* Is it a square Matrix?
*
* Will determine whether or not the matrix is valid, and if it
* is, will determine if the matrix is a square matrix (n by n).
*
* @param array|bool $mArray Matrix array, if not assigned will use $this->matrix
* @return bool True/False depending on whether or not the matrix is square
*/
public function isSquare($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
if(!$this->_verify($mArray)) {
return false;
}
$rows = count($mArray);
$cols = count($mArray[0]);
return ($rows == $cols);
}
/**
* Get 'n' from a square (n by n) Matrix
*
* Will check to see if a matrix is square, if so, will return 'n', which
* is the number of rows==columns in the matrix
*
* @param array|bool $mArray Matrix array, uses $this->matrix if not assigned
* @return int The 'n' of a square matrix, or false if not square
* @throws \Exception If not a square matrix, throws an exception
*/
public function _getN($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
if($this->isSquare($mArray)) {
return count($mArray);
} else {
$m = $this->toString($mArray);
throw new \Exception("'{$m}' is not a square matrix", Matrix::E_NOT_SQUARE);
}
}
/**
* Create an Identity Matrix
*
* Creates an Identity Matrix of size 'n'.
*
* @link http://en.wikipedia.org/wiki/Identity_matrix
* @param int $n The rows/cols of identity matrix
* @param bool $useInternal If true will set $this->matrix
* @return Matrix|bool Return an identity matrix if $useInternal is false, otherwise 'true'
*/
public function createIdentity($n, $useInternal = true)
{
$mArray = array();
for($rows=0;$rows<$n;$rows++) {
for($cols=0;$cols<$n;$cols++) {
if($rows==$cols) {
$mArray[$rows][$cols] = 1;
} else {
$mArray[$rows][$cols] = 0;
}
}
}
if($useInternal == true) {
$this->matrix = $mArray;
return true;
} else {
$nMatrix = new Matrix($this->toString($mArray));
return $nMatrix;
}
}
/**
* Convert current Matrix to string format
*
* Convert an array to the string format used by this class.
*
* @see Matrix::_assign()
* @param array|bool $mArray if not assigned will use this instance's matrix.
* @throws \Exception If matrix is not an array
* @return string The array broken down in to string format
*/
public function toString($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
$rows=array();
if(is_array($mArray))
{
foreach($mArray as $cols){
$rows[] = implode(",", $cols);
}
$retString = sprintf("[%s]", implode($rows, ";"));
return $retString;
} else {
throw new \Exception("No matrix to convert", Matrix::E_NO_MATRIX);
}
}
/**
* Overload PHP's class __toString() method
*
* PHP magic method for "echoing" this object without a specific method called
* Will use {@link Matrix::toString()} with no parameters for it's return.
*
* @return string Returns the $matrix value in string format
*/
public function __toString()
{
return $this->toString();
}
/**
* Get Matrix Array
*
* Will return the matrix array of the current instance.
*
* @return array The matrix array of the current instance
*/
public function getArray()
{
return $this->matrix;
}
/**
* Formatted Matrix output for use in console
*
* Will output the matrix in 'pretty' format, if used with 'echo' and
* HTML, surround it by the '<<pre>>' and '<</pre>>' tags to display properly
*
* @param int $width The width of printing space to use
* @param array|bool $mArray Matrix array, defaults to $this->matrix
* @return string "Pretty-Printed" matrix in ASCII format
*/
public function prettyPrint($width=80, $mArray=false)
{
if(!$mArray) $mArray = $this->matrix;
if(!$this->_verify($mArray)) return false;
$out = "";
$aCount = count($mArray[0]);
$space = floor(($width-4)/$aCount);
$space_2 = floor($space/2);
foreach($mArray as $row)
{
$out .= sprintf("| %{$space_2}.2f", $row[0]);
for($i=1;$i<$aCount;$i++)
{
$out .= sprintf("%{$space}.2f", $row[$i]);
}
$out .= sprintf("%{$space_2}s |\n", " ");
}
return $out;
}
/**
* Adds two matrices together
*
* Will add the inputted Matrix to the current instance, and return
* the result as Matrix class.
*
* @link http://en.wikipedia.org/wiki/Matrix_addition
* @param Matrix $nMatrix Matrix class to be added to current instance
* @return Matrix The result of the addition
* @throws \Exception $msg of exception explains problem
*/
public function addMatrix(Matrix $nMatrix)
{
if(!$this->_verify() || !$nMatrix->_verify())
throw new \Exception("Matrices have varying column sizes", Matrix::E_INVALID_MATRIX);
$matrix1 = $this->getArray();
$matrix2 = $nMatrix->getArray();
if((count($matrix1)!=count($matrix2)) || (count($matrix1[0])!=count($matrix2[0])))
{
$m1 = $this->toString($matrix1);
$m2 = $this->toString($matrix2);
throw new \Exception("The rows and/or columns '{$m1}' and '{$m2}' are not the same", Matrix::E_NOT_EQUAL);
}
$rArray = array();
for($row=0;$row<count($matrix1);$row++) {
for($col=0;$col<count($matrix1[0]);$col++) {
$rArray[$row][$col] = $matrix1[$row][$col] + $matrix2[$row][$col];
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Subtract Matrices
*
* Will subtract the inputted Matrix from the current instance, and return
* the result as Matrix class.
*
* @link http://en.wikipedia.org/wiki/Matrix_subtraction
* @param Matrix $nMatrix Matrix class to be subtracted from current instance
* @return Matrix The result of the subtraction
* @throws \Exception $msg of exception explains problem
*/
public function subMatrix(Matrix $nMatrix)
{
if(!$this->_verify() || !$nMatrix->_verify())
throw new \Exception("Matrices have varying column sizes", Matrix::E_INVALID_MATRIX);
$matrix1 = $this->getArray();
$matrix2 = $nMatrix->getArray();
if((count($matrix1)!=count($matrix2)) || (count($matrix1[0])!=count($matrix2[0])))
{
$m1 = $this->toString($matrix1);
$m2 = $this->toString($matrix2);
throw new \Exception("The rows and/or columns '{$m1}' and '{$m2}' are not the same", Matrix::E_NOT_EQUAL);
}
$rArray = array();
for($row=0;$row<count($matrix1);$row++) {
for($col=0;$col<count($matrix1[0]);$col++) {
$rArray[$row][$col] = $matrix1[$row][$col] - $matrix2[$row][$col];
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Multiply current matrix by a scalar value
*
* Multiplies a matrix by a scalar value (int/float/etc) (constant, ie '2')
*
* @link http://en.wikipedia.org/wiki/Scalar_multiplication
* @param float $k The value to multiply the matrix by
* @return Matrix Returns a new Matrix instance with the result
* @throws \Exception if the instance matrix is not valid
*/
public function mpScalar($k)
{
//we'll verify a true matrix to ... help the user
if(!$this->_verify())
throw new \Exception("Matrix '{$this}' has varying column sizes", Matrix::E_INVALID_MATRIX);
$cArray = $this->getArray();
$rArray = array();
$rows = count($cArray);
$cols = count($cArray[0]);
for($i=0;$i<$rows;$i++) {
for($j=0;$j<$cols;$j++) {
$rArray[$i][$j] = $cArray[$i][$j] * $k;
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Get the Matrix Determinant
*
* Finds the determinant of the square matrix, user should not use
* the parameter, as that is meant to allow recursive calling
* of this function from within itself.
*
* @link http://en.wikipedia.org/wiki/Matrix_determinant
* @param array|bool $mArray The array to find a determinate of
* @return float The Determinate of the square matrix
* @throws \Exception If matrix is 1,1 or is not square
*/
public function getDeterminant($mArray = false)
{
if(!$mArray) $mArray = $this->matrix;
//print_r($mArray);
if(!$this->isSquare($mArray))
throw new \Exception("'{$this}' is not a square matrix", Matrix::E_NOT_SQUARE);
$n = $this->_getN($mArray);
if($n < 1){
// @codeCoverageIgnoreStart
// Should never get this far
throw new \Exception("No Matrix", Matrix::E_NO_MATRIX);
// @codeCoverageIgnoreEnd
} elseif ($n == 1) {
$det = $mArray[0][0];
} elseif ($n == 2) {
$det = $mArray[0][0]*$mArray[1][1] - $mArray[1][0]*$mArray[0][1];
} else {
$det = 0;
$nArray = array();
for($j1=0;$j1<$n;$j1++) {
for($i=1;$i<$n;$i++) {
$j2 = 0;
for($j=0;$j<$n;$j++) {
if($j==$j1) {
continue;
}
$nArray[$i-1][$j2] = $mArray[$i][$j];
$j2++;
}
}
$det += pow(-1,2+$j1)*$mArray[0][$j1]*$this->getDeterminant($nArray);
}
}
return $det;
}
/**
* coFactor Matrix
*
* Will return a Matrix of coFactors for the matrix provided, or an array
* of the matrix as is the default.
*
* @link http://en.wikipedia.org/wiki/Matrix_cofactors
* @param array|bool $cArray A matrix in array format (or $this->matrix by default)
* @param bool $asArray When set to true, will return an array, when false a Matrix Object
* @return Matrix|array A matrix of coFactors for the array provided (or current matrix)
* @throws \Exception if the matrix is not square
*/
public function coFactor($cArray=false,$asArray=true)
{
if(!$cArray) $cArray = $this->matrix;
if(!$this->isSquare($cArray))
throw new \Exception("'{$this}' is not a square matrix", Matrix::E_NOT_SQUARE);
$n = $this->_getN($cArray);
$minor = array();
$rArray = array();
for($j=0;$j<$n;$j++){
for($i=0;$i<$n;$i++) {
//Form the adjugate
$i1 = 0;
for($ii=0;$ii<$n;$ii++) {
if($ii==$i) {
continue;
}
$j1=0;
for($jj=0;$jj<$n;$jj++) {
if($jj==$j) {
continue;
}
$minor[$i1][$j1] = $cArray[$ii][$jj];
$j1++;
}
$i1++;
}
$det = $this->getDeterminant($minor);
$rArray[$i][$j] = pow(-1,$i+$j+2)*$det;
}
}
if($asArray==false){
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
} else {
return $rArray;
}
}
/**
* Will transpose the current matrix or array provided
*
* Transposes the current matrix, or the array provided
*
* @link http://en.wikipedia.org/wiki/Matrix_transpose
* @param array|bool $cArray the array to transpose (defaults to $this->matrix)
* @param bool $asArray whether to return an array or Matrix object
* @return array|Matrix Defaults to returning an array of the transposed matrix
* @throws \Exception if the matrix is not square
*/
public function transpose($cArray=false,$asArray=true)
{
if(!$cArray) $cArray = $this->matrix;
if(!$this->isSquare($cArray))
throw new \Exception("'{$this}' is not a square matrix", Matrix::E_NOT_SQUARE);
$n = $this->_getN();
$nArray = array();
for($i=0;$i<$n;$i++) {
for($j=0;$j<$n;$j++) {
$nArray[$j][$i] = $cArray[$i][$j];
}
}
if($asArray==true) {
return $nArray;
} else {
$nMatrix = new Matrix($this->toString($nArray));
return $nMatrix;
}
}
/**
* Adjugate Matrix
*
* Will return the Adjugate matrix of the array provided
* or the current matrix instance if not provided.
*
* @link http://en.wikipedia.org/wiki/Adjugate_matrix
* @param array|bool $cArray Defaults to $this->matrix if not provided
* @param bool $asArray Whether to return an array or Matrix object
* @return array|Matrix Defaults to return the array of the Adjugate matrix
*/
public function adjugate($cArray=false,$asArray=true)
{
if(!$cArray) $cArray = $this->matrix;
$rArray = $this->transpose($this->coFactor($cArray));
if($asArray==true)
return $rArray;
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
/**
* Inverse of current matrix
*
* Will give the inverse of the array provided or the current matrix
* Matrix returned denoted by A^-1
*
* @link http://en.wikipedia.org/wiki/Inverse_matrix
* @param array|bool $cArray Array to invert (defaults to $this->matrix)
* @return Matrix By default returns a new instance of Matrix
* @throws \Exception for any number of reasons that would make the inverse not available
*/
public function inverse($cArray = false)
{
if(!$cArray) $cArray = $this->matrix;
$det = $this->getDeterminant($cArray);
if($det == 0)
throw new \Exception("Determinant of {$this} is 0, No Inverse found", Matrix::E_NO_INVERSE);
$scalar = 1/$det;
$adj = $this->adjugate($cArray, false);
$iMatrix = $adj->mpScalar($scalar);
return $iMatrix;
}
/**
* Multiply Matrices
*
* This function will multiply the current matrix with the matrix provided.
* If current Matrix is denoted by 'A' and the inputted is denoted by 'B',
* When written, this will return AB.
*
* @link http://en.wikipedia.org/wiki/Matrix_multiplication
* @param Matrix $bMatrix The matrix to multiply with the current
* @return Matrix The result of multiplication.
* @throws \Exception $msg explains why operation failed
*/
public function mpMatrix(Matrix $bMatrix)
{
if(!$this->_verify() || !$bMatrix->_verify()) {
// @codeCoverageIgnoreStart
// Should never get this far
$eM1 = $this->toString();
$eM2 = $bMatrix->toString();
throw new \Exception("Either '{$eM1}' and/or '{$eM2}' is not a valid Matrix", Matrix::E_INVALID_MATRIX);
// @codeCoverageIgnoreEnd
}
$aArray = $this->matrix;
$bArray = $bMatrix->getArray();
//The number of columns in A must match the number of rows in B
if(count($aArray[0]) != count($bArray)) {
$mA = $this->toString();
$mB = $bMatrix->toString();
throw new \Exception("Columns in '{$mA}' don't match Rows of '{$mB}'", Matrix::E_NOT_EQUAL);
}
$rArray = array();
//Loop through rows of Matrix A
for($i=0;$i<count($aArray);$i++) {
//Loop through the columns of Matrix B
for($j=0;$j<count($bArray[0]);$j++) {
$value = 0;
//loop through the rows of Matrix B
for($k=0;$k<count($bArray);$k++) {
$value += $aArray[$i][$k] * $bArray[$k][$j];
}
$rArray[$i][$j] = $value;
}
}
$rMatrix = new Matrix($this->toString($rArray));
return $rMatrix;
}
}
?>

View File

@@ -0,0 +1,593 @@
<?php
/**
* Equation Operating System Classes.
*
* This class was created for the safe parsing of mathematical equations
* in PHP. There is a need for a way to successfully parse equations
* in PHP that do NOT require the use of `eval`. `eval` at its core
* opens the system using it to so many security vulnerabilities it is oft
* suggested /never/ to use it, and for good reason. This class set will
* successfully take an equation, parse it, and provide solutions to the
* developer. It is a safe way to evaluate expressions without putting
* the system at risk.
*
*
* @author Jon Lawrence <jlawrence11@gmail.com>
* @copyright Copyright ©2005-2015, Jon Lawrence
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @version 3.0.0
*/
namespace jlawrence\eos;
/**
* Equation Operating System (EOS) Parser
*
* A class that can safely parse mathematical equations. Re-written portions
* from version 2.x to be extend-able with custom functions.
*
* @author Jon Lawrence <jlawrence11@gmail.com>
* @copyright Copyright ©2005-2015, Jon Lawrence
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @version 3.0.0
*/
class Parser {
/**
* @var string Infix equation
* Public so advanced/user-defined/etc can access it when throwing exceptions.
*/
public static $inFix;
/**
* @var array Opening and closing selectors
*/
protected static $SEP = array(
'open' => array('(', '['),
'close' => array(')', ']')
);
// Top precedence following operator - not in use
protected static $SGL = array('!');
// Order of operations arrays follow
protected static $ST = array('^', '!');
protected static $ST1 = array('/', '*', '%');
protected static $ST2 = array('+', '-');
/**
* @var array Allowed functions
*/
protected static $FNC = array(
'sin', 'cos', 'tan',
'csc', 'sec', 'cot',
'abs', 'ln', 'sqrt'
);
/**
* @var array Advanced functions container
*/
protected static $AFNC = array();
/**
* Initialize
*/
public static function init() {
if (empty(self::$AFNC)) {
//No advanced functions yet, so this function has not run, do so now.
self::$AFNC = AdvancedFunctions::map();
}
}
/**
* Add Advanced Function Class
*
* Adds a function class to the parser for user/programmer defined
* functions that can be parsed with the parser. For example
* class structure see jlawrence\eos\AdvancedFunctions.
* Class must be static, and must have a function named 'map'
*
* @param string $class Fully Qualified String to class (must include namespace)
* @return bool True on success
* @throws \Exception When the added class doesn't have the 'map' function or doesn't exist.
*
* @codeCoverageIgnore
*/
public static function addFunctionClass($class)
{
self::init();
if(is_callable("{$class}::map")) {
$a = call_user_func($class.'::map');
self::$AFNC = array_merge($a, self::$AFNC);
} else {
throw new \Exception("{$class}::map() is not callable");
}
return true;
}
/**
* Check Infix for opening closing pair matches.
*
* This function is meant to solely check to make sure every opening
* statement has a matching closing one, and throws an exception if
* it doesn't.
*
* @param string $infix Equation to check
* @throws \Exception if malformed.
* @return Bool true if passes - throws an exception if not.
*/
private static function checkInfix($infix) {
self::init();
if(trim($infix) == "") {
throw new \Exception("No Equation given", Math::E_NO_EQ);
}
//Make sure we have the same number of '(' as we do ')'
// and the same # of '[' as we do ']'
if(substr_count($infix, '(') != substr_count($infix, ')')) {
throw new \Exception("Mismatched parenthesis in ". self::$inFix, Math::E_NO_SET);
} elseif(substr_count($infix, '[') != substr_count($infix, ']')) {
throw new \Exception("Mismatched brackets in '". self::$inFix, Math::E_NO_SET);
}
return true;
}
/**
* Infix to Postfix
*
* Converts an infix (standard) equation to postfix (RPN) notation.
* Sets the internal variable $this->postFix for the Parser::solvePF()
* function to use.
*
* @link http://en.wikipedia.org/wiki/Infix_notation Infix Notation
* @link http://en.wikipedia.org/wiki/Reverse_Polish_notation Reverse Polish Notation
* @param string $infix A standard notation equation
* @throws \Exception When parenthesis are mismatched
* @return array Fully formed RPN Stack
*/
public static function in2post($infix) {
// if an equation was not passed, use the one that was passed in the constructor
//$infix = (isset($infix)) ? $infix : $this->inFix;
//check to make sure 'valid' equation
self::checkInfix($infix);
$pf = array();
$ops = new Stack();
//$vars = new Stack();
// remove all white-space
$infix = preg_replace("/\s/", "", $infix);
// Create postfix array index
$pfIndex = 0;
//what was the last character? (useful for discerning between a sign for negation and subtraction)
$lChar = '';
//loop through all the characters and start doing stuff ^^
for($i=0;$i<strlen($infix);$i++) {
// pull out 1 character from the string
$chr = substr($infix, $i, 1);
// if the character is numerical
if(preg_match('/[0-9.]/i', $chr)) {
// if the previous character was not a '-' or a number
if((!preg_match('/[0-9.]/i', $lChar) && ($lChar != "")) && (isset($pf[$pfIndex]) && ($pf[$pfIndex]!="-")))
$pfIndex++; // increase the index so as not to overlap anything
// Add the number character to the array
if(isset($pf[$pfIndex])) {
$pf[$pfIndex] .= $chr;
} else {
$pf[$pfIndex] = $chr;
}
}
// If the character opens a set e.g. '(' or '['
elseif(in_array($chr, self::$SEP['open'])) {
// if the last character was a number, place an assumed '*' on the stack
if(preg_match('/[0-9.]/i', $lChar))
$ops->push('*');
$ops->push($chr);
}
// if the character closes a set e.g. ')' or ']'
elseif(in_array($chr, self::$SEP['close'])) {
// find what set it was i.e. matches ')' with '(' or ']' with '['
$key = array_search($chr, self::$SEP['close']);
// while the operator on the stack isn't the matching pair...pop it off
while($ops->peek() != self::$SEP['open'][$key]) {
$nchr = $ops->pop();
if($nchr)
$pf[++$pfIndex] = $nchr;
else {
//Should NEVER get here...
// @codeCoverageIgnoreStart
throw new \Exception("Error while searching for '". self::$SEP['open'][$key] ."' in ". self::$inFix, Math::E_NO_SET);
// @codeCoverageIgnoreEnd
}
}
$ops->pop();
}
// If a special operator that has precedence over everything else
elseif(in_array($chr, self::$ST)) {
while(in_array($ops->peek(), self::$ST))
$pf[++$pfIndex] = $ops->pop();
$ops->push($chr);
$pfIndex++;
}
// Any other operator other than '+' and '-'
elseif(in_array($chr, self::$ST1)) {
while(in_array($ops->peek(), self::$ST1) || in_array($ops->peek(), self::$ST))
$pf[++$pfIndex] = $ops->pop();
$ops->push($chr);
$pfIndex++;
}
// if a '+' or '-'
elseif(in_array($chr, self::$ST2)) {
// if it is a '-' and the character before it was an operator or nothingness (e.g. it negates a number)
if((in_array($lChar, array_merge(self::$ST1, self::$ST2, self::$ST, self::$SEP['open'])) || $lChar=="") && $chr=="-") {
// increase the index because there is no reason that it shouldn't..
$pfIndex++;
$pf[$pfIndex] = $chr;
}
// Otherwise it will function like a normal operator
else {
while(in_array($ops->peek(), array_merge(self::$ST1, self::$ST2, self::$ST)))
$pf[++$pfIndex] = $ops->pop();
$ops->push($chr);
$pfIndex++;
}
}
// make sure we record this character to be referred to by the next one
$lChar = $chr;
}
// if there is anything on the stack after we are done...add it to the back of the RPN array
while(($tmp = $ops->pop()) !== false)
$pf[++$pfIndex] = $tmp;
// re-index the array at 0
$pf = array_values($pf);
// set the private variable for later use if needed
//self::$postFix = $pf;
// return the RPN array in case developer wants to use it for some insane reason (bug testing ;] )
// Also... because we pass it right in to the RPN solver. So I guess there's that too.
return $pf;
}
/**
* Solve Postfix (RPN)
*
* This function will solve a RPN array. Default action is to solve
* the RPN array stored in the class from Parser::in2post(), can take
* an array input to solve as well, though default action is preferred.
*
* @link http://en.wikipedia.org/wiki/Reverse_Polish_notation Postix Notation
* @param array $pfArray RPN formatted array. Optional.
* @throws \Exception on division by 0
* @return float Result of the operation.
*/
public static function solvePF($pfArray) {
$pf = $pfArray;
// create our temporary function variables
$temp = array();
//$tot = 0;
$hold = 0;
// Loop through each number/operator
for($i=0;$i<count($pf); $i++) {
// If the string isn't an operator, add it to the temp var as a holding place
if(!in_array($pf[$i], array_merge(self::$ST, self::$ST1, self::$ST2))) {
$temp[$hold++] = $pf[$i];
}
// ...Otherwise perform the operator on the last two numbers
else {
switch ($pf[$i]) {
case '+':
$temp[$hold-2] = $temp[$hold-2] + $temp[$hold-1];
break;
case '-':
$temp[$hold-2] = $temp[$hold-2] - $temp[$hold-1];
break;
case '*':
$temp[$hold-2] = $temp[$hold-2] * $temp[$hold-1];
break;
case '/':
if($temp[$hold-1] == 0) {
throw new \Exception("Division by 0 on: '{$temp[$hold-2]} / {$temp[$hold-1]}' in ". self::$inFix, Math::E_DIV_ZERO);
}
$temp[$hold-2] = $temp[$hold-2] / $temp[$hold-1];
break;
case '^':
$temp[$hold-2] = pow($temp[$hold-2], $temp[$hold-1]);
break;
case '!':
$temp[$hold-1] = self::factorial($temp[$hold-1]);
$hold++;
break;
case '%':
if($temp[$hold-1] == 0) {
throw new \Exception("Division by 0 on: '{$temp[$hold-2]} % {$temp[$hold-1]}' in ". self::$inFix, Math::E_DIV_ZERO);
}
$temp[$hold-2] = bcmod($temp[$hold-2], $temp[$hold-1]);
break;
}
// Decrease the hold var to one above where the last number is
$hold = $hold-1;
}
}
// return the last number in the array
return $temp[$hold-1];
}
/**
* Solve
*
* This function is called by the user to solve an equation within the parser system
* No internal functions or added advanced functions should ever call this. Sets
* the internal $infix variable for use in thrown exceptions. The variable array must
* be in the format of 'variable' => value. If variable array is scalar (ie 5), all
* variables will be replaced with it.
*
* @param string $equation Equation to Solve
* @param array|double $values variable values
* @return float Answer to the equation
*/
public static function solve($equation, $values = null) {
if(is_array($equation)) {
return self::solvePF($equation);
} else {
self::$inFix = $equation;
return self::solveIF($equation, $values);
}
}
/**
* Solve Infix (Standard) Notation Equation
*
* Will take a standard equation with optional variables and solve it.
* This function is the one for programmers making modules for this
* package should call as it does not set the internal variable for
* the equation. This should not be used by the programmer/user that
* is using this package to solve equations.
*
* @param string $infix Standard Equation to solve
* @param string|array $vArray Variable replacement
* @throws \Exception On division by zero or NaN
* @return float Solved equation
*/
public static function solveIF($infix, $vArray = null) {
//Check to make sure a 'valid' expression
self::checkInfix($infix);
//$ops = new Stack();
//$vars = new Stack();
$hand = null;
//remove all white-space
$infix = preg_replace("/\s/", "", $infix);
$infix = self::checkAdvancedInput($infix,$vArray);
// Finds all the 'functions' within the equation and calculates them
//Nested parenthesis are now a go!
while((preg_match("/(". implode("|", self::$FNC) . ")\(((?:[^()]|\((?2)\))*+)\)/", $infix, $match)) != 0) {
$func = self::solveIF($match[2], $vArray);
switch($match[1]) {
case "cos":
$ans = Trig::cos($func);
break;
case "sin":
$ans = Trig::sin($func);
break;
case "tan":
$ans = Trig::tan($func);
break;
case "sec":
$ans = Trig::sec($func);
break;
case "csc":
$ans = Trig::csc($func);
break;
case "cot":
$ans = Trig::cot($func);
break;
case "abs":
$ans = abs($func);
break;
case "ln":
$ans = log($func);
if(is_nan($ans) || is_infinite($ans)) {
throw new \Exception("Result of 'ln({$func}) = {$ans}' is either infinite or a non-number in ". self::$inFix, Math::E_NAN);
}
break;
case "sqrt":
if($func < 0) {
throw new \Exception("Result of 'sqrt({$func}) = i' in ". self::$inFix .". We can't handle imaginary numbers", Math::E_NAN);
}
$ans = sqrt($func);
break;
// @codeCoverageIgnoreStart
default:
$ans = 0;
break;
// @codeCoverageIgnoreEnd
}
$infix = str_replace($match[0], "({$ans})", $infix);
}
//replace scientific notation with normal notation (2e-9 to 2*10^-9)
$infix = preg_replace('/([\d])([eE])(-?\d)/', '$1*10^$3', $infix);
$infix = self::replaceVars($infix, $vArray);
return self::solvePF(self::in2post($infix));
}
/**
* checkAdvancedInput
*
* Will take the input from `Parser::solveIF()` and solve all the advanced functions
* that exist within it, returning it to the function when done for further
* processing.
*
* @param string $input Check for advanced functions, recursively go through them.
* @param array|int|null $vArray Variables from user-input
* @return string The input with all advanced functions solved for.
*/
protected static function checkAdvancedInput($input, $vArray)
{
$infix = $input;
//Advanced/User-defined functions
while((preg_match("/(". implode("|", array_keys(self::$AFNC)) . ")\(((?:[^()]|\((?2)\))*+)\)/", $infix, $match)) != 0) {
$method = self::$AFNC[$match[1]];
if(stripos($match[2], '(') !== false) {
$match[2] = self::checkAdvancedInput($match[2], $vArray);
}
$ans = call_user_func($method, $match[2], $vArray);
$infix = str_replace($match[0], "({$ans})", $infix);
}
return $infix;
}
/**
* @param string $infix
* @param array $vArray
* @return string
* @throws \Exception
*/
protected static function replaceVars($infix, $vArray)
{
//Remove old '$' and '&' signis so the regex works properly.
$infix = preg_replace('/[$&]/', "", $infix);
//Find all the variables that were passed and replaces them
while((preg_match('/([^a-zA-Z]){0,1}([a-zA-Z]+)([^a-zA-Z]){0,1}/', $infix, $match)) != 0) {
//remove notices by defining if undefined.
if(!isset($match[3])) {
$match[3] = "";
}
// Ensure that the variable has an operator or something of that sort in front and back - if it doesn't, add an implied '*'
if((!in_array($match[1], array_merge(self::$ST, self::$ST1, self::$ST2, self::$SEP['open'])) && $match[1] != "") || is_numeric($match[1])) //$this->SEP['close'] removed
$front = "*";
else
$front = "";
if((!in_array($match[3], array_merge(self::$ST, self::$ST1, self::$ST2, self::$SEP['close'])) && $match[3] != "") || is_numeric($match[3])) //$this->SEP['open'] removed
$back = "*";
else
$back = "";
//Make sure that the variable does have a replacement
//First check for pi and e variables that wll automatically be replaced
if(in_array(strtolower($match[2]), array('pi', 'e'))) {
$t = (strtolower($match[2])=='pi') ? pi() : exp(1);
$infix = str_replace($match[0], $match[1] . $front. $t. $back . $match[3], $infix);
} elseif(!isset($vArray[$match[2]]) && (!is_array($vArray != "") && !is_numeric($vArray) && 0 !== $vArray)) {
throw new \Exception("Variable replacement does not exist for '". $match[2] ."' in ". self::$inFix .".", Math::E_NO_VAR);
} elseif(!isset($vArray[$match[2]]) && (!is_array($vArray != "") && is_numeric($vArray))) {
$infix = str_replace($match[0], $match[1] . $front. $vArray. $back . $match[3], $infix);
} elseif(isset($vArray[$match[2]])) {
$infix = str_replace($match[0], $match[1] . $front. $vArray[$match[2]]. $back . $match[3], $infix);
}
}
return $infix;
}
/**
* Solve factorial (!)
*
* Will take an integer and solve for it's factorial. Eg.
* `5!` will become `1*2*3*4*5` = `120`
* DONE:
* Solve for non-integer factorials 2015/07/02
*
* @param float $num Non-negative real number to get factorial of
* @throws \Exception if number is at or less than 0
* @return float Solved factorial
*/
protected static function factorial($num) {
if($num < 0) {
throw new \Exception("Factorial Error: Factorials don't exist for numbers < 0 in ". self::$inFix, Math::E_NAN);
}
//A non-integer! Gamma that sucker up!
if(intval($num) != $num) {
return self::gamma($num + 1);
}
$tot = 1;
for($i=1;$i<=$num;$i++) {
$tot *= $i;
}
return $tot;
}
/**
* Gamma Function
*
* Because we can. This function exists as a catch-all for different
* numerical approx. of gamma if I decide to add any past Lanczos'.
*
* @param $z Number to compute gamma from
* @return float The gamma (hopefully, I'll test it after writing the code)
*/
public static function gamma($z)
{
return self::laGamma($z);
}
/**
* Lanczos Approximation
*
* The Lanczos Approximation method of finding gamma values
*
* @link http://www.rskey.org/CMS/index.php/the-library/11
* @link http://algolist.manual.ru/maths/count_fast/gamma_function.php
* @link https://en.wikipedia.org/wiki/Lanczos_approximation
* @param $z Number to obtain the gamma of
* @return float Answer
*/
protected static function laGamma($z)
{
// Set up coefficients
$p = array(
0 => 1.000000000190015,
1 => 76.18009172947146,
2 => -86.50532032941677,
3 => 24.01409824083091,
4 => -1.231739572450155,
5 => 1.208650973866179E-3,
6 => -5.395239384953E-6
);
// Formula:
// ((sqrt(2pi)/z)(p[0]+sum(p[n]/(z+n), 1, 6)))(z+5.5)^(z+0.5)*e^(-(z+5.5))
// Break it down now...
$g1 = sqrt(2*pi())/$z;
// Next comes our summation
$g2 =0;
for($n=1;$n<=6;$n++) {
$g2 += $p[$n]/($z+$n);
}
// Don't forget to add p[0] to it...
$g2 += $p[0];
$g3 = pow($z+5.5, $z + .5);
$g4 = exp(-($z+5.5));
//now just multiply them all together
$gamma = $g1 * $g2 * $g3 * $g4;
return $gamma;
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace jlawrence\eos;
/**
* Basic Stack Class.
*
* Created for use with eqEOS. May eventually be replaced with native
* PHP functions `array_pop()`, `array_push()`, and `end()`
*
* @author Jon Lawrence <jlawrence11@gmail.com>
* @copyright Copyright ©2005-2015 Jon Lawrence
* @license http://opensource.org/licenses/LGPL-2.1 LGPL 2.1 License
* @package Math
* @version 0.1
*/
class Stack {
private $index;
private $locArray;
/**
* Constructor
*
* Initializes the stack
*/
public function __construct() {
//define the private vars
$this->locArray = array();
$this->index = -1;
}
/**
* Peek
*
* Will view the last element of the stack without removing it
*
* @return mixed An element of the array or false if none exist
*/
public function peek() {
if($this->index > -1)
return $this->locArray[$this->index];
else
return false;
}
/**
* Poke
*
* Will add an element to the end of the stack
*
* @param mixed $data Element to add
*/
public function poke($data) {
$this->locArray[++$this->index] = $data;
}
/**
* Push
*
* Alias of {@see Stack::poke()}
* Adds element to the stack
*
* @param mixed $data Element to add
*/
public function push($data) {
//alias for 'poke'
$this->poke($data);
}
/**
* Pop
*
* Retrieves an element from the end of the stack, and removes it from
* the stack at the same time. If no elements, returns boolean false
*
* @return mixed Element at end of stack or false if none exist
*/
public function pop() {
if($this->index > -1)
{
$this->index--;
return $this->locArray[$this->index+1];
}
else
return false;
}
/**
* Clear
*
* Clears the stack to be reused.
*/
public function clear() {
$this->index = -1;
$this->locArray = array();
}
/**
* Get Stack
*
* Returns the array of stack elements, keeping all, indexed at 0
*
* @return mixed Array of stack elements or false if none exist.
*/
public function getStack() {
if($this->index > -1)
{
return array_values($this->locArray);
}
else
return false;
}
}
?>

View File

@@ -0,0 +1,75 @@
<?php
/**
* Created by: Jon Lawrence on 2015-07-02 3:10 PM
*/
namespace jlawrence\eos;
/**
* Class Trig
* @package jlawrence\Math
*
* Trig functions, primarily because there's a difference when dealing with
* radians and degrees, and this class will help with that - defaulting
* to radians, but allowing the default to be set to degrees for the
* project. Thus simplifying the amount of calculations the user
* of this project needs to do.
*/
class Trig
{
/**
* @var bool Whether or not to convert to radians before calculation
* (Meaning input is in degree form)
*/
public static $DEGREES = false;
protected static function getRadDeg($x)
{
if(self::$DEGREES == true) {
return deg2rad($x);
}
return $x;
}
public static function cos($x)
{
return cos(self::getRadDeg($x));
}
public static function sin($x)
{
return sin(self::getRadDeg($x));
}
public static function tan($x)
{
return tan(self::getRadDeg($x));
}
public static function sec($x)
{
$tmp = self::cos($x);
if($tmp == 0)
throw new \Exception("Division by 0 on: 'sec({$x}) = 1/cos({$x})' in ". Parser::$inFix, Math::E_DIV_ZERO);
return 1/$tmp;
}
public static function csc($x)
{
$tmp = self::sin($x);
if($tmp == 0)
throw new \Exception("Division by 0 on: 'csc({$x})) = 1/sin({$x})' in ". Parser::$inFix, Math::E_DIV_ZERO);
return 1/$tmp;
}
public static function cot($x)
{
$tmp = self::tan($x);
if($tmp == 0)
throw new \Exception("Division by 0 on: 'cot({$x})) = 1/tan({$x})' in ". Parser::$inFix, Math::E_DIV_ZERO);
return 1/$tmp;
}
}