<?php
  /**************************************************************************\
  * phpGroupWare API - Homebrewing Class Structures                          *
  * This file written by Miles Lott <milosch@phpgroupware.org>               *
  * Copyright (C) 2001 Miles Lott                                            *
  * -------------------------------------------------------------------------*
  * This library is part of the phpGroupWare API                             *
  * http://www.phpgroupware.org/api                                          * 
  * ------------------------------------------------------------------------ *
  * This library is free software; you can redistribute it and/or modify it  *
  * under the terms of the GNU Lesser General Public License as published by *
  * the Free Software Foundation; either version 2.1 of the License,         *
  * or any later version.                                                    *
  * This library is distributed in the hope that it will be useful, but      *
  * WITHOUT ANY WARRANTY; without even the implied warranty of               *
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
  * See the GNU Lesser General Public License for more details.              *
  * You should have received a copy of the GNU Lesser General Public License *
  * along with this library; if not, write to the Free Software Foundation,  *
  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA            *
  \**************************************************************************/

  /* $Id: class.recipe.inc.php,v 1.7 2001/09/11 12:38:28 milosch Exp $ */

	class recipe
	{
		var $db = '';
		var $table = 'phpgw_brewer_recipes';

		var $id = 0;

		var $recipe = array();
		var $data = array(
			'id'             => 0,
			'name'           => '',
			'style'          => 0,
			'total_vol'      => 0,
			'vol_units'      => '',
			'boil_vol'       => 0,
			'boil_units'     => '',
			'boil_time'      => 0,
			'yeasts'         => array(),
			'num_yeasts'     => 0,
			'yeast_amounts'  => array(),
			'yeast_units'    => '',
			'malts'          => '',
			'num_malts'      => 0,
			'malt_amounts'   => array(),
			'malt_colors'    => array(),
			'malt_gravities' => array(),
			'malt_units'     => '',
			'hops'           => '',
			'num_hops'       => 0,
			'hop_amounts'    => array(),
			'hop_units'      => '',
			'hop_times'      => array(),
			'hop_IBUs'       => array(),
			'hop_bag'        => '',
			'IBU_method'     => '',
			'extraction_efficiency' => 0,
			'apparent_attenuation'  => 0,
			'real_attenuation'      => 0,
			'original_gravity'      => 0,
			'final_gravity'         => 0,
			'alcohol_by_volume'     => 0,
			'color'          => '',
			'IBUs'           => ''
		);

		var $total = 0;
		var $Garetz_data = array();

		var $volume_units = array();
		var $malt_weight_units = array();
		var $yeast_weight_units = array();
		var $hop_weight_units = array();

		var $IBU_method_names = array();

		function recipe($new = False)
		{
			$this->db = $GLOBALS['phpgw']->db;
			if ($new)
			{
				$this->recipe = $this->data;
			}
			$this->set_units();
			$this->set_IBU_methods();
		}

		/*
		Fetch each ingredient's pertinent data, populate the recipe array, then run
		the calculations.
		*/
		function recalc($obj)
		{
			global $DEBUG;

			$this->recipe = $obj->recipe->recipe;

			$this->recipe['malt_colors'] = array();
			$this->recipe['malt_gravities'] = array();
			$this->recipe['hop_IBUs'] = array();

			$this->recipe['num_malts'] = count($this->recipe['malts']);
			$tmp_amt  = array();
			$tmp_grav = array();
			@reset($this->recipe['malts']);
			while(list($key,$val) = @each($this->recipe['malts']))
			{
				$t = array();
				$tmp = $obj->malt->get($val);
				$t = $tmp[$val];

				$this->recipe['malt_colors'][] = $t['avg_color'];
				$this->recipe['malt_gravities'][] = $t['gravity'];
				$tmp_amt[] = $this->recipe['malt_amounts'][$key];
			}
			$this->recipe['malt_amounts'] = $tmp_amt;
			if($DEBUG) { echo '<br>Malt amounts:'; }
			if($DEBUG) { print_r($tmp_amt); }

			@reset($this->recipe['malt_gravities']);
			if($DEBUG) { echo '<br>Malt gravities:'; }
			if($DEBUG) { print_r($this->recipe['malt_gravities']); }
			while(list($key,$val) = @each($this->recipe['malt_gravities']))
			{
				$tmp_grav[$key] = 
					$this->specific_gravity(
						$this->recipe['total_vol'] * $this->volume_units[$this->recipe['vol_units']]['conversion_factor'],
						$this->recipe['malt_amounts'][$key] * $this->malt_weight_units[$this->recipe['malt_units']]['conversion_factor'],
						$this->recipe['malt_gravities'][$key]
					);
				if($DEBUG) { echo '<br>IN:' . $val . ',OUT: ' . $tmp_grav[$key]; }
				if($DEBUG) { echo '<br>Weight: ' . $this->recipe['malt_amounts'][$key] * $this->malt_weight_units[$this->recipe['malt_units']]['conversion_factor']; }
				if($DEBUG) { echo '<br>Conversion: ' . $this->malt_weight_units[$this->recipe['malt_units']]['conversion_factor']; }
					/*
					Specific_Gravity(
						(current_brew.total_volume * volume_units[current_brew.total_volume_units].conversion_factor),
						(current_brew.malt_amounts[i] * malt_weight_units[current_brew.malt_units[i]].conversion_factor),
						current_brew.malts[i].gravity
					);
					*/
			}
			$this->recipe['malt_gravities'] = $tmp_grav;
			if($DEBUG) { print_r($this->recipe['malt_gravities']); }

			$tmp_amt = array();
			$this->recipe['apparent_attenuation'] = 0;
			@reset($this->recipe['yeasts']);
			while(list($key,$val) = @each($this->recipe['yeasts']))
			{
				$tmp = $obj->yeast->get($val);
				$t = $tmp[$val];
				$this->recipe['apparent_attenuation'] = $this->recipe['apparent_attenuation'] + $t['avgatten'];
				$tmp_amt[] = $this->recipe['yeast_amounts'][$key];
			}
			$this->recipe['yeast_amounts'] = $tmp_amt;

			$this->real_attenuation();
			$this->total_gravity();
			$this->total_alcohol();

			$this->total_IBUs();

			$this->recipe['num_hops'] = count($this->recipe['hops']);
			$tmp_amt = array();
			$this->recipe['IBUs'] = 0.00;
			@reset($this->recipe['hops']);
			while(list($key,$val) = @each($this->recipe['hops']))
			{
				$t = array();
				$tmp = $obj->hop->get($val);
				$t = $tmp[$val];

				$this->recipe['hop_IBUs'][] = $t['avg_alpha'];
				$this->recipe['IBUs'] = $this->recipe['IBUs'] + $this->bitterness($this->recipe['hop_amounts'][$key],$t['avg_alpha']);
				$tmp_amt[] = $this->recipe['hop_amounts'][$key];
			}
			$this->recipe['hop_amounts'] = $tmp_amt;

			$obj->save_sessiondata($this->recipe);

			$this->total_color();
			//print_r($this->recipe);
			$obj->save_sessiondata($this->recipe);
		}

		function get($id=0)
		{
			$fields = $this->fields();
			$fieldstr = implode(',',$fields);
			if (!$id)
			{
				$id = $this->id;
			}

			$sql = "SELECT " . $fieldstr . " FROM " . $this->table . " WHERE id=" . $id;
			$this->db->query($sql,__LINE__,__FILE__);
			$this->db->next_record();

			reset($fields);
			while(list($a,$b) = each($fields))
			{
				if ($b == 'malts' || $b == 'yeasts' || $b == 'hops' ||
					$b == 'malt_amounts' || $b == 'yeast_amounts' || $b == 'hop_amounts')
				{
					$this->recipe[$this->db->f('id')][$b] = unserialize($this->db->f($b));
				}
				else
				{
					$this->recipe[$this->db->f('id')][$b] = ereg_replace('_',' ',$this->db->f($b));
				}
			}
			return $this->recipe;
		}

		function set($id=0)
		{
			global $DEBUG;

			$fields = $this->fields();
			$fieldstr = implode(',',$fields); 

			if($id)
			{
				$sql = "UPDATE " . $this->table . " SET ";
				while(list($a,$b) = each($fields))
				{
					if($a != 'id')
					{
						if(gettype($this->recipe[$b]) == 'array')
						{
							$data = serialize($this->recipe[$b]);
						}
						else
						{
							$data = $this->recipe[$b];
						}
						$sql .= $b . "='" . $data . "',";
					}
				}
				$sql = substr($sql,0,-1) . " WHERE id=" . $id;
			}
			else
			{
				while(list($key,$val) = each($fields))
				{
					if($key =='id')
					{
						continue;
					}
					if($DEBUG) { echo '<br>Checking: ' . $val . '=' .$this->recipe[$val]; }
					if(gettype($this->recipe[$val]) == 'array')
					{
						$values[$key] = serialize($this->recipe[$val]);
					}
					else
					{
						$values[$key] = $this->recipe[$val];
					}
					$str .= $val . ',';
				}
				$fieldstr = substr($str,0,-1);

				$values = "'" . implode("','",$values) . "'";
				$sql = "INSERT INTO " . $this->table . "(" . $fieldstr . ") VALUES (" . $values . ")";
			}

			$this->db->query($sql,__LINE__,__FILE__);
			if ($id)
			{
				return $id;
			}
			else
			{
				$this->db->query('SELECT max(id) FROM '.$this->table,__LINE__,__FILE__);
				$this->db->next_record();
				return $this->db->f(0);
			}
		}

		function get_list($start,$query='',$sort='ASC',$order='',$limit=True)
		{
			$fields = $this->fields();
			$fieldstr = implode(',',$fields);

			if($query)
			{
				$querystr = $this->makequery($query);
			}

			$qfunc = 'query';
			$sql = "SELECT " . $fieldstr . " FROM " . $this->table . $querystr;

			if($sort && $order)
			{
				$sort = ' ORDER BY ' . $order . ' ' . $sort . ' ';
			}
			else
			{
				$sort = '';
			}

			if($limit)
			{
				$qfunc = '$this->db->limit_query("' . $sql . $sort . '",' . $start . ',__LINE__,__FILE__);';
			}
			else
			{
				$qfunc = '$this->db->query("' . $sql . $sort . '",__LINE__,__FILE__);';
			}

			$this->db->query($sql,__LINE__,__FILE__);
			$this->total = $this->db->num_rows();

			eval($qfunc);

			while ($this->db->next_record())
			{
				reset($fields);
				while(list($a,$b) = each($fields))
				{
					$data[$this->db->f('id')][$b] = $this->db->f($b);
				}
			}
			return $data;
		}

		function fields()
		{
			@reset($this->data);
			while (list($key,$val) = each($this->data))
			{
				$fields[] = $key;
			}
			return $fields;
		}

		function makequery($query='')
		{
			if (!$query)
			{
				return;
			}

			$s = " WHERE (";

			$fields = $this->fields();
			while (list($key,$val) = each($fields))
			{
				if ($val != 'id')
				{
					$s .= $val . " LIKE '%$query%' OR ";
				}
			}
			$s = substr($s,0,-4);
			$s .= ")";

			return $s;
		}

		/****************************************************************/
		/*  Routine Set_Units initializes the weights and volumes       */
		/*  units/conversion factors.                                   */
		/****************************************************************/
		function set_units()
		{
			$i = 0;
			$j = 0;

			/* Initialize the unit names. */
			for ($j = 0; $j < 4; $j++)
			{
				if ($j <= 1)
				{
					for ($i = 0; $i < 8; $i++)
					{
						$this->volume_units[$j]['name'][$i]      = 0;
						$this->hop_weight_units[$j]['name'][$i] = 0;
					}
				}
				for ($i = 0; $i < 8; $i++)
				{
					$this->malt_weight_units[$j]['name'][$i] = 0;
				}
			}

			/* Volume units/conversion factors. */
			$this->volume_units[0]['name'] = 'gallons';
			$this->volume_units[1]['name'] = 'liters';
			$this->volume_units[0]['conversion_factor'] = 1.0;
			$this->volume_units[1]['conversion_factor'] = 2.645503E-1;

			/* Malt weight units/conversion factors. */
			$this->malt_weight_units[0]['name'] = 'lb';
			$this->malt_weight_units[1]['name'] = 'oz';
			$this->malt_weight_units[2]['name'] = 'kg';
			$this->malt_weight_units[3]['name'] = 'g';
			$this->malt_weight_units[0]['conversion_factor'] = 1.0;
			$this->malt_weight_units[1]['conversion_factor'] = 6.25E-2;
			$this->malt_weight_units[2]['conversion_factor'] = 2.2;
			$this->malt_weight_units[3]['conversion_factor'] = 2.20E-3;

			/* yeast weight units. */
			$this->yeast_weight_units[0]['name'] = 'oz';
			$this->yeast_weight_units[1]['name'] = 'g';

			/* Hop weight units/conversion factors. */
			$this->hop_weight_units[0]['name'] = 'oz';
			$this->hop_weight_units[1]['name'] = 'g';
			$this->hop_weight_units[0]['conversion_factor'] = 1.0;
			$this->hop_weight_units[1]['conversion_factor'] = 3.524229E-2;
		}  /* End Set_Units */

		/****************************************************************/
		/*  Routine Set_IBU_Methods initializes the names of the IBU    */
		/*  calculation methods, as well as the data used by the        */
		/*  Garetz IBU calculation model.                               */
		/****************************************************************/
		function set_IBU_methods()
		{
			/* Initialize the IBU calculation method names. */
			$this->IBU_method_names[0]['name'] = 'Rager';
			$this->IBU_method_names[1]['name'] = 'Garetz';
			$this->IBU_method_names[2]['name'] = 'Tinseth';

			/* Initialize the Garetz cubic spline data structure. */
			$this->Garetz_data[0]['t_data']  =  2.5;
			$this->Garetz_data[0]['y_data']  =  0.0;
			$this->Garetz_data[0]['z_data']  =  0.000000E+00;
			$this->Garetz_data[1]['t_data']  =  7.5;
			$this->Garetz_data[1]['y_data']  =  0.0;
			$this->Garetz_data[1]['z_data']  = -1.713653E-02;
			$this->Garetz_data[2]['t_data']  =  12.5;
			$this->Garetz_data[2]['y_data']  =  2.0;
			$this->Garetz_data[2]['z_data']  =  6.854610E-02;
			$this->Garetz_data[3]['t_data']  =  17.5;
			$this->Garetz_data[3]['y_data']  =  5.0;
			$this->Garetz_data[3]['z_data']  = -1.704788E-02;
			$this->Garetz_data[4]['t_data']  =  22.5;
			$this->Garetz_data[4]['y_data']  =  8.0;
			$this->Garetz_data[4]['z_data']  = -3.545873E-04;
			$this->Garetz_data[5]['t_data']  =  27.5;
			$this->Garetz_data[5]['y_data']  =  11.0;
			$this->Garetz_data[5]['z_data']  =  1.846623E-02;
			$this->Garetz_data[6]['t_data']  =  32.5;
			$this->Garetz_data[6]['y_data']  =  14.0;
			$this->Garetz_data[6]['z_data']  = -7.351033E-02;
			$this->Garetz_data[7]['t_data']  =  37.5;
			$this->Garetz_data[7]['y_data']  =  16.0;
			$this->Garetz_data[7]['z_data']  =  3.557507E-02;
			$this->Garetz_data[8]['t_data']  =  42.5;
			$this->Garetz_data[8]['y_data']  =  18.0;
			$this->Garetz_data[8]['z_data']  = -6.878996E-02;
			$this->Garetz_data[9]['t_data']  =  47.5;
			$this->Garetz_data[9]['y_data']  =  19.0;
			$this->Garetz_data[9]['z_data']  = -4.152313E-04;
			$this->Garetz_data[10]['t_data'] =  55.0;
			$this->Garetz_data[10]['y_data'] =  20.0;
			$this->Garetz_data[10]['z_data'] = -6.089251E-03;
			$this->Garetz_data[11]['t_data'] =  65.0;
			$this->Garetz_data[11]['y_data'] =  21.0;
			$this->Garetz_data[11]['z_data'] =  1.623800E-03;
			$this->Garetz_data[12]['t_data'] =  75.0;
			$this->Garetz_data[12]['y_data'] =  22.0;
			$this->Garetz_data[12]['z_data'] = -4.059501E-04;
			$this->Garetz_data[13]['t_data'] =  85.0;
			$this->Garetz_data[13]['y_data'] =  23.0;
			$this->Garetz_data[13]['z_data'] =  0.000000E+00;
		}  /* End Set_IBU_Methods */

		/****************************************************************/
		/*  Routine Total_IBUs calculates the bitterness of the wort    */
		/*  (IBUs) by summing over all the chosen hops.                 */
		/****************************************************************/
		function total_IBUs()
		{
			$i = 0;

			/* Calculate the bitterness by summing over */
			/* all of the hops used in the current brew. */
			$this->recipe['IBUs'] = 0;
			for ($i = 0; $i < $this->recipe['num_hops']; $i++)
			{
				$this->recipe['IBUs'] = $this->recipe['IBUs'] + $this->recipe['hop_IBUs'][$i];
			}
			// TODO move(21+MAX_NUM_MALTS+MAX_NUM_HOPS, 45);
			// TODO printw("%5.1f", current_brew.IBUs);

		}  /* End Total_IBUs */

		/****************************************************************/
		/*  Routine Total_Gravity calculates the original specific      */
		/*  gravity of the wort by summing over all the chosen grains   */
		/*  and adjuncts.                                               */
		/****************************************************************/
		function total_gravity()
		{
			$i = 0;

			/* Calculate the original gravity by summing over */
			/* all of the malts used in the current brew. */
			$this->recipe['original_gravity'] = 0;
			for ($i = 0; $i < $this->recipe['num_malts']; $i++)
			{
				$this->recipe['original_gravity'] = $this->recipe['original_gravity'] + $this->recipe['malt_gravities'][$i];
			}

			/* Calculate the final gravity from the original */
			/* gravity and apparent attenuation of the brew. */
			$this->recipe['final_gravity'] = ($this->recipe['original_gravity']) * (1 - ($this->recipe['apparent_attenuation'])/100) + 1;

			$this->recipe['original_gravity'] = $this->recipe['original_gravity'] + 1;
			// TODO move(18+MAX_NUM_MALTS+MAX_NUM_HOPS, 45);
			// TODO printw("%5.3f", $this->original_gravity);
		}

		/****************************************************************/
		/*  Routine Total_Alcohol calculates the alcohol by volume in   */
		/*  the finished beer based upon the original and final spec-   */
		/*  ific gravities of the wort.                                 */
		/****************************************************************/
		function total_alcohol()
		{
			/* Calculate the alcohol by volume from the */
			/* original and final gravities of the brew.. */
			$this->recipe['alcohol_by_volume'] = $this->alcohol_by_volume($this->recipe['original_gravity'],$this->recipe['final_gravity']);
			$this->recipe['alcohol_by_weight'] = $this->alcohol_by_weight($this->recipe['original_gravity'],$this->recipe['final_gravity']);

			// TODO move(19+MAX_NUM_MALTS+MAX_NUM_HOPS, 46);
			// TODO printw("%4.1f", current_brew.alcohol_by_volume);
		}

		/****************************************************************/
		/*  Routine Total_Color calculates the color of the wort by     */
		/*  summing over all the chosen grains and adjuncts.            */
		/****************************************************************/
		function total_color()
		{
			$i = 0;

			/* Calculate the color by summing over */
			/* all of the malts used in the current brew. */
			$this->recipe['color'] = 0;
			for ($i = 0; $i < $this->recipe['num_malts']; $i++)
			{
				$this->recipe['color'] = $this->recipe['color'] + $this->recipe['malt_colors'][$i];
			}
			// TODO move(20+MAX_NUM_MALTS+MAX_NUM_HOPS, 45);
			// TODO printw("%5.1f", current_brew.color);
		}

		/****************************************************************/
		/*  Routine Bitterness calculates the bitterness (IBUs) of the  */
		/*  specified weight of the specified hops.                     */
		/****************************************************************/
		function bitterness($weight,$alpha)
		{
			$boil_time    = $this->recipe['boil_time'];
			$total_volume = $this->recipe['total_vol'];
			$boil_volume  = $this->recipe['boil_vol'];
			$gravity      = $this->recipe['original_gravity'];
			$bag          = $this->recipe['hop_bag'];

			if ($this->recipe['IBU_method'] == 0)
			{
				/* Jackie Rager's specific gravity adjustment formula. */
				if ((1 + ($gravity - 1)*($total_volume/$boil_volume)) > 1.050)
				{
					$gravity_adjustment = 1 + ((1 + ($gravity - 1) * ($total_volume/$boil_volume)) - 1.050)/0.2;
				}
				else
				{
					$gravity_adjustment = 1;
				}
				$adjustment = 1/$gravity_adjustment;
			}
			elseif ($this->recipe['IBU_method'] == 1)
			{
				/* Mark Garetz's adjustment formula: specific gravity and */
				/* hopping rate adjustments are included, but temperature */
				/* (elevation) adjustments are neglected. */
				if ((1 + ($gravity - 1)*($total_volume/$boil_volume)) > 1.050)
				{
					$gravity_adjustment = 1 + ((1 + ($gravity - 1)*
						($total_volume/$boil_volume)) - 1.050)/0.2;
				}
				else
				{
					$gravity_adjustment = 1;
				}
				$IBUs_old = 100;
				$adjustment = 1/($gravity_adjustment * (1 + (($total_volume/$boil_volume) * $IBUs_old)/260.0));
				$iterations = 0;
			}
			elseif ($this->recipe['IBU_method'] == 2)
			{
				/* Glenn Tinseth's specific gravity adjustment formula. */ 
				$adjustment = 1.65 * (float)pow((double)1.25E-4,
					(double)(($gravity - 1)*
					($total_volume/$boil_volume)));
			}

			while(1)
			{
				$IBUs = $this->hop_utilization($boil_time) * $alpha * $weight * 74.90 * ($adjustment / $total_volume);
				if ($this->recipe['IBU_method'] == 1)
				{
					/* Mark Garetz's hopping rate adjustment term */
					/* requires an iteration solution technique. */
					if (abs($IBUs - $IBUs_old) >= 1.0E-4)
					{
						$adjustment = 1 / ( $gravity_adjustment * (1 + ( ($total_volume/$boil_volume) * ($IBUs)/260.0) ) );
						$IBUs_old = $IBUs;
						/* old iteration repeated this */
					}
					else
					{
						break;
					}
				}
				else
				{
					break;
				}
			}
    
			/* Adjust the calculated bitterness of the hops to */
			/* account for the yeast flocculation rate. */
			if ($this->recipe['yeasts'][0]['flocculation'] == 0)
			{
				/* For LOW flocculation yeast, decrease the hops */
				/* bitterness by 5 percent. */
				$IBUs = $IBUs/1.05;
			}
			else if ($this->recipe['yeasts'][0]['flocculation'] == 2)
			{
				/* For HIGH flocculation yeast, increase the hops */
				/* bitterness by 5 percent. */
				$IBUs = $IBUs*1.05;
			}

			/* Adjust the calculated bitterness of the hops to */
			/* account for the use of a hop bag. */
			if ($bag)
			{
				/* Decrease the calculated bitterness of the hops */
				/* by 10 percent when using a loosely to moderately */
				/* stuffed hop bag. */
				$IBUs = $IBUs/1.10;
			}
			return($IBUs);
		}

		/****************************************************************/
		/*  Routine Hop_Utilization calculates the utilization of the   */
		/*  hops for the specified boiling time.                        */
		/****************************************************************/
		function hop_utilization($minutes)
		{
			$minutes = $this->recipe['boil_time'];

			if ($this->recipe['IBU_method'] == 0)
			{
				/* Jackie Rager's hop utilization formula. tan was tanh in C */
				$hop_utilization = (18.10907 + 13.86204 * tan(($minutes - 31.32275) / 18.26774)) / 100;
			}
			elseif ($this->recipe['IBU_method'] == 1)
			{
				/* Mark Garetz's hop utilization cubic-spline curve fit. */
				$hop_utilization = $this->evaluate_cubic_spline($minutes, 14, $this->Garetz_data);
				$hop_utilization = $hop_utilization / 100.0;
				if ($hop_utilization < 0.0)
				{
					$hop_utilization = 0.0;
				}
			}
			elseif ($this->recipe['IBU_method'] == 2)
			{
				/* Glenn Tinseth's hop utilization formula. */ 
				$hop_utilization = (1 - exp(-0.04 * $minutes) ) / 4.15;
			}
			$this->recipe['hop_utilization'] = $hop_utilization;
			return($hop_utilization);
		}

		/****************************************************************/
		/*  Routine Real_Attenuation calculates the real attenuation    */
		/*  of the yeast based upon the input apparent attenuation.     */
		/****************************************************************/
		function real_attenuation()
		{
			$this->recipe['real_attenuation'] = 0.8192 * $this->recipe['apparent_attenuation'];
		}

		/****************************************************************/
		/*  Routine Evaluate_Cubic_Spline evaluates the cubic spline    */
		/*  curve-fitting function at the specified value of t.         */
		/****************************************************************/
		function evaluate_cubic_spline($t_value, $num_data_points, $data)
		{
			for ($i = ($num_data_points - 2); $i >= 1; $i--)
			{
				$d_value = $t_value - $data[$i]['t_data'];
				if ($d_value >= 0.0)
				{
					$found = 1;
					break;
				}
			}
			if (!$found)
			{
				$i = 0;
				$d_value = $t_value - $data[0]['t_data'];
			}

			$h_value = ($data[$i+1]['t_data'] - $data[$i]['t_data']);
			$b_value = (($data[$i+1]['y_data'] - $data[$i]['y_data']) / $h_value) -
				($h_value * ($data[$i+1]['z_data'] + 2.0 * $data[$i]['z_data'])/6.0);
			$p_value = ($data[$i]['z_data']/2.0) +
				($data[$i+1]['z_data'] - $data[$i]['z_data']) * ($d_value / (6.0 * $h_value));
			$p_value = $b_value + ($d_value * $p_value);
			$interpolated_value = $data[$i]['y_data'] + ($d_value * $p_value);

			return($interpolated_value);
		}

		/****************************************************************/
		/*  Routine Specific_Gravity calculates the contribution to     */
		/*  the specific gravity of the wort due to a single grain or   */
		/*  adjunct for the specified weight (pounds) and total volume  */
		/*  (gallons).                                                  */
		/****************************************************************/
		function specific_gravity($batch_size, $weight, $gravity)
		{
			$partial_specific_gravity = 0;

			$partial_specific_gravity = ($weight * ($gravity - 1)) / $batch_size;

			return($partial_specific_gravity);
		}

		/****************************************************************/
		/*  Routine Color calculates the contribution to the color of   */
		/*  the wort due to a single grain/adjunct for the specified    */
		/*  weight (pounds) and total volume (gallons).                 */
		/****************************************************************/
		function color($batch_size, $weight, $color)
		{
			$partial_color = 0;

			$partial_color = ($weight * $color) / $batch_size;

			return($partial_color);
		}  /* End Color */

		/****************************************************************/
		/*  Routine Alcohol_by_Weight calculates the percentage of      */
		/*  alcohol by weight of the finished beer based upon the       */
		/*  original and final specific gravities of the wort.          */
		/****************************************************************/
		function alcohol_by_weight($original_gravity, $final_gravity)
		{
			$alcohol_by_weight = 0;

			$alcohol_by_weight = ($original_gravity - $final_gravity)/
				(2.310742E-2 - 1.301758E-2 * $original_gravity);

			return($alcohol_by_weight);
		}

		/****************************************************************/
		/*  Routine Alcohol_by_Volume calculates the percentage of      */
		/*  alcohol by volume of the finished beer based upon the       */
		/*  original and final specific gravities of the wort.          */
		/****************************************************************/
		function alcohol_by_volume($original_gravity, $final_gravity)
		{
			$alcohol_by_volume = 0;

			$alcohol_by_volume = ($original_gravity - $final_gravity) / (1.892960E-2 - (1.066400E-2 * $original_gravity));

			return($alcohol_by_volume);
		}
	}
?>
