spec/hamiltonian/LocalConnectivityConstraintSpec.js

/* 
	TODO
	- implement some checks for a 3D CPM
	- further test paramChecker
	- implement a stress test (somewhere else?): run a simulation at parameters where the cell has risk of
	breaking, and test at several times whether the connectedness remains intact.

*/

/** @test {LocalConnectivityConstraint} */
describe("LocalConnectivityConstraint", function () {
	let CPM = require("../../build/artistoo-cjs.js")
	let C, conn

	let fakeNeighi = function(i){
		//console.log( i + " " + testGrid.neighi( i ) )
		if( i === 3 ){ return [12674,12675,12676,2,4,130,131,132]}
		if( i === 259 ){ return [130,131,132,258,260,386,387,388]}
		if( i === 515 ){ return [386,387,388,514,516,642,643,644]}
		if( i === 131 ){ return [2,3,4,130,132,258,259,260]}
		if( i === 387 ){ return [258,259,260,386,388,514,515,516]}
		if( i === 0 ){ return [12771,12672,12673,99,1,227,128,129]}
		if( i === 129 ){ return [0,1,2,128,130,256,257,258]}
		if( i === 258 ){ return [129,130,131,257,259,385,386,387]}
		if( i === 516 ){ return [258,259,260,386,388,514,515,516]}
	}

	beforeEach(function () {
		C = new CPM.CPM([100, 100], {T: 20})
		conn = new CPM.LocalConnectivityConstraint({
			CONNECTED: [false, true]
		})
		C.add( conn )

	})

	describe( "[ Unit tests ]", function() {
		/* Testing the connected components method for specific cases */
		/** @test {LocalConnectivityConstraint#connectedComponentsOf} */
		describe("method [ connectedComponentsOf ]", function () {

			beforeEach(function () {
				// Replace the .grid.p2i function with mock code for real unit tests.
				C = jasmine.createSpyObj("C", [ "pixti","getConstraint" ])
				C.grid = jasmine.createSpyObj( "C.grid", ["p2i","i2p","neighi","pixti"] )
				C.grid.p2i.and.callFake( function(p) {
					let x = p[0], y = p[1]
					// hard code the required values.
					if( x === 2 && y === 2 ){ return 258 }
					if( x === 3 && y === 3 ){ return 387 }
					if( x === 4 && y === 4 ){ return 516 }
					if( x === 2 && y === 3 ){ return 259 }
					if( x === 0 && y === 3 ){ return 3 }
					if( x === 4 && y === 3 ){ return 515 }
					if( x === 0 && y === 0 ){ return 0 }
					if( x === 1 && y === 3 ){ return 131 }
					if( x === 1 && y === 1 ){ return 129 }

					// This should not happen.
					return NaN
				})
				// hard code the neighborhoods returned:
				C.grid.neighi.and.callFake( function(i){
					//console.log( i + " " + testGrid.neighi( i ) )
					return fakeNeighi(i)
				})

				// i2p always returns the same array, this shouldn't matter because
				// only the length of the connected components is tested here.
				C.grid.i2p.and.returnValue( [0,0] )

				conn = new CPM.LocalConnectivityConstraint({
					CONNECTED: [false, true]
				})
				conn.C = C
				C.getConstraint.and.callFake( function(){
					return conn
				})

				// pixti doesn't have to work, assume the whole neighborhood is
				// the same type and just look at whether a pixel is in the
				// object or not.
				C.pixti.and.returnValue( 1 )
				C.grid.pixti.and.returnValue( 1 )
			})

			it("should return only one component in connected case", function () {
				let nbhObj = {}
				for (let x = 0; x < 5; x++) {
					nbhObj[C.grid.p2i([x, 3])] = true
				}
				expect(C.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(1)

				nbhObj = {}
				for (let x = 0; x < 5; x++) {
					nbhObj[C.grid.p2i([x, x])] = true
				}
				expect(C.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(1)
			})
			it("should return multiple components in disconnected case", function () {
				let nbhObj = {}
				for (let x = 0; x < 5; x++) {
					if (x % 2 === 0) {
						nbhObj[C.grid.p2i([x, 3])] = true
					}
				}
				expect(C.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(3)

			})

		})
	})



	/* Testing if the overall constraint works, specific case*/


	describe( "[ Integration tests ]", function() {

		/* Integration test: Testing the parameter checker for this constraint*/
		/** @test {LocalConnectivityConstraint#confChecker} */
		describe("integration with ParameterChecker", function () {
			it("should throw an error when CONNECTED parameter is unspecified", function () {
				expect(function () {
					//noinspection JSCheckFunctionSignatures
					C.add(new CPM.LocalConnectivityConstraint({}))
				}).toThrow("Cannot find parameter CONNECTED in the conf object!")
			})
		})
		/* Integration test: constraint as a whole but with a mock CPM*/
		describe("when copy attempt would disrupt local connectivity", function () {
			let src_i, tgt_i, src_type, tgt_type

			beforeEach(function () {
				C = jasmine.createSpyObj("C", [ "pixti","getConstraint","cellKind" ])
				C.grid = jasmine.createSpyObj( "C.grid", ["neighi","pixti","i2p","p2i"] )
				conn = new CPM.LocalConnectivityConstraint({
					CONNECTED: [false, true]
				})
				conn.CPM = C
				C.getConstraint.and.callFake( function() {return conn})
				C.pixti.and.callFake(function(i){
					if( i === 0 || i === 1 || i === 2 ){
						return 1
					}
					return 0
					//if( i === 258 || i === 387 || i === 516  ){ return 1 }

				})
				C.grid.pixti.and.callFake( function(i) {
					if( i === 0 || i === 1 || i === 2 ){
						return 1
					}
					return 0
				})
				C.grid.neighi.and.callFake( function(i){
					return [ i - 1, i + 1 ]
				})
				C.grid.i2p.and.callFake( function(i){
					return [0,i]
				})
				C.grid.p2i.and.callFake( function(p){
					return p[1]
				})
				C.cellKind.and.callFake( function(t){
					if( t === 1 ){return 1 }
					return 0
				})
				src_i = 102 //259 //C.grid.p2i([2, 3])
				tgt_i = 1 //387 //C.grid.p2i([3, 3])
				src_type = C.pixti(src_i)
				tgt_type = C.pixti(tgt_i)
			})

			/** @test {LocalConnectivityConstraint#checkConnected} */
			it("#checkConnected should return false", function () {
				expect(C.getConstraint("LocalConnectivityConstraint").checkConnected(tgt_i, src_type, tgt_type)).toBeFalsy()
			})

			/** @test {LocalConnectivityConstraint#fulfilled} */
			describe("and when CONNECTED for the tgt cellKind", function () {
				it("is true, constraint should not be fulfilled", function () {
					expect(C.getConstraint("LocalConnectivityConstraint").fulfilled(src_i, tgt_i, src_type, tgt_type)).toBeFalsy()
				})
				it("is false, constraint should be fulfilled", function () {
					C.getConstraint("LocalConnectivityConstraint").conf.CONNECTED[1] = false
					expect(C.getConstraint("LocalConnectivityConstraint").fulfilled(src_i, tgt_i, src_type, tgt_type)).toBeTruthy()
				})
			})

		})


		/* Integration test: method connectedComponentsOf should listen to the torus property*/
		describe( "method [ connectedComponentsOf ]" , function () {

			it("should listen to the grid torus property correctly", function () {
				let nbhObj = {}
				let pix = C.grid.p2i([0, 0])

				// add pixel and its whole neighborhood
				nbhObj[pix] = true
				for (let n of C.grid.neighi(pix)) {
					nbhObj[n] = true
				}
				expect(C.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(1)

				// now change torus to false in one or both dimensions while keeping the same nbhObj
				let C_noTorus = new CPM.CPM([100, 100], {
					T: 20,
					torus: [false, false]
				})
				C_noTorus.add(new CPM.LocalConnectivityConstraint({
					CONNECTED: [false, true]
				}))
				expect(C_noTorus.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(4)

				let C_yTorus = new CPM.CPM([100, 100], {
					T: 20,
					torus: [false, true]
				})
				C_yTorus.add(new CPM.LocalConnectivityConstraint({
					CONNECTED: [false, true]
				}))
				expect(C_yTorus.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(2)

				let C_xTorus = new CPM.CPM([100, 100], {
					T: 20,
					torus: [true, false]
				})
				C_xTorus.add(new CPM.LocalConnectivityConstraint({
					CONNECTED: [false, true]
				}))
				expect(C_xTorus.getConstraint("LocalConnectivityConstraint").connectedComponentsOf(nbhObj).length).toEqual(2)
			})
		})
	})
})