/**
   * Constructor with restricted access, solely used for deserialization.
   *
   * @param wrapRowDim Whether to wrap the first dimension (i.e the first and last neurons will be
   *     linked together).
   * @param wrapColDim Whether to wrap the second dimension (i.e the first and last neurons will be
   *     linked together).
   * @param neighbourhoodType Neighbourhood type.
   * @param featuresList Arrays that will initialize the features sets of the network's neurons.
   * @throws NumberIsTooSmallException if {@code numRows < 2} or {@code numCols < 2}.
   */
  NeuronSquareMesh2D(
      boolean wrapRowDim,
      boolean wrapColDim,
      SquareNeighbourhood neighbourhoodType,
      double[][][] featuresList) {
    numberOfRows = featuresList.length;
    numberOfColumns = featuresList[0].length;

    if (numberOfRows < 2) {
      throw new NumberIsTooSmallException(numberOfRows, 2, true);
    }
    if (numberOfColumns < 2) {
      throw new NumberIsTooSmallException(numberOfColumns, 2, true);
    }

    wrapRows = wrapRowDim;
    wrapColumns = wrapColDim;
    neighbourhood = neighbourhoodType;

    final int fLen = featuresList[0][0].length;
    network = new Network(0, fLen);
    identifiers = new long[numberOfRows][numberOfColumns];

    // Add neurons.
    for (int i = 0; i < numberOfRows; i++) {
      for (int j = 0; j < numberOfColumns; j++) {
        identifiers[i][j] = network.createNeuron(featuresList[i][j]);
      }
    }

    // Add links.
    createLinks();
  }
  /**
   * Retrieves the neuron at location {@code (i, j)} in the map. The neuron at position {@code (0,
   * 0)} is located at the upper-left corner of the map.
   *
   * @param i Row index.
   * @param j Column index.
   * @return the neuron at {@code (i, j)}.
   * @throws OutOfRangeException if {@code i} or {@code j} is out of range.
   * @see #getNeuron(int,int,HorizontalDirection,VerticalDirection)
   */
  public Neuron getNeuron(int i, int j) {
    if (i < 0 || i >= numberOfRows) {
      throw new OutOfRangeException(i, 0, numberOfRows - 1);
    }
    if (j < 0 || j >= numberOfColumns) {
      throw new OutOfRangeException(j, 0, numberOfColumns - 1);
    }

    return network.getNeuron(identifiers[i][j]);
  }
  /**
   * Performs a deep copy of this instance. Upon return, the copied and original instances will be
   * independent: Updating one will not affect the other.
   *
   * @return a new instance with the same state as this instance.
   * @since 3.6
   */
  public synchronized NeuronSquareMesh2D copy() {
    final long[][] idGrid = new long[numberOfRows][numberOfColumns];
    for (int r = 0; r < numberOfRows; r++) {
      for (int c = 0; c < numberOfColumns; c++) {
        idGrid[r][c] = identifiers[r][c];
      }
    }

    return new NeuronSquareMesh2D(wrapRows, wrapColumns, neighbourhood, network.copy(), idGrid);
  }
  /**
   * Creates a two-dimensional network composed of square cells: Each neuron not located on the
   * border of the mesh has four neurons linked to it. <br>
   * The links are bi-directional. <br>
   * The topology of the network can also be a cylinder (if one of the dimensions is wrapped) or a
   * torus (if both dimensions are wrapped).
   *
   * @param numRows Number of neurons in the first dimension.
   * @param wrapRowDim Whether to wrap the first dimension (i.e the first and last neurons will be
   *     linked together).
   * @param numCols Number of neurons in the second dimension.
   * @param wrapColDim Whether to wrap the second dimension (i.e the first and last neurons will be
   *     linked together).
   * @param neighbourhoodType Neighbourhood type.
   * @param featureInit Array of functions that will initialize the corresponding element of the
   *     features set of each newly created neuron. In particular, the size of this array defines
   *     the size of feature set.
   * @throws NumberIsTooSmallException if {@code numRows < 2} or {@code numCols < 2}.
   */
  public NeuronSquareMesh2D(
      int numRows,
      boolean wrapRowDim,
      int numCols,
      boolean wrapColDim,
      SquareNeighbourhood neighbourhoodType,
      FeatureInitializer[] featureInit) {
    if (numRows < 2) {
      throw new NumberIsTooSmallException(numRows, 2, true);
    }
    if (numCols < 2) {
      throw new NumberIsTooSmallException(numCols, 2, true);
    }

    numberOfRows = numRows;
    wrapRows = wrapRowDim;
    numberOfColumns = numCols;
    wrapColumns = wrapColDim;
    neighbourhood = neighbourhoodType;
    identifiers = new long[numberOfRows][numberOfColumns];

    final int fLen = featureInit.length;
    network = new Network(0, fLen);

    // Add neurons.
    for (int i = 0; i < numRows; i++) {
      for (int j = 0; j < numCols; j++) {
        final double[] features = new double[fLen];
        for (int fIndex = 0; fIndex < fLen; fIndex++) {
          features[fIndex] = featureInit[fIndex].value();
        }
        identifiers[i][j] = network.createNeuron(features);
      }
    }

    // Add links.
    createLinks();
  }
  /** Creates the neighbour relationships between neurons. */
  private void createLinks() {
    // "linkEnd" will store the identifiers of the "neighbours".
    final List<Long> linkEnd = new ArrayList<Long>();
    final int iLast = numberOfRows - 1;
    final int jLast = numberOfColumns - 1;
    for (int i = 0; i < numberOfRows; i++) {
      for (int j = 0; j < numberOfColumns; j++) {
        linkEnd.clear();

        switch (neighbourhood) {
          case MOORE:
            // Add links to "diagonal" neighbours.
            if (i > 0) {
              if (j > 0) {
                linkEnd.add(identifiers[i - 1][j - 1]);
              }
              if (j < jLast) {
                linkEnd.add(identifiers[i - 1][j + 1]);
              }
            }
            if (i < iLast) {
              if (j > 0) {
                linkEnd.add(identifiers[i + 1][j - 1]);
              }
              if (j < jLast) {
                linkEnd.add(identifiers[i + 1][j + 1]);
              }
            }
            if (wrapRows) {
              if (i == 0) {
                if (j > 0) {
                  linkEnd.add(identifiers[iLast][j - 1]);
                }
                if (j < jLast) {
                  linkEnd.add(identifiers[iLast][j + 1]);
                }
              } else if (i == iLast) {
                if (j > 0) {
                  linkEnd.add(identifiers[0][j - 1]);
                }
                if (j < jLast) {
                  linkEnd.add(identifiers[0][j + 1]);
                }
              }
            }
            if (wrapColumns) {
              if (j == 0) {
                if (i > 0) {
                  linkEnd.add(identifiers[i - 1][jLast]);
                }
                if (i < iLast) {
                  linkEnd.add(identifiers[i + 1][jLast]);
                }
              } else if (j == jLast) {
                if (i > 0) {
                  linkEnd.add(identifiers[i - 1][0]);
                }
                if (i < iLast) {
                  linkEnd.add(identifiers[i + 1][0]);
                }
              }
            }
            if (wrapRows && wrapColumns) {
              if (i == 0 && j == 0) {
                linkEnd.add(identifiers[iLast][jLast]);
              } else if (i == 0 && j == jLast) {
                linkEnd.add(identifiers[iLast][0]);
              } else if (i == iLast && j == 0) {
                linkEnd.add(identifiers[0][jLast]);
              } else if (i == iLast && j == jLast) {
                linkEnd.add(identifiers[0][0]);
              }
            }

            // Case falls through since the "Moore" neighbourhood
            // also contains the neurons that belong to the "Von
            // Neumann" neighbourhood.

            // fallthru (CheckStyle)
          case VON_NEUMANN:
            // Links to preceding and following "row".
            if (i > 0) {
              linkEnd.add(identifiers[i - 1][j]);
            }
            if (i < iLast) {
              linkEnd.add(identifiers[i + 1][j]);
            }
            if (wrapRows) {
              if (i == 0) {
                linkEnd.add(identifiers[iLast][j]);
              } else if (i == iLast) {
                linkEnd.add(identifiers[0][j]);
              }
            }

            // Links to preceding and following "column".
            if (j > 0) {
              linkEnd.add(identifiers[i][j - 1]);
            }
            if (j < jLast) {
              linkEnd.add(identifiers[i][j + 1]);
            }
            if (wrapColumns) {
              if (j == 0) {
                linkEnd.add(identifiers[i][jLast]);
              } else if (j == jLast) {
                linkEnd.add(identifiers[i][0]);
              }
            }
            break;

          default:
            throw new MathInternalError(); // Cannot happen.
        }

        final Neuron aNeuron = network.getNeuron(identifiers[i][j]);
        for (long b : linkEnd) {
          final Neuron bNeuron = network.getNeuron(b);
          // Link to all neighbours.
          // The reverse links will be added as the loop proceeds.
          network.addLink(aNeuron, bNeuron);
        }
      }
    }
  }
 /** {@inheritDoc} */
 @Override
 public Iterator<Neuron> iterator() {
   return network.iterator();
 }