Location: PHPKode > scripts > Equation Operating System > equation-operating-system/eos.class.php
<?php

//Equation Operating System
//created by Jon Lawrence
// (c) 2005 AAB Software

if(!defined(DEBUG))
	define('DEBUG', false);

require_once "stack.class.php";

/* Define arrays for use in the parser */
$eqSEP = array('open' => array('(', '['), 'close' => array(')', ']'));
$eqSGL = array('!');
$eqST = array('^');
$eqST1 = array('/', '*', '%');
$eqST2 = array('+', '-');
$eqFNC = array('sin', 'cos', 'tan', 'csc', 'sec', 'cot');


//Create the basic EOS for solving equations and the likes ;]
class eqEOS
{
	/* PRIVATE variable scope */
	var $postFix;
	var $inFix;

	/* __construct() */
	function eqEOS($inFix = "")
	{
		//set up our variables
		if($inFix)
			$this->inFix = $inFix;
		else
			$this->inFix = "";
		$this->postFix = array();
	}

	/* convert a standard equation into RPN notation */
	function in2post($inFix = "")
	{
		/* Put our operator arrays in scope for this function */
		global $eqSEP, $eqSGL, $eqST1, $eqST2, $eqFNC, $eqST;

		/* if a new equation was not passed, use the one that was passed in the constructor */
		$infix = ($inFix != "") ? $inFix : $this->inFix;
		$pf = array();
		$ops = new phpStack();
		$vars = new phpStack();
		$lChar = "";

		/* remove all white-space */
		preg_replace("/\s/", "", $infix);

		/* Create postfix array index */
		$pfIndex = 0;

		//what was the last character? (useful for decerning 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 a number character */
			if(eregi('[0-9.]', $chr))
			{
				/* if the previous character was not a '-' or a number */
				if((!eregi('[0-9.]', $lChar) && ($lChar != "")) && ($pf[$pfIndex]!="-"))
					$pfIndex++;	/* increase the index so as not to overlap anything */
				/* Add the number character to the array */
				$pf[$pfIndex] .= $chr;
			}
			/* If the character opens a set e.g. '(' or '[' */
			else if(in_array($chr, $eqSEP['open']))
			{
				/* if the last character was a number, place an assumed '*' on the stack */
				if(eregi('[0-9.]', $lChar))
					$ops->push('*');

				$ops->push($chr);
			}
			/* if the character closes a set e.g. ')' or ']' */
			else if(in_array($chr, $eqSEP['close']))
			{
				/* find what set it was i.e. matches ')' with '(' or ']' with '[' */
				$key = array_search($chr, $eqSEP['close']);
				/* while the operator on the stack isn't the matching pair...pop it off */
				while($ops->peek() != $eqSEP['open'][$key] && $ops->peek() != false)
				{
					$nchr = $ops->pop();
					if($nchr)
						$pf[++$pfIndex] = $nchr;
					else
						return "Error while searching for '". $eqSEP['open'][$key] ."'";
				}
				$ops->pop();
			}
			/* If a special operator that has precedence over everything else */
			else if(in_array($chr, $eqST))
			{
				//if(in_array($ops->peek(), $eqST))
				//	$pf[++$pfIndex] = $ops->pop();

				$ops->push($chr);
				$pfIndex++;
			}
			/* Any other operator other than '+' and '-' */
			else if(in_array($chr, $eqST1))
			{
				while(in_array($ops->peek(), $eqST1) || in_array($ops->peek(), $eqST))
					$pf[++$pfIndex] = $ops->pop();

				$ops->push($chr);
				$pfIndex++;
			}
			/* if a '+' or '-' */
			else if(in_array($chr, $eqST2))
			{
				/* if it is a '-' and the character before it was an operator or nothingness (e.g. it negates a number) */
				if(((in_array($lChar, $eqST1) || in_array($lChar, $eqST2) || in_array($lChar, $eqST)) || $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(), $eqST1) || in_array($ops->peek(), $eqST2) || in_array($ops->peek(), $eqST))
						$pf[++$pfIndex] = $ops->pop();

					$ops->push($chr);
					$pfIndex++;
				}
			}
			/* make sure we record this character to be refered 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 */
		$i = 0;
		foreach($pf as $tmp)
			$pfTemp[$i++] = $tmp;
		$pf = $pfTemp;
		
		/* set the private variable for later use if needed */
		$this->postFix = $pf;

		/* return the RPN array in case developer wants to use it fro some insane reason (bug testing ;] */
		return $pf;
	} //end function in2post

	/* This function will solve a RPN array */
	function solvePF($pfArray = "")
	{
		/* put operator arrays in function scope */
		global $eqSEP, $eqSGL, $eqST1, $eqST2, $eqFNC, $eqST;

		/* if no RPN array is passed - use the one stored in the private var */
		$pf = (!is_array($pfArray)) ? $this->postFix : $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], $eqST1) && !in_array($pf[$i], $eqST2) && !in_array($pf[$i], $eqST))
			{
				$temp[$hold++] = $pf[$i];
			}
			/* ...Otherwise preform the operator on the last two numbers */
			else
			{
				$opr = $pf[$i];
				if($opr=="+")
					$temp[$hold-2] = $temp[$hold-2] + $temp[$hold-1];
				else if($opr=="-")
					$temp[$hold-2] = $temp[$hold-2] - $temp[$hold-1];
				else if($opr=="*")
					$temp[$hold-2] = $temp[$hold-2] * $temp[$hold-1];
				else if($opr=="/" && $temp[$hold-1] != 0)
					$temp[$hold-2] = $temp[$hold-2] / $temp[$hold-1];
				else if($opr=="^")
					$temp[$hold-2] = pow($temp[$hold-2], $temp[$hold-1]);
				else if($opr=="%" && $temp[$hold-2] > -1)
					$temp[$hold-2] = bcmod($temp[$hold-2], $temp[$hold-1]);

				/* 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];

	} //end function solvePF


	/* This will take a standard equation and solve it for the developer */
	/* INPUTS:
		$infix -> standard equation
			unknowns must be passed with a pre-fixed ampersand (e.g. '&x' for 'x')
		$vArray -> either a numeric value or a hash of variables with their equivelent values
			e.g. "6" for all unknowns to become 6
					or
				array('x' => 3, 'y' => 2, 'fun' => 7) for x=3; y=2; fun=7
	*/
	function solveIF($infix, $vArray = "")
	{
		/* put operator arrays in function scope */
		global $eqSEP, $eqSGL, $eqST1, $eqST2, $eqFNC, $eqST;

		$if = ($infix != "") ? $infix : $this->inFix;
		if($infix=="") die;

		$ops = new phpStack();
		$vars = new phpStack();

		//remove all white-space
		preg_replace("/\s/", "", $infix);
		if(DEBUG)
			$hand=fopen("eq.txt","a");

		//Find all the variables that were passed and replaces them
		while((preg_match("/(.){0,1}\&([a-zA-Z]+)(.){0,1}/", $infix, $match)) != 0)
		{

			if(DEBUG)
				fwrite($hand, "{$match[1]} || {$match[3]}\n");
			/* 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], $eqST1) && !in_array($match[1], $eqST2) && !in_array($match[1], $eqST) && !in_array($match[1], $eqSEP['open']) && !in_array($match[1], $eqSEP['close']) && ($match[1] != "")) || is_numeric($match[1]))
				$front = "*";
			else
				$front = "";

			if(!in_array($match[3], $eqST1) && !in_array($match[3], $eqST2) && !in_array($match[3], $eqST) && !in_array($match[3], $eqSEP['open']) && !in_array($match[3], $eqSEP['close']) && ($match[3] != ""))
				$back = "*";
			else
				$back = "";
			
			//Make sure that the variable does have a replacement
			if(!isset($vArray[$match[2]]) && (!is_array($vArray != "") && !is_numeric($vArray)))
				return "Mal-formed equation : variable '{$match[2]}' not found";
			else if(!isset($vArray[$match[2]]) && (!is_array($vArray != "") && is_numeric($vArray)))
				$infix = str_replace($match[0], $match[1] . $front. $vArray. $back . $match[3], $infix);
			else if(isset($vArray[$match[2]]))
				$infix = str_replace($match[0], $match[1] . $front. $vArray[$match[2]]. $back . $match[3], $infix);
		}

		if(DEBUG)
			fwrite($hand, "$infix\n");

		/* Finds all the 'functions' within the equation and calculates them */
		/* NOTE - when using function, only 1 set of paranthesis will be found, instead use brackets for sets within functions!! */
		while((preg_match("/(". implode("|", $eqFNC) . ")\(([^\)\(]*(\([^\)]*\)[^\(\)]*)*[^\)\(]*)\)/", $infix, $match)) != 0)
		{
			$func = $this->solveIF($match[2]);
			$func = ($func);
			switch($match[1])
			{
				case "cos":
					$ans = cos($func);
					break;
				case "sin":
					$ans = sin($func);
					break;
				case "tan":
					$ans = tan($func);
					break;
				case "sec":
					if(($tmp = cos($func)) != 0)
						$ans = 1/$tmp;
					break;
				case "csc":
					if(($tmp = sin($func)) != 0)
						$ans = 1/$tmp;
					break;
				case "cot":
					if(($tmp = tan($func)) != 0)
						$ans = 1/$tmp;
					break;
				default:
					break;
			}
			$infix = str_replace($match[0], $ans, $infix);
		}
		if(DEBUG)
			fclose($hand);
		return $this->solvePF($this->in2post($infix));


	} //end function solveIF
} //end class 'eqEOS'


/* fun class that requires the GD libraries to give visual output to the user */
/* extends the eqEOS class so that it doesn't need to create it as a private var 
    - and it extends the functionality of that class */
class eqGraph extends eqEOS
{
	var $width, $height;	//width and heigt of the graph to be created
	var $image;				//image reference

	/* __construct */
	function eqGraph($width=640, $height=480)
	{
		/* default width and height equal to that of a poor monitor */
		$this->width = $width;
		$this->height = $height;
	} //end function eqGraph

	/* fill the private var '$image' with a GD image of the graph */
	/* INPUTS:
		$eq -> the equation; same as inputed for eqEOS::solveIF()
		$xLow -> lower x-bound of the graph
		$xHight -> upper x-bound of the graph
		$xStep -> precision of the graph (No need to go less than 0.01 )
		$xyGrid -> (true/false) show a grid on the map at every x,y point
		$yGuess -> (true/false) have the function calculate the upper and lower y-bounds
		$yLow -> lower y-bound (if assigned and $yGuess is true, this will be the starting lower bound
			i.e. if the function would calculate $yLow to be '5' and this is set at '0'; '0' will be the lower bound
		$yHigh -> upper y-bound.  same type of functionality as the $yLower if set and $yGuess is true
	*/
	function graph($eq, $xLow, $xHigh, $xStep, $xyGrid = false, $yGuess = true, $yLow=false, $yHigh=false)
	{	
		//create our image and allocate the two colors
		$img = ImageCreate($this->width, $this->height);
		$white = ImageColorAllocate($img, 255, 255, 255);
		$black = ImageColorAllocate($img, 0, 0, 0);
		$grey = ImageColorAllocate($img, 220, 220, 220);
		$xStep = abs($xStep);
		if($xLow > $xHigh)
			list($xLow, $xHigh) = array($xHigh, $xLow);	//swap function
		
		$xScale = $this->width/($xHigh-$xLow);
		$counter = 0;
		if(DEBUG)
		{
			$hand=fopen("crappers.txt","w");
			fwrite($hand, "$eq\n");
		}
		for($i=$xLow;$i<=$xHigh;$i+=$xStep)
		{
			$tester = sprintf("%10.3f",$i);
			if($tester == "-0.000") $i = 0;
			$y = $this->solveIF($eq, $i);
			//eval('$y='. str_replace('&x', $i, $eq).";"); /* used to debug my eqEOS class results */
			if(DEBUG)
			{
				$tmp1 = sprintf("y(%5.3f) = %10.3f\n", $i, $y);
				fwrite($hand, $tmp1);
			}

			/* If developer asked us to find the upper and lower bounds for y... */
			if($yGuess==true)
			{
				$yLow = ($yLow===false || ($y<$yLow)) ? $y : $yLow;
				$yHigh = ($yHigh===false || $y>$yHigh) ? $y : $yHigh;
			}
			$xVars[$counter] = $y;
			$counter++;			
		}
		if(DEBUG)
			fclose($hand);
		/* 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;

		//Now that we have all the variables stored...find the yScale
		$yScale = $this->height/(($yHigh)-($yLow));

		/* if developer wanted a grid on the graph, add it now */
		if($xyGrid==true)
		{
			for($i=ceil($yLow);$i<=floor($yHigh);$i++)
			{
				$i0 = abs($yHigh-$i);
				ImageLine($img, 0, $i0*$yScale, $this->width, $i0*$yScale, $grey);
			}
			for($i=ceil($xLow);$i<=floor($xHigh);$i++)
			{
				$i0 = abs($xLow-$i);
				ImageLine($img, $i0*$xScale, 0, $i0*$xScale, $this->height, $grey);
			}
		}
		
		//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, $this->height, $black);
			for($i=ceil($yLow);$i<=floor($yHigh);$i++)
			{
				$i0 = abs($yHigh-$i);
				ImageLine($img, $x0-3, $i0*$yScale, $x0+3, $i0*$yScale, $black);
			}
		}
		if($yLow <= 0 && $yHigh >= 0)
		{
			//the x-axis is within our range - draw it.
			$y0 = abs($yHigh)*$yScale;
			ImageLine($img, 0, $y0, $this->width, $y0, $black);
			for($i=ceil($xLow);$i<=floor($xHigh);$i++)
			{
				$i0 = abs($xLow-$i);
				ImageLine($img, $i0*$xScale, $y0-3, $i0*$xScale, $y0+3, $black);
			}
		}
		$counter=2;

		//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, $black);
			$counter++;
		}
		$this->image = $img;
	} //end function 'graph'

	function outJPG()
	{
		header("Content-type: image/jpeg");
		ImageJpeg($this->image);
	}

	function outPNG()
	{
		header("Content-type: image/png");
		ImagePng($this->image);
	}
	
	/* will give the developer the GD resource for the graph */
	/* can be used to store the graph to the FS or other mediu, */
	function outGD()
	{
		return $this->image;
	}

} //end class 'eqGraph'
?>
Return current item: Equation Operating System