<commit_before>// This file contains functions that are designed to summarise information about individuals in the simulation. 
// There are two main types of statistics:
// 1) Instantaneous count at that point in time e.g. total number of people currently infected with HCV on 1/6/2013
// 2) Total number of events that occur over a period e.g. the number of infections that occur over a period 2013=2016

// A summary stats call is structured as follows:
// 1) The name of the variable that you want to observe
// 2) The particular status of the variable you want to observe
// 3) The conditions on the person to be included. All people are included unless otherwise stated
// 3.1) An array of set values or
// 3.2) A range of values (inclusive)
// 4) The year that you want to observe it


 
// Example usage

/*
// Count of prevalent cases of HIV
Settings.Name="Currently Living With HIV";
Settings.XLabel="Year";
Settings.YLabel="Currently Living With HIV";
Settings.Type="InstantaneousCount";

Settings.Function= function (Individual, Time){
	if (Individual.HIVInfection<Time && Individual.Alive(Time)){
		return true; //count this individual
	}
	else{
		return false; //don't count this individual
	}
}

CurrentlyLivingWithHIV=new SummaryStatistic(Settings); //Create the summary statistic
CurrentlyLivingWithHIV.run(Population); //Population is an array individuals



// Count of prevalent cases into multiple categories
// A categorical function example might be to look at people with HIV above or below 50 years of age. 
// Define the function as below:

Settings.Function= function (Individual, Time){
	if (Individual.HIVInfection<Time && Individual.Alive(Time)){
		if (Individual.Age(Time)<50){
			return 0;// first category
		}
		else{//over 50
			return 1;// second category
		}
	}
	else{
		return NaN;// no category
	}
}
*/


function CountStatistic(Settings, InputFunction){
	this.Name="";
	// A descriptive text entry about the statistic (optional)
	
	this.XLabel="Time";
	// A string that would be placed on the x axis of the graph (e.g. Year, Month, etc) (optional)
	
	this.YLabel="Value";
	// A string that would be placed on the y axis of the graph (e.g. Diagnoses, Infections, etc) (optional)
	
	this.StatisticType="countstatistic"; // this is an indicator for the main program to use in case the object information gets stripped passing between workers
	
	this.CountType="";
	// 'Instantaneous': how many people at time t have quality a (good for prevalence etc) (mandatory)
	// 'Count': how many events between two times occur to each person. This value is added to an aggregate 
	
	
	this.VectorFunction=false;
	// VectorFunction is a flag to indicate whether Function has arguments of (optional, defaults to non-vector function)
	// false: Function(Person, Time) and returns a single value at time or
	// true: Function(Person, StartTime, EndTime, StepSize)and returns a vector of values over the times specified
	// Setting this flag to true will allow the vector in summary statistic to to be filled by the function under inspection (e.g. EventVector) that could be a lot faster
	
	this.FunctionReturnsCategory=false;
	// FunctionReturnsCategory is a flag to indicate whether Function returns (optional, defaults to simple count)
	// true or false, or it returns an index
	// This flag can only be used with InstantaneousCount. 
	// It cannot be used with CountEvents
	
	this.NumberOfCategories=1; 
	//(mandatory if FunctionReturnsCategory==true)
	
	this.CategoryLabel=[];
	// Used to store the text associated with the categorical numbers (optional)
	
	//this.Function; 
	// Function is user defined
	// If CountType=='Instantaneous' it returns either a 0 or a 1 given a specific time
	// If CountType=='Count' it returns a number >= 0, and is given StartTime, EndTime
	
	// If VectorFunction==true if returns a vector over the period stated
	
	this.StartTime=0;
	this.EndTime=1;
	this.StepSize=1;//set by default to 1
	
	
	this.TimeVector=[];//the reference time for when the statistic was taken
	this.Count=[];//where the counts are stored
	
	// Set the settings
	if (typeof Settings.Name === 'string'){
		this.Name=Settings.Name;
	}
	if (typeof Settings.XLabel === 'string'){
		this.XLabel=Settings.XLabel;
	}
	if (typeof Settings.YLabel === 'string'){
		this.YLabel=Settings.YLabel;
	}
	
	//  Set the CountType of statistic to be collected
	if (typeof Settings.CountType === 'string'){
		this.CountType=Settings.CountType;
	}
	else{
		console.error("A CountType must be specified. CountTypes include: Instantaneous: how many people at time t have quality a (good for prevalence etc), Events: how many events between two times occur to each person. This value is added to an aggregate.") ;
	}
	
	//Set whether the function will be a boolean operator or not.
	if (typeof Settings.VectorFunction === 'boolean'){
		this.VectorFunction=Settings.VectorFunction;
	}
	else if (typeof Settings.VectorFunction != 'undefined'){
		console.error("CountStatistic: VectorFunction must be a boolean operator");
	}
	
	
	
	// Set the Category settings
	if (typeof Settings.FunctionReturnsCategory === 'boolean'){
		if (Settings.FunctionReturnsCategory==true){
			this.FunctionReturnsCategory=true;
			if (typeof Settings.NumberOfCategories === 'number'){
				this.NumberOfCategories=Settings.NumberOfCategories;
				if (typeof Settings.CategoryLabel === 'object'){
					this.CategoryLabel=Settings.CategoryLabel;
				}
			}
			else {
				console.error("CountStatistic: If FunctionReturnsCategory is true, NumberOfCategories must be set to a whole number >1;")
			}
		}
	}
	else if (typeof Settings.FunctionReturnsCategory != 'undefined'){
		console.error("CountStatistic: FunctionReturnsCategory should have only a value of true or false");
	}
	
	// Set the function for the summary statistic
	if (typeof InputFunction === 'function'){
		this.Function=InputFunction;
	}
	else{
		console.error("A function must be set that returns the value to be included in the statistic");
	}
	
	// Set the times for the summary statistic
	if (typeof Settings.StartTime === 'number'){
		this.StartTime=Settings.StartTime;
	}
	else{
		console.error("CountStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.EndTime === 'number'){
		this.EndTime=Settings.EndTime;
		if (this.EndTime<this.StartTime){
			console.error("End time cannot be before start time");
		}
	}
	else{
		console.error("CountStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.StepSize === 'number'){
		this.StepSize=Settings.StepSize;
	}
	

}

CountStatistic.prototype.Run=function(Population){
	// Check that the settings line up
	
	// Set up the time vector
	var CurrentTimeStep=this.StartTime;
	this.NumberOfTimeSteps=Math.round((this.EndTime-this.StartTime)/this.StepSize)+1; //This is used to avoid rounding errors
	for (var TimeIndex=0; TimeIndex<this.NumberOfTimeSteps; TimeIndex++){
		this.TimeVector[TimeIndex]=CurrentTimeStep;
		CurrentTimeStep=CurrentTimeStep+this.StepSize;// Increment the time step
	}
	

		// Set up the Count array
		if (this.FunctionReturnsCategory==false){// inspecting only a single category
			this.Count=ZeroArray(this.NumberOfTimeSteps);
			
			if (this.CountType.toLowerCase()=='instantaneous'){
				this.InstantaneousCount(Population);
			}
			else if (this.CountType.toLowerCase()=='events'){
				this.CountEvents(Population);
			}
		}
		else{ // If there are a number of categories
			this.Count=ZeroMatrix(this.NumberOfCategories, this.NumberOfTimeSteps);
			
			if (this.CountType.toLowerCase()=='instantaneous'){
				this.InstantaneousCountCategorical(Population);
			}
			else if (this.CountType.toLowerCase()=='events'){
				this.CountEventsCategorical(Population);
				console.error("The way this works has not been determined yet. Please do not use countevents and FunctionReturnsCategory together");
			}
		}

	// Destroy the function to make passing back to the main thread easy (the function can break parallelisation)
	this.Function=mull;//The function seems to need to be destroyed before passing the results back to the main controller of the webworker
	
}

CountStatistic.prototype.InstantaneousCount= function (Population){//Used to determine the number of people that satisfy a condition at a particular point in time
	for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
		for (var TimeIndex=0; TimeIndex<this.TimeVector.length; TimeIndex++){
			if (this.Function(Population[PersonIndex], this.TimeVector[TimeIndex])==true){
				this.Count[TimeIndex]++;
			}
		}
	}
}

CountStatistic.prototype.InstantaneousCountCategorical= function (Population){//Used to determine the number of people that satisfy a condition at a particular point in time
	var CategoryIndex;
	for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
		for (var TimeIndex=0; TimeIndex<this.TimeVector.length; TimeIndex++){
			CategoryIndex=this.Function(Population[PersonIndex], this.TimeVector[TimeIndex]);
			if (CategoryIndex>=0){//if it is not equal to NaN
				this.Count[CategoryIndex][TimeIndex]++;
			}
		}
	}
}



CountStatistic.prototype.CountEvents= function (Population){//Used to count how many times something happens over a number of finite periods
	
	// initialise vector with a zero vector
	this.Count=ZeroArray(NumberInVector);

	// for each time step
	var TimeStep=StartTime;
	for (var TimeStepIndex=0; TimeStepIndex<NumberInVector; TimeStepIndex++){
		// for each person
		for (var PIndex=0; PIndex<Population.length; PIndex++){
			//run the SelectionFunction (takes the person, start and endpoints). If you only one once, the count function should express itself as (count between 
			this.Count[TimeStepIndex]=this.Count[TimeStepIndex]+this.CountEventFunction(Population[PIndex], TimeStep, TimeStep+this.StepSize);
			
		}
		TimeStep=TimeStep+StepSize;// note that this may be a little off at the end (+/- 10^-6), but this should be OK for most statistical reporting
	}
};




// Factor: multiply by a factor to accommodate for using a representative sample, for example
CountStatistic.prototype.Adjust= function (Multiplier){
	// for all of the counting functions
	this.Count=Multiply(this.Count, Multiplier);
}

//****************************************************************************************************

function SummaryStatistic(Settings, InputFunction){
	this.Name="";
	// A descriptive text entry about the statistic (optional)
	
	this.XLabel="Time";
	// A string that would be placed on the x axis of the graph (e.g. Year, Month, etc) (optional)
	
	this.YLabel="Value";
	// A string that would be placed on the y axis of the graph (e.g. Diagnoses, Infections, etc) (optional)
	
	this.StatisticType="summarystatistic"; // this is an indicator for the main program to use in case the object information gets stripped passing between workers
	
	
	this.VectorFunction=false;
	// VectorFunction is a flag to indicate whether Function has arguments of (optional, defaults to non-vector function)
	// false: Function(Person, Time) and returns a single value at time or
	// true: Function(Person, StartTime, EndTime, StepSize)and returns a vector of values over the times specified
	// Setting this flag to true will allow the vector in summary statistic to to be filled by the function under inspection (e.g. EventVector) that could be a lot faster
	
	//this.Function; 
	// Function is user defined
	// If CountType=='Instantaneous' it returns either a 0 or a 1 given a specific time
	// If CountType=='Count' it returns a number >= 0, and is given StartTime, EndTime
	
	// If VectorFunction==true if returns a vector over the period stated
	
	this.StartTime=0;
	this.EndTime=1;
	this.StepSize=1;//set by default to 1
	
	
	this.TimeVector=[]; //the reference time for when the statistic was taken
	// A vector/table that stores the results for each eligible person until the simulation is ready to do calculations such as mean etc
	// this value should be deleted at the end of extraction, as it could get extremely large.
	
	this.N;
	this.Mean=[];
	this.Median=[];
	this.Upper95Percentile=[];
	this.Lower95Percentile=[];
	this.StandardDeviation=[];
	this.UpperQuartile=[];
	this.LowerQuartile=[];
	this.Max;
	this.Min;
	
	// Set the settings
	if (typeof Settings.Name === 'string'){
		this.Name=Settings.Name;
	}
	if (typeof Settings.XLabel === 'string'){
		this.XLabel=Settings.XLabel;
	}
	if (typeof Settings.YLabel === 'string'){
		this.YLabel=Settings.YLabel;
	}
	
	//  Set the CountType of statistic to be collected
	if (typeof Settings.CountType === 'string'){
		this.CountType=Settings.CountType;
	}
	else{
		console.error("A CountType must be specified. CountTypes include: Instantaneous: how many people at time t have quality a (good for prevalence etc), Events: how many events between two times occur to each person. This value is added to an aggregate.") ;
	}
	
	//Set whether the function will be a boolean operator or not.
	if (typeof Settings.VectorFunction === 'boolean'){
		this.VectorFunction=Settings.VectorFunction;
	}
	else if (typeof Settings.VectorFunction != 'undefined'){
		console.error("SummaryStatistic: VectorFunction must be a boolean operator");
	}
	
	
	
	// Set the function for the summary statistic
	if (typeof InputFunction === 'function'){
		this.Function=InputFunction;
	}
	else{
		console.error("A function must be set that returns the value to be included in the statistic");
	}
	
	// Set the times for the summary statistic
	if (typeof Settings.StartTime === 'number'){
		this.StartTime=Settings.StartTime;
	}
	else{
		console.error("SummaryStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.EndTime === 'number'){
		this.EndTime=Settings.EndTime;
		if (this.EndTime<this.StartTime){
			console.error("End time cannot be before start time");
		}
	}
	else{
		console.error("SummaryStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.StepSize === 'number'){
		this.StepSize=Settings.StepSize;
	}
	

}

SummaryStatistic.prototype.Run=function(Population){
	
	
	// Set up the time vector
	var CurrentTimeStep=this.StartTime;
	this.NumberOfTimeSteps=Math.round((this.EndTime-this.StartTime)/this.StepSize)+1; //This is used to avoid rounding errors
	for (var TimeIndex=0; TimeIndex<this.NumberOfTimeSteps; TimeIndex++){
		this.TimeVector[TimeIndex]=CurrentTimeStep;
		CurrentTimeStep=CurrentTimeStep+this.StepSize;// Increment the time step
	}
	

	var ValueStorage=ZeroArray(this.NumberOfTimeSteps);
	// A vector/table that stores the results for each eligible person until the simulation is ready to do calculations such as mean etc
	// this value should be deleted at the end of extraction, as it could get extremely large.
	
	var ReturnedResult;
	for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
		// Determine if it is a vector returning function
		if 
		for (var TimeIndex=0; TimeIndex<this.TimeVector.length; TimeIndex++){
			ReturnedResult=this.Function(Population[PersonIndex], this.TimeVector[TimeIndex]);
			if (ReturnedResult==null){
				// Do nothing
			}
			else{//Push the value to the end of the ValueStorage
				ValueStorage[TimeIndex].push(ReturnedResult);
			}
		}
		
		
		
	}
	
	// Perform statistical analyses
	
	
		
	// Destroy the function to make passing back to the main thread easy (the function can break parallelisation)
	this.Function=null;//The function seems to need to be destroyed before passing the results back to the main controller of the webworker
}









<commit_msg>Removed mull error<commit_after>// This file contains functions that are designed to summarise information about individuals in the simulation. 
// There are two main types of statistics:
// 1) Instantaneous count at that point in time e.g. total number of people currently infected with HCV on 1/6/2013
// 2) Total number of events that occur over a period e.g. the number of infections that occur over a period 2013=2016

// A summary stats call is structured as follows:
// 1) The name of the variable that you want to observe
// 2) The particular status of the variable you want to observe
// 3) The conditions on the person to be included. All people are included unless otherwise stated
// 3.1) An array of set values or
// 3.2) A range of values (inclusive)
// 4) The year that you want to observe it


 
// Example usage

/*
// Count of prevalent cases of HIV
Settings.Name="Currently Living With HIV";
Settings.XLabel="Year";
Settings.YLabel="Currently Living With HIV";
Settings.Type="InstantaneousCount";

Settings.Function= function (Individual, Time){
	if (Individual.HIVInfection<Time && Individual.Alive(Time)){
		return true; //count this individual
	}
	else{
		return false; //don't count this individual
	}
}

CurrentlyLivingWithHIV=new SummaryStatistic(Settings); //Create the summary statistic
CurrentlyLivingWithHIV.run(Population); //Population is an array individuals



// Count of prevalent cases into multiple categories
// A categorical function example might be to look at people with HIV above or below 50 years of age. 
// Define the function as below:

Settings.Function= function (Individual, Time){
	if (Individual.HIVInfection<Time && Individual.Alive(Time)){
		if (Individual.Age(Time)<50){
			return 0;// first category
		}
		else{//over 50
			return 1;// second category
		}
	}
	else{
		return NaN;// no category
	}
}
*/


function CountStatistic(Settings, InputFunction){
	this.Name="";
	// A descriptive text entry about the statistic (optional)
	
	this.XLabel="Time";
	// A string that would be placed on the x axis of the graph (e.g. Year, Month, etc) (optional)
	
	this.YLabel="Value";
	// A string that would be placed on the y axis of the graph (e.g. Diagnoses, Infections, etc) (optional)
	
	this.StatisticType="countstatistic"; // this is an indicator for the main program to use in case the object information gets stripped passing between workers
	
	this.CountType="";
	// 'Instantaneous': how many people at time t have quality a (good for prevalence etc) (mandatory)
	// 'Count': how many events between two times occur to each person. This value is added to an aggregate 
	
	
	this.VectorFunction=false;
	// VectorFunction is a flag to indicate whether Function has arguments of (optional, defaults to non-vector function)
	// false: Function(Person, Time) and returns a single value at time or
	// true: Function(Person, StartTime, EndTime, StepSize)and returns a vector of values over the times specified
	// Setting this flag to true will allow the vector in summary statistic to to be filled by the function under inspection (e.g. EventVector) that could be a lot faster
	
	this.FunctionReturnsCategory=false;
	// FunctionReturnsCategory is a flag to indicate whether Function returns (optional, defaults to simple count)
	// true or false, or it returns an index
	// This flag can only be used with InstantaneousCount. 
	// It cannot be used with CountEvents
	
	this.NumberOfCategories=1; 
	//(mandatory if FunctionReturnsCategory==true)
	
	this.CategoryLabel=[];
	// Used to store the text associated with the categorical numbers (optional)
	
	//this.Function; 
	// Function is user defined
	// If CountType=='Instantaneous' it returns either a 0 or a 1 given a specific time
	// If CountType=='Count' it returns a number >= 0, and is given StartTime, EndTime
	
	// If VectorFunction==true if returns a vector over the period stated
	
	this.StartTime=0;
	this.EndTime=1;
	this.StepSize=1;//set by default to 1
	
	
	this.TimeVector=[];//the reference time for when the statistic was taken
	this.Count=[];//where the counts are stored
	
	// Set the settings
	if (typeof Settings.Name === 'string'){
		this.Name=Settings.Name;
	}
	if (typeof Settings.XLabel === 'string'){
		this.XLabel=Settings.XLabel;
	}
	if (typeof Settings.YLabel === 'string'){
		this.YLabel=Settings.YLabel;
	}
	
	//  Set the CountType of statistic to be collected
	if (typeof Settings.CountType === 'string'){
		this.CountType=Settings.CountType;
	}
	else{
		console.error("A CountType must be specified. CountTypes include: Instantaneous: how many people at time t have quality a (good for prevalence etc), Events: how many events between two times occur to each person. This value is added to an aggregate.") ;
	}
	
	//Set whether the function will be a boolean operator or not.
	if (typeof Settings.VectorFunction === 'boolean'){
		this.VectorFunction=Settings.VectorFunction;
	}
	else if (typeof Settings.VectorFunction != 'undefined'){
		console.error("CountStatistic: VectorFunction must be a boolean operator");
	}
	
	
	
	// Set the Category settings
	if (typeof Settings.FunctionReturnsCategory === 'boolean'){
		if (Settings.FunctionReturnsCategory==true){
			this.FunctionReturnsCategory=true;
			if (typeof Settings.NumberOfCategories === 'number'){
				this.NumberOfCategories=Settings.NumberOfCategories;
				if (typeof Settings.CategoryLabel === 'object'){
					this.CategoryLabel=Settings.CategoryLabel;
				}
			}
			else {
				console.error("CountStatistic: If FunctionReturnsCategory is true, NumberOfCategories must be set to a whole number >1;")
			}
		}
	}
	else if (typeof Settings.FunctionReturnsCategory != 'undefined'){
		console.error("CountStatistic: FunctionReturnsCategory should have only a value of true or false");
	}
	
	// Set the function for the summary statistic
	if (typeof InputFunction === 'function'){
		this.Function=InputFunction;
	}
	else{
		console.error("A function must be set that returns the value to be included in the statistic");
	}
	
	// Set the times for the summary statistic
	if (typeof Settings.StartTime === 'number'){
		this.StartTime=Settings.StartTime;
	}
	else{
		console.error("CountStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.EndTime === 'number'){
		this.EndTime=Settings.EndTime;
		if (this.EndTime<this.StartTime){
			console.error("End time cannot be before start time");
		}
	}
	else{
		console.error("CountStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.StepSize === 'number'){
		this.StepSize=Settings.StepSize;
	}
	

}

CountStatistic.prototype.Run=function(Population){
	// Check that the settings line up
	
	// Set up the time vector
	var CurrentTimeStep=this.StartTime;
	this.NumberOfTimeSteps=Math.round((this.EndTime-this.StartTime)/this.StepSize)+1; //This is used to avoid rounding errors
	for (var TimeIndex=0; TimeIndex<this.NumberOfTimeSteps; TimeIndex++){
		this.TimeVector[TimeIndex]=CurrentTimeStep;
		CurrentTimeStep=CurrentTimeStep+this.StepSize;// Increment the time step
	}
	

		// Set up the Count array
		if (this.FunctionReturnsCategory==false){// inspecting only a single category
			this.Count=ZeroArray(this.NumberOfTimeSteps);
			
			if (this.CountType.toLowerCase()=='instantaneous'){
				this.InstantaneousCount(Population);
			}
			else if (this.CountType.toLowerCase()=='events'){
				this.CountEvents(Population);
			}
		}
		else{ // If there are a number of categories
			this.Count=ZeroMatrix(this.NumberOfCategories, this.NumberOfTimeSteps);
			
			if (this.CountType.toLowerCase()=='instantaneous'){
				this.InstantaneousCountCategorical(Population);
			}
			else if (this.CountType.toLowerCase()=='events'){
				this.CountEventsCategorical(Population);
				console.error("The way this works has not been determined yet. Please do not use countevents and FunctionReturnsCategory together");
			}
		}

	// Destroy the function to make passing back to the main thread easy (the function can break parallelisation)
	this.Function=null;//The function seems to need to be destroyed before passing the results back to the main controller of the webworker
	
}

CountStatistic.prototype.InstantaneousCount= function (Population){//Used to determine the number of people that satisfy a condition at a particular point in time
	for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
		for (var TimeIndex=0; TimeIndex<this.TimeVector.length; TimeIndex++){
			if (this.Function(Population[PersonIndex], this.TimeVector[TimeIndex])==true){
				this.Count[TimeIndex]++;
			}
		}
	}
}

CountStatistic.prototype.InstantaneousCountCategorical= function (Population){//Used to determine the number of people that satisfy a condition at a particular point in time
	var CategoryIndex;
	for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
		for (var TimeIndex=0; TimeIndex<this.TimeVector.length; TimeIndex++){
			CategoryIndex=this.Function(Population[PersonIndex], this.TimeVector[TimeIndex]);
			if (CategoryIndex>=0){//if it is not equal to NaN
				this.Count[CategoryIndex][TimeIndex]++;
			}
		}
	}
}



CountStatistic.prototype.CountEvents= function (Population){//Used to count how many times something happens over a number of finite periods
	
	// initialise vector with a zero vector
	this.Count=ZeroArray(NumberInVector);

	// for each time step
	var TimeStep=StartTime;
	for (var TimeStepIndex=0; TimeStepIndex<NumberInVector; TimeStepIndex++){
		// for each person
		for (var PIndex=0; PIndex<Population.length; PIndex++){
			//run the SelectionFunction (takes the person, start and endpoints). If you only one once, the count function should express itself as (count between 
			this.Count[TimeStepIndex]=this.Count[TimeStepIndex]+this.CountEventFunction(Population[PIndex], TimeStep, TimeStep+this.StepSize);
			
		}
		TimeStep=TimeStep+StepSize;// note that this may be a little off at the end (+/- 10^-6), but this should be OK for most statistical reporting
	}
};




// Factor: multiply by a factor to accommodate for using a representative sample, for example
CountStatistic.prototype.Adjust= function (Multiplier){
	// for all of the counting functions
	this.Count=Multiply(this.Count, Multiplier);
}

//****************************************************************************************************

function SummaryStatistic(Settings, InputFunction){
	this.Name="";
	// A descriptive text entry about the statistic (optional)
	
	this.XLabel="Time";
	// A string that would be placed on the x axis of the graph (e.g. Year, Month, etc) (optional)
	
	this.YLabel="Value";
	// A string that would be placed on the y axis of the graph (e.g. Diagnoses, Infections, etc) (optional)
	
	this.StatisticType="summarystatistic"; // this is an indicator for the main program to use in case the object information gets stripped passing between workers
	
	
	this.VectorFunction=false;
	// VectorFunction is a flag to indicate whether Function has arguments of (optional, defaults to non-vector function)
	// false: Function(Person, Time) and returns a single value at time or
	// true: Function(Person, StartTime, EndTime, StepSize)and returns a vector of values over the times specified
	// Setting this flag to true will allow the vector in summary statistic to to be filled by the function under inspection (e.g. EventVector) that could be a lot faster
	
	//this.Function; 
	// Function is user defined
	// If CountType=='Instantaneous' it returns either a 0 or a 1 given a specific time
	// If CountType=='Count' it returns a number >= 0, and is given StartTime, EndTime
	
	// If VectorFunction==true if returns a vector over the period stated
	
	this.StartTime=0;
	this.EndTime=1;
	this.StepSize=1;//set by default to 1
	
	
	this.TimeVector=[]; //the reference time for when the statistic was taken
	// A vector/table that stores the results for each eligible person until the simulation is ready to do calculations such as mean etc
	// this value should be deleted at the end of extraction, as it could get extremely large.
	
	this.N;
	this.Mean=[];
	this.Median=[];
	this.Upper95Percentile=[];
	this.Lower95Percentile=[];
	this.StandardDeviation=[];
	this.UpperQuartile=[];
	this.LowerQuartile=[];
	this.Max;
	this.Min;
	
	// Set the settings
	if (typeof Settings.Name === 'string'){
		this.Name=Settings.Name;
	}
	if (typeof Settings.XLabel === 'string'){
		this.XLabel=Settings.XLabel;
	}
	if (typeof Settings.YLabel === 'string'){
		this.YLabel=Settings.YLabel;
	}
	
	//  Set the CountType of statistic to be collected
	if (typeof Settings.CountType === 'string'){
		this.CountType=Settings.CountType;
	}
	else{
		console.error("A CountType must be specified. CountTypes include: Instantaneous: how many people at time t have quality a (good for prevalence etc), Events: how many events between two times occur to each person. This value is added to an aggregate.") ;
	}
	
	//Set whether the function will be a boolean operator or not.
	if (typeof Settings.VectorFunction === 'boolean'){
		this.VectorFunction=Settings.VectorFunction;
	}
	else if (typeof Settings.VectorFunction != 'undefined'){
		console.error("SummaryStatistic: VectorFunction must be a boolean operator");
	}
	
	
	
	// Set the function for the summary statistic
	if (typeof InputFunction === 'function'){
		this.Function=InputFunction;
	}
	else{
		console.error("A function must be set that returns the value to be included in the statistic");
	}
	
	// Set the times for the summary statistic
	if (typeof Settings.StartTime === 'number'){
		this.StartTime=Settings.StartTime;
	}
	else{
		console.error("SummaryStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.EndTime === 'number'){
		this.EndTime=Settings.EndTime;
		if (this.EndTime<this.StartTime){
			console.error("End time cannot be before start time");
		}
	}
	else{
		console.error("SummaryStatistic: StartTime and EndTime must be set");
	}
	if (typeof Settings.StepSize === 'number'){
		this.StepSize=Settings.StepSize;
	}
	

}

SummaryStatistic.prototype.Run=function(Population){
	
	
	// Set up the time vector
	var CurrentTimeStep=this.StartTime;
	this.NumberOfTimeSteps=Math.round((this.EndTime-this.StartTime)/this.StepSize)+1; //This is used to avoid rounding errors
	for (var TimeIndex=0; TimeIndex<this.NumberOfTimeSteps; TimeIndex++){
		this.TimeVector[TimeIndex]=CurrentTimeStep;
		CurrentTimeStep=CurrentTimeStep+this.StepSize;// Increment the time step
	}
	

	var ValueStorage=ZeroArray(this.NumberOfTimeSteps);
	// A vector/table that stores the results for each eligible person until the simulation is ready to do calculations such as mean etc
	// this value should be deleted at the end of extraction, as it could get extremely large.
	
	// Determine if it is a vector returning function
	if (this.VectorFunction==false){
		var ReturnedResult;
		for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
			for (var TimeIndex=0; TimeIndex<this.TimeVector.length; TimeIndex++){
				ReturnedResult=this.Function(Population[PersonIndex], this.TimeVector[TimeIndex]);
				if (ReturnedResult==null){
					// Do nothing
				}
				else{//Push the value to the end of the ValueStorage
					ValueStorage[TimeIndex].push(ReturnedResult);
				}
			}
		}
	}
	else{// using a vector function to return the result
		var ReturnedArray;
		var ReturnedResult;
		for (var PersonIndex=0; PersonIndex<Population.length; PersonIndex++){
			if (ReturnedArray.length!=TimeIndex.length){
				console.error("The returned array was not of the same length as the time vector");
			}
			
			
			
		}
	}
			

	
	// Perform statistical analyses
	this.N[TimeIndex]=ValueStorage[TimeIndex].length;
	this.Mean[TimeIndex]=Mean(ValueStorage[TimeIndex]);
		
	// Destroy the function to make passing back to the main thread easy (the function can break parallelisation)
	this.Function=null;//The function seems to need to be destroyed before passing the results back to the main controller of the webworker
}










{'fraction_non_alphanumeric': 0.10456623904483678, 'fraction_numerical': 0.0043503111901435285, 'mean_word_length': 4.304530907865925, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7294123', 'n_tokens_mistral': 9581, 'n_tokens_neox': 8677, 'n_words': 3308}