src/hamiltonian/LocalConnectivityConstraint.js



import HardConstraint from "./HardConstraint.js"
import ParameterChecker from "./ParameterChecker.js"

/** Version of the {@link ConnectivityConstraint} which only checks local
 * connectivity.
 * @experimental
 * */
class LocalConnectivityConstraint extends HardConstraint {

	/** The constructor of the LocalConnectivityConstraint requires a conf
	 * object with one parameter.
	 * @param {object} conf - parameter object for this constraint.
	 * @param {PerKindBoolean} conf.CONNECTED - should the cellkind be connected
	 * or not?
	 * */
	constructor( conf ){
		super(conf)
	}
	

	/** 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( "CONNECTED", "KindArray", "Boolean" )
	}
	
	/** Get the connected components of a set of pixels.
	 * @param {object} pixelObject - an object with as keys the
	 * {@link IndexCoordinate}s of the pixels to check.
	 * @return {object} an array with an element for every connected component,
	 * which is in turn an array of the {@link ArrayCoordinate}s of the pixels
	 * belonging to that component.
	 * */
	connectedComponentsOf( pixelObject ){
	
		let cbpi = Object.keys( pixelObject )
		
		let visited = {}, k=0, pixels = [], C = this.C
		let labelComponent = function(seed, k){
			let q = [seed]
			let cellId = C.grid.pixti(q)
			visited[q[0]] = 1
			pixels[k] = []
			while( q.length > 0 ){
				let e = q.pop()
				pixels[k].push(C.grid.i2p(e) )
				let ne = C.grid.neighi( e )
				for( let i = 0 ; i < ne.length ; i ++ ){
					if( C.grid.pixti( ne[i] ) === cellId &&
						!(ne[i] in visited) && (ne[i] in pixelObject) ){
						q.push(ne[i])
						visited[ne[i]]=1
					}
				}
			}
		}
		for( let i = 0 ; i < cbpi.length ; i ++ ){
			let pi = parseInt( cbpi[i] )
			if( !(pi in visited) ){
				labelComponent( pi, k )
				k++
			}
		}
		return pixels
	}

	
	/** This method checks if the connectivity still holds after pixel tgt_i is
	 * changed from tgt_type to src_type.
	 * @param {IndexCoordinate} tgt_i - the pixel to change.
	 * @param {CellId} src_type - the new cell for this pixel.
	 * @param {CellId} tgt_type - the cell the pixel belonged to previously.
	 * */
	checkConnected( tgt_i, src_type, tgt_type ){
	
		// Get neighbors of the target pixel
		let nbh = this.C.grid.neighi( tgt_i )
		
		// object storing the neighbors of tgt_type
		let nbhObj = {}
		
		for( let n of nbh ){
		
			// add it and its neighbors to the neighborhood object
			if( this.C.grid.pixti(n) === tgt_type ){
				nbhObj[n] = true
			}
			
			/*for( let i of this.C.grid.neighi(n) ){
			
				if( ( this.C.grid.pixti(i) == tgt_type ) && !( i == tgt_i ) ){
					nbhObj[i] = true
				}
			}*/
		}
		
		// Get connected components.
		let conn = this.connectedComponentsOf( nbhObj )
		
		return ( conn.length === 1 )
		
	}

	/** Method for hard constraints to compute whether the copy attempt fulfills
	 * the rule.
	 * @param {IndexCoordinate} src_i - coordinate of the source pixel that
	 * tries to copy.
	 * @param {IndexCoordinate} tgt_i - 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 {boolean} whether the copy attempt satisfies the constraint.
	 * */
	fulfilled( src_i, tgt_i, src_type, tgt_type ){
		// connectedness of src cell cannot change if it was connected in the first place.
		
		// connectedness of tgt cell
		if( tgt_type !== 0 && this.cellParameter("CONNECTED",tgt_type) ){
			return this.checkConnected( tgt_i, src_type, tgt_type )
		}
		
		return true
	}
}

export default LocalConnectivityConstraint