/* --------------------------------------------------------------------- * Numenta Platform for Intelligent Computing (NuPIC) * Copyright (C) 2014, 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.network; import static org.numenta.nupic.network.NetworkTestHarness.getInferredFieldsMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.numenta.nupic.algorithms.Anomaly.KEY_MODE; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.numenta.nupic.Parameters; import org.numenta.nupic.Parameters.KEY; import org.numenta.nupic.algorithms.Anomaly; import org.numenta.nupic.algorithms.Anomaly.Mode; import org.numenta.nupic.algorithms.CLAClassifier; import org.numenta.nupic.algorithms.SpatialPooler; import org.numenta.nupic.algorithms.TemporalMemory; import org.numenta.nupic.datagen.ResourceLocator; import org.numenta.nupic.encoders.MultiEncoder; import org.numenta.nupic.network.sensor.FileSensor; import org.numenta.nupic.network.sensor.Sensor; import org.numenta.nupic.network.sensor.SensorParams; import org.numenta.nupic.network.sensor.SensorParams.Keys; import org.numenta.nupic.util.MersenneTwister; import rx.Observer; import rx.Subscriber; import rx.observers.TestObserver; public class RegionTest extends ObservableTestBase { @Test public void testClose() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("4", p) .add(MultiEncoder.builder().name("").build())) .close()); assertTrue(n.lookup("r1").isClosed()); try { n.lookup("r1").add(Network.createLayer("5", p)); fail(); }catch(Exception e) { assertTrue(e.getClass().isAssignableFrom(IllegalStateException.class)); assertEquals("Cannot add Layers when Region has already been closed.", e.getMessage()); } } @Test public void testCheckName() { Parameters p = NetworkTestHarness.getParameters(); // Positive test try { Network.createRegion("r1") .add(Network.createLayer("l1", p)) .add(Network.createLayer("l2", p)); }catch(Exception e) { fail(); } // Negative test should fail try { Network.createRegion("r1") .add(Network.createLayer("l1", p)) .add(Network.createLayer("l1", p)); fail(); // should not reach here }catch(Exception e) { assertEquals("A Layer with the name: l1 has already been added to this Region.", e.getMessage()); } } @Test public void testResetMethod() { Parameters p = NetworkTestHarness.getParameters(); Region r1 = Network.createRegion("r1"); r1.add(Network.createLayer("l1", p).add(new TemporalMemory())); try { r1.reset(); assertTrue(r1.lookup("l1").hasTemporalMemory()); }catch(Exception e) { fail(); } r1 = Network.createRegion("r1"); r1.add(Network.createLayer("l1", p).add(new SpatialPooler())); try { r1.reset(); assertFalse(r1.lookup("l1").hasTemporalMemory()); }catch(Exception e) { fail(); } } @Test public void testResetRecordNum() { Parameters p = NetworkTestHarness.getParameters(); Region r1 = Network.createRegion("r1"); r1.add(Network.createLayer("l1", p).add(new TemporalMemory())); r1.observe().subscribe(new Observer<Inference>() { @Override public void onCompleted() {} @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(Inference output) { System.out.println("output = " + Arrays.toString(output.getSDR())); } }); r1.compute(new int[] { 2,3,4 }); r1.compute(new int[] { 2,3,4 }); assertEquals(1, r1.lookup("l1").getRecordNum()); r1.resetRecordNum(); assertEquals(0, r1.lookup("l1").getRecordNum()); } @Test public void testAutomaticClose() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("4", p) .add(MultiEncoder.builder().name("").build()))); //.close(); // Not necessary due to implicit call during start() or compute() Region r1 = n.lookup("r1"); r1.start(); assertTrue(r1.isClosed()); try { r1.add(Network.createLayer("5", p)); fail(); }catch(Exception e) { assertTrue(e.getClass().isAssignableFrom(IllegalStateException.class)); assertEquals("Cannot add Layers when Region has already been closed.", e.getMessage()); } } @Test public void testAdd() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("4", p) .add(MultiEncoder.builder().name("").build()))); Region r1 = n.lookup("r1"); Layer<?>layer4 = r1.lookup("4"); assertNotNull(layer4); assertEquals("r1:4", layer4.getName()); try { r1.add(Network.createLayer("4", p)); fail(); }catch(Exception e) { assertTrue(e.getClass().isAssignableFrom(IllegalArgumentException.class)); assertEquals("A Layer with the name: 4 has already been added to this Region.", e.getMessage()); } } boolean isHalted; @Test public void testHalt() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); p.set(KEY.INFERRED_FIELDS, getInferredFieldsMap("dayOfWeek", CLAClassifier.class)); Map<String, Object> params = new HashMap<>(); params.put(KEY_MODE, Mode.PURE); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("1", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE)) .add(Network.createLayer("2", p) .add(Anomaly.create(params))) .add(Network.createLayer("3", p) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(Sensor.create(FileSensor::create, SensorParams.create( Keys::path, "", ResourceLocator.path("days-of-week.csv")))) .add(new SpatialPooler())) .connect("1", "2") .connect("2", "3") .connect("3", "4")); Region r1 = n.lookup("r1"); r1.observe().subscribe(new Subscriber<Inference>() { int seq = 0; @Override public void onCompleted() { // System.out.println("onCompleted() called"); } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(Inference i) { if(seq == 2) { isHalted = true; } seq++; // System.out.println("output: " + i.getSDR()); } }); (new Thread() { public void run() { while(!isHalted) { try { Thread.sleep(1); }catch(Exception e) {e.printStackTrace();} } r1.halt(); } }).start(); r1.start(); try { r1.lookup("4").getLayerThread().join(); }catch(Exception e) { e.printStackTrace(); } } /** * Test that we automatically calculate the input dimensions despite * there being an improper Parameter setting. */ @Test public void testInputDimensionsAutomaticallyInferredFromEncoderWidth() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); // Purposefully set this to be wrong p.set(KEY.INPUT_DIMENSIONS, new int[] { 40, 40 }); Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("4", p) .add(MultiEncoder.builder().name("").build())) .close()); // Should correct the above ( {40,40} ) to have only one dimension whose width is 8 ( {8} ) assertTrue(Arrays.equals(new int[] { 8 }, (int[])p.get(KEY.INPUT_DIMENSIONS))); } /** * Test encoder bubbles up to L1 */ @Test public void testEncoderPassesUpToTopLayer() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); Map<String, Object> params = new HashMap<>(); params.put(KEY_MODE, Mode.PURE); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("1", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE)) .add(Network.createLayer("2", p) .add(Anomaly.create(params))) .add(Network.createLayer("3", p) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(new SpatialPooler()) .add(MultiEncoder.builder().name("").build()))); Region r1 = n.lookup("r1"); r1.connect("1", "2").connect("2", "3").connect("3", "4"); assertNotNull(r1.lookup("1").getEncoder()); } /** * Test that we can assemble a multi-layer Region and manually feed in * input and have the processing pass through each Layer. */ @Test public void testMultiLayerAssemblyNoSensor() { Parameters p = NetworkTestHarness.getParameters().copy(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.COLUMN_DIMENSIONS, new int[] { 30 }); p.set(KEY.SYN_PERM_INACTIVE_DEC, 0.1); p.set(KEY.SYN_PERM_ACTIVE_INC, 0.1); p.set(KEY.SYN_PERM_TRIM_THRESHOLD, 0.05); p.set(KEY.GLOBAL_INHIBITION, true); p.set(KEY.SYN_PERM_CONNECTED, 0.4); p.set(KEY.MAX_BOOST, 10.0); p.set(KEY.DUTY_CYCLE_PERIOD, 7); p.set(KEY.RANDOM, new MersenneTwister(42)); p.set(KEY.INFERRED_FIELDS, getInferredFieldsMap("dayOfWeek", CLAClassifier.class)); Map<String, Object> params = new HashMap<>(); params.put(KEY_MODE, Mode.PURE); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("1", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE)) .add(Network.createLayer("2", p) .add(Anomaly.create(params))) .add(Network.createLayer("3", p) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(new SpatialPooler()) .add(MultiEncoder.builder().name("").build())) .connect("1", "2") .connect("2", "3") .connect("3", "4")); Region r1 = n.lookup("r1"); r1.lookup("3").using(r1.lookup("4").getConnections()); // How to share Connections object between Layers r1.observe().subscribe(new Subscriber<Inference>() { @Override public void onCompleted() {} @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(Inference i) { // UNCOMMENT TO VIEW STABILIZATION OF PREDICTED FIELDS // System.out.println("Day: " + r1.getInput() + " - predictive cells: " + i.getPreviousPredictiveCells() + // " - " + Arrays.toString(i.getFeedForwardSparseActives()) + " - " + // ((int)Math.rint(((Number)i.getClassification("dayOfWeek").getMostProbableValue(1)).doubleValue()))); } }); final int NUM_CYCLES = 500; final int INPUT_GROUP_COUNT = 7; // Days of Week Map<String, Object> multiInput = new HashMap<>(); for(int i = 0;i < NUM_CYCLES;i++) { for(double j = 0;j < INPUT_GROUP_COUNT;j++) { multiInput.put("dayOfWeek", j); r1.compute(multiInput); } r1.reset(); } r1.setLearn(false); r1.reset(); // Test that we get proper output after prediction stabilization r1.observe().subscribe(new Subscriber<Inference>() { @Override public void onCompleted() {} @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(Inference i) { int nextDay = ((int)Math.rint(((Number)i.getClassification("dayOfWeek").getMostProbableValue(1)).doubleValue())); assertEquals(6, nextDay); } }); multiInput.put("dayOfWeek", 5.0); r1.compute(multiInput); } @Test public void testIsLearn() { Parameters p = NetworkTestHarness.getParameters().copy(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.COLUMN_DIMENSIONS, new int[] { 30 }); p.set(KEY.SYN_PERM_INACTIVE_DEC, 0.1); p.set(KEY.SYN_PERM_ACTIVE_INC, 0.1); p.set(KEY.SYN_PERM_TRIM_THRESHOLD, 0.05); p.set(KEY.SYN_PERM_CONNECTED, 0.4); p.set(KEY.MAX_BOOST, 10.0); p.set(KEY.DUTY_CYCLE_PERIOD, 7); p.set(KEY.RANDOM, new MersenneTwister(42)); p.set(KEY.INFERRED_FIELDS, getInferredFieldsMap("dayOfWeek", CLAClassifier.class)); Map<String, Object> params = new HashMap<>(); params.put(KEY_MODE, Mode.PURE); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("1", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE)) .add(Network.createLayer("2", p) .add(Anomaly.create(params))) .add(Network.createLayer("3", p) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(new SpatialPooler()) .add(MultiEncoder.builder().name("").build())) .connect("1", "2") .connect("2", "3") .connect("3", "4")); n.lookup("r1").close(); n.setLearn(false); assertFalse(n.isLearn()); Region r1 = n.lookup("r1"); assertFalse(n.isLearn()); Layer<?> layer = r1.getTail(); assertFalse(layer.isLearn()); while(layer.getNext() != null) { layer = layer.getNext(); assertFalse(layer.isLearn()); } } int idx0 = 0; int idx1 = 0; int idx2 = 0; /** * For this test, see that we can subscribe to each layer and also to the * Region itself and that emissions for each sequence occur for all * subscribers. */ @Test public void test2LayerAssemblyWithSensor() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); p.set(KEY.INFERRED_FIELDS, getInferredFieldsMap("dayOfWeek", CLAClassifier.class)); Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("2/3", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(Sensor.create(FileSensor::create, SensorParams.create( Keys::path, "", ResourceLocator.path("days-of-week.csv")))) .add(new SpatialPooler())) .connect("2/3", "4")); final int[][] inputs = new int[7][8]; inputs[0] = new int[] { 1, 1, 0, 0, 0, 0, 0, 1 }; inputs[1] = new int[] { 1, 1, 1, 0, 0, 0, 0, 0 }; inputs[2] = new int[] { 0, 1, 1, 1, 0, 0, 0, 0 }; inputs[3] = new int[] { 0, 0, 1, 1, 1, 0, 0, 0 }; inputs[4] = new int[] { 0, 0, 0, 1, 1, 1, 0, 0 }; inputs[5] = new int[] { 0, 0, 0, 0, 1, 1, 1, 0 }; inputs[6] = new int[] { 0, 0, 0, 0, 0, 1, 1, 1 }; Region r1 = n.lookup("r1"); // Observe the top layer TestObserver<Inference> tester; r1.lookup("4").observe().subscribe(tester = new TestObserver<Inference>() { @Override public void onCompleted() {} @Override public void onNext(Inference i) { assertTrue(Arrays.equals(inputs[idx0++], i.getEncoding())); } }); // Observe the bottom layer TestObserver<Inference> tester2; r1.lookup("2/3").observe().subscribe(tester2 = new TestObserver<Inference>() { @Override public void onCompleted() {} @Override public void onNext(Inference i) { assertTrue(Arrays.equals(inputs[idx1++], i.getEncoding())); } }); // Observe the Region output TestObserver<Inference> tester3; r1.observe().subscribe(tester3 = new TestObserver<Inference>() { @Override public void onCompleted() {} @Override public void onNext(Inference i) { assertTrue(Arrays.equals(inputs[idx2++], i.getEncoding())); } }); r1.start(); try { r1.lookup("4").getLayerThread().join(); }catch(Exception e) { e.printStackTrace(); } assertEquals(7, idx0); assertEquals(7, idx1); assertEquals(7, idx2); checkObserver(tester); checkObserver(tester2); checkObserver(tester3); } /** * Tests that we can detect if the occurrence of algorithms within a region's layers * is repeated or not. If they are repeated, then we don't allow the Inference object's * values to be passed from one layer to another because it is assumed that values * such as "activeColumns" or "previousPrediction" should not be overwritten in the case * where algorithms are not repeated, and should be overwritten when algorithms are repeated. * * The SDR is <em>always</em> passed between layers however. */ @Test public void testAlgorithmRepetitionDetection() { Parameters p = NetworkTestHarness.getParameters(); p = p.union(NetworkTestHarness.getDayDemoTestEncoderParams()); p.set(KEY.RANDOM, new MersenneTwister(42)); // -- No overlap Network n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("2/3", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(Sensor.create(FileSensor::create, SensorParams.create( Keys::path, "", ResourceLocator.path("days-of-week.csv")))) .add(new SpatialPooler())) .connect("2/3", "4")); Region r = n.lookup("r1"); assertTrue(r.layersDistinct); byte flags = r.flagAccumulator; flags ^= Layer.SPATIAL_POOLER; flags ^= Layer.TEMPORAL_MEMORY; flags ^= Layer.CLA_CLASSIFIER; assertEquals(0, flags); assertEquals(r.lookup("2/3").getMask(), (Layer.TEMPORAL_MEMORY | Layer.CLA_CLASSIFIER)); assertEquals(r.lookup("4").getMask(), Layer.SPATIAL_POOLER); // -- Test overlap detection n = Network.create("test network", p) .add(Network.createRegion("r1") .add(Network.createLayer("2/3", p) .alterParameter(KEY.AUTO_CLASSIFY, Boolean.TRUE) .add(new TemporalMemory())) .add(Network.createLayer("4", p) .add(Sensor.create(FileSensor::create, SensorParams.create( Keys::path, "", ResourceLocator.path("days-of-week.csv")))) .add(new TemporalMemory()) .add(new SpatialPooler())) .connect("2/3", "4")); r = n.lookup("r1"); assertFalse(r.layersDistinct); assertEquals(r.lookup("2/3").getMask(), (Layer.TEMPORAL_MEMORY | Layer.CLA_CLASSIFIER)); assertEquals(r.lookup("4").getMask(), (Layer.SPATIAL_POOLER | Layer.TEMPORAL_MEMORY)); } }