• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

numenta / htm.java / #1206

26 Jan 2015 12:57PM UTC coverage: 14.404% (-0.005%) from 14.409%
#1206

push

David Ray
Merge pull request #168 from cogmission/network_api_work

testing the RNG is not part of the scope

714 of 4957 relevant lines covered (14.4%)

0.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/org/numenta/nupic/research/SpatialPooler.java
1
/* ---------------------------------------------------------------------
2
 * Numenta Platform for Intelligent Computing (NuPIC)
3
 * Copyright (C) 2014, Numenta, Inc.  Unless you have an agreement
4
 * with Numenta, Inc., for a separate license for this software code, the
5
 * following terms and conditions apply:
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License version 3 as
9
 * published by the Free Software Foundation.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 * See the GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program.  If not, see http://www.gnu.org/licenses.
18
 *
19
 * http://numenta.org/licenses/
20
 * ---------------------------------------------------------------------
21
 */
22

23
package org.numenta.nupic.research;
24

25
import gnu.trove.list.array.TDoubleArrayList;
26
import gnu.trove.list.array.TIntArrayList;
27
import gnu.trove.set.hash.TIntHashSet;
28
import org.numenta.nupic.Connections;
29
import org.numenta.nupic.model.Column;
30
import org.numenta.nupic.model.Pool;
31
import org.numenta.nupic.util.ArrayUtils;
32
import org.numenta.nupic.util.Condition;
33
import org.numenta.nupic.util.SparseBinaryMatrix;
34
import org.numenta.nupic.util.SparseMatrix;
35
import org.numenta.nupic.util.SparseObjectMatrix;
36

37
import java.util.ArrayList;
38
import java.util.Arrays;
39
import java.util.List;
40
import java.util.Random;
41

42

43
/**
44
 * Handles the relationships between the columns of a region 
45
 * and the inputs bits. The primary public interface to this function is the 
46
 * "compute" method, which takes in an input vector and returns a list of 
47
 * activeColumns columns.
48
 * Example Usage:
49
 * >
50
 * > SpatialPooler sp = SpatialPooler();
51
 * > Connections c = new Connections();
52
 * > sp.init(c);
53
 * > for line in file:
54
 * >   inputVector = numpy.array(line)
55
 * >   sp.compute(inputVector)
56
 * 
57
 * @author David Ray
58
 *
59
 */
60
public class SpatialPooler {
61
    /**
62
     * Constructs a new {@code SpatialPooler}
63
     */
64
    public SpatialPooler() {}
×
65
    
66
    /**
67
     * Initializes the specified {@link Connections} object which contains
68
     * the memory and structural anatomy this spatial pooler uses to implement
69
     * its algorithms.
70
     * 
71
     * @param c                a {@link Connections} object
72
     */
73
    public void init(Connections c) {
74
            initMatrices(c);
×
75
            connectAndConfigureInputs(c);
×
76
    }
×
77
    
78
    /**
79
     * Called to initialize the structural anatomy with configured values and prepare
80
     * the anatomical entities for activation.
81
     * 
82
     * @param c
83
     */
84
    public void initMatrices(final Connections c) {
85
            SparseObjectMatrix<Column> mem = c.getMemory();
×
86
            c.setMemory(mem == null ? 
×
87
                    mem = new SparseObjectMatrix<>(c.getColumnDimensions()) : mem);
×
88
        c.setInputMatrix(new SparseBinaryMatrix(c.getInputDimensions()));
×
89
        
90
        //Calculate numInputs and numColumns
91
        int numInputs = c.getInputMatrix().getMaxIndex() + 1;
×
92
        int numColumns = c.getMemory().getMaxIndex() + 1;
×
93
        c.setNumInputs(numInputs);
×
94
        c.setNumColumns(numColumns);
×
95
        
96
        //Fill the sparse matrix with column objects
97
        for(int i = 0;i < numColumns;i++) { mem.set(i, new Column(c.getCellsPerColumn(), i)); }
×
98
        
99
        c.setPotentialPools(new SparseObjectMatrix<Pool>(c.getMemory().getDimensions()));
×
100
        
101
        c.setConnectedMatrix(new SparseBinaryMatrix(new int[] { numColumns, numInputs }));
×
102
        
103
        double[] tieBreaker = new double[numColumns];
×
104
        for(int i = 0;i < numColumns;i++) {
×
105
            tieBreaker[i] = 0.01 * c.getRandom().nextDouble();
×
106
        }
107
        c.setTieBreaker(tieBreaker);
×
108
        
109
        //Initialize state meta-management statistics
110
        c.setOverlapDutyCycles(new double[numColumns]);
×
111
        Arrays.fill(c.getOverlapDutyCycles(), 0);
×
112
        c.setActiveDutyCycles(new double[numColumns]);
×
113
        Arrays.fill(c.getActiveDutyCycles(), 0);
×
114
        c.setMinOverlapDutyCycles(new double[numColumns]);
×
115
        Arrays.fill(c.getOverlapDutyCycles(), 0);
×
116
        c.setMinActiveDutyCycles(new double[numColumns]);
×
117
        Arrays.fill(c.getMinActiveDutyCycles(), 0);
118
        c.setBoostFactors(new double[numColumns]);
119
        Arrays.fill(c.getBoostFactors(), 1);
120
    }
121
    
122
    /**
123
     * Step two of pooler initialization kept separate from initialization
124
     * of static members so that they may be set at a different point in 
125
     * the initialization (as sometimes needed by tests).
126
     * 
127
     * @param c                the {@link Connections} memory
128
     */
129
    public void connectAndConfigureInputs(Connections c) {
×
130
            // Initialize the set of permanence values for each column. Ensure that
×
131
        // each column is connected to enough input bits to allow it to be
×
132
        // activated.
×
133
            int numColumns = c.getNumColumns();
×
134
        for(int i = 0;i < numColumns;i++) {
×
135
            int[] potential = mapPotential(c, i, true);
×
136
            Column column = c.getColumn(i);
137
            c.getPotentialPools().set(i, column.createPotentialPool(c, potential));
138
            double[] perm = initPermanence(c, potential, i, c.getInitConnectedPct());
×
139
            updatePermanencesForColumn(c, perm, column, potential, true);
×
140
        }
141
        
142
        updateInhibitionRadius(c);
143
    }
144
    
145
    /**
146
     * This is the primary public method of the SpatialPooler class. This
147
     * function takes a input vector and outputs the indices of the active columns.
148
     * If 'learn' is set to True, this method also updates the permanences of the
149
     * columns. 
150
     * @param inputVector       An array of 0's and 1's that comprises the input to
151
     *                          the spatial pooler. The array will be treated as a one
152
     *                          dimensional array, therefore the dimensions of the array
153
     *                          do not have to match the exact dimensions specified in the
154
     *                          class constructor. In fact, even a list would suffice.
155
     *                          The number of input bits in the vector must, however,
156
     *                          match the number of bits specified by the call to the
157
     *                          constructor. Therefore there must be a '0' or '1' in the
158
     *                          array for every input bit.
159
     * @param activeArray       An array whose size is equal to the number of columns.
160
     *                          Before the function returns this array will be populated
161
     *                          with 1's at the indices of the active columns, and 0's
162
     *                          everywhere else.
163
     * @param learn             A boolean value indicating whether learning should be
164
     *                          performed. Learning entails updating the  permanence
165
     *                          values of the synapses, and hence modifying the 'state'
166
     *                          of the model. Setting learning to 'off' freezes the SP
167
     *                          and has many uses. For example, you might want to feed in
×
168
     *                          various inputs and examine the resulting SDR's.
×
169
     */
170
    public void compute(Connections c, int[] inputVector, int[] activeArray, boolean learn, boolean stripNeverLearned) {
171
        if(inputVector.length != c.getNumInputs()) {
×
172
            throw new IllegalArgumentException("Input array must be same size as the defined number of inputs");
×
173
        }
174
        
175
        updateBookeepingVars(c, learn);
×
176
        int[] overlaps = calculateOverlap(c, inputVector);
×
177
        
178
        double[] boostedOverlaps;
×
179
        if(learn) {
180
                boostedOverlaps = ArrayUtils.multiply(c.getBoostFactors(), overlaps);
181
        }else{
×
182
                boostedOverlaps = ArrayUtils.toDoubleArray(overlaps);
183
        }
×
184
        
×
185
        int[] activeColumns = inhibitColumns(c, boostedOverlaps);
×
186
        
×
187
        if(learn) {
×
188
                adaptSynapses(c, inputVector, activeColumns);
×
189
                updateDutyCycles(c, overlaps, activeColumns);
×
190
                bumpUpWeakColumns(c);
×
191
                updateBoostFactors(c);
192
                if(isUpdateRound(c)) {
×
193
                        updateInhibitionRadius(c);
×
194
                        updateMinDutyCycles(c);
195
                }
196
        }else if(stripNeverLearned){
×
197
                activeColumns = stripUnlearnedColumns(c, activeColumns).toArray();
×
198
        }
×
199
        
200
        Arrays.fill(activeArray, 0);
×
201
        if(activeColumns.length > 0) {
202
                ArrayUtils.setIndexesTo(activeArray, activeColumns, 1);
203
        }
204
    }
205
    
206
    /**
207
     * Removes the set of columns who have never been active from the set of
208
     * active columns selected in the inhibition round. Such columns cannot
209
     * represent learned pattern and are therefore meaningless if only inference
210
     * is required. This should not be done when using a random, unlearned SP
211
     * since you would end up with no active columns.
212
     *  
213
     * @param activeColumns        An array containing the indices of the active columns
×
214
     * @return        a list of columns with a chance of activation
×
215
     */
×
216
    public TIntArrayList stripUnlearnedColumns(Connections c, int[] activeColumns) {
×
217
            TIntHashSet active = new TIntHashSet(activeColumns);
×
218
            TIntHashSet aboveZero = new TIntHashSet();
×
219
            int numCols = c.getNumColumns();
×
220
            double[] colDutyCycles = c.getActiveDutyCycles();
221
            for(int i = 0;i < numCols;i++) {
222
                    if(colDutyCycles[i] <= 0) {
×
223
                            aboveZero.add(i);
×
224
                    }
×
225
            }
×
226
            active.removeAll(aboveZero);
227
            TIntArrayList l = new TIntArrayList(active);
228
            l.sort();
229
            return l;
230
    }
231
    
232
    /**
233
     * Updates the minimum duty cycles defining normal activity for a column. A
234
     * column with activity duty cycle below this minimum threshold is boosted.
235
     *  
×
236
     * @param c
×
237
     */
238
    public void updateMinDutyCycles(Connections c) {
×
239
            if(c.getGlobalInhibition() || c.getInhibitionRadius() > c.getNumInputs()) {
240
                    updateMinDutyCyclesGlobal(c);
×
241
            }else{
242
                    updateMinDutyCyclesLocal(c);
243
            }
244
    }
245
    
246
    /**
247
     * Updates the minimum duty cycles in a global fashion. Sets the minimum duty
248
     * cycles for the overlap and activation of all columns to be a percent of the
249
     * maximum in the region, specified by {@link Connections#getMinOverlapDutyCycles()} and
250
     * minPctActiveDutyCycle respectively. Functionality it is equivalent to
251
     * {@link #updateMinDutyCyclesLocal(Connections)}, but this function exploits the globalness of the
252
     * computation to perform it in a straightforward, and more efficient manner.
253
     * 
×
254
     * @param c
×
255
     */
×
256
    public void updateMinDutyCyclesGlobal(Connections c) {
×
257
            Arrays.fill(c.getMinOverlapDutyCycles(), 
×
258
                    c.getMinPctOverlapDutyCycles() * ArrayUtils.max(c.getOverlapDutyCycles()));
259
            Arrays.fill(c.getMinActiveDutyCycles(), 
260
                    c.getMinPctActiveDutyCycles() * ArrayUtils.max(c.getActiveDutyCycles()));
261
    }
262
    
263
    /**
264
     * Updates the minimum duty cycles. The minimum duty cycles are determined
265
     * locally. Each column's minimum duty cycles are set to be a percent of the
266
     * maximum duty cycles in the column's neighborhood. Unlike
267
     * {@link #updateMinDutyCyclesGlobal(Connections)}, here the values can be 
268
     * quite different for different columns.
269
     * 
×
270
     * @param c
×
271
     */
×
272
    public void updateMinDutyCyclesLocal(Connections c) {
×
273
            int len = c.getNumColumns();
×
274
            for(int i = 0;i < len;i++) {
×
275
                    int[] maskNeighbors = getNeighborsND(c, i, c.getMemory(), c.getInhibitionRadius(), true).toArray();
×
276
                    c.getMinOverlapDutyCycles()[i] = ArrayUtils.max(
×
277
                            ArrayUtils.sub(c.getOverlapDutyCycles(), maskNeighbors)) *
×
278
                                    c.getMinPctOverlapDutyCycles();
279
                    c.getMinActiveDutyCycles()[i] = ArrayUtils.max(
×
280
                            ArrayUtils.sub(c.getActiveDutyCycles(), maskNeighbors)) *
281
                                    c.getMinPctActiveDutyCycles();
282
            }
283
    }
284
    
285
    /**
286
     * Updates the duty cycles for each column. The OVERLAP duty cycle is a moving
287
     * average of the number of inputs which overlapped with the each column. The
288
     * ACTIVITY duty cycles is a moving average of the frequency of activation for
289
     * each column.
290
     * 
291
     * @param c                                        the {@link Connections} (spatial pooler memory)
292
     * @param overlaps                        an array containing the overlap score for each column.
293
     *                                      The overlap score for a column is defined as the number
294
     *                                      of synapses in a "connected state" (connected synapses)
295
     *                                      that are connected to input bits which are turned on.
296
     * @param activeColumns                An array containing the indices of the active columns,
×
297
     *                                      the sparse set of columns which survived inhibition
×
298
     */
×
299
    public void updateDutyCycles(Connections c, int[] overlaps, int[] activeColumns) {
×
300
            double[] overlapArray = new double[c.getNumColumns()];
×
301
            double[] activeArray = new double[c.getNumColumns()];
302
            ArrayUtils.greaterThanXThanSetToY(overlaps, 0, 1);
303
            if(activeColumns.length > 0) {
×
304
                    ArrayUtils.setIndexesTo(activeArray, activeColumns, 1);
×
305
            }
×
306
            
307
            int period = c.getDutyCyclePeriod();
308
            if(period > c.getIterationNum()) {
×
309
                    period  = c.getIterationNum();
×
310
            }
311
            
×
312
            c.setOverlapDutyCycles(
×
313
                    updateDutyCyclesHelper(c, c.getOverlapDutyCycles(), overlapArray, period));
×
314
            
315
            c.setActiveDutyCycles(
316
                updateDutyCyclesHelper(c, c.getActiveDutyCycles(), activeArray, period));
317
    }
318
   
319
    /**
320
     * Updates a duty cycle estimate with a new value. This is a helper
321
     * function that is used to update several duty cycle variables in
322
     * the Column class, such as: overlapDutyCucle, activeDutyCycle,
323
     * minPctDutyCycleBeforeInh, minPctDutyCycleAfterInh, etc. returns
324
     * the updated duty cycle. Duty cycles are updated according to the following
325
     * formula:
326
     * 
327
     *  
328
     *                      (period - 1)*dutyCycle + newValue
329
     *        dutyCycle := ----------------------------------
330
     *                        period
331
         *
332
     * @param c                                the {@link Connections} (spatial pooler memory)
333
     * @param dutyCycles        An array containing one or more duty cycle values that need
334
     *                              to be updated
335
     * @param newInput                A new numerical value used to update the duty cycle
336
     * @param period                The period of the duty cycle
×
337
     * @return
338
     */
339
    public double[] updateDutyCyclesHelper(Connections c, double[] dutyCycles, double[] newInput, double period) {
340
            return ArrayUtils.divide(ArrayUtils.d_add(ArrayUtils.multiply(dutyCycles, period - 1), newInput), period);
341
    }
342
    
343
    /**
344
     * The range of connectedSynapses per column, averaged for each dimension.
345
     * This value is used to calculate the inhibition radius. This variation of
346
     * the function supports arbitrary column dimensions.
347
     *  
348
     * @param c             the {@link Connections} (spatial pooler memory)
349
     * @param columnIndex   the current column for which to avg.
×
350
     * @return
×
351
     */
×
352
    public double avgConnectedSpanForColumnND(Connections c, int columnIndex) {
353
        int[] dimensions = c.getInputDimensions();
×
354
        int[] connected = c.getColumn(columnIndex).getProximalDendrite().getConnectedSynapsesSparse(c);
×
355
        if(connected == null || connected.length == 0) return 0;
×
356
        
×
357
        int[] maxCoord = new int[c.getInputDimensions().length];
×
358
        int[] minCoord = new int[c.getInputDimensions().length];
×
359
        Arrays.fill(maxCoord, -1);
×
360
        Arrays.fill(minCoord, ArrayUtils.max(dimensions));
×
361
        SparseMatrix<?> inputMatrix = c.getInputMatrix();
362
        for(int i = 0;i < connected.length;i++) {
×
363
            maxCoord = ArrayUtils.maxBetween(maxCoord, inputMatrix.computeCoordinates(connected[i]));
364
            minCoord = ArrayUtils.minBetween(minCoord, inputMatrix.computeCoordinates(connected[i]));
365
        }
366
        return ArrayUtils.average(ArrayUtils.add(ArrayUtils.subtract(maxCoord, minCoord), 1));
367
    }
368
    
369
    /**
370
     * Update the inhibition radius. The inhibition radius is a measure of the
371
     * square (or hypersquare) of columns that each a column is "connected to"
372
     * on average. Since columns are are not connected to each other directly, we
373
     * determine this quantity by first figuring out how many *inputs* a column is
374
     * connected to, and then multiplying it by the total number of columns that
375
     * exist for each input. For multiple dimension the aforementioned
376
     * calculations are averaged over all dimensions of inputs and columns. This
377
     * value is meaningless if global inhibition is enabled.
378
     * 
×
379
     * @param c                the {@link Connections} (spatial pooler memory)
×
380
     */
×
381
    public void updateInhibitionRadius(Connections c) {
382
        if(c.getGlobalInhibition()) {
383
            c.setInhibitionRadius(ArrayUtils.max(c.getColumnDimensions()));
×
384
            return;
×
385
        }
×
386
        
×
387
        TDoubleArrayList avgCollected = new TDoubleArrayList();
388
        int len = c.getNumColumns();
×
389
        for(int i = 0;i < len;i++) {
×
390
            avgCollected.add(avgConnectedSpanForColumnND(c, i));
×
391
        }
×
392
        double avgConnectedSpan = ArrayUtils.average(avgCollected.toArray());
×
393
        double diameter = avgConnectedSpan * avgColumnsPerInput(c);
×
394
        double radius = (diameter - 1) / 2.0d;
395
        radius = Math.max(1, radius);
396
        c.setInhibitionRadius((int)Math.round(radius));
397
    }
398
    
399
    /**
400
     * The average number of columns per input, taking into account the topology
401
     * of the inputs and columns. This value is used to calculate the inhibition
402
     * radius. This function supports an arbitrary number of dimensions. If the
403
     * number of column dimensions does not match the number of input dimensions,
404
     * we treat the missing, or phantom dimensions as 'ones'.
405
     *  
406
     * @param c                the {@link Connections} (spatial pooler memory)
×
407
     * @return
×
408
     */
×
409
    public double avgColumnsPerInput(Connections c) {
×
410
        int[] colDim = Arrays.copyOf(c.getColumnDimensions(), c.getColumnDimensions().length);
×
411
        int[] inputDim = Arrays.copyOf(c.getInputDimensions(), c.getInputDimensions().length);
412
        double[] columnsPerInput = ArrayUtils.divide(
413
            ArrayUtils.toDoubleArray(colDim), ArrayUtils.toDoubleArray(inputDim), 0, 0);
414
        return ArrayUtils.average(columnsPerInput);
415
    }
416
    
417
    /**
418
     * The primary method in charge of learning. Adapts the permanence values of
419
     * the synapses based on the input vector, and the chosen columns after
420
     * inhibition round. Permanence values are increased for synapses connected to
421
     * input bits that are turned on, and decreased for synapses connected to
422
     * inputs bits that are turned off.
423
     * 
424
     * @param c                                        the {@link Connections} (spatial pooler memory)
425
     * @param inputVector                a integer array that comprises the input to
426
     *                                       the spatial pooler. There exists an entry in the array
427
     *                                      for every input bit.
428
     * @param activeColumns                an array containing the indices of the columns that
×
429
     *                                      survived inhibition.
430
     */
×
431
    public void adaptSynapses(Connections c, int[] inputVector, int[] activeColumns) {
432
            int[] inputIndices = ArrayUtils.where(inputVector, new Condition.Adapter<Object>() {
×
433
            @Override
×
434
                    public boolean eval(int i) { return i > 0; }
×
435
            });
×
436
            double[] permChanges = new double[c.getNumInputs()];
×
437
            Arrays.fill(permChanges, -1 * c.getSynPermInactiveDec());
×
438
            ArrayUtils.setIndexesTo(permChanges, inputIndices, c.getSynPermActiveInc());
×
439
            for(int i = 0;i < activeColumns.length;i++) {
×
440
                    Pool pool = c.getPotentialPools().getObject(activeColumns[i]);
×
441
                    double[] perm = pool.getDensePermanences(c);
×
442
                    int[] indexes = pool.getSparseConnections();
443
                    ArrayUtils.raiseValuesBy(permChanges, perm);
×
444
                    Column col = c.getColumn(activeColumns[i]);
445
                    updatePermanencesForColumn(c, perm, col, indexes, true);
446
            }
447
    }
448
    
449
    /**
450
     * This method increases the permanence values of synapses of columns whose
451
     * activity level has been too low. Such columns are identified by having an
452
     * overlap duty cycle that drops too much below those of their peers. The
453
     * permanence values for such columns are increased.
454
     *  
×
455
     * @param c
456
     */
×
457
    public void bumpUpWeakColumns(final Connections c) {
458
            int[] weakColumns = ArrayUtils.where(c.getMemory().get1DIndexes(), new Condition.Adapter<Integer>() {
459
                    @Override public boolean eval(int i) {
460
                            return c.getOverlapDutyCycles()[i] < c.getMinOverlapDutyCycles()[i];
×
461
                    }
×
462
            });
×
463
            
×
464
            for(int i = 0;i < weakColumns.length;i++) {
×
465
                    Pool pool = c.getPotentialPools().getObject(weakColumns[i]);
×
466
                    double[] perm = pool.getSparsePermanences();
×
467
                    ArrayUtils.raiseValuesBy(c.getSynPermBelowStimulusInc(), perm);
468
                    int[] indexes = pool.getSparseConnections();
×
469
                    Column col = c.getColumn(weakColumns[i]);
470
                    updatePermanencesForColumnSparse(c, perm, col, indexes, true);
471
            }
472
    }
473
    
474
    /**
475
     * This method ensures that each column has enough connections to input bits
476
     * to allow it to become active. Since a column must have at least
477
     * 'stimulusThreshold' overlaps in order to be considered during the
478
     * inhibition phase, columns without such minimal number of connections, even
479
     * if all the input bits they are connected to turn on, have no chance of
480
     * obtaining the minimum threshold. For such columns, the permanence values
481
     * are increased until the minimum number of connections are formed.
482
     * 
483
     * @param c                                        the {@link Connections} memory
484
     * @param perm                                the permanence values
×
485
     * @param maskPotential                        
486
     */
×
487
    public void raisePermanenceToThreshold(Connections c, double[] perm, int[] maskPotential) {
×
488
        ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
489
        while(true) {
490
            int numConnected = ArrayUtils.valueGreaterCountAtIndex(c.getSynPermConnected(), perm, maskPotential);
×
491
            if(numConnected >= c.getStimulusThreshold()) return;
×
492
            //Skipping version of "raiseValuesBy" that uses the maskPotential until bug #1322 is fixed
493
            //in NuPIC - for now increment all bits until numConnected >= stimulusThreshold
494
            ArrayUtils.raiseValuesBy(c.getSynPermBelowStimulusInc(), perm, maskPotential);
495
        }
496
    }
497
    
498
    /**
499
     * This method ensures that each column has enough connections to input bits
500
     * to allow it to become active. Since a column must have at least
501
     * 'self._stimulusThreshold' overlaps in order to be considered during the
502
     * inhibition phase, columns without such minimal number of connections, even
503
     * if all the input bits they are connected to turn on, have no chance of
504
     * obtaining the minimum threshold. For such columns, the permanence values
505
     * are increased until the minimum number of connections are formed.
506
     * 
507
     * Note: This method services the "sparse" versions of corresponding methods
508
     * 
509
     * @param c         The {@link Connections} memory
×
510
     * @param perm                permanence values
511
     */
×
512
    public void raisePermanenceToThresholdSparse(Connections c, double[] perm) {
×
513
        ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
×
514
        while(true) {
×
515
            int numConnected = ArrayUtils.valueGreaterCount(c.getSynPermConnected(), perm);
516
            if(numConnected >= c.getStimulusThreshold()) return;
517
            ArrayUtils.raiseValuesBy(c.getSynPermBelowStimulusInc(), perm);
518
        }
519
    }
520
    
521
    /**
522
     * This method updates the permanence matrix with a column's new permanence
523
     * values. The column is identified by its index, which reflects the row in
524
     * the matrix, and the permanence is given in 'sparse' form, i.e. an array
525
     * whose members are associated with specific indexes. It is in
526
     * charge of implementing 'clipping' - ensuring that the permanence values are
527
     * always between 0 and 1 - and 'trimming' - enforcing sparseness by zeroing out
528
     * all permanence values below 'synPermTrimThreshold'. It also maintains
529
     * the consistency between 'permanences' (the matrix storing the
530
     * permanence values), 'connectedSynapses', (the matrix storing the bits
531
     * each column is connected to), and 'connectedCounts' (an array storing
532
     * the number of input bits each column is connected to). Every method wishing
533
     * to modify the permanence matrix should do so through this method.
534
     * 
535
     * @param c                 the {@link Connections} which is the memory model.
536
     * @param perm              An array of permanence values for a column. The array is
537
     *                          "dense", i.e. it contains an entry for each input bit, even
538
     *                          if the permanence value is 0.
539
     * @param column                    The column in the permanence, potential and connectivity matrices
540
     * @param maskPotential                The indexes of inputs in the specified {@link Column}'s pool.
×
541
     * @param raisePerm         a boolean value indicating whether the permanence values
×
542
     */
543
    public void updatePermanencesForColumn(Connections c, double[] perm, Column column, int[] maskPotential, boolean raisePerm) {
544
            if(raisePerm) {
×
545
            raisePermanenceToThreshold(c, perm, maskPotential);
×
546
        }
×
547
        
×
548
        ArrayUtils.lessThanOrEqualXThanSetToY(perm, c.getSynPermTrimThreshold(), 0);
549
        ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
550
        column.setProximalPermanences(c, perm);
551
    }
552
    
553
    /**
554
     * This method updates the permanence matrix with a column's new permanence
555
     * values. The column is identified by its index, which reflects the row in
556
     * the matrix, and the permanence is given in 'sparse' form, (i.e. an array
557
     * whose members are associated with specific indexes). It is in
558
     * charge of implementing 'clipping' - ensuring that the permanence values are
559
     * always between 0 and 1 - and 'trimming' - enforcing sparseness by zeroing out
560
     * all permanence values below 'synPermTrimThreshold'. Every method wishing
561
     * to modify the permanence matrix should do so through this method.
562
     * 
563
     * @param c                 the {@link Connections} which is the memory model.
564
     * @param perm              An array of permanence values for a column. The array is
565
     *                          "sparse", i.e. it contains an entry for each input bit, even
566
     *                          if the permanence value is 0.
567
     * @param column                    The column in the permanence, potential and connectivity matrices
×
568
     * @param raisePerm         a boolean value indicating whether the permanence values
×
569
     */
570
    public void updatePermanencesForColumnSparse(Connections c, double[] perm, Column column, int[] maskPotential, boolean raisePerm) {
571
            if(raisePerm) {
×
572
            raisePermanenceToThresholdSparse(c, perm);
×
573
        }
×
574
        
×
575
        ArrayUtils.lessThanOrEqualXThanSetToY(perm, c.getSynPermTrimThreshold(), 0);
576
        ArrayUtils.clip(perm, c.getSynPermMin(), c.getSynPermMax());
577
        column.setProximalPermanencesSparse(c, perm, maskPotential);
578
    }
579
    
580
    /**
581
     * Returns a randomly generated permanence value for a synapses that is
582
     * initialized in a connected state. The basic idea here is to initialize
583
     * permanence values very close to synPermConnected so that a small number of
584
     * learning steps could make it disconnected or connected.
585
     *
586
     * Note: experimentation was done a long time ago on the best way to initialize
587
     * permanence values, but the history for this particular scheme has been lost.
588
     * 
×
589
     * @return  a randomly generated permanence value
590
     */
591
    public static double initPermConnected(Connections c) {
592
        double p = c.getSynPermConnected() + c.getRandom().nextDouble() * c.getSynPermActiveInc() / 4.0;
593
        
594
        // Note from Python implementation on conditioning below:
×
595
        // Ensure we don't have too much unnecessary precision. A full 64 bits of
×
596
        // precision causes numerical stability issues across platforms and across
597
        // implementations
598
        p = ((int)(p * 100000)) / 100000.0d;
599
        return p;
600
    }
601
    
602
    /**
603
     * Returns a randomly generated permanence value for a synapses that is to be
604
     * initialized in a non-connected state.
605
     * 
×
606
     * @return  a randomly generated permanence value
607
     */
608
    public static double initPermNonConnected(Connections c) {
609
        double p = c.getSynPermConnected() * c.getRandom().nextDouble();
610
        
611
        // Note from Python implementation on conditioning below:
×
612
        // Ensure we don't have too much unnecessary precision. A full 64 bits of
×
613
        // precision causes numerical stability issues across platforms and across
614
        // implementations
615
        p = ((int)(p * 100000)) / 100000.0d;
616
        return p;
617
    }
618
    /**
619
     * Initializes the permanences of a column. The method
620
     * returns a 1-D array the size of the input, where each entry in the
621
     * array represents the initial permanence value between the input bit
622
     * at the particular index in the array, and the column represented by
623
     * the 'index' parameter.
624
     * 
625
     * @param c                 the {@link Connections} which is the memory model
626
     * @param potentialPool     An array specifying the potential pool of the column.
627
     *                          Permanence values will only be generated for input bits
628
     *                          corresponding to indices for which the mask value is 1.
629
     *                          WARNING: potentialPool is sparse, not an array of "1's"
630
     * @param index                                the index of the column being initialized
631
     * @param connectedPct      A value between 0 or 1 specifying the percent of the input
632
     *                          bits that will start off in a connected state.
×
633
     * @return
×
634
     */
×
635
    public double[] initPermanence(Connections c, int[] potentialPool, int index, double connectedPct) {
×
636
            int count = (int)Math.round((double)potentialPool.length * connectedPct);
×
637
        TIntHashSet pick = new TIntHashSet();
×
638
        Random random = c.getRandom();
×
639
        while(pick.size() < count) {
640
                int randIdx = random.nextInt(potentialPool.length);
×
641
                pick.add(potentialPool[randIdx]);
×
642
        }
×
643
        
×
644
        double[] perm = new double[c.getNumInputs()];
645
        Arrays.fill(perm, 0);
×
646
        for(int idx : potentialPool) {
647
                if(pick.contains(idx)) {
648
                perm[idx] = initPermConnected(c);
×
649
            }else{
650
                perm[idx] = initPermNonConnected(c);
×
651
            }
×
652
                
653
                perm[idx] = perm[idx] < c.getSynPermTrimThreshold() ? 0 : perm[idx];
654
        }
655
        c.getColumn(index).setProximalPermanences(c, perm);
656
        return perm;
657
    }
658
    
659
    /**
660
     * Maps a column to its respective input index, keeping to the topology of
661
     * the region. It takes the index of the column as an argument and determines
662
     * what is the index of the flattened input vector that is to be the center of
663
     * the column's potential pool. It distributes the columns over the inputs
664
     * uniformly. The return value is an integer representing the index of the
665
     * input bit. Examples of the expected output of this method:
666
     * * If the topology is one dimensional, and the column index is 0, this
667
     *   method will return the input index 0. If the column index is 1, and there
668
     *   are 3 columns over 7 inputs, this method will return the input index 3.
669
     * * If the topology is two dimensional, with column dimensions [3, 5] and
670
     *   input dimensions [7, 11], and the column index is 3, the method
671
     *   returns input index 8. 
672
     *   
673
     * @param columnIndex   The index identifying a column in the permanence, potential
674
     *                      and connectivity matrices.
×
675
     * @return              A boolean value indicating that boundaries should be
×
676
     *                      ignored.
×
677
     */
×
678
    public int mapColumn(Connections c, int columnIndex) {
×
679
        int[] columnCoords = c.getMemory().computeCoordinates(columnIndex);
×
680
        double[] colCoords = ArrayUtils.toDoubleArray(columnCoords);
×
681
        double[] ratios = ArrayUtils.divide(
×
682
            colCoords, ArrayUtils.toDoubleArray(c.getColumnDimensions()), 0, 0);
×
683
        double[] inputCoords = ArrayUtils.multiply(
684
            ArrayUtils.toDoubleArray(c.getInputDimensions()), ratios, 0, 0);
×
685
        inputCoords = ArrayUtils.d_add(inputCoords, 
×
686
                ArrayUtils.multiply(ArrayUtils.divide(
687
                        ArrayUtils.toDoubleArray(c.getInputDimensions()), ArrayUtils.toDoubleArray(c.getColumnDimensions()), 0, 0), 
688
                                0.5));
689
        int[] inputCoordInts = ArrayUtils.clip(ArrayUtils.toIntArray(inputCoords), c.getInputDimensions(), -1);
690
        return c.getInputMatrix().computeIndex(inputCoordInts);
691
    }
692
    
693
    /**
694
     * Maps a column to its input bits. This method encapsulates the topology of
695
     * the region. It takes the index of the column as an argument and determines
696
     * what are the indices of the input vector that are located within the
697
     * column's potential pooc. The return value is a list containing the indices
698
     * of the input bits. The current implementation of the base class only
699
     * supports a 1 dimensional topology of columns with a 1 dimensional topology
700
     * of inputs. To extend this class to support 2-D topology you will need to
701
     * override this method. Examples of the expected output of this method:
702
     * * If the potentialRadius is greater than or equal to the entire input
703
     *   space, (global visibility), then this method returns an array filled with
704
     *   all the indices
705
     * * If the topology is one dimensional, and the potentialRadius is 5, this
706
     *   method will return an array containing 5 consecutive values centered on
707
     *   the index of the column (wrapping around if necessary).
708
     * * If the topology is two dimensional (not implemented), and the
709
     *   potentialRadius is 5, the method should return an array containing 25
710
     *   '1's, where the exact indices are to be determined by the mapping from
711
     *   1-D index to 2-D position.
712
     * 
713
     * @param c                    {@link Connections} the main memory model
714
     * @param columnIndex   The index identifying a column in the permanence, potential
715
     *                      and connectivity matrices.
716
     * @param wrapAround    A boolean value indicating that boundaries should be
×
717
     *                      ignored.
718
     * @return
×
719
     */
×
720
    public int[] mapPotential(Connections c, int columnIndex, boolean wrapAround) {
721
        int inputIndex = mapColumn(c, columnIndex);
×
722
        
723
        TIntArrayList indices = getNeighborsND(c, inputIndex, c.getInputMatrix(), c.getPotentialRadius(), wrapAround);
×
724
        indices.add(inputIndex);
725
        //TODO: See https://github.com/numenta/nupic.core/issues/128
726
        indices.sort();
727
        
728
        return ArrayUtils.sample((int)Math.round(indices.size() * c.getPotentialPct()), indices, c.getRandom());
729
    }
730

731
    /**
732
     * Similar to _getNeighbors1D and _getNeighbors2D (Not included in this implementation), 
733
     * this function Returns a list of indices corresponding to the neighbors of a given column. 
734
     * Since the permanence values are stored in such a way that information about topology
735
     * is lost. This method allows for reconstructing the topology of the inputs,
736
     * which are flattened to one array. Given a column's index, its neighbors are
737
     * defined as those columns that are 'radius' indices away from it in each
738
     * dimension. The method returns a list of the flat indices of these columns.
739
     * 
740
     * @param c                             matrix configured to this {@code SpatialPooler}'s dimensions
741
     *                                      for transformation work.
742
     * @param columnIndex                   The index identifying a column in the permanence, potential
743
     *                                      and connectivity matrices.
744
     * @param topology                            A {@link SparseMatrix} with dimensionality info.
745
     * @param inhibitionRadius      Indicates how far away from a given column are other
746
     *                                      columns to be considered its neighbors. In the previous 2x3
747
     *                                      example, each column with coordinates:
748
     *                                      [2+/-radius, 3+/-radius] is considered a neighbor.
749
     * @param wrapAround                    A boolean value indicating whether to consider columns at
750
     *                                      the border of a dimensions to be adjacent to columns at the
751
     *                                      other end of the dimension. For example, if the columns are
752
     *                                      laid out in one dimension, columns 1 and 10 will be
753
     *                                      considered adjacent if wrapAround is set to true:
754
     *                                      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
×
755
     *               
×
756
     * @return              a list of the flat indices of these columns
×
757
     */
×
758
    public TIntArrayList getNeighborsND(Connections c, int columnIndex, SparseMatrix<?> topology, int inhibitionRadius, boolean wrapAround) {
×
759
        final int[] dimensions = topology.getDimensions();
×
760
        int[] columnCoords = topology.computeCoordinates(columnIndex);
761
        List<int[]> dimensionCoords = new ArrayList<>();
×
762
        for(int i = 0;i < dimensions.length;i++) {
×
763
            int[] range = ArrayUtils.range(columnCoords[i] - inhibitionRadius, columnCoords[i] + inhibitionRadius + 1);
×
764
            int[] curRange = new int[range.length];
765
            
766
            if(wrapAround) {
×
767
                for(int j = 0;j < curRange.length;j++) {
×
768
                    curRange[j] = (int)ArrayUtils.positiveRemainder(range[j], dimensions[i]);
769
                }
×
770
            }else{
×
771
                final int idx = i;
772
                curRange = ArrayUtils.retainLogicalAnd(range, 
×
773
                    new Condition[] {
×
774
                        new Condition.Adapter<Integer>() {
775
                            @Override public boolean eval(int n) { return n >= 0; }
776
                        },
777
                        new Condition.Adapter<Integer>() {
778
                            @Override public boolean eval(int n) { return n < dimensions[idx]; }
×
779
                        }
780
                    }
781
                );
×
782
            }
×
783
            dimensionCoords.add(ArrayUtils.unique(curRange));
×
784
        }
×
785
        
×
786
        List<int[]> neighborList = ArrayUtils.dimensionsToCoordinateList(dimensionCoords);
×
787
        TIntArrayList neighbors = new TIntArrayList(neighborList.size());
×
788
        int size = neighborList.size();
789
        for(int i = 0;i < size;i++) {
×
790
                int flatIndex = c.getInputMatrix().computeIndex(neighborList.get(i), false);
791
            if(flatIndex == columnIndex) continue;
792
            neighbors.add(flatIndex);
793
        }
794
        return neighbors;
795
    }
796
    
797
    /**
798
     * Returns true if enough rounds have passed to warrant updates of
799
     * duty cycles
800
     * 
×
801
     * @param c        the {@link Connections} memory encapsulation
802
     * @return
803
     */
804
    public boolean isUpdateRound(Connections c) {
805
            return c.getIterationNum() % c.getUpdatePeriod() == 0;
806
    }
807
    
808
    /**
809
     * Updates counter instance variables each cycle.
810
     *  
811
     * @param c         the {@link Connections} memory encapsulation
812
     * @param learn     a boolean value indicating whether learning should be
813
     *                  performed. Learning entails updating the  permanence
814
     *                  values of the synapses, and hence modifying the 'state'
×
815
     *                  of the model. setting learning to 'off' might be useful
×
816
     *                  for indicating separate training vs. testing sets.
×
817
     */
818
    public void updateBookeepingVars(Connections c, boolean learn) {
819
        c.iterationNum += 1;
820
        if(learn) c.iterationLearnNum += 1;
821
    }
822
    
823
    /**
824
     * This function determines each column's overlap with the current input
825
     * vector. The overlap of a column is the number of synapses for that column
826
     * that are connected (permanence value is greater than '_synPermConnected')
827
     * to input bits which are turned on. Overlap values that are lower than
828
     * the 'stimulusThreshold' are ignored. The implementation takes advantage of
829
     * the SpraseBinaryMatrix class to perform this calculation efficiently.
830
     *  
831
     * @param c                                the {@link Connections} memory encapsulation
832
     * @param inputVector   an input array of 0's and 1's that comprises the input to
×
833
     *                      the spatial pooler.
×
834
     * @return
×
835
     */
×
836
    public int[] calculateOverlap(Connections c, int[] inputVector) {
837
        int[] overlaps = new int[c.getNumColumns()];
838
        c.getConnectedCounts().rightVecSumAtNZ(inputVector, overlaps);
839
        ArrayUtils.lessThanXThanSetToY(overlaps, (int)c.getStimulusThreshold(), 0);
840
        return overlaps;
841
    }
842
    
843
    /**
844
     * Return the overlap to connected counts ratio for a given column
845
     * @param c
×
846
     * @param overlaps
847
     * @return
848
     */
849
    public double[] calculateOverlapPct(Connections c, int[] overlaps) {
850
            return ArrayUtils.divide(overlaps, c.getConnectedCounts().getTrueCounts());
851
    }
852
    
853
    /**
854
     * Performs inhibition. This method calculates the necessary values needed to
855
     * actually perform inhibition and then delegates the task of picking the
856
     * active columns to helper functions.
857
     * 
858
     * @param c                                the {@link Connections} matrix
859
     * @param overlaps                an array containing the overlap score for each  column.
860
     *                              The overlap score for a column is defined as the number
861
     *                              of synapses in a "connected state" (connected synapses)
×
862
     *                              that are connected to input bits which are turned on.
863
     * @return
864
     */
865
    public int[] inhibitColumns(Connections c, double[] overlaps) {
×
866
            overlaps = Arrays.copyOf(overlaps, overlaps.length);
×
867
            
×
868
            double density;
×
869
            double inhibitionArea;
×
870
            if((density = c.getLocalAreaDensity()) <= 0) {
871
                    inhibitionArea = Math.pow(2 * c.getInhibitionRadius() + 1, c.getColumnDimensions().length);
872
                    inhibitionArea = Math.min(c.getNumColumns(), inhibitionArea);
873
                    density = c.getNumActiveColumnsPerInhArea() / inhibitionArea;
×
874
                    density = Math.min(density, 0.5);
875
            }
×
876
            
×
877
            //Add our fixed little bit of random noise to the scores to help break ties.
878
            ArrayUtils.d_add(overlaps, c.getTieBreaker());
×
879
            
880
            if(c.getGlobalInhibition() || c.getInhibitionRadius() > ArrayUtils.max(c.getColumnDimensions())) {
881
                    return inhibitColumnsGlobal(c, overlaps, density);
882
            }
883
            return inhibitColumnsLocal(c, overlaps, density);
884
    }
885
    
886
    /**
887
     * Perform global inhibition. Performing global inhibition entails picking the
888
     * top 'numActive' columns with the highest overlap score in the entire
889
     * region. At most half of the columns in a local neighborhood are allowed to
890
     * be active.
891
     * 
892
     * @param c                                the {@link Connections} matrix
893
     * @param overlaps                an array containing the overlap score for each  column.
894
     *                              The overlap score for a column is defined as the number
895
     *                              of synapses in a "connected state" (connected synapses)
896
     *                              that are connected to input bits which are turned on.
897
     * @param density                The fraction of columns to survive inhibition.
×
898
     * 
×
899
     * @return
×
900
     */
×
901
    public int[] inhibitColumnsGlobal(Connections c, double[] overlaps, double density) {
×
902
            int numCols = c.getNumColumns();
903
            int numActive = (int)(density * numCols);
904
            int[] activeColumns = new int[numCols];
905
            Arrays.fill(activeColumns, 0);
906
            int[] winners = ArrayUtils.nGreatest(overlaps, numActive);
907
            Arrays.sort(winners);
908
            return winners;
909
    }
910
    
911
    /**
912
     * Performs inhibition. This method calculates the necessary values needed to
913
     * actually perform inhibition and then delegates the task of picking the
914
     * active columns to helper functions.
915
     * 
916
     * @param c                        the {@link Connections} matrix
917
     * @param overlaps        an array containing the overlap score for each  column.
×
918
     *                      The overlap score for a column is defined as the number
×
919
     *                      of synapses in a "connected state" (connected synapses)
×
920
     *                      that are connected to input bits which are turned on.
×
921
     * @return
×
922
     */
×
923
    public int[] inhibitColumnsLocal(Connections c, double[] overlaps, double density) {
×
924
            int numCols = c.getNumColumns();
×
925
            int[] activeColumns = new int[numCols];
×
926
            Arrays.fill(activeColumns, 0);
×
927
            double addToWinners = ArrayUtils.max(overlaps) / 1000.0;
×
928
            for(int i = 0;i < numCols;i++) {
929
                    TIntArrayList maskNeighbors = getNeighborsND(c, i, c.getMemory(), c.getInhibitionRadius(), false);
930
                    double[] overlapSlice = ArrayUtils.sub(overlaps, maskNeighbors.toArray());
×
931
                    int numActive = (int)(0.5 + density * (maskNeighbors.size() + 1));
932
                    int numBigger = ArrayUtils.valueGreaterCount(overlaps[i], overlapSlice);
×
933
                    if(numBigger < numActive) {
934
                            activeColumns[i] = 1;
935
                            overlaps[i] += addToWinners;
936
                    }
937
            }
938
            return ArrayUtils.where(activeColumns, new Condition.Adapter<Integer>() {
939
                    @Override public boolean eval(int n) {
940
                                return n > 0;
941
                        }
942
            });
943
    }
944
    
945
    /**
946
     * Update the boost factors for all columns. The boost factors are used to
947
     * increase the overlap of inactive columns to improve their chances of
948
     * becoming active. and hence encourage participation of more columns in the
949
     * learning process. This is a line defined as: y = mx + b boost =
950
     * (1-maxBoost)/minDuty * dutyCycle + maxFiringBoost. Intuitively this means
951
     * that columns that have been active enough have a boost factor of 1, meaning
952
     * their overlap is not boosted. Columns whose active duty cycle drops too much
953
     * below that of their neighbors are boosted depending on how infrequently they
954
     * have been active. The more infrequent, the more they are boosted. The exact
955
     * boost factor is linearly interpolated between the points (dutyCycle:0,
956
     * boost:maxFiringBoost) and (dutyCycle:minDuty, boost:1.0).
957
         * 
958
     *         boostFactor
959
     *             ^
960
     * maxBoost _  |
961
     *             |\
962
     *             | \
963
     *       1  _  |  \ _ _ _ _ _ _ _
×
964
     *             |
×
965
     *             +--------------------> activeDutyCycle
966
     *                |
967
     *         minActiveDutyCycle
×
968
     */
×
969
    public void updateBoostFactors(Connections c) {
970
            //Indexes of values > 0
971
            int[] mask = ArrayUtils.where(c.getMinActiveDutyCycles(), new Condition.Adapter<Object>() {
×
972
                    @Override public boolean eval(double d) { return d > 0; }
×
973
            });
974
 
×
975
            final double[] activeDutyCycles = c.getActiveDutyCycles();
×
976
            final double[] minActiveDutyCycles = c.getMinActiveDutyCycles();
×
977
            
×
978
            double[] boostInterim;
×
979
            if(mask.length < 1) {
980
                    boostInterim = c.getBoostFactors();
981
            }else{
×
982
                    double[] numerator = new double[c.getNumColumns()];
×
983
                    Arrays.fill(numerator, 1 - c.getMaxBoost());
×
984
                    boostInterim = ArrayUtils.divide(numerator, minActiveDutyCycles, 0, 0);
985
                    boostInterim = ArrayUtils.multiply(boostInterim, activeDutyCycles, 0, 0);
986
                    boostInterim = ArrayUtils.d_add(boostInterim, c.getMaxBoost());
×
987
            }
×
988
            
989
            ArrayUtils.setIndexesTo(boostInterim, ArrayUtils.where(activeDutyCycles, new Condition.Adapter<Object>() {
990
                    int i = 0;
991
                    @Override public boolean eval(double d) { return d > minActiveDutyCycles[i++]; }
992
            }), 1.0d);
993
            
994
            c.setBoostFactors(boostInterim);
995
    }
996
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc