/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.rdf.model.test; import java.util.Random; import org.apache.jena.rdf.model.Model ; import org.apache.jena.rdf.model.Resource ; import org.apache.jena.rdf.model.Statement ; import org.apache.jena.rdf.model.test.helpers.TestingModelFactory ; import org.apache.jena.vocabulary.RDF ; import org.junit.Assert; public class IsomorphicTests extends AbstractModelTestBase { // This is not part of the standard test suite // It's not stable enough for inclusion in the automatic test suite. // Often, they pass, but there is a significant number of times they don't. // It also seems to be machine-dependent - failures are more frequent // on Apache Jenkins (hardware influening "random" numbers?) /** * A theoretical graph for testing purposes. * All nodes are anonymous resources. All edges are labelled * rdf:value. * The basic DiHyperCube consists of the nodes being the * corners of a hypercube (e.g. in 3D a cube) * with the statements being the edges of the cube, directed * from one corner labelled 2^n-1 to the opposite corner * labelled 0. The labels are not present in the model. * This basic graph is then extended, for test purposes * by duplicating a node. */ static class DiHyperCube extends java.lang.Object { static int bitCount( final int i ) { return java.math.BigInteger.valueOf(i).bitCount(); } /* * We have two DiHyperCube's * to one we have added N a1's * to the other we have added N b1's * Returns true if they are equal. */ static boolean equal( final int a1, final int b1 ) { return DiHyperCube.bitCount(a1) == DiHyperCube.bitCount(b1); } /* * We have two DiHyperCube's * to one we have added N a1's and N a2's. * to the other we have added N b1's and N b2's. * Returns true if they are equal. */ static boolean equal( final int a1, final int a2, final int b1, final int b2 ) { return (DiHyperCube.bitCount(a1 ^ a2) == DiHyperCube.bitCount(b1 ^ b2)) && (DiHyperCube.bitCount(a1 & a2) == DiHyperCube .bitCount(b1 & b2)) && (DiHyperCube.bitCount(a1 | a2) == DiHyperCube .bitCount(b1 | b2)) && (Math.min(DiHyperCube.bitCount(a1), DiHyperCube.bitCount(a2)) == Math.min( DiHyperCube.bitCount(b1), DiHyperCube.bitCount(b2))); } final private Resource corners[]; final private int dim; final private Model model; /** Creates new DiHyperCube */ public DiHyperCube( final int dimension, final Model m ) { dim = dimension; model = m; corners = new Resource[1 << dim]; for (int i = 0; i < corners.length; i++) { corners[i] = m.createResource(); } for (int i = 0; i < corners.length; i++) { addDown(i, corners[i]); } } private void addDown( final int corner, final Resource r ) { for (int j = 0; j < dim; j++) { final int bit = 1 << j; if ((corner & bit) != 0) { model.add(r, RDF.value, corners[corner ^ bit]); } } } DiHyperCube dupe( final int corner ) { final Resource dup = model.createResource(); for (int j = 0; j < dim; j++) { final int bit = 1 << j; if ((corner & bit) != 0) { model.add(dup, RDF.value, corners[corner ^ bit]); } else { model.add(corners[corner ^ bit], RDF.value, dup); } } return this; } } /** * A theoretical graph for testing purposes. * All nodes are anonymous resources. All edges are labelled * rdf:value. * The basic HyperCube consists of the nodes being the * corners of a hypercube (e.g. in 3D a cube) * with the statements being the edges of the cube, in both * directions. The labels are not present in the model. * This basic graph is then extended, for test purposes * by duplicating a node. Or by adding/deleting an edge between * two nodes. */ static class HyperCube extends java.lang.Object { static int bitCount( final int i ) { return java.math.BigInteger.valueOf(i).bitCount(); } /* * We have two HyperCube's * to one we have added N a1's and M a2's. * to the other we have added N b1's and M b2's. * or we have toggled an edge between a1 and a2, and * between b1 and b2. * Returns true if they are equal. */ static boolean equal( final int a1, final int a2, final int b1, final int b2 ) { return HyperCube.bitCount(a1 ^ a2) == HyperCube.bitCount(b1 ^ b2); } final private Resource corners[]; final private int dim; final private Model model; /** Creates new DiHyperCube */ public HyperCube( final int dimension, final Model m ) { dim = dimension; model = m; corners = new Resource[1 << dim]; for (int i = 0; i < corners.length; i++) { corners[i] = m.createResource(); } for (int i = 0; i < corners.length; i++) { add(i, corners[i]); } } private void add( final int corner, final Resource r ) { for (int j = 0; j < dim; j++) { final int bit = 1 << j; model.add(r, RDF.value, corners[corner ^ bit]); } } HyperCube dupe( final int corner ) { final Resource dup = model.createResource(); add(corner, dup); return this; } HyperCube toggle( final int from, final int to ) { final Resource f = corners[from]; final Resource t = corners[to]; final Statement s = model.createStatement(f, RDF.value, t); if (model.contains(s)) { model.remove(s); } else { model.add(s); } return this; } } private static int QUANTITY = 10; private static int DIMENSION = 6; private final int sz = 1 << IsomorphicTests.DIMENSION; private Random random; private Model model2; public IsomorphicTests( final TestingModelFactory modelFactory, final String name ) { super(modelFactory, name); } @Override public void setUp() { super.setUp(); random = new Random(); model2 = createModel(); } @Override public void tearDown() { model.close(); super.tearDown(); } private void test2DiHyperCube( int quantity, final boolean type ) { if (IsomorphicTests.QUANTITY < 6) { // (Guessing) If the number is too small, the probability // of passing the test is too small. return; } for (int i = 0; i < quantity; i++) { int a1, b1; do { a1 = random.nextInt(sz); b1 = random.nextInt(sz); } while (type != DiHyperCube.equal(a1, b1)); new DiHyperCube(IsomorphicTests.DIMENSION, model).dupe(a1).dupe(a1) .dupe(a1); new DiHyperCube(IsomorphicTests.DIMENSION, model2).dupe(b1).dupe(b1) .dupe(b1); Assert.assertEquals(type, model.isIsomorphicWith(model2)); } } public void test2DiHyperCubeFalse() { test2DiHyperCube(IsomorphicTests.QUANTITY, false); } public void test2DiHyperCubeTrue() { test2DiHyperCube(IsomorphicTests.QUANTITY, true); } public void test2HyperCube() { for (int i = 0; i < IsomorphicTests.QUANTITY; i++) { int a1, b1; a1 = random.nextInt(sz); b1 = random.nextInt(sz); new HyperCube(IsomorphicTests.DIMENSION, model).dupe(a1).dupe(a1) .dupe(a1); new HyperCube(IsomorphicTests.DIMENSION, model2).dupe(b1).dupe(b1) .dupe(b1); Assert.assertTrue("Models not isomorphic", model.isIsomorphicWith(model2)); } } private void test4DiHyperCube( int quantity, final boolean type ) { for (int i = 0; i < quantity; i++) { int a1, b1, a2, b2; do { a1 = random.nextInt(sz); b1 = random.nextInt(sz); a2 = random.nextInt(sz); b2 = random.nextInt(sz); } while (type != DiHyperCube.equal(a1, a2, b1, b2)); new DiHyperCube(IsomorphicTests.DIMENSION, model).dupe(a1).dupe(a1) .dupe(a1).dupe(a2).dupe(a2).dupe(a2); new DiHyperCube(IsomorphicTests.DIMENSION, model2).dupe(b1).dupe(b1) .dupe(b1).dupe(b2).dupe(b2).dupe(b2); final String msg = "(" + a1 + "," + a2 + "),(" + b1 + "," + b2 + ")"; Assert.assertEquals(msg, type, model.isIsomorphicWith(model2)); } } public void test4DiHyperCubeFalse() { test4DiHyperCube(IsomorphicTests.QUANTITY, false); } public void test4DiHyperCubeTrue() { test4DiHyperCube(IsomorphicTests.QUANTITY, true); } private void test4HyperCube( int quantity, final boolean type ) { for (int i = 0; i < quantity; i++) { int a1, b1, a2, b2; do { a1 = random.nextInt(sz); b1 = random.nextInt(sz); a2 = random.nextInt(sz); b2 = random.nextInt(sz); } while (type != HyperCube.equal(a1, a2, b1, b2)); new HyperCube(IsomorphicTests.DIMENSION, model).dupe(a1).dupe(a1) .dupe(a1).dupe(a2).dupe(a2).dupe(a2); new HyperCube(IsomorphicTests.DIMENSION, model2).dupe(b1).dupe(b1) .dupe(b1).dupe(b2).dupe(b2).dupe(b2); final String msg = "(" + a1 + "," + a2 + "),(" + b1 + "," + b2 + ")"; Assert.assertEquals(msg, type, model.isIsomorphicWith(model2)); } } public void test4HyperCubeFalse() { // Pragmatically, needs more loops test4HyperCube(2 * IsomorphicTests.QUANTITY, false); } public void test4HyperCubeTrue() { test4HyperCube(IsomorphicTests.QUANTITY, true); } private void test4ToggleHyperCube( int quantity, final boolean type ) { for (int i = 0; i < quantity; i++) { int a1, b1, a2, b2; do { a1 = random.nextInt(sz); b1 = random.nextInt(sz); a2 = random.nextInt(sz); b2 = random.nextInt(sz); } while (type != HyperCube.equal(a1, a2, b1, b2)); new HyperCube(IsomorphicTests.DIMENSION, model).toggle(a1, a2); new HyperCube(IsomorphicTests.DIMENSION, model2).toggle(b1, b2); final String msg = "(" + a1 + "," + a2 + "),(" + b1 + "," + b2 + ")"; Assert.assertEquals(msg, type, model.isIsomorphicWith(model2)); } } public void test4ToggleHyperCubeFalse() { test4ToggleHyperCube(2*IsomorphicTests.QUANTITY, false); } public void test4ToggleHyperCubeTrue() { test4ToggleHyperCube(IsomorphicTests.QUANTITY, true); } }