/* --------------------------------------------------------------------- * Numenta Platform for Intelligent Computing (NuPIC) * Copyright (C) 2016, Numenta, Inc. Unless you have an agreement * with Numenta, Inc., for a separate license for this software code, the * following terms and conditions apply: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero Public License for more details. * * You should have received a copy of the GNU Affero Public License * along with this program. If not, see http://www.gnu.org/licenses. * * http://numenta.org/licenses/ * --------------------------------------------------------------------- */ package org.numenta.nupic.algorithms; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.lang.reflect.Method; import java.util.Arrays; import java.util.stream.IntStream; import org.junit.Test; import org.numenta.nupic.Parameters; import org.numenta.nupic.Parameters.KEY; import org.numenta.nupic.model.Column; import org.numenta.nupic.model.Connections; import org.numenta.nupic.model.Pool; import org.numenta.nupic.util.Tuple; import org.numenta.nupic.util.UniversalRandom; /** * Tests the {@link SpatialPooler} output of the Java SP against the * generated activations, permanences and connected state of the Python * SP. * * This is accomplished by use of the {@link UniversalRandom} RNG in both the * Python and Java versions, to produce Python output which is then stored as * a Java class file which can return the Python SP's state at any given * processing cycle. * * @author cogmission * @see SpatialPoolerCompatibilityActives * @see SpatialPoolerCompatibilityPermanences * @see UniversalRandom */ public class SpatialPoolerCompatibilityTest { /** * Test the Java output against stored Python output allowing "learning" * to be randomly turned on and off. Uses the {@link UniversalRandom} RNG * in both the Python and Java versions to enable identical output generation. */ @Test public void testCompatability1() { int numRecords = 100; Tuple tuple = createSP(); Connections c = (Connections)tuple.get(0); SpatialPooler sp = (SpatialPooler)tuple.get(1); int[][] potentials = c.getPotentials(); int[][] connecteds = c. getConnecteds(); double[][] perms = c.getPermanences(); ///////////////////////////////////////////////////////////////////////////// // Used to write the 3 needed outputs to be inserted in the initialization // // part of the SP compatibility test // ///////////////////////////////////////////////////////////////////////////// // for(int[] pots : potentials) { // System.out.println(Arrays.toString(pots)); // } // System.out.println("\n\n"); // for(int[] conns : connecteds) { // System.out.println(Arrays.toString(conns)); // } // System.out.println("\n\n"); // for(double[] perm : perms) { // System.out.println(Arrays.toString(perm)); // } int[][] expectedInitialPotentials = getPythonInitialPotentialMapping1(); int[][] expectedInitialColumnConnects = getPythonInitialColumnConnections(); double[][] expectedInitialPerms = getPythonInitialPermanences1(); assertTrue( IntStream.range(0, potentials.length) .allMatch(i -> Arrays.equals(potentials[i], expectedInitialPotentials[i]))); assertTrue( IntStream.range(0, potentials.length) .allMatch(i -> Arrays.equals(connecteds[i], expectedInitialColumnConnects[i]))); assertTrue( IntStream.range(0, perms.length) .allMatch(d -> { return IntStream.range(0, perms[d].length).allMatch(i -> { return areEqualDouble(perms[d][i], expectedInitialPerms[d][i], 4); }); })); // Set the percentage of 1's in the test inputs double sparsity = 0.4d; int[][] inputMatrix = new UniversalRandom(42).binDistrib(numRecords, c.getNumInputs(), sparsity); int[][] pythonInputMatrix = getPythonInputs1(); assertTrue( IntStream.range(0, numRecords) .allMatch(i -> Arrays.equals(inputMatrix[i], pythonInputMatrix[i]))); runSideBySide(sp, c, pythonInputMatrix, numRecords, new SpatialPoolerCompatibilityActives(), new SpatialPoolerCompatibilityPermanences()); } /** * Used to specify the number of places after the decimal for which * the comparison should be made. * * @param a the first double to compare * @param b the second double to compare * @param precision the number of places after the decimal point * @return a flag indicating whether the two specified doubles are equal * at the specified precision. */ public static boolean areEqualDouble(double a, double b, int precision) { return Math.abs(a - b) <= Math.pow(10, -precision); } /** * Main loop of the test which calls compute and then compares the {@link SpatialPooler}'s * state to the stored test from the Python version of the same test. * @param sp the SpatialPooler * @param c The {@link Connections} object * @param inputs the test input data * @param numRecords the number of iterations to run * @param actives the Class storing the Python activations * @param perms the Class storing the Python permanences */ private void runSideBySide(SpatialPooler sp, Connections c, int[][] inputs, int numRecords, SpatialPoolerCompatibilityActives actives, SpatialPoolerCompatibilityPermanences perms) { UniversalRandom univ = new UniversalRandom(42); int[] activeArray = new int[c.getNumColumns()]; for(int i = 0;i < numRecords;i++) { boolean learn = univ.nextDouble() > 0.5; sp.compute(c, inputs[i], activeArray, learn); //System.out.println("i: " + Arrays.toString(inputs[i])); //System.out.println("v: " + Arrays.toString(activeArray)); //printConnecteds(c); //printPermanences(c); assertTrue(Arrays.equals(actives.getActivations()[i], activeArray)); try { comparePermanences(c, perms, i); compareActivations(actives, activeArray, i); }catch(Exception e) { System.out.println("failed for record #: " + i); fail(); } if(i % 10 == 0) { //System.out.println("\n---------------------------- converting ------------------------------"); convertPermanences(c, sp, perms, i); //System.out.println("---------------------------- converted ------------------------------"); //return; } } } /** * Prints the connected column mapping to standard out * @param c */ @SuppressWarnings("unused") private void printConnecteds(Connections c) { int[][] cons = c.getConnecteds(); for(int i = 0;i < cons.length;i++) { System.out.println("c: " + Arrays.toString(cons[i])); } } /** * Prints the each columns synapse permanences * @param c */ @SuppressWarnings("unused") private void printPermanences(Connections c) { double[][] perms = c.getPermanences(); for(int i = 0;i < perms.length;i++) { System.out.println(Arrays.toString(perms[i])); } } /** * Compares the stored python spatial pooler permanences against those * generated by the java {@link SpatialPooler} * * @param c the {@link Connections} object * @param compats the class containing stored python output * @param iteration the current iteration num */ private void comparePermanences(Connections c, SpatialPoolerCompatibilityPermanences compats, int iteration) { double[][] comp = getPermsForIteration(compats, iteration); double[][] actual = c.getPermanences(); for(int i = 0;i < comp.length;i++) { for(int j = 0;j < comp[i].length;j++) { try { assertEquals(comp[i][j], actual[i][j], 0.0001); }catch(AssertionError e) { System.out.println("failed for comp: " + Arrays.toString(comp[i]) + " actual: " + Arrays.toString(actual[i])); throw e; } } } } /** * Compares the stored python column activations against the * column activations of the java {@link SpatialPooler} * * @param actives the stored activations generated from the python compatibility test * @param activations the column activations for the current cycle. * @param iteration the current iteration num */ private void compareActivations(SpatialPoolerCompatibilityActives actives, int[] activations, int iteration) { int[] pythonActivations = actives.getActivations()[iteration]; try { assertTrue(Arrays.equals(pythonActivations, activations)); }catch(AssertionError e) { System.out.println("compareActivations failed for iteration: " + iteration); } } /** * Transfer the permanences from source to dest SP's. This is used in test * routines to counteract some drift between implementations. * We assume the two SP's have identical configurations/parameters. * * @param c * @param sp * @param compats * @param iteration */ private void convertPermanences(Connections c, SpatialPooler sp, SpatialPoolerCompatibilityPermanences compats, int iteration) { double[][] pythonSpPerms = getPermsForIteration(compats, iteration); for(int i = 0;i < c.getNumColumns();i++) { double[] perms = pythonSpPerms[i]; Column col = c.getColumn(i); col.setProximalPermanences(c, perms); } comparePermanences(c, compats, iteration); } /** * Returns the array of persisted Python SP column synapse permanences * @param compats the generated class which returns the permanences * @param iteration the current iteration * @return a 2 dim array containing each column's synapse permanences */ private double[][] getPermsForIteration(SpatialPoolerCompatibilityPermanences compats, int iteration) { double[][] comp = null; try { Method getPermanences = SpatialPoolerCompatibilityPermanences.class.getMethod( "getPermanences" + iteration, (Class[])null); comp = (double[][])getPermanences.invoke(compats, (Object[])null); } catch(Exception e) { e.printStackTrace(); } return comp; } /** * Creates a {@link SpatialPooler} with predetermined parameters. * @return */ private Tuple createSP() { Parameters parameters = Parameters.getAllDefaultParameters(); parameters.set(KEY.INPUT_DIMENSIONS, new int[] { 4, 4 }); parameters.set(KEY.COLUMN_DIMENSIONS, new int[] { 5, 3 }); parameters.set(KEY.CELLS_PER_COLUMN, 1); parameters.set(KEY.RANDOM, new UniversalRandom(42)); //SpatialPooler specific parameters.set(KEY.POTENTIAL_RADIUS, 20);//3 parameters.set(KEY.POTENTIAL_PCT, 0.5);//0.5 parameters.set(KEY.GLOBAL_INHIBITION, true); parameters.set(KEY.LOCAL_AREA_DENSITY, 0.0); parameters.set(KEY.NUM_ACTIVE_COLUMNS_PER_INH_AREA, 5.0); parameters.set(KEY.STIMULUS_THRESHOLD, 0.0); parameters.set(KEY.SYN_PERM_INACTIVE_DEC, 0.01); parameters.set(KEY.SYN_PERM_ACTIVE_INC, 0.1); parameters.set(KEY.SYN_PERM_TRIM_THRESHOLD, 0.05); parameters.set(KEY.SYN_PERM_CONNECTED, 0.1); parameters.set(KEY.MIN_PCT_OVERLAP_DUTY_CYCLES, 0.001); parameters.set(KEY.MIN_PCT_ACTIVE_DUTY_CYCLES, 0.001); parameters.set(KEY.DUTY_CYCLE_PERIOD, 30); parameters.set(KEY.MAX_BOOST, 10.0); Connections conn = new Connections(); parameters.apply(conn); SpatialPooler sp = new SpatialPooler(); sp.init(conn); return new Tuple(conn, sp); } ////////////////////////////////////////////////////// // Stored Initial State of Python SP // ////////////////////////////////////////////////////// /** * Returns the initial column connections considered to be "connected" * to the input vector bits in that columns pool. * @return */ private int[][] getPythonInitialColumnConnections() { return new int[][] { {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0}, {1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1}, {0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0}, {1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1} }; } /** * Returns the mapping of which inputs belong to each {@link Column}'s * {@link Pool}. * @return a 2 dim array where a "1" indicates membership */ private int[][] getPythonInitialPotentialMapping1() { return new int[][] { {1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0}, {1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0}, {0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0}, {0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1}, {0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0}, {1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0}, {0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1}, {1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0}, {1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0}, {1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0}, {1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1}, {0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1}, {1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1} }; } /** * The initial permanences before any processing has occurred. * @return */ private double[][] getPythonInitialPermanences1() { return new double[][] { {0.0, 0.0, 0.74377, 0.0, 0.0, 0.062710002, 0.0623, 0.81379002, 0.0, 0.0, 0.51138997, 0.99207997, 0.0, 0.080179997, 0.0, 0.0}, {0.68185002, 0.057909999, 0.0, 0.11953, 0.0, 0.0, 0.0, 0.5941, 0.33390999, 0.0, 0.0, 0.0, 0.05948, 0.0, 0.45874, 0.0}, {0.0, 0.0, 0.56431001, 0.0, 0.66772002, 0.83538997, 0.95185, 0.0, 0.81081998, 0.07339, 0.0, 0.0638, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.05192, 0.0, 0.46153, 0.0, 0.0, 0.0, 0.0, 0.55891001, 0.0, 0.0, 0.71109998, 0.54064, 0.94915003}, {0.0, 0.0, 0.099880002, 0.0, 0.080080003, 0.0, 0.82801002, 0.0, 0.0, 0.66267997, 0.35613999, 0.96174997, 0.0, 0.0, 0.24013001, 0.75304002}, {0.0, 0.0, 0.0, 0.0, 0.0, 0.067429997, 0.0, 0.0, 0.0, 0.49294001, 0.068449996, 0.0, 0.51796001, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.79075003, 0.61381, 0.0, 0.083920002, 0.0, 0.61768001, 0.59040999, 0.0, 0.0, 0.43821999, 0.0, 0.0, 0.0}, {0.0, 0.34362999, 0.05401, 0.0, 0.65979999, 0.13402, 0.0, 0.0, 0.0, 0.0, 0.079460002, 0.0, 0.07423, 0.35227001, 0.0, 0.082649998}, {0.64512998, 0.0, 0.0, 0.067340001, 0.0, 0.0, 0.0, 0.061069999, 0.16858, 0.067280002, 0.0, 0.0, 0.0, 0.35163999, 0.074349999, 0.0}, {0.27271, 0.90486997, 0.0, 0.67374998, 0.071489997, 0.19324, 0.0, 0.0, 0.0, 0.0, 0.092809997, 0.0, 0.0, 0.0, 0.37125999, 0.0}, {0.0, 0.0, 0.30159, 0.0, 0.0, 0.84898001, 0.18082, 0.0, 0.053240001, 0.0, 0.80101001, 0.075759999, 0.0, 0.0, 0.89740002, 0.0}, {0.0, 0.0, 0.62568998, 0.0, 0.0, 0.0, 0.0, 0.0, 0.46845999, 0.80119002, 0.0, 0.074770004, 0.094690003, 0.0, 0.0, 0.0}, {0.062820002, 0.0, 0.098190002, 0.0, 0.0, 0.0, 0.79606003, 0.0, 0.21277, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.52516001}, {0.0, 0.48618999, 0.45532, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.088950001, 0.0, 0.09234}, {0.2368, 0.0, 0.0, 0.99505001, 0.33265001, 0.0, 0.0, 0.0, 0.0, 0.33741999, 0.07621, 0.0, 0.0, 0.0, 0.0, 0.20547999} }; } /** * The randomly generated inputs for the test copied over from the Python * test. * @return */ private int[][] getPythonInputs1() { return new int[][] { { 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1 }, { 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1 }, { 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, { 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1 }, { 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0 }, { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1 }, { 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1 }, { 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0 }, { 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1 }, { 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0 }, { 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1 }, { 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, { 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0 }, { 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0 }, { 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0 }, { 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0 }, { 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0 }, { 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0 }, { 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0 }, { 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0 }, { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1 }, { 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1 }, { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1 }, { 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1 }, { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1 }, { 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1 }, { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1 }, { 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1 }, { 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, { 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1 }, { 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, { 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1 }, { 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1 }, { 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1 }, { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1 }, { 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0 }, { 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, { 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0 }, { 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, { 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1 }, { 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, { 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1 }, { 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0 }, { 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, { 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, { 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0 }, { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0 }, { 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 }, { 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 }, { 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 }, { 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0 }, { 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1 }, { 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0 }, { 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1 }, { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1 }, { 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 }, { 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0 }, { 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1 }, { 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1 }, { 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, { 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1 }, { 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1 }, { 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1 }, { 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0 }, { 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0 }, { 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1 }, { 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0 }, { 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1 }, { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1 }, { 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0 }, { 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, { 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1 }, { 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, { 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1 }, { 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0 }, { 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1 }, { 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1 }, { 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, { 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0 }, { 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, { 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1 }, { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1 }, { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1 } }; } }