def testDependencyValidity(self): # Model needs to pass in mock for dependencyChecker, make sure gets called exactly once per dependency mockRelDepChecker = MagicMock( wraps=RelationalValidity.checkRelationalDependencyValidity) schema = Schema() dependencies = [] Model(schema, dependencies, relationalDependencyChecker=mockRelDepChecker) self.assertEqual(0, mockRelDepChecker.call_count) schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addAttribute('A', 'V') schema.addEntity('B') schema.addAttribute('B', 'Y') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addEntity('C') schema.addAttribute('C', 'Z') schema.addAttribute('C', 'W') schema.addRelationship('BC', ('B', Schema.ONE), ('C', Schema.MANY)) dependencies = [ '[B, AB, A].X -> [B].Y', '[C, BC, B].Y -> [C].Z', '[C].Z -> [C].W', '[A].X -> [A].V', '[A, AB, B, BC, C].W -> [A].V' ] mockRelDepChecker = MagicMock( wraps=RelationalValidity.checkRelationalDependencyValidity) Model(schema, dependencies, relationalDependencyChecker=mockRelDepChecker) self.assertEqual(5, mockRelDepChecker.call_count)
def testIntersectingInputRelVars(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') model = Model(schema, []) dsep = DSeparation(model) self.assertFalse(dsep.dSeparated(0, ['[A].X1'], ['[A].X1'], [])) self.assertFalse( dsep.dSeparated(0, ['[A].X2', '[A].X1'], ['[A].X1', '[A].X2'], [])) self.assertFalse( dsep.dSeparated(0, ['[A].X2', '[A].X1'], ['[A].X1'], [])) schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addEntity('B') schema.addAttribute('B', 'Y') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addEntity('C') schema.addAttribute('C', 'Z') schema.addRelationship('BC', ('B', Schema.ONE), ('C', Schema.MANY)) model = Model(schema, []) dsep = DSeparation(model) self.assertFalse( dsep.dSeparated(6, ['[A, AB, B, AB, A, AB, B].Y'], ['[A, AB, B, BC, C, BC, B].Y'], []))
def testSetUndirectedSkeleton(self): schema = Schema() model = Model(schema, []) pc = PC(schema, Oracle(model)) undirectedSkeleton = nx.DiGraph() pc.setUndirectedSkeleton(undirectedSkeleton) self.assertEqual(undirectedSkeleton, pc.undirectedSkeleton) TestUtil.assertRaisesMessage(self, Exception, "Undirected skeleton must be a networkx DiGraph: found None", pc.setUndirectedSkeleton, None) # nodes must match the attributes of the schema undirectedSkeleton = nx.DiGraph() undirectedSkeleton.add_node(ParserUtil.parseRelVar('[A].X')) TestUtil.assertRaisesMessage(self, Exception, "Undirected skeleton's nodes must match schema attributes", pc.setUndirectedSkeleton, undirectedSkeleton) schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') model = Model(schema, []) pc = PC(schema, Oracle(model)) undirectedSkeleton = nx.DiGraph() TestUtil.assertRaisesMessage(self, Exception, "Undirected skeleton's nodes must match schema attributes", pc.setUndirectedSkeleton, undirectedSkeleton)
def testPropositionalAGG(self): schema = Schema() schema.addEntity('A') model = Model(schema, []) agg = AbstractGroundGraph(model, 'A', 0) self.assertAGGEqualNoIntersection(schema, agg, []) schema.addAttribute('A', 'A') schema.addAttribute('A', 'B') schema.addAttribute('A', 'C') model = Model(schema, []) agg = AbstractGroundGraph(model, 'A', 0) self.assertAGGEqualNoIntersection(schema, agg, []) schema.addAttribute('A', 'D') schema.addAttribute('A', 'E') schema.addAttribute('A', 'F') schema.addAttribute('A', 'G') schema.addAttribute('A', 'H') dependencies = [ '[A].A -> [A].B', '[A].A -> [A].C', '[A].B -> [A].D', '[A].C -> [A].D', '[A].E -> [A].F', '[A].E -> [A].G', '[A].F -> [A].H', '[A].G -> [A].H' ] model = Model(schema, dependencies) agg = AbstractGroundGraph(model, 'A', 0) self.assertAGGEqualNoIntersection(schema, agg, dependencies)
def testPhaseI(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') model = Model(schema, []) mockOracle = MagicMock(wraps=Oracle(model)) mockModelProperty = PropertyMock() type(mockOracle).model = mockModelProperty pc = PC(schema, mockOracle) pc.pcPhaseI() self.assertEqual(0, mockModelProperty.call_count) # forces us not to cheat by simply returning the model self.assertPCOutputEqual(['[A].X'], [], {}, 0, pc.undirectedSkeleton, pc.sepsets, mockOracle) schema.addAttribute('A', 'Y') model = Model(schema, []) mockOracle = MagicMock(wraps=Oracle(model)) mockModelProperty = PropertyMock() type(mockOracle).model = mockModelProperty pc = PC(schema, mockOracle) pc.pcPhaseI() self.assertEqual(0, mockModelProperty.call_count) # forces us not to cheat by simply returning the model expectedSepset = {('[A].X', '[A].Y'): set(), ('[A].Y', '[A].X'): set()} self.assertPCOutputEqual(['[A].X', '[A].Y'], [], expectedSepset, 1, pc.undirectedSkeleton, pc.sepsets, mockOracle) model = Model(schema, ['[A].X -> [A].Y']) mockOracle = MagicMock(wraps=Oracle(model)) pc = PC(schema, mockOracle) pc.pcPhaseI() expectedNodes = ['[A].X', '[A].Y'] expectedEdges = [('[A].X', '[A].Y'), ('[A].Y', '[A].X')] self.assertPCOutputEqual(expectedNodes, expectedEdges, {}, 2, pc.undirectedSkeleton, pc.sepsets, mockOracle) schema.addAttribute('A', 'Z') model = Model(schema, ['[A].X -> [A].Y']) mockOracle = MagicMock(wraps=Oracle(model)) pc = PC(schema, mockOracle) pc.pcPhaseI() expectedNodes = ['[A].X', '[A].Y', '[A].Z'] expectedEdges = [('[A].X', '[A].Y'), ('[A].Y', '[A].X')] expectedSepset = {('[A].X', '[A].Z'): set(), ('[A].Z', '[A].X'): set(), ('[A].Y', '[A].Z'): set(), ('[A].Z', '[A].Y'): set()} self.assertPCOutputEqual(expectedNodes, expectedEdges, expectedSepset, 4, pc.undirectedSkeleton, pc.sepsets, mockOracle) model = Model(schema, ['[A].X -> [A].Z', '[A].Z -> [A].Y']) mockOracle = MagicMock(wraps=Oracle(model)) pc = PC(schema, mockOracle) pc.pcPhaseI() expectedNodes = ['[A].X', '[A].Y', '[A].Z'] expectedEdges = [('[A].X', '[A].Z'), ('[A].Z', '[A].X'), ('[A].Z', '[A].Y'), ('[A].Y', '[A].Z')] expectedSepset = {('[A].X', '[A].Y'): {'[A].Z'}, ('[A].Y', '[A].X'): {'[A].Z'}} expectedDSepCount = 9 self.assertPCOutputEqual(expectedNodes, expectedEdges, expectedSepset, expectedDSepCount, pc.undirectedSkeleton, pc.sepsets, mockOracle)
def testGetAncestors(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') model = Model(schema, []) agg = AbstractGroundGraph(model, 'A', 0) self.assertGetAncestorsEquals(['[A].X'], agg, '[A].X') schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'A') schema.addAttribute('A', 'B') schema.addAttribute('A', 'C') schema.addAttribute('A', 'D') schema.addAttribute('A', 'E') schema.addAttribute('A', 'F') schema.addAttribute('A', 'G') schema.addAttribute('A', 'H') dependencies = [ '[A].A -> [A].C', '[A].A -> [A].D', '[A].B -> [A].D', '[A].C -> [A].E', '[A].D -> [A].E', '[A].E -> [A].G', '[A].E -> [A].H', '[A].F -> [A].H' ] model = Model(schema, dependencies) agg = AbstractGroundGraph(model, 'A', 0) self.assertGetAncestorsEquals( ['[A].G', '[A].A', '[A].B', '[A].C', '[A].D', '[A].E'], agg, '[A].G') self.assertGetAncestorsEquals( ['[A].F', '[A].A', '[A].B', '[A].C', '[A].D', '[A].E', '[A].H'], agg, '[A].H') schema = Schema() schema.addEntity('A') schema.addEntity('B') schema.addEntity('C') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addRelationship('BC', ('B', Schema.ONE), ('C', Schema.MANY)) schema.addAttribute('A', 'X') schema.addAttribute('B', 'Y') schema.addAttribute('C', 'Z') schema.addAttribute('AB', 'XY') schema.addAttribute('BC', 'YZ') model = Model(schema, [ '[BC, B, AB, A].X -> [BC].YZ', '[AB, B, BC, C].Z -> [AB].XY', '[AB, B, AB, A, AB, B].Y -> [AB].XY' ]) agg = AbstractGroundGraph(model, 'A', 6) self.assertGetAncestorsEquals([ '[A, AB].XY', '[A, AB, B, BC, C].Z', '[A, AB, B, AB, A, AB, B].Y', ('[A, AB, B, AB, A, AB, B].Y', '[A, AB, B, BC, C, BC, B].Y') ], agg, '[A, AB].XY') self.assertGetAncestorsEquals( [('[A, AB, B, AB, A, AB, B].Y', '[A, AB, B, BC, C, BC, B].Y')], agg, ('[A, AB, B, AB, A, AB, B].Y', '[A, AB, B, BC, C, BC, B].Y'))
def testGetSubsumedRelVarInts(self): schema = Schema() schema.addEntity('A') schema.addEntity('B') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.ONE)) schema.addAttribute('A', 'X') schema.addAttribute('B', 'Y') model = Model(schema, []) abAGG = AbstractGroundGraph(model, 'AB', 3) self.assertSameSubsumedVariables(['[AB, A].X'], abAGG, '[AB, A].X') self.assertSameSubsumedVariables( ['[AB, B].Y', ('[AB, B].Y', '[AB, A, AB, B].Y')], abAGG, '[AB, B].Y') self.assertSameSubsumedVariables( ['[AB, A, AB, B].Y', ('[AB, B].Y', '[AB, A, AB, B].Y')], abAGG, '[AB, A, AB, B].Y') schema = Schema() schema.addEntity('A') schema.addEntity('B') schema.addRelationship('AB1', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addRelationship('AB2', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addAttribute('A', 'X') schema.addAttribute('B', 'Y') schema.addAttribute('AB1', 'XY1') schema.addAttribute('AB2', 'XY2') model = Model(schema, []) aAGG = AbstractGroundGraph(model, 'A', 4) self.assertSameSubsumedVariables([ '[A, AB1, B, AB1, A].X', ('[A, AB1, B, AB1, A].X', '[A, AB1, B, AB2, A].X'), ('[A, AB1, B, AB1, A].X', '[A, AB2, B, AB1, A].X'), ('[A, AB1, B, AB1, A].X', '[A, AB2, B, AB2, A].X') ], aAGG, '[A, AB1, B, AB1, A].X') # test bad relVar input to getSubsumedVariables schema = Schema() schema.addEntity('A') model = Model(schema, []) agg = AbstractGroundGraph(model, 'A', 0) TestUtil.assertRaisesMessage( self, Exception, "relVar must be a RelationalVariable: found 'None'", agg.getSubsumedVariables, None) schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') model = Model(schema, []) agg = AbstractGroundGraph(model, 'A', 0) TestUtil.assertRaisesMessage( self, Exception, "relVar '[A].X2' is not a node in the abstract ground graph", agg.getSubsumedVariables, RelationalVariable(['A'], 'X2'))
def testRemoveEdgesForDependency(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'A') schema.addAttribute('A', 'B') schema.addAttribute('A', 'C') schema.addAttribute('A', 'D') schema.addAttribute('A', 'E') schema.addAttribute('A', 'F') schema.addAttribute('A', 'G') schema.addAttribute('A', 'H') dependencies = [ '[A].A -> [A].B', '[A].A -> [A].C', '[A].B -> [A].D', '[A].C -> [A].D', '[A].E -> [A].F', '[A].E -> [A].G', '[A].F -> [A].H', '[A].G -> [A].H' ] model = Model(schema, dependencies) agg = AbstractGroundGraph(model, 'A', 0) agg.removeEdgesForDependency(ParserUtil.parseRelDep('[A].B -> [A].A')) self.assertEqual(8, len(agg.edges())) agg.removeEdgesForDependency(ParserUtil.parseRelDep('[A].A -> [A].B')) self.assertEqual(7, len(agg.edges())) self.assertNotIn( (ParserUtil.parseRelVar('[A].A'), ParserUtil.parseRelVar('[A].B')), agg.edges()) agg.removeEdgesForDependency(ParserUtil.parseRelDep('[A].F -> [A].H')) self.assertEqual(6, len(agg.edges())) self.assertNotIn( (ParserUtil.parseRelVar('[A].F'), ParserUtil.parseRelVar('[A].H')), agg.edges()) schema = Schema() schema.addEntity('A') schema.addEntity('B') schema.addEntity('C') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addRelationship('BC', ('B', Schema.ONE), ('C', Schema.MANY)) schema.addAttribute('A', 'X') schema.addAttribute('B', 'Y') schema.addAttribute('C', 'Z') schema.addAttribute('AB', 'XY') schema.addAttribute('BC', 'YZ') model = Model(schema, [ '[BC, B, AB, A].X -> [BC].YZ', '[AB, B, BC, C].Z -> [AB].XY', '[AB, B, AB, A, AB, B].Y -> [AB].XY' ]) aAGG = AbstractGroundGraph(model, 'A', 6) self.assertEqual(9, len(aAGG.edges())) aAGG.removeEdgesForDependency( ParserUtil.parseRelDep('[BC, B, AB, A].X -> [BC].YZ')) self.assertEqual(7, len(aAGG.edges())) aAGG.removeEdgesForDependency( ParserUtil.parseRelDep('[AB, B, AB, A, AB, B].Y -> [AB].XY')) self.assertEqual(2, len(aAGG.edges()))
def testTwoVariableModels(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') model = Model(schema, []) dsep = DSeparation(model) self.assertTrue(dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], [])) self.assertTrue(dsep.dSeparated(0, ['[A].X2'], ['[A].X1'], [])) model = Model(schema, ['[A].X1 -> [A].X2']) dsep = DSeparation(model) self.assertFalse(dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], [])) self.assertFalse(dsep.dSeparated(0, ['[A].X2'], ['[A].X1'], []))
def testOneToManyRDS(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addEntity('B') schema.addAttribute('B', 'Y') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.ONE)) model = Model(schema, ['[B, AB, A].X -> [B].Y']) dsep = DSeparation(model) self.assertFalse(dsep.dSeparated(4, ['[B].Y'], ['[B, AB, A].X'], [])) self.assertFalse(dsep.dSeparated(4, ['[B, AB, A].X'], ['[B].Y'], [])) self.assertFalse(dsep.dSeparated(2, ['[A, AB, B].Y'], ['[A].X'], [])) self.assertFalse(dsep.dSeparated(2, ['[A].X'], ['[A, AB, B].Y'], [])) self.assertFalse(dsep.dSeparated(3, ['[AB, B].Y'], ['[AB, A].X'], [])) self.assertFalse(dsep.dSeparated(3, ['[AB, A].X'], ['[AB, B].Y'], [])) self.assertFalse( dsep.dSeparated(3, ['[AB, A].X'], ['[AB, A, AB, B].Y'], [])) schema.addAttribute('A', 'Z') model = Model(schema, ['[B, AB, A].X -> [B].Y', '[A, AB, B].Y -> [A].Z']) dsep = DSeparation(model) self.assertFalse( dsep.dSeparated(4, ['[B, AB, A].X'], ['[B, AB, A].Z'], [])) self.assertFalse(dsep.dSeparated(2, ['[A].Z'], ['[A].X'], [])) self.assertFalse(dsep.dSeparated(3, ['[AB, A].X'], ['[AB, A].Z'], [])) self.assertTrue( dsep.dSeparated(2, ['[A].X'], ['[A].Z'], ['[A, AB, B].Y'])) self.assertFalse( dsep.dSeparated(4, ['[B, AB, A].X'], ['[B, AB, A].Z'], ['[B].Y'])) self.assertFalse( dsep.dSeparated(4, ['[B, AB, A].X'], ['[B, AB, A].Z'], ['[B, AB, A, AB, B].Y'])) self.assertTrue( dsep.dSeparated(4, ['[B, AB, A].X'], ['[B, AB, A].Z'], ['[B].Y', '[B, AB, A, AB, B].Y'])) self.assertFalse( dsep.dSeparated(3, ['[AB, A].X'], ['[AB, A].Z'], ['[AB, B].Y'])) self.assertFalse( dsep.dSeparated(3, ['[AB, A].X'], ['[AB, A].Z'], ['[AB, A, AB, B].Y'])) # true with both, forces intersection between '[AB, B].Y', '[AB, A, AB, B].Y' to be included automatically self.assertTrue( dsep.dSeparated(3, ['[AB, A].X'], ['[AB, A].Z'], ['[AB, B].Y', '[AB, A, AB, B].Y']))
def testNoSkeletonBeforePhaseII(self): # must have an undirected skeleton and sepsets before running Phase II schema = Schema() model = Model(schema, []) pc = PC(schema, Oracle(model)) TestUtil.assertRaisesMessage(self, Exception, "No undirected skeleton found. Try running Phase I first.", pc.pcPhaseII) # what if we set the skeleton to None? pc = PC(schema, Oracle(model)) pc.undirectedSkeleton = None TestUtil.assertRaisesMessage(self, Exception, "No undirected skeleton found. Try running Phase I first.", pc.pcPhaseII) # what if we don't set the sepset? pc = PC(schema, Oracle(model)) pc.setUndirectedSkeleton(nx.DiGraph()) TestUtil.assertRaisesMessage(self, Exception, "No sepsets found. Try running Phase I first.", pc.pcPhaseII) # what if we set the sepsets to None? pc = PC(schema, Oracle(model)) pc.setUndirectedSkeleton(nx.DiGraph()) pc.sepsets = None TestUtil.assertRaisesMessage(self, Exception, "No sepsets found. Try running Phase I first.", pc.pcPhaseII)
def testPCObj(self): schema = Schema() model = Model(schema, []) oracle = Oracle(model) pc = PC(schema, oracle) self.assertEqual(schema, pc.schema) self.assertEqual(oracle.model, pc.oracle.model)
def testBadRelVarInput(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') model = Model(schema, []) dsep = DSeparation(model) TestUtil.assertRaisesMessage( self, Exception, "relVars1 must be a non-empty sequence of parseable RelationalVariable strings", dsep.dSeparated, 0, None, ['[A].X2'], []) TestUtil.assertRaisesMessage( self, Exception, "relVars2 must be a non-empty sequence of parseable RelationalVariable strings", dsep.dSeparated, 0, ['[A].X1'], None, []) TestUtil.assertRaisesMessage( self, Exception, "condRelVars must be a sequence of parseable RelationalVariable strings", dsep.dSeparated, 0, ['[A].X1'], ['[A].X2'], None) TestUtil.assertRaisesMessage( self, Exception, "relVars1 must be a non-empty sequence of parseable RelationalVariable strings", dsep.dSeparated, 0, [], ['[A].X2'], []) TestUtil.assertRaisesMessage( self, Exception, "relVars2 must be a non-empty sequence of parseable RelationalVariable strings", DSeparation.dSeparated, model, 0, ['[A].X1'], [], [])
def testCondRelVarsSameAsOneRelVar(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') schema.addAttribute('A', 'X3') model = Model(schema, ['[A].X1 -> [A].X2', '[A].X1 -> [A].X3']) dsep = DSeparation(model) self.assertTrue(dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], ['[A].X2'])) self.assertTrue(dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], ['[A].X1'])) self.assertTrue( dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], ['[A].X1', '[A].X2'])) self.assertTrue( dsep.dSeparated(0, ['[A].X1'], ['[A].X2', '[A].X3'], ['[A].X1', '[A].X2'])) self.assertTrue( dsep.dSeparated(0, ['[A].X1'], ['[A].X2', '[A].X3'], ['[A].X2', '[A].X3'])) self.assertTrue( dsep.dSeparated(0, ['[A].X2', '[A].X3'], ['[A].X1'], ['[A].X2', '[A].X3'])) # test for sequences of length > 1 schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'A') schema.addAttribute('A', 'B') schema.addAttribute('A', 'C') schema.addAttribute('A', 'D') schema.addAttribute('A', 'E') schema.addAttribute('A', 'F') schema.addAttribute('A', 'G') schema.addAttribute('A', 'H') dependencies = [ '[A].A -> [A].B', '[A].A -> [A].C', '[A].B -> [A].D', '[A].C -> [A].D', '[A].E -> [A].F', '[A].E -> [A].G', '[A].F -> [A].H', '[A].G -> [A].H' ] model = Model(schema, dependencies) dsep = DSeparation(model) self.assertTrue( dsep.dSeparated(0, ['[A].A', '[A].E'], ['[A].D', '[A].H'], ['[A].B', '[A].C', '[A].E', '[A].H'])) self.assertFalse( dsep.dSeparated(0, ['[A].A', '[A].E', '[A].F'], ['[A].D', '[A].H', '[A].G'], ['[A].B', '[A].C', '[A].E', '[A].H']))
def testOracle(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') schema.addAttribute('A', 'X3') schema.addAttribute('A', 'X4') model = Model( schema, ['[A].X1 -> [A].X3', '[A].X2 -> [A].X3', '[A].X3 -> [A].X4']) oracle = Oracle(model) self.assertTrue(isinstance(oracle, CITest)) self.assertTrue( oracle.isConditionallyIndependent('[A].X1', '[A].X2', [])) self.assertFalse( oracle.isConditionallyIndependent('[A].X1', '[A].X2', ['[A].X3'])) self.assertFalse( oracle.isConditionallyIndependent( '[A].X1', '[A].X2', ['[A].X4'])) # tests conditioning on descendant of a collider self.assertFalse( oracle.isConditionallyIndependent('[A].X1', '[A].X2', ['[A].X3', '[A].X4'])) # model has multiple paths model = Model(schema, [ '[A].X1 -> [A].X3', '[A].X1 -> [A].X2', '[A].X2 -> [A].X4', '[A].X4 -> [A].X3' ]) oracle = Oracle(model) self.assertFalse( oracle.isConditionallyIndependent('[A].X1', '[A].X4', [])) self.assertTrue( oracle.isConditionallyIndependent('[A].X1', '[A].X4', ['[A].X2'])) self.assertFalse( oracle.isConditionallyIndependent('[A].X1', '[A].X4', ['[A].X2', '[A].X3'])) self.assertFalse( oracle.isConditionallyIndependent('[A].X2', '[A].X3', [])) self.assertFalse( oracle.isConditionallyIndependent('[A].X2', '[A].X3', ['[A].X1'])) self.assertFalse( oracle.isConditionallyIndependent('[A].X2', '[A].X3', ['[A].X4'])) self.assertTrue( oracle.isConditionallyIndependent('[A].X2', '[A].X3', ['[A].X1', '[A].X4']))
def testSetSepsets(self): schema = Schema() model = Model(schema, []) pc = PC(schema, Oracle(model)) pc.setSepsets({}) self.assertEqual({}, pc.sepsets) TestUtil.assertRaisesMessage(self, Exception, "Sepsets must be a dictionary: found None", pc.setSepsets, None)
def testBadPerspective(self): schema = Schema() model = Model(schema, []) # non-string TestUtil.assertRaisesMessage( self, Exception, "Perspective must be a valid schema item name", AbstractGroundGraph, model, None, 0) # string, but bad item name TestUtil.assertRaisesMessage( self, Exception, "Perspective must be a valid schema item name", AbstractGroundGraph, model, 'A', 0) schema = Schema() schema.addEntity('A') model = Model(schema, []) TestUtil.assertRaisesMessage( self, Exception, "Perspective must be a valid schema item name", AbstractGroundGraph, model, 'B', 0)
def testOneToOneRDS(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addEntity('B') schema.addAttribute('B', 'Y') schema.addRelationship('AB', ('A', Schema.ONE), ('B', Schema.ONE)) model = Model(schema, []) dsep = DSeparation(model) self.assertTrue(dsep.dSeparated(2, ['[A, AB, B].Y'], ['[A].X'], [])) self.assertTrue(dsep.dSeparated(2, ['[A].X'], ['[A, AB, B].Y'], [])) self.assertTrue(dsep.dSeparated(2, ['[B].Y'], ['[B, AB, A].X'], [])) self.assertTrue(dsep.dSeparated(2, ['[B, AB, A].X'], ['[B].Y'], [])) self.assertTrue(dsep.dSeparated(1, ['[AB, B].Y'], ['[AB, A].X'], [])) self.assertTrue(dsep.dSeparated(1, ['[AB, A].X'], ['[AB, B].Y'], [])) model = Model(schema, ['[B, AB, A].X -> [B].Y']) dsep = DSeparation(model) self.assertFalse(dsep.dSeparated(2, ['[B].Y'], ['[B, AB, A].X'], [])) self.assertFalse(dsep.dSeparated(2, ['[B, AB, A].X'], ['[B].Y'], [])) self.assertFalse(dsep.dSeparated(2, ['[A, AB, B].Y'], ['[A].X'], [])) self.assertFalse(dsep.dSeparated(2, ['[A].X'], ['[A, AB, B].Y'], [])) self.assertFalse(dsep.dSeparated(1, ['[AB, B].Y'], ['[AB, A].X'], [])) self.assertFalse(dsep.dSeparated(1, ['[AB, A].X'], ['[AB, B].Y'], [])) schema.addAttribute('A', 'Z') model = Model(schema, ['[B, AB, A].X -> [B].Y', '[A, AB, B].Y -> [A].Z']) dsep = DSeparation(model) self.assertFalse( dsep.dSeparated(2, ['[B, AB, A].X'], ['[B, AB, A].Z'], [])) self.assertFalse(dsep.dSeparated(2, ['[A].Z'], ['[A].X'], [])) self.assertFalse(dsep.dSeparated(1, ['[AB, A].X'], ['[AB, A].Z'], [])) self.assertTrue( dsep.dSeparated(2, ['[A].X'], ['[A].Z'], ['[A, AB, B].Y'])) self.assertTrue( dsep.dSeparated(1, ['[AB, A].X'], ['[AB, A].Z'], ['[AB, B].Y'])) self.assertTrue( dsep.dSeparated(2, ['[B, AB, A].X'], ['[B, AB, A].Z'], ['[B].Y']))
def testLongRangeDependencyIsIgnored(self): # Build AGG with model with a dependency that is longer than hop threshold # the long-range dependence is not (B,h)-reachable for the AGG from perspective B schema = Schema() schema.addEntity('A') schema.addEntity('B') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.ONE)) schema.addAttribute('B', 'Y1') schema.addAttribute('B', 'Y2') model = Model(schema, ['[B, AB, A, AB, B].Y1 -> [B].Y2']) self.assertAGGEqualNoIntersection(schema, AbstractGroundGraph(model, 'B', 2), [])
def testRemoveCommonCondRelVarOnlyFromRelVars(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') schema.addAttribute('A', 'X3') model = Model(schema, ['[A].X1 -> [A].X2', '[A].X2 -> [A].X3']) dsep = DSeparation(model) self.assertTrue( dsep.dSeparated(0, ['[A].X1', '[A].X2'], ['[A].X3'], ['[A].X2'])) self.assertTrue( dsep.dSeparated(0, ['[A].X1', '[A].X2'], ['[A].X2', '[A].X3'], ['[A].X2']))
def testPCLearnModel(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addAttribute('A', 'Y') schema.addAttribute('A', 'Z') schema.addAttribute('A', 'W') model = Model(schema, ['[A].X -> [A].Y', '[A].X -> [A].W', '[A].X -> [A].Z', '[A].Y -> [A].Z', '[A].W -> [A].Z']) pc = PC(schema, Oracle(model)) pc.learnModel() expectedNodes = ['[A].X', '[A].Y', '[A].Z', '[A].W'] expectedEdges = [('[A].X', '[A].Z'), ('[A].Y', '[A].Z'), ('[A].W', '[A].Z'), ('[A].X', '[A].Y'), ('[A].Y', '[A].X'), ('[A].W', '[A].X'), ('[A].X', '[A].W')] self.assertPCOutputEqual(expectedNodes, expectedEdges, None, None, pc.partiallyDirectedGraph, None, None)
def testBadHopThresholdInput(self): # hop thresholds must be non-negative integers schema = Schema() schema.addEntity('A') model = Model(schema, []) TestUtil.assertRaisesMessage( self, Exception, "hopThreshold must be a non-negative integer", AbstractGroundGraph, model, 'A', None) TestUtil.assertRaisesMessage( self, Exception, "hopThreshold must be a non-negative integer", AbstractGroundGraph, model, 'A', 1.5) TestUtil.assertRaisesMessage( self, Exception, "hopThreshold must be a non-negative integer", AbstractGroundGraph, model, 'A', -1)
def testFourVariableModels(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X1') schema.addAttribute('A', 'X2') schema.addAttribute('A', 'X3') schema.addAttribute('A', 'X4') model = Model( schema, ['[A].X1 -> [A].X3', '[A].X2 -> [A].X3', '[A].X3 -> [A].X4']) dsep = DSeparation(model) self.assertTrue(dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], [])) self.assertFalse(dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], ['[A].X3'])) self.assertFalse(dsep.dSeparated( 0, ['[A].X1'], ['[A].X2'], ['[A].X4'])) # tests conditioning on descendant of a collider self.assertFalse( dsep.dSeparated(0, ['[A].X1'], ['[A].X2'], ['[A].X3', '[A].X4'])) # model has multiple paths model = Model(schema, [ '[A].X1 -> [A].X3', '[A].X1 -> [A].X2', '[A].X2 -> [A].X4', '[A].X4 -> [A].X3' ]) dsep = DSeparation(model) self.assertFalse(dsep.dSeparated(0, ['[A].X1'], ['[A].X4'], [])) self.assertTrue(dsep.dSeparated(0, ['[A].X1'], ['[A].X4'], ['[A].X2'])) self.assertFalse( dsep.dSeparated(0, ['[A].X1'], ['[A].X4'], ['[A].X2', '[A].X3'])) self.assertFalse(dsep.dSeparated(0, ['[A].X2'], ['[A].X3'], [])) self.assertFalse(dsep.dSeparated(0, ['[A].X2'], ['[A].X3'], ['[A].X1'])) self.assertFalse(dsep.dSeparated(0, ['[A].X2'], ['[A].X3'], ['[A].X4'])) self.assertTrue( dsep.dSeparated(0, ['[A].X2'], ['[A].X3'], ['[A].X1', '[A].X4']))
def testMakeModel(self): model = Model(self.schema, []) self.assertEqual([], model.dag.nodes()) self.schema.addEntity('A') self.schema.addAttribute('A', 'X') self.schema.addEntity('B') self.schema.addRelationship('AB', ('A', Schema.ONE), ('B', Schema.ONE)) model = Model(self.schema, []) TestUtil.assertUnorderedListEqual( self, ['[A].exists', '[B].exists', '[AB].exists', '[A].X'], [str(attr) for attr in model.dag.nodes()]) TestUtil.assertUnorderedListEqual(self, [('[A].exists', '[AB].exists'), ('[B].exists', '[AB].exists'), ('[A].exists', '[A].X')], [(str(edge[0]), str(edge[1])) for edge in model.dag.edges()]) self.assertEqual([], model.dependencies) self.schema.addAttribute('B', 'Y') dependencies = ['[A, AB, B].Y -> [A].X'] model = Model(self.schema, dependencies) TestUtil.assertUnorderedListEqual( self, ['[A].exists', '[B].exists', '[AB].exists', '[B].Y', '[A].X'], [str(attr) for attr in model.dag.nodes()]) TestUtil.assertUnorderedListEqual(self, [('[A].exists', '[AB].exists'), ('[B].exists', '[AB].exists'), ('[A].exists', '[A].X'), ('[AB].exists', '[A].X'), ('[B].exists', '[A].X'), ('[B].exists', '[B].Y'), ('[B].Y', '[A].X')], [(str(edge[0]), str(edge[1])) for edge in model.dag.edges()]) self.assertEqual(dependencies, [str(relDep) for relDep in model.dependencies])
def testPhaseIBiggerConditionalSets(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addAttribute('A', 'Y') schema.addAttribute('A', 'Z') schema.addAttribute('A', 'W') model = Model(schema, ['[A].X -> [A].Y', '[A].X -> [A].Z', '[A].Y -> [A].W', '[A].Z -> [A].W']) pc = PC(schema, Oracle(model)) pc.pcPhaseI() expectedNodes = ['[A].X', '[A].Y', '[A].Z', '[A].W'] expectedEdges = [('[A].X', '[A].Y'), ('[A].Y', '[A].X'), ('[A].X', '[A].Z'), ('[A].Z', '[A].X'), ('[A].W', '[A].Y'), ('[A].Y', '[A].W'), ('[A].W', '[A].Z'), ('[A].Z', '[A].W')] expectedSepset = {('[A].X', '[A].W'): {'[A].Y', '[A].Z'}, ('[A].W', '[A].X'): {'[A].Y', '[A].Z'}, ('[A].Y', '[A].Z'): {'[A].X'}, ('[A].Z', '[A].Y'): {'[A].X'}} self.assertPCOutputEqual(expectedNodes, expectedEdges, expectedSepset, None, pc.undirectedSkeleton, pc.sepsets, None)
def testBadInput(self): model = Model(Schema(), []) for method in [ ModelEvaluation.skeletonPrecision, ModelEvaluation.skeletonRecall, ModelEvaluation.orientedPrecision, ModelEvaluation.orientedRecall ]: TestUtil.assertRaisesMessage( self, Exception, "learnedDependencies must be a list of RelationalDependencies " "or parseable RelationalDependency strings", method, model, None) TestUtil.assertRaisesMessage( self, Exception, "learnedDependencies must be a list of RelationalDependencies " "or parseable RelationalDependency strings", method, model, 'dependency')
def testSetPhaseIPattern(self): # edges in skeleton and sepsets should be useful for Phase II schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addAttribute('A', 'Y') schema.addAttribute('A', 'Z') model = Model(schema, ['[A].X -> [A].Z', '[A].Y -> [A].Z']) pc = PC(schema, Oracle(model)) undirectedSkeleton = nx.DiGraph() relVarX = ParserUtil.parseRelVar('[A].X') relVarY = ParserUtil.parseRelVar('[A].Y') relVarZ = ParserUtil.parseRelVar('[A].Z') undirectedSkeleton.add_edges_from([(relVarX, relVarZ), (relVarZ, relVarX), (relVarY, relVarZ), (relVarZ, relVarY)]) pc.setUndirectedSkeleton(undirectedSkeleton) pc.setSepsets({(relVarX, relVarY): set(), (relVarY, relVarX): set()}) pc.pcPhaseII() self.assertPCOutputEqual(['[A].X', '[A].Y', '[A].Z'], [('[A].X', '[A].Z'), ('[A].Y', '[A].Z')], None, None, pc.partiallyDirectedGraph, None, None)
def testEightVariableModel(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'A') schema.addAttribute('A', 'B') schema.addAttribute('A', 'C') schema.addAttribute('A', 'D') schema.addAttribute('A', 'E') schema.addAttribute('A', 'F') schema.addAttribute('A', 'G') schema.addAttribute('A', 'H') dependencies = [ '[A].A -> [A].C', '[A].A -> [A].D', '[A].B -> [A].D', '[A].B -> [A].F', '[A].C -> [A].E', '[A].D -> [A].E', '[A].E -> [A].G', '[A].E -> [A].H', '[A].F -> [A].H' ] model = Model(schema, dependencies) dsep = DSeparation(model) self.assertFalse(dsep.dSeparated(0, ['[A].A'], ['[A].E'], [])) self.assertFalse(dsep.dSeparated(0, ['[A].A'], ['[A].E'], ['[A].C'])) self.assertTrue( dsep.dSeparated(0, ['[A].A'], ['[A].E'], ['[A].C', '[A].D'])) self.assertFalse(dsep.dSeparated(0, ['[A].B'], ['[A].E'], [])) self.assertFalse(dsep.dSeparated(0, ['[A].B'], ['[A].E'], ['[A].D'])) self.assertTrue( dsep.dSeparated(0, ['[A].B'], ['[A].E'], ['[A].C', '[A].D'])) self.assertFalse( dsep.dSeparated(0, ['[A].B'], ['[A].E'], ['[A].C', '[A].D', '[A].H'])) # testing d-separation with sets of relVars self.assertFalse( dsep.dSeparated(0, ['[A].A', '[A].C'], {'[A].F', '[A].H'}, [])) self.assertFalse( dsep.dSeparated(0, ('[A].A', '[A].C'), {'[A].F', '[A].H'}, ['[A].E'])) self.assertTrue( dsep.dSeparated(0, ('[A].A', '[A].C'), {'[A].F', '[A].H'}, {'[A].B', '[A].E'}))
def incompleteness_example(): schema = Schema() schema.addEntity("E1") schema.addEntity("E2") schema.addEntity("E3") schema.addRelationship("R1", ("E1", Schema.ONE), ("E2", Schema.ONE)) schema.addRelationship("R2", ("E2", Schema.ONE), ("E3", Schema.ONE)) schema.addRelationship("R3", ("E2", Schema.ONE), ("E3", Schema.ONE)) schema.addAttribute("R1", "X") schema.addAttribute("R2", "Y") schema.addAttribute("E2", "Z") d1 = RelationalDependency(RelationalVariable(["R2", "E2", "R1"], "X"), RelationalVariable(["R2"], "Y")) d2 = RelationalDependency( RelationalVariable(["R2", "E3", "R3", "E2"], "Z"), RelationalVariable(["R2"], "Y")) d3 = RelationalDependency( RelationalVariable(["R1", "E2", "R2", "E3", "R3", "E2"], "Z"), RelationalVariable(["R1"], "X")) model = Model(schema, [d1, d2, d3]) return schema, model
def testRelVarCheckerUsed(self): schema = Schema() schema.addEntity('A') schema.addAttribute('A', 'X') schema.addEntity('B') schema.addAttribute('B', 'Y') schema.addRelationship('AB', ('A', Schema.MANY), ('B', Schema.MANY)) schema.addEntity('C') schema.addAttribute('C', 'Z') schema.addRelationship('BC', ('B', Schema.ONE), ('C', Schema.MANY)) model = Model(schema, ['[B, AB, A].X -> [B].Y', '[C, BC, B].Y -> [C].Z']) dsep = DSeparation(model) mockRelVarSetChecker = MagicMock( wraps=RelationalValidity.checkValidityOfRelationalVariableSet) dsep.dSeparated(8, ['[A].X'], ['[A, AB, B, BC, C].Z'], [], relationalVariableSetChecker=mockRelVarSetChecker) self.assertEqual(1, mockRelVarSetChecker.call_count) relVarsPassed = { ParserUtil.parseRelVar('[A].X'), ParserUtil.parseRelVar('[A, AB, B, BC, C].Z') } mockRelVarSetChecker.assert_called_with(model.schema, 8, relVarsPassed) mockRelVarSetChecker = MagicMock( wraps=RelationalValidity.checkValidityOfRelationalVariableSet) dsep.dSeparated(8, ['[A].X'], ['[A, AB, B, BC, C].Z'], ['[A, AB, B].Y', '[A, AB, B, BC, C, BC, B].Y'], relationalVariableSetChecker=mockRelVarSetChecker) self.assertEqual(1, mockRelVarSetChecker.call_count) relVarsPassed = { ParserUtil.parseRelVar('[A].X'), ParserUtil.parseRelVar('[A, AB, B, BC, C].Z'), ParserUtil.parseRelVar('[A, AB, B].Y'), ParserUtil.parseRelVar('[A, AB, B, BC, C, BC, B].Y') } mockRelVarSetChecker.assert_called_with(model.schema, 8, relVarsPassed)