src/hamiltonian/ActivityMultiBackground.js



import ActivityConstraint from "./ActivityConstraint.js"
import ParameterChecker from "./ParameterChecker.js"
/**
 * The ActivityMultiBackground constraint implements the activity constraint of Potts models,
 but allows users to specify locations on the grid where LAMBDA_ACT is different. 
 See {@link ActivityConstraint} for the normal version of this constraint.
 See {@link ActivityMultiBackground#constructor} for an explanation of the parameters.
 */
class ActivityMultiBackground extends ActivityConstraint {

	/** Creates an instance of the ActivityMultiBackground constraint 
	* @param {object} conf - Configuration object with the parameters.
	* ACT_MEAN is a single string determining whether the activity mean should be computed
	* using a "geometric" or "arithmetic" mean. 
	*/
	/** The constructor of the ActivityConstraint requires a conf object with parameters.
	@param {object} conf - parameter object for this constraint
	@param {string} [conf.ACT_MEAN="geometric"] - should local mean activity be measured with an
	"arithmetic" or a "geometric" mean?
	@param {PerKindArray} conf.LAMBDA_ACT_MBG - strength of the activityconstraint per cellkind and per background.
	@param {PerKindNonNegative} conf.MAX_ACT - how long do pixels remember their activity? Given per cellkind.
	@param {Array} conf.BACKGROUND_VOXELS - an array where each element represents a different background type.
	This is again an array of {@ArrayCoordinate}s of the pixels belonging to that backgroundtype. These pixels
	will have the LAMBDA_ACT_MBG value of that backgroundtype, instead of the standard value.
	*/
	constructor( conf ){
		super( conf )

		/** Activity of all cellpixels with a non-zero activity is stored in this object,
		with the {@link IndexCoordinate} of each pixel as key and its current activity as
		value. When the activity reaches 0, the pixel is removed from the object until it
		is added again. 
		@type {object}*/
		this.cellpixelsact = {} // activity of cellpixels with a non-zero activity
		
		/** Wrapper: select function to compute activities based on ACT_MEAN in conf.
		Default is to use the {@link activityAtGeom} for a geometric mean.
		@type {function}*/
		this.activityAt = this.activityAtGeom
		if( this.conf.ACT_MEAN == "arithmetic" ){
			this.activityAt = this.activityAtArith
		} 
		
		/** Store which pixels belong to which background type 
		@type {Array}*/
		this.bgvoxels = []
		
		/** Track if this.bgvoxels has been set.
		@type {boolean}*/
		this.setup = false
	}
	
	/** This method checks that all required parameters are present in the object supplied to
	the constructor, and that they are of the right format. It throws an error when this
	is not the case.*/
	confChecker(){
		let checker = new ParameterChecker( this.conf, this.C )
		checker.confCheckParameter( "ACT_MEAN", "SingleValue", "String", [ "geometric", "arithmetic" ] )
		checker.confCheckPresenceOf( "LAMBDA_ACT_MBG" )
		checker.confCheckParameter( "MAX_ACT", "KindArray", "NonNegative" )
		
		// Custom checks
		checker.confCheckStructureKindArray( this.conf["LAMBDA_ACT_MBG"], "LAMBDA_ACT_MBG" )
		for( let e of this.conf["LAMBDA_ACT_MBG"] ){
			for( let i of e ){
				if( !checker.isNonNegative(i) ){
					throw("Elements of LAMBDA_ACT_MBG must be non-negative numbers!")
				}
			}
		}
		checker.confCheckPresenceOf( "BACKGROUND_VOXELS" )
		let bgvox = this.conf["BACKGROUND_VOXELS"]
		// Background voxels must be an array of arrays
		if( !(bgvox instanceof Array) ){
			throw( "Parameter BACKGROUND_VOXELS should be an array of at least two arrays!" )
		} else if ( bgvox.length < 2 ){
			throw( "Parameter BACKGROUND_VOXELS should be an array of at least two arrays!" )
		}
		// Elements of the initial array must be arrays.
		for( let e of bgvox ){
			if( !(e instanceof Array) ){
				throw( "Parameter BACKGROUND_VOXELS should be an array of at least two arrays!" )
			}
			
			// Entries of this array must be pixel coordinates, which are arrays of length C.extents.length
			for( let ee of e ){
				let isCoordinate = true
				if( !(ee instanceof Array) ){
					isCoordinate = false
				} else if ( ee.length != this.C.extents.length ){
					isCoordinate = false
				}
				if( !isCoordinate ){
					throw( "Parameter BACKGROUND_VOXELS: subarray elements should be ArrayCoordinates; arrays of length " + this.C.extents.length + "!" )
				}
			}
		}
	}
	
	/** Get the background voxels from input argument or the conf object and store them in a correct format
	in this.bgvoxels. This only has to be done once, but can be called from outside to
	change the background voxels during a simulation (eg in a HTML page).
	 */	
	setBackgroundVoxels( voxels ){
	
		voxels = voxels || this.conf["BACKGROUND_VOXELS"]
	
		// reset if any exist already
		this.bgvoxels = []
		for( let bgkind = 0; bgkind < voxels.length; bgkind++ ){
			this.bgvoxels.push({})
			for( let v of voxels[bgkind] ){
				this.bgvoxels[bgkind][ this.C.grid.p2i(v) ] = true
			}
		}
		this.setup = true

	}
	
	/* ======= ACT MODEL ======= */

	/* Act model : compute local activity values within cell around pixel i.
	 * Depending on settings in conf, this is an arithmetic (activityAtArith)
	 * or geometric (activityAtGeom) mean of the activities of the neighbors
	 * of pixel i.
	 */
	/** Method to compute the Hamiltonian for this constraint. 
	 @param {IndexCoordinate} sourcei - coordinate of the source pixel that tries to copy.
	 @param {IndexCoordinate} targeti - coordinate of the target pixel the source is trying
	 to copy into.
	 @param {CellId} src_type - cellid of the source pixel.
	 @param {CellId} tgt_type - cellid of the target pixel. 
	 @return {number} the change in Hamiltonian for this copy attempt and this constraint.*/ 
	deltaH ( sourcei, targeti, src_type, tgt_type ){
	
		if( ! this.setup ){
			this.setBackgroundVoxels()
		}

		let deltaH = 0, maxact, lambdaact
		let bgindex1 = 0, bgindex2 = 0
		
		for( let bgkind = 0; bgkind < this.bgvoxels.length; bgkind++ ){
			if( sourcei in this.bgvoxels[bgkind] ){
				bgindex1 = bgkind
			}
			if( targeti in this.bgvoxels[bgkind] ){
				bgindex2 = bgkind
			}
		}
		

		// use parameters for the source cell, unless that is the background.
		// In that case, use parameters of the target cell.
		if( src_type != 0 ){
			maxact = this.cellParameter("MAX_ACT", src_type)
			lambdaact = this.cellParameter("LAMBDA_ACT_MBG", src_type)[bgindex1]
		} else {
			// special case: punishment for a copy attempt from background into
			// an active cell. This effectively means that the active cell retracts,
			// which is different from one cell pushing into another (active) cell.
			maxact = this.cellParameter("MAX_ACT", tgt_type)
			lambdaact = this.cellParameter("LAMBDA_ACT_MBG", tgt_type)[bgindex2]
		}
		if( !maxact || !lambdaact ){
			return 0
		}

		// compute the Hamiltonian. The activityAt method is a wrapper for either activityAtArith
		// or activityAtGeom, depending on conf (see constructor).	
		deltaH += lambdaact*(this.activityAt( targeti ) - this.activityAt( sourcei ))/maxact
		return deltaH
	}



}

export default ActivityMultiBackground