<?php
/**
 * PHP Unit tests for RunningStat class.
 * @covers RunningStat
 */
class RunningStatTest extends PHPUnit_Framework_TestCase {

	public $points = array(
		49.7168, 74.3804,  7.0115, 96.5769, 34.9458,
		36.9947, 33.8926, 89.0774, 23.7745, 73.5154,
		86.1322, 53.2124, 16.2046, 73.5130, 10.4209,
		42.7299, 49.3330, 47.0215, 34.9950, 18.2914,
	);

	/**
	 * Verify that the statistical moments and extrema computed by RunningStat
	 * match expected values.
	 * @covers RunningStat::push
	 * @covers RunningStat::count
	 * @covers RunningStat::getMean
	 * @covers RunningStat::getVariance
	 * @covers RunningStat::getStdDev
	 */
	public function testRunningStatAccuracy() {
		$rstat = new RunningStat();
		foreach( $this->points as $point ) {
			$rstat->push( $point );
		}

		$mean = array_sum( $this->points ) / count( $this->points );
		$variance = array_sum( array_map( function ( $x ) use ( $mean ) {
			return pow( $mean - $x, 2 );
		}, $this->points ) ) / ( count( $rstat ) - 1 );
		$stddev = sqrt( $variance );

		$this->assertEquals( count( $rstat ), count( $this->points ) );
		$this->assertEquals( $rstat->min, min( $this->points ) );
		$this->assertEquals( $rstat->max, max( $this->points ) );
		$this->assertEquals( $rstat->getMean(), $mean );
		$this->assertEquals( $rstat->getVariance(), $variance );
		$this->assertEquals( $rstat->getStdDev(), $stddev );
	}

	/**
	 * When one RunningStat instance is merged into another, the state of the
	 * target RunningInstance should have the state that it would have had if
	 * all the data had been accumulated by it alone.
	 * @covers RunningStat::merge
	 * @covers RunningStat::count
	 */
	public function testRunningStatMerge() {
		$expected = new RunningStat();

		foreach( $this->points as $point ) {
			$expected->push( $point );
		}

		// Split the data into two sets
		$sets = array_chunk( $this->points, floor( count( $this->points ) / 2 ) );

		// Accumulate the first half into one RunningStat object
		$first = new RunningStat();
		foreach( $sets[0] as $point ) {
			$first->push( $point );
		}

		// Accumulate the second half into another RunningStat object
		$second = new RunningStat();
		foreach( $sets[1] as $point ) {
			$second->push( $point );
		}

		// Merge the second RunningStat object into the first
		$first->merge( $second );

		$this->assertEquals( count( $first ), count( $this->points ) );
		$this->assertEquals( $first, $expected );
	}
}
