BlockFieldMatrix.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.math4.legacy.linear;

  18. import java.io.Serializable;

  19. import org.apache.commons.math4.legacy.core.Field;
  20. import org.apache.commons.math4.legacy.core.FieldElement;
  21. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  22. import org.apache.commons.math4.legacy.exception.NoDataException;
  23. import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
  24. import org.apache.commons.math4.legacy.exception.NullArgumentException;
  25. import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
  26. import org.apache.commons.math4.legacy.exception.OutOfRangeException;
  27. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  28. import org.apache.commons.math4.core.jdkmath.JdkMath;
  29. import org.apache.commons.math4.legacy.core.MathArrays;

  30. /**
  31.  * Cache-friendly implementation of FieldMatrix using a flat arrays to store
  32.  * square blocks of the matrix.
  33.  * <p>
  34.  * This implementation is specially designed to be cache-friendly. Square blocks are
  35.  * stored as small arrays and allow efficient traversal of data both in row major direction
  36.  * and columns major direction, one block at a time. This greatly increases performances
  37.  * for algorithms that use crossed directions loops like multiplication or transposition.
  38.  * </p>
  39.  * <p>
  40.  * The size of square blocks is a static parameter. It may be tuned according to the cache
  41.  * size of the target computer processor. As a rule of thumbs, it should be the largest
  42.  * value that allows three blocks to be simultaneously cached (this is necessary for example
  43.  * for matrix multiplication). The default value is to use 36x36 blocks.
  44.  * </p>
  45.  * <p>
  46.  * The regular blocks represent {@link #BLOCK_SIZE} x {@link #BLOCK_SIZE} squares. Blocks
  47.  * at right hand side and bottom side which may be smaller to fit matrix dimensions. The square
  48.  * blocks are flattened in row major order in single dimension arrays which are therefore
  49.  * {@link #BLOCK_SIZE}<sup>2</sup> elements long for regular blocks. The blocks are themselves
  50.  * organized in row major order.
  51.  * </p>
  52.  * <p>
  53.  * As an example, for a block size of 36x36, a 100x60 matrix would be stored in 6 blocks.
  54.  * Block 0 would be a Field[1296] array holding the upper left 36x36 square, block 1 would be
  55.  * a Field[1296] array holding the upper center 36x36 square, block 2 would be a Field[1008]
  56.  * array holding the upper right 36x28 rectangle, block 3 would be a Field[864] array holding
  57.  * the lower left 24x36 rectangle, block 4 would be a Field[864] array holding the lower center
  58.  * 24x36 rectangle and block 5 would be a Field[672] array holding the lower right 24x28
  59.  * rectangle.
  60.  * </p>
  61.  * <p>
  62.  * The layout complexity overhead versus simple mapping of matrices to java
  63.  * arrays is negligible for small matrices (about 1%). The gain from cache efficiency leads
  64.  * to up to 3-fold improvements for matrices of moderate to large size.
  65.  * </p>
  66.  * @param <T> the type of the field elements
  67.  * @since 2.0
  68.  */
  69. public class BlockFieldMatrix<T extends FieldElement<T>> extends AbstractFieldMatrix<T> implements Serializable {
  70.     /** Block size. */
  71.     public static final int BLOCK_SIZE = 36;
  72.     /** Serializable version identifier. */
  73.     private static final long serialVersionUID = -4602336630143123183L;
  74.     /** Blocks of matrix entries. */
  75.     private final T[][] blocks;
  76.     /** Number of rows of the matrix. */
  77.     private final int rows;
  78.     /** Number of columns of the matrix. */
  79.     private final int columns;
  80.     /** Number of block rows of the matrix. */
  81.     private final int blockRows;
  82.     /** Number of block columns of the matrix. */
  83.     private final int blockColumns;

  84.     /**
  85.      * Create a new matrix with the supplied row and column dimensions.
  86.      *
  87.      * @param field Field to which the elements belong.
  88.      * @param rows Number of rows in the new matrix.
  89.      * @param columns Number of columns in the new matrix.
  90.      * @throws NotStrictlyPositiveException if row or column dimension is not
  91.      * positive.
  92.      */
  93.     public BlockFieldMatrix(final Field<T> field, final int rows,
  94.                             final int columns)
  95.         throws NotStrictlyPositiveException {
  96.         super(field, rows, columns);
  97.         this.rows    = rows;
  98.         this.columns = columns;

  99.         // number of blocks
  100.         blockRows    = (rows    + BLOCK_SIZE - 1) / BLOCK_SIZE;
  101.         blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;

  102.         // allocate storage blocks, taking care of smaller ones at right and bottom
  103.         blocks = createBlocksLayout(field, rows, columns);
  104.     }

  105.     /**
  106.      * Create a new dense matrix copying entries from raw layout data.
  107.      * <p>The input array <em>must</em> already be in raw layout.</p>
  108.      * <p>Calling this constructor is equivalent to call:
  109.      * {@code matrix = new BlockFieldMatrix<T>(getField(), rawData.length, rawData[0].length,
  110.      *                                   toBlocksLayout(rawData), false);}
  111.      *
  112.      * @param rawData Data for the new matrix, in raw layout.
  113.      * @throws DimensionMismatchException if the {@code blockData} shape is
  114.      * inconsistent with block layout.
  115.      * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
  116.      */
  117.     public BlockFieldMatrix(final T[][] rawData)
  118.         throws DimensionMismatchException {
  119.         this(rawData.length, rawData[0].length, toBlocksLayout(rawData), false);
  120.     }

  121.     /**
  122.      * Create a new dense matrix copying entries from block layout data.
  123.      * <p>The input array <em>must</em> already be in blocks layout.</p>
  124.      * @param rows  the number of rows in the new matrix
  125.      * @param columns  the number of columns in the new matrix
  126.      * @param blockData data for new matrix
  127.      * @param copyArray if true, the input array will be copied, otherwise
  128.      * it will be referenced
  129.      *
  130.      * @throws DimensionMismatchException if the {@code blockData} shape is
  131.      * inconsistent with block layout.
  132.      * @throws NotStrictlyPositiveException if row or column dimension is not
  133.      * positive.
  134.      * @see #createBlocksLayout(Field, int, int)
  135.      * @see #toBlocksLayout(FieldElement[][])
  136.      * @see #BlockFieldMatrix(FieldElement[][])
  137.      */
  138.     public BlockFieldMatrix(final int rows, final int columns,
  139.                             final T[][] blockData, final boolean copyArray)
  140.         throws DimensionMismatchException, NotStrictlyPositiveException {
  141.         super(extractField(blockData), rows, columns);
  142.         this.rows    = rows;
  143.         this.columns = columns;

  144.         // number of blocks
  145.         blockRows    = (rows    + BLOCK_SIZE - 1) / BLOCK_SIZE;
  146.         blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;

  147.         if (copyArray) {
  148.             // allocate storage blocks, taking care of smaller ones at right and bottom
  149.             blocks = MathArrays.buildArray(getField(), blockRows * blockColumns, -1);
  150.         } else {
  151.             // reference existing array
  152.             blocks = blockData;
  153.         }

  154.         int index = 0;
  155.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  156.             final int iHeight = blockHeight(iBlock);
  157.             for (int jBlock = 0; jBlock < blockColumns; ++jBlock, ++index) {
  158.                 if (blockData[index].length != iHeight * blockWidth(jBlock)) {
  159.                     throw new DimensionMismatchException(blockData[index].length,
  160.                                                          iHeight * blockWidth(jBlock));
  161.                 }
  162.                 if (copyArray) {
  163.                     blocks[index] = blockData[index].clone();
  164.                 }
  165.             }
  166.         }
  167.     }

  168.     /**
  169.      * Convert a data array from raw layout to blocks layout.
  170.      * <p>
  171.      * Raw layout is the straightforward layout where element at row i and
  172.      * column j is in array element <code>rawData[i][j]</code>. Blocks layout
  173.      * is the layout used in {@link BlockFieldMatrix} instances, where the matrix
  174.      * is split in square blocks (except at right and bottom side where blocks may
  175.      * be rectangular to fit matrix size) and each block is stored in a flattened
  176.      * one-dimensional array.
  177.      * </p>
  178.      * <p>
  179.      * This method creates an array in blocks layout from an input array in raw layout.
  180.      * It can be used to provide the array argument of the {@link
  181.      * #BlockFieldMatrix(int, int, FieldElement[][], boolean)}
  182.      * constructor.
  183.      * </p>
  184.      * @param <T> Type of the field elements.
  185.      * @param rawData Data array in raw layout.
  186.      * @return a new data array containing the same entries but in blocks layout
  187.      * @throws DimensionMismatchException if {@code rawData} is not rectangular
  188.      *  (not all rows have the same length).
  189.      * @see #createBlocksLayout(Field, int, int)
  190.      * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
  191.      */
  192.     public static <T extends FieldElement<T>> T[][] toBlocksLayout(final T[][] rawData)
  193.         throws DimensionMismatchException {

  194.         final int rows         = rawData.length;
  195.         final int columns      = rawData[0].length;
  196.         final int blockRows    = (rows    + BLOCK_SIZE - 1) / BLOCK_SIZE;
  197.         final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;

  198.         // safety checks
  199.         for (int i = 0; i < rawData.length; ++i) {
  200.             final int length = rawData[i].length;
  201.             if (length != columns) {
  202.                 throw new DimensionMismatchException(columns, length);
  203.             }
  204.         }

  205.         // convert array
  206.         final Field<T> field = extractField(rawData);
  207.         final T[][] blocks = MathArrays.buildArray(field, blockRows * blockColumns, -1);
  208.         int blockIndex = 0;
  209.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  210.             final int pStart  = iBlock * BLOCK_SIZE;
  211.             final int pEnd    = JdkMath.min(pStart + BLOCK_SIZE, rows);
  212.             final int iHeight = pEnd - pStart;
  213.             for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  214.                 final int qStart = jBlock * BLOCK_SIZE;
  215.                 final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  216.                 final int jWidth = qEnd - qStart;

  217.                 // allocate new block
  218.                 final T[] block = MathArrays.buildArray(field, iHeight * jWidth);
  219.                 blocks[blockIndex] = block;

  220.                 // copy data
  221.                 int index = 0;
  222.                 for (int p = pStart; p < pEnd; ++p) {
  223.                     System.arraycopy(rawData[p], qStart, block, index, jWidth);
  224.                     index += jWidth;
  225.                 }

  226.                 ++blockIndex;
  227.             }
  228.         }

  229.         return blocks;
  230.     }

  231.     /**
  232.      * Create a data array in blocks layout.
  233.      * <p>
  234.      * This method can be used to create the array argument of the {@link
  235.      * #BlockFieldMatrix(int, int, FieldElement[][], boolean)}
  236.      * constructor.
  237.      * </p>
  238.      * @param <T> Type of the field elements.
  239.      * @param field Field to which the elements belong.
  240.      * @param rows Number of rows in the new matrix.
  241.      * @param columns Number of columns in the new matrix.
  242.      * @return a new data array in blocks layout.
  243.      * @see #toBlocksLayout(FieldElement[][])
  244.      * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
  245.      */
  246.     public static <T extends FieldElement<T>> T[][] createBlocksLayout(final Field<T> field,
  247.                                                                        final int rows, final int columns) {
  248.         final int blockRows    = (rows    + BLOCK_SIZE - 1) / BLOCK_SIZE;
  249.         final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;

  250.         final T[][] blocks = MathArrays.buildArray(field, blockRows * blockColumns, -1);
  251.         int blockIndex = 0;
  252.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  253.             final int pStart  = iBlock * BLOCK_SIZE;
  254.             final int pEnd    = JdkMath.min(pStart + BLOCK_SIZE, rows);
  255.             final int iHeight = pEnd - pStart;
  256.             for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  257.                 final int qStart = jBlock * BLOCK_SIZE;
  258.                 final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  259.                 final int jWidth = qEnd - qStart;
  260.                 blocks[blockIndex] = MathArrays.buildArray(field, iHeight * jWidth);
  261.                 ++blockIndex;
  262.             }
  263.         }

  264.         return blocks;
  265.     }

  266.     /** {@inheritDoc} */
  267.     @Override
  268.     public FieldMatrix<T> createMatrix(final int rowDimension,
  269.                                        final int columnDimension)
  270.         throws NotStrictlyPositiveException {
  271.         return new BlockFieldMatrix<>(getField(), rowDimension,
  272.                                        columnDimension);
  273.     }

  274.     /** {@inheritDoc} */
  275.     @Override
  276.     public FieldMatrix<T> copy() {

  277.         // create an empty matrix
  278.         BlockFieldMatrix<T> copied = new BlockFieldMatrix<>(getField(), rows, columns);

  279.         // copy the blocks
  280.         for (int i = 0; i < blocks.length; ++i) {
  281.             System.arraycopy(blocks[i], 0, copied.blocks[i], 0, blocks[i].length);
  282.         }

  283.         return copied;
  284.     }

  285.     /** {@inheritDoc} */
  286.     @Override
  287.     public FieldMatrix<T> add(final FieldMatrix<T> m)
  288.         throws MatrixDimensionMismatchException {
  289.         if (m instanceof BlockFieldMatrix) {
  290.             return add((BlockFieldMatrix<T>) m);
  291.         }

  292.         // safety check
  293.         checkAdd(m);

  294.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, columns);

  295.         // perform addition block-wise, to ensure good cache behavior
  296.         int blockIndex = 0;
  297.         for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
  298.             for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {

  299.                 // perform addition on the current block
  300.                 final T[] outBlock = out.blocks[blockIndex];
  301.                 final T[] tBlock   = blocks[blockIndex];
  302.                 final int      pStart   = iBlock * BLOCK_SIZE;
  303.                 final int      pEnd     = JdkMath.min(pStart + BLOCK_SIZE, rows);
  304.                 final int      qStart   = jBlock * BLOCK_SIZE;
  305.                 final int      qEnd     = JdkMath.min(qStart + BLOCK_SIZE, columns);
  306.                 int k = 0;
  307.                 for (int p = pStart; p < pEnd; ++p) {
  308.                     for (int q = qStart; q < qEnd; ++q) {
  309.                         outBlock[k] = tBlock[k].add(m.getEntry(p, q));
  310.                         ++k;
  311.                     }
  312.                 }

  313.                 // go to next block
  314.                 ++blockIndex;
  315.             }
  316.         }

  317.         return out;
  318.     }

  319.     /**
  320.      * Compute the sum of {@code this} and {@code m}.
  321.      *
  322.      * @param m matrix to be added
  323.      * @return {@code this + m}
  324.      * @throws MatrixDimensionMismatchException if {@code m} is not the same
  325.      * size as {@code this}
  326.      */
  327.     public BlockFieldMatrix<T> add(final BlockFieldMatrix<T> m)
  328.         throws MatrixDimensionMismatchException {

  329.         // safety check
  330.         checkAdd(m);

  331.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, columns);

  332.         // perform addition block-wise, to ensure good cache behavior
  333.         for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
  334.             final T[] outBlock = out.blocks[blockIndex];
  335.             final T[] tBlock   = blocks[blockIndex];
  336.             final T[] mBlock   = m.blocks[blockIndex];
  337.             for (int k = 0; k < outBlock.length; ++k) {
  338.                 outBlock[k] = tBlock[k].add(mBlock[k]);
  339.             }
  340.         }

  341.         return out;
  342.     }

  343.     /** {@inheritDoc} */
  344.     @Override
  345.     public FieldMatrix<T> subtract(final FieldMatrix<T> m)
  346.         throws MatrixDimensionMismatchException {
  347.         if (m instanceof BlockFieldMatrix) {
  348.             return subtract((BlockFieldMatrix<T>) m);
  349.         }

  350.         // safety check
  351.         checkAdd(m);

  352.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, columns);

  353.         // perform subtraction block-wise, to ensure good cache behavior
  354.         int blockIndex = 0;
  355.         for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
  356.             for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {

  357.                 // perform subtraction on the current block
  358.                 final T[] outBlock = out.blocks[blockIndex];
  359.                 final T[] tBlock   = blocks[blockIndex];
  360.                 final int      pStart   = iBlock * BLOCK_SIZE;
  361.                 final int      pEnd     = JdkMath.min(pStart + BLOCK_SIZE, rows);
  362.                 final int      qStart   = jBlock * BLOCK_SIZE;
  363.                 final int      qEnd     = JdkMath.min(qStart + BLOCK_SIZE, columns);
  364.                 int k = 0;
  365.                 for (int p = pStart; p < pEnd; ++p) {
  366.                     for (int q = qStart; q < qEnd; ++q) {
  367.                         outBlock[k] = tBlock[k].subtract(m.getEntry(p, q));
  368.                         ++k;
  369.                     }
  370.                 }

  371.                 // go to next block
  372.                 ++blockIndex;
  373.             }
  374.         }

  375.         return out;
  376.     }

  377.     /**
  378.      * Compute {@code this - m}.
  379.      *
  380.      * @param m matrix to be subtracted
  381.      * @return {@code this - m}
  382.      * @throws MatrixDimensionMismatchException if {@code m} is not the same
  383.      * size as {@code this}
  384.      */
  385.     public BlockFieldMatrix<T> subtract(final BlockFieldMatrix<T> m) throws MatrixDimensionMismatchException {
  386.         // safety check
  387.         checkAdd(m);

  388.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, columns);

  389.         // perform subtraction block-wise, to ensure good cache behavior
  390.         for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
  391.             final T[] outBlock = out.blocks[blockIndex];
  392.             final T[] tBlock   = blocks[blockIndex];
  393.             final T[] mBlock   = m.blocks[blockIndex];
  394.             for (int k = 0; k < outBlock.length; ++k) {
  395.                 outBlock[k] = tBlock[k].subtract(mBlock[k]);
  396.             }
  397.         }

  398.         return out;
  399.     }

  400.     /** {@inheritDoc} */
  401.     @Override
  402.     public FieldMatrix<T> scalarAdd(final T d) {
  403.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, columns);

  404.         // perform subtraction block-wise, to ensure good cache behavior
  405.         for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
  406.             final T[] outBlock = out.blocks[blockIndex];
  407.             final T[] tBlock   = blocks[blockIndex];
  408.             for (int k = 0; k < outBlock.length; ++k) {
  409.                 outBlock[k] = tBlock[k].add(d);
  410.             }
  411.         }

  412.         return out;
  413.     }

  414.     /** {@inheritDoc} */
  415.     @Override
  416.     public FieldMatrix<T> scalarMultiply(final T d) {

  417.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, columns);

  418.         // perform subtraction block-wise, to ensure good cache behavior
  419.         for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
  420.             final T[] outBlock = out.blocks[blockIndex];
  421.             final T[] tBlock   = blocks[blockIndex];
  422.             for (int k = 0; k < outBlock.length; ++k) {
  423.                 outBlock[k] = tBlock[k].multiply(d);
  424.             }
  425.         }

  426.         return out;
  427.     }

  428.     /** {@inheritDoc} */
  429.     @Override
  430.     public FieldMatrix<T> multiply(final FieldMatrix<T> m)
  431.         throws DimensionMismatchException {
  432.         if (m instanceof BlockFieldMatrix) {
  433.             return multiply((BlockFieldMatrix<T>) m);
  434.         }

  435.         // safety check
  436.         checkMultiply(m);

  437.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, m.getColumnDimension());
  438.         final T zero = getField().getZero();

  439.         // perform multiplication block-wise, to ensure good cache behavior
  440.         int blockIndex = 0;
  441.         for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {

  442.             final int pStart = iBlock * BLOCK_SIZE;
  443.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);

  444.             for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {

  445.                 final int qStart = jBlock * BLOCK_SIZE;
  446.                 final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, m.getColumnDimension());

  447.                 // select current block
  448.                 final T[] outBlock = out.blocks[blockIndex];

  449.                 // perform multiplication on current block
  450.                 for (int kBlock = 0; kBlock < blockColumns; ++kBlock) {
  451.                     final int kWidth      = blockWidth(kBlock);
  452.                     final T[] tBlock = blocks[iBlock * blockColumns + kBlock];
  453.                     final int rStart      = kBlock * BLOCK_SIZE;
  454.                     int k = 0;
  455.                     for (int p = pStart; p < pEnd; ++p) {
  456.                         final int lStart = (p - pStart) * kWidth;
  457.                         final int lEnd   = lStart + kWidth;
  458.                         for (int q = qStart; q < qEnd; ++q) {
  459.                             T sum = zero;
  460.                             int r = rStart;
  461.                             for (int l = lStart; l < lEnd; ++l) {
  462.                                 sum = sum.add(tBlock[l].multiply(m.getEntry(r, q)));
  463.                                 ++r;
  464.                             }
  465.                             outBlock[k] = outBlock[k].add(sum);
  466.                             ++k;
  467.                         }
  468.                     }
  469.                 }

  470.                 // go to next block
  471.                 ++blockIndex;
  472.             }
  473.         }

  474.         return out;
  475.     }

  476.     /**
  477.      * Returns the result of postmultiplying {@code this} by {@code m}.
  478.      *
  479.      * @param m matrix to postmultiply by
  480.      * @return {@code this * m}
  481.      * @throws DimensionMismatchException if the matrices are not compatible.
  482.      */
  483.     public BlockFieldMatrix<T> multiply(BlockFieldMatrix<T> m)
  484.         throws DimensionMismatchException {

  485.         // safety check
  486.         checkMultiply(m);

  487.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, m.columns);
  488.         final T zero = getField().getZero();

  489.         // perform multiplication block-wise, to ensure good cache behavior
  490.         int blockIndex = 0;
  491.         for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {

  492.             final int pStart = iBlock * BLOCK_SIZE;
  493.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);

  494.             for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
  495.                 final int jWidth = out.blockWidth(jBlock);
  496.                 final int jWidth2 = jWidth  + jWidth;
  497.                 final int jWidth3 = jWidth2 + jWidth;
  498.                 final int jWidth4 = jWidth3 + jWidth;

  499.                 // select current block
  500.                 final T[] outBlock = out.blocks[blockIndex];

  501.                 // perform multiplication on current block
  502.                 for (int kBlock = 0; kBlock < blockColumns; ++kBlock) {
  503.                     final int kWidth = blockWidth(kBlock);
  504.                     final T[] tBlock = blocks[iBlock * blockColumns + kBlock];
  505.                     final T[] mBlock = m.blocks[kBlock * m.blockColumns + jBlock];
  506.                     int k = 0;
  507.                     for (int p = pStart; p < pEnd; ++p) {
  508.                         final int lStart = (p - pStart) * kWidth;
  509.                         final int lEnd   = lStart + kWidth;
  510.                         for (int nStart = 0; nStart < jWidth; ++nStart) {
  511.                             T sum = zero;
  512.                             int l = lStart;
  513.                             int n = nStart;
  514.                             while (l < lEnd - 3) {
  515.                                 sum = sum.
  516.                                       add(tBlock[l].multiply(mBlock[n])).
  517.                                       add(tBlock[l + 1].multiply(mBlock[n + jWidth])).
  518.                                       add(tBlock[l + 2].multiply(mBlock[n + jWidth2])).
  519.                                       add(tBlock[l + 3].multiply(mBlock[n + jWidth3]));
  520.                                 l += 4;
  521.                                 n += jWidth4;
  522.                             }
  523.                             while (l < lEnd) {
  524.                                 sum = sum.add(tBlock[l++].multiply(mBlock[n]));
  525.                                 n += jWidth;
  526.                             }
  527.                             outBlock[k] = outBlock[k].add(sum);
  528.                             ++k;
  529.                         }
  530.                     }
  531.                 }

  532.                 // go to next block
  533.                 ++blockIndex;
  534.             }
  535.         }

  536.         return out;
  537.     }

  538.     /** {@inheritDoc} */
  539.     @Override
  540.     public T[][] getData() {

  541.         final T[][] data = MathArrays.buildArray(getField(), getRowDimension(), getColumnDimension());
  542.         final int lastColumns = columns - (blockColumns - 1) * BLOCK_SIZE;

  543.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  544.             final int pStart = iBlock * BLOCK_SIZE;
  545.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  546.             int regularPos   = 0;
  547.             int lastPos      = 0;
  548.             for (int p = pStart; p < pEnd; ++p) {
  549.                 final T[] dataP = data[p];
  550.                 int blockIndex = iBlock * blockColumns;
  551.                 int dataPos    = 0;
  552.                 for (int jBlock = 0; jBlock < blockColumns - 1; ++jBlock) {
  553.                     System.arraycopy(blocks[blockIndex++], regularPos, dataP, dataPos, BLOCK_SIZE);
  554.                     dataPos += BLOCK_SIZE;
  555.                 }
  556.                 System.arraycopy(blocks[blockIndex], lastPos, dataP, dataPos, lastColumns);
  557.                 regularPos += BLOCK_SIZE;
  558.                 lastPos    += lastColumns;
  559.             }
  560.         }

  561.         return data;
  562.     }

  563.     /** {@inheritDoc} */
  564.     @Override
  565.     public FieldMatrix<T> getSubMatrix(final int startRow, final int endRow,
  566.                                        final int startColumn,
  567.                                        final int endColumn)
  568.         throws OutOfRangeException, NumberIsTooSmallException {
  569.         // safety checks
  570.         checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);

  571.         // create the output matrix
  572.         final BlockFieldMatrix<T> out =
  573.             new BlockFieldMatrix<>(getField(), endRow - startRow + 1, endColumn - startColumn + 1);

  574.         // compute blocks shifts
  575.         final int blockStartRow    = startRow    / BLOCK_SIZE;
  576.         final int rowsShift        = startRow    % BLOCK_SIZE;
  577.         final int blockStartColumn = startColumn / BLOCK_SIZE;
  578.         final int columnsShift     = startColumn % BLOCK_SIZE;

  579.         // perform extraction block-wise, to ensure good cache behavior
  580.         int pBlock = blockStartRow;
  581.         for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
  582.             final int iHeight = out.blockHeight(iBlock);
  583.             int qBlock = blockStartColumn;
  584.             for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
  585.                 final int jWidth = out.blockWidth(jBlock);

  586.                 // handle one block of the output matrix
  587.                 final int      outIndex = iBlock * out.blockColumns + jBlock;
  588.                 final T[] outBlock = out.blocks[outIndex];
  589.                 final int      index    = pBlock * blockColumns + qBlock;
  590.                 final int      width    = blockWidth(qBlock);

  591.                 final int heightExcess = iHeight + rowsShift - BLOCK_SIZE;
  592.                 final int widthExcess  = jWidth + columnsShift - BLOCK_SIZE;
  593.                 if (heightExcess > 0) {
  594.                     // the submatrix block spans on two blocks rows from the original matrix
  595.                     if (widthExcess > 0) {
  596.                         // the submatrix block spans on two blocks columns from the original matrix
  597.                         final int width2 = blockWidth(qBlock + 1);
  598.                         copyBlockPart(blocks[index], width,
  599.                                       rowsShift, BLOCK_SIZE,
  600.                                       columnsShift, BLOCK_SIZE,
  601.                                       outBlock, jWidth, 0, 0);
  602.                         copyBlockPart(blocks[index + 1], width2,
  603.                                       rowsShift, BLOCK_SIZE,
  604.                                       0, widthExcess,
  605.                                       outBlock, jWidth, 0, jWidth - widthExcess);
  606.                         copyBlockPart(blocks[index + blockColumns], width,
  607.                                       0, heightExcess,
  608.                                       columnsShift, BLOCK_SIZE,
  609.                                       outBlock, jWidth, iHeight - heightExcess, 0);
  610.                         copyBlockPart(blocks[index + blockColumns + 1], width2,
  611.                                       0, heightExcess,
  612.                                       0, widthExcess,
  613.                                       outBlock, jWidth, iHeight - heightExcess, jWidth - widthExcess);
  614.                     } else {
  615.                         // the submatrix block spans on one block column from the original matrix
  616.                         copyBlockPart(blocks[index], width,
  617.                                       rowsShift, BLOCK_SIZE,
  618.                                       columnsShift, jWidth + columnsShift,
  619.                                       outBlock, jWidth, 0, 0);
  620.                         copyBlockPart(blocks[index + blockColumns], width,
  621.                                       0, heightExcess,
  622.                                       columnsShift, jWidth + columnsShift,
  623.                                       outBlock, jWidth, iHeight - heightExcess, 0);
  624.                     }
  625.                 } else {
  626.                     // the submatrix block spans on one block row from the original matrix
  627.                     if (widthExcess > 0) {
  628.                         // the submatrix block spans on two blocks columns from the original matrix
  629.                         final int width2 = blockWidth(qBlock + 1);
  630.                         copyBlockPart(blocks[index], width,
  631.                                       rowsShift, iHeight + rowsShift,
  632.                                       columnsShift, BLOCK_SIZE,
  633.                                       outBlock, jWidth, 0, 0);
  634.                         copyBlockPart(blocks[index + 1], width2,
  635.                                       rowsShift, iHeight + rowsShift,
  636.                                       0, widthExcess,
  637.                                       outBlock, jWidth, 0, jWidth - widthExcess);
  638.                     } else {
  639.                         // the submatrix block spans on one block column from the original matrix
  640.                         copyBlockPart(blocks[index], width,
  641.                                       rowsShift, iHeight + rowsShift,
  642.                                       columnsShift, jWidth + columnsShift,
  643.                                       outBlock, jWidth, 0, 0);
  644.                     }
  645.                }
  646.                 ++qBlock;
  647.             }
  648.             ++pBlock;
  649.         }

  650.         return out;
  651.     }

  652.     /**
  653.      * Copy a part of a block into another one
  654.      * <p>This method can be called only when the specified part fits in both
  655.      * blocks, no verification is done here.</p>
  656.      * @param srcBlock source block
  657.      * @param srcWidth source block width ({@link #BLOCK_SIZE} or smaller)
  658.      * @param srcStartRow start row in the source block
  659.      * @param srcEndRow end row (exclusive) in the source block
  660.      * @param srcStartColumn start column in the source block
  661.      * @param srcEndColumn end column (exclusive) in the source block
  662.      * @param dstBlock destination block
  663.      * @param dstWidth destination block width ({@link #BLOCK_SIZE} or smaller)
  664.      * @param dstStartRow start row in the destination block
  665.      * @param dstStartColumn start column in the destination block
  666.      */
  667.     private void copyBlockPart(final T[] srcBlock, final int srcWidth,
  668.                                final int srcStartRow, final int srcEndRow,
  669.                                final int srcStartColumn, final int srcEndColumn,
  670.                                final T[] dstBlock, final int dstWidth,
  671.                                final int dstStartRow, final int dstStartColumn) {
  672.         final int length = srcEndColumn - srcStartColumn;
  673.         int srcPos = srcStartRow * srcWidth + srcStartColumn;
  674.         int dstPos = dstStartRow * dstWidth + dstStartColumn;
  675.         for (int srcRow = srcStartRow; srcRow < srcEndRow; ++srcRow) {
  676.             System.arraycopy(srcBlock, srcPos, dstBlock, dstPos, length);
  677.             srcPos += srcWidth;
  678.             dstPos += dstWidth;
  679.         }
  680.     }

  681.     /** {@inheritDoc} */
  682.     @Override
  683.     public void setSubMatrix(final T[][] subMatrix, final int row,
  684.                              final int column)
  685.         throws DimensionMismatchException, OutOfRangeException,
  686.         NoDataException, NullArgumentException {
  687.         // safety checks
  688.         NullArgumentException.check(subMatrix);
  689.         final int refLength = subMatrix[0].length;
  690.         if (refLength == 0) {
  691.             throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
  692.         }
  693.         final int endRow    = row + subMatrix.length - 1;
  694.         final int endColumn = column + refLength - 1;
  695.         checkSubMatrixIndex(row, endRow, column, endColumn);
  696.         for (final T[] subRow : subMatrix) {
  697.             if (subRow.length != refLength) {
  698.                 throw new DimensionMismatchException(refLength, subRow.length);
  699.             }
  700.         }

  701.         // compute blocks bounds
  702.         final int blockStartRow    = row / BLOCK_SIZE;
  703.         final int blockEndRow      = (endRow + BLOCK_SIZE) / BLOCK_SIZE;
  704.         final int blockStartColumn = column / BLOCK_SIZE;
  705.         final int blockEndColumn   = (endColumn + BLOCK_SIZE) / BLOCK_SIZE;

  706.         // perform copy block-wise, to ensure good cache behavior
  707.         for (int iBlock = blockStartRow; iBlock < blockEndRow; ++iBlock) {
  708.             final int iHeight  = blockHeight(iBlock);
  709.             final int firstRow = iBlock * BLOCK_SIZE;
  710.             final int iStart   = JdkMath.max(row,    firstRow);
  711.             final int iEnd     = JdkMath.min(endRow + 1, firstRow + iHeight);

  712.             for (int jBlock = blockStartColumn; jBlock < blockEndColumn; ++jBlock) {
  713.                 final int jWidth      = blockWidth(jBlock);
  714.                 final int firstColumn = jBlock * BLOCK_SIZE;
  715.                 final int jStart      = JdkMath.max(column,    firstColumn);
  716.                 final int jEnd        = JdkMath.min(endColumn + 1, firstColumn + jWidth);
  717.                 final int jLength     = jEnd - jStart;

  718.                 // handle one block, row by row
  719.                 final T[] block = blocks[iBlock * blockColumns + jBlock];
  720.                 for (int i = iStart; i < iEnd; ++i) {
  721.                     System.arraycopy(subMatrix[i - row], jStart - column,
  722.                                      block, (i - firstRow) * jWidth + (jStart - firstColumn),
  723.                                      jLength);
  724.                 }
  725.             }
  726.         }
  727.     }

  728.     /** {@inheritDoc} */
  729.     @Override
  730.     public FieldMatrix<T> getRowMatrix(final int row)
  731.         throws OutOfRangeException {
  732.         checkRowIndex(row);
  733.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), 1, columns);

  734.         // perform copy block-wise, to ensure good cache behavior
  735.         final int iBlock  = row / BLOCK_SIZE;
  736.         final int iRow    = row - iBlock * BLOCK_SIZE;
  737.         int outBlockIndex = 0;
  738.         int outIndex      = 0;
  739.         T[] outBlock = out.blocks[outBlockIndex];
  740.         for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  741.             final int jWidth     = blockWidth(jBlock);
  742.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  743.             final int available  = outBlock.length - outIndex;
  744.             if (jWidth > available) {
  745.                 System.arraycopy(block, iRow * jWidth, outBlock, outIndex, available);
  746.                 outBlock = out.blocks[++outBlockIndex];
  747.                 System.arraycopy(block, iRow * jWidth, outBlock, 0, jWidth - available);
  748.                 outIndex = jWidth - available;
  749.             } else {
  750.                 System.arraycopy(block, iRow * jWidth, outBlock, outIndex, jWidth);
  751.                 outIndex += jWidth;
  752.             }
  753.         }

  754.         return out;
  755.     }

  756.     /** {@inheritDoc} */
  757.     @Override
  758.     public void setRowMatrix(final int row, final FieldMatrix<T> matrix)
  759.         throws MatrixDimensionMismatchException, OutOfRangeException {
  760.         if (matrix instanceof BlockFieldMatrix) {
  761.             setRowMatrix(row, (BlockFieldMatrix<T>) matrix);
  762.         } else {
  763.             super.setRowMatrix(row, matrix);
  764.         }
  765.     }

  766.     /**
  767.      * Sets the entries in row number <code>row</code>
  768.      * as a row matrix.  Row indices start at 0.
  769.      *
  770.      * @param row the row to be set
  771.      * @param matrix row matrix (must have one row and the same number of columns
  772.      * as the instance)
  773.      * @throws MatrixDimensionMismatchException if the matrix dimensions do
  774.      * not match one instance row.
  775.      * @throws OutOfRangeException if the specified row index is invalid.
  776.      */
  777.     public void setRowMatrix(final int row, final BlockFieldMatrix<T> matrix)
  778.         throws MatrixDimensionMismatchException, OutOfRangeException {
  779.         checkRowIndex(row);
  780.         final int nCols = getColumnDimension();
  781.         if (matrix.getRowDimension() != 1 ||
  782.             matrix.getColumnDimension() != nCols) {
  783.             throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
  784.                                                        matrix.getColumnDimension(),
  785.                                                        1, nCols);
  786.         }

  787.         // perform copy block-wise, to ensure good cache behavior
  788.         final int iBlock = row / BLOCK_SIZE;
  789.         final int iRow   = row - iBlock * BLOCK_SIZE;
  790.         int mBlockIndex  = 0;
  791.         int mIndex       = 0;
  792.         T[] mBlock  = matrix.blocks[mBlockIndex];
  793.         for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  794.             final int jWidth     = blockWidth(jBlock);
  795.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  796.             final int available  = mBlock.length - mIndex;
  797.             if (jWidth > available) {
  798.                 System.arraycopy(mBlock, mIndex, block, iRow * jWidth, available);
  799.                 mBlock = matrix.blocks[++mBlockIndex];
  800.                 System.arraycopy(mBlock, 0, block, iRow * jWidth, jWidth - available);
  801.                 mIndex = jWidth - available;
  802.             } else {
  803.                 System.arraycopy(mBlock, mIndex, block, iRow * jWidth, jWidth);
  804.                 mIndex += jWidth;
  805.            }
  806.         }
  807.     }

  808.     /** {@inheritDoc} */
  809.     @Override
  810.     public FieldMatrix<T> getColumnMatrix(final int column)
  811.         throws OutOfRangeException {
  812.         checkColumnIndex(column);
  813.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), rows, 1);

  814.         // perform copy block-wise, to ensure good cache behavior
  815.         final int jBlock  = column / BLOCK_SIZE;
  816.         final int jColumn = column - jBlock * BLOCK_SIZE;
  817.         final int jWidth  = blockWidth(jBlock);
  818.         int outBlockIndex = 0;
  819.         int outIndex      = 0;
  820.         T[] outBlock = out.blocks[outBlockIndex];
  821.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  822.             final int iHeight = blockHeight(iBlock);
  823.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  824.             for (int i = 0; i < iHeight; ++i) {
  825.                 if (outIndex >= outBlock.length) {
  826.                     outBlock = out.blocks[++outBlockIndex];
  827.                     outIndex = 0;
  828.                 }
  829.                 outBlock[outIndex++] = block[i * jWidth + jColumn];
  830.             }
  831.         }

  832.         return out;
  833.     }

  834.     /** {@inheritDoc} */
  835.     @Override
  836.     public void setColumnMatrix(final int column, final FieldMatrix<T> matrix)
  837.         throws MatrixDimensionMismatchException, OutOfRangeException {
  838.         if (matrix instanceof BlockFieldMatrix) {
  839.             setColumnMatrix(column, (BlockFieldMatrix<T>) matrix);
  840.         } else {
  841.             super.setColumnMatrix(column, matrix);
  842.         }
  843.     }

  844.     /**
  845.      * Sets the entries in column number {@code column}
  846.      * as a column matrix.  Column indices start at 0.
  847.      *
  848.      * @param column Column to be set.
  849.      * @param matrix Column matrix (must have one column and the same number of rows
  850.      * as the instance).
  851.      * @throws MatrixDimensionMismatchException if the matrix dimensions do
  852.      * not match one instance column.
  853.      * @throws OutOfRangeException if the specified column index is invalid.
  854.      */
  855.     void setColumnMatrix(final int column, final BlockFieldMatrix<T> matrix)
  856.         throws MatrixDimensionMismatchException, OutOfRangeException {
  857.         checkColumnIndex(column);
  858.         final int nRows = getRowDimension();
  859.         if (matrix.getRowDimension() != nRows ||
  860.             matrix.getColumnDimension() != 1) {
  861.             throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
  862.                                                        matrix.getColumnDimension(),
  863.                                                        nRows, 1);
  864.         }

  865.         // perform copy block-wise, to ensure good cache behavior
  866.         final int jBlock  = column / BLOCK_SIZE;
  867.         final int jColumn = column - jBlock * BLOCK_SIZE;
  868.         final int jWidth  = blockWidth(jBlock);
  869.         int mBlockIndex = 0;
  870.         int mIndex      = 0;
  871.         T[] mBlock = matrix.blocks[mBlockIndex];
  872.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  873.             final int iHeight = blockHeight(iBlock);
  874.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  875.             for (int i = 0; i < iHeight; ++i) {
  876.                 if (mIndex >= mBlock.length) {
  877.                     mBlock = matrix.blocks[++mBlockIndex];
  878.                     mIndex = 0;
  879.                 }
  880.                 block[i * jWidth + jColumn] = mBlock[mIndex++];
  881.             }
  882.         }
  883.     }

  884.     /** {@inheritDoc} */
  885.     @Override
  886.     public FieldVector<T> getRowVector(final int row)
  887.         throws OutOfRangeException {
  888.         checkRowIndex(row);
  889.         final T[] outData = MathArrays.buildArray(getField(), columns);

  890.         // perform copy block-wise, to ensure good cache behavior
  891.         final int iBlock  = row / BLOCK_SIZE;
  892.         final int iRow    = row - iBlock * BLOCK_SIZE;
  893.         int outIndex      = 0;
  894.         for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  895.             final int jWidth     = blockWidth(jBlock);
  896.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  897.             System.arraycopy(block, iRow * jWidth, outData, outIndex, jWidth);
  898.             outIndex += jWidth;
  899.         }

  900.         return new ArrayFieldVector<>(getField(), outData, false);
  901.     }

  902.     /** {@inheritDoc} */
  903.     @Override
  904.     public void setRowVector(final int row, final FieldVector<T> vector)
  905.         throws MatrixDimensionMismatchException, OutOfRangeException {
  906.         if (vector instanceof ArrayFieldVector) {
  907.             setRow(row, ((ArrayFieldVector<T>) vector).getDataRef());
  908.         } else {
  909.             super.setRowVector(row, vector);
  910.         }
  911.     }

  912.     /** {@inheritDoc} */
  913.     @Override
  914.     public FieldVector<T> getColumnVector(final int column)
  915.         throws OutOfRangeException {
  916.         checkColumnIndex(column);
  917.         final T[] outData = MathArrays.buildArray(getField(), rows);

  918.         // perform copy block-wise, to ensure good cache behavior
  919.         final int jBlock  = column / BLOCK_SIZE;
  920.         final int jColumn = column - jBlock * BLOCK_SIZE;
  921.         final int jWidth  = blockWidth(jBlock);
  922.         int outIndex      = 0;
  923.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  924.             final int iHeight = blockHeight(iBlock);
  925.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  926.             for (int i = 0; i < iHeight; ++i) {
  927.                 outData[outIndex++] = block[i * jWidth + jColumn];
  928.             }
  929.         }

  930.         return new ArrayFieldVector<>(getField(), outData, false);
  931.     }

  932.     /** {@inheritDoc} */
  933.     @Override
  934.     public void setColumnVector(final int column, final FieldVector<T> vector)
  935.         throws OutOfRangeException, MatrixDimensionMismatchException {
  936.         if (vector instanceof ArrayFieldVector) {
  937.             setColumn(column, ((ArrayFieldVector<T>) vector).getDataRef());
  938.         } else {
  939.             super.setColumnVector(column, vector);
  940.         }
  941.     }

  942.     /** {@inheritDoc} */
  943.     @Override
  944.     public T[] getRow(final int row) throws OutOfRangeException {
  945.         checkRowIndex(row);
  946.         final T[] out = MathArrays.buildArray(getField(), columns);

  947.         // perform copy block-wise, to ensure good cache behavior
  948.         final int iBlock  = row / BLOCK_SIZE;
  949.         final int iRow    = row - iBlock * BLOCK_SIZE;
  950.         int outIndex      = 0;
  951.         for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  952.             final int jWidth     = blockWidth(jBlock);
  953.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  954.             System.arraycopy(block, iRow * jWidth, out, outIndex, jWidth);
  955.             outIndex += jWidth;
  956.         }

  957.         return out;
  958.     }

  959.     /** {@inheritDoc} */
  960.     @Override
  961.     public void setRow(final int row, final T[] array)
  962.         throws OutOfRangeException, MatrixDimensionMismatchException {
  963.         checkRowIndex(row);
  964.         final int nCols = getColumnDimension();
  965.         if (array.length != nCols) {
  966.             throw new MatrixDimensionMismatchException(1, array.length, 1, nCols);
  967.         }

  968.         // perform copy block-wise, to ensure good cache behavior
  969.         final int iBlock  = row / BLOCK_SIZE;
  970.         final int iRow    = row - iBlock * BLOCK_SIZE;
  971.         int outIndex      = 0;
  972.         for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  973.             final int jWidth     = blockWidth(jBlock);
  974.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  975.             System.arraycopy(array, outIndex, block, iRow * jWidth, jWidth);
  976.             outIndex += jWidth;
  977.         }
  978.     }

  979.     /** {@inheritDoc} */
  980.     @Override
  981.     public T[] getColumn(final int column) throws OutOfRangeException {
  982.         checkColumnIndex(column);
  983.         final T[] out = MathArrays.buildArray(getField(), rows);

  984.         // perform copy block-wise, to ensure good cache behavior
  985.         final int jBlock  = column / BLOCK_SIZE;
  986.         final int jColumn = column - jBlock * BLOCK_SIZE;
  987.         final int jWidth  = blockWidth(jBlock);
  988.         int outIndex      = 0;
  989.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  990.             final int iHeight = blockHeight(iBlock);
  991.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  992.             for (int i = 0; i < iHeight; ++i) {
  993.                 out[outIndex++] = block[i * jWidth + jColumn];
  994.             }
  995.         }

  996.         return out;
  997.     }

  998.     /** {@inheritDoc} */
  999.     @Override
  1000.     public void setColumn(final int column, final T[] array)
  1001.         throws MatrixDimensionMismatchException, OutOfRangeException {
  1002.         checkColumnIndex(column);
  1003.         final int nRows = getRowDimension();
  1004.         if (array.length != nRows) {
  1005.             throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1);
  1006.         }

  1007.         // perform copy block-wise, to ensure good cache behavior
  1008.         final int jBlock  = column / BLOCK_SIZE;
  1009.         final int jColumn = column - jBlock * BLOCK_SIZE;
  1010.         final int jWidth  = blockWidth(jBlock);
  1011.         int outIndex      = 0;
  1012.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1013.             final int iHeight = blockHeight(iBlock);
  1014.             final T[] block = blocks[iBlock * blockColumns + jBlock];
  1015.             for (int i = 0; i < iHeight; ++i) {
  1016.                 block[i * jWidth + jColumn] = array[outIndex++];
  1017.             }
  1018.         }
  1019.     }

  1020.     /** {@inheritDoc} */
  1021.     @Override
  1022.     public T getEntry(final int row, final int column)
  1023.         throws OutOfRangeException {
  1024.         checkRowIndex(row);
  1025.         checkColumnIndex(column);

  1026.         final int iBlock = row    / BLOCK_SIZE;
  1027.         final int jBlock = column / BLOCK_SIZE;
  1028.         final int k      = (row    - iBlock * BLOCK_SIZE) * blockWidth(jBlock) +
  1029.             (column - jBlock * BLOCK_SIZE);

  1030.         return blocks[iBlock * blockColumns + jBlock][k];
  1031.     }

  1032.     /** {@inheritDoc} */
  1033.     @Override
  1034.     public void setEntry(final int row, final int column, final T value)
  1035.         throws OutOfRangeException {
  1036.         checkRowIndex(row);
  1037.         checkColumnIndex(column);

  1038.         final int iBlock = row    / BLOCK_SIZE;
  1039.         final int jBlock = column / BLOCK_SIZE;
  1040.         final int k      = (row    - iBlock * BLOCK_SIZE) * blockWidth(jBlock) +
  1041.             (column - jBlock * BLOCK_SIZE);

  1042.         blocks[iBlock * blockColumns + jBlock][k] = value;
  1043.     }

  1044.     /** {@inheritDoc} */
  1045.     @Override
  1046.     public void addToEntry(final int row, final int column, final T increment)
  1047.         throws OutOfRangeException {
  1048.         checkRowIndex(row);
  1049.         checkColumnIndex(column);

  1050.         final int iBlock = row    / BLOCK_SIZE;
  1051.         final int jBlock = column / BLOCK_SIZE;
  1052.         final int k      = (row    - iBlock * BLOCK_SIZE) * blockWidth(jBlock) +
  1053.             (column - jBlock * BLOCK_SIZE);
  1054.         final T[] blockIJ = blocks[iBlock * blockColumns + jBlock];

  1055.         blockIJ[k] = blockIJ[k].add(increment);
  1056.     }

  1057.     /** {@inheritDoc} */
  1058.     @Override
  1059.     public void multiplyEntry(final int row, final int column, final T factor)
  1060.         throws OutOfRangeException {
  1061.         checkRowIndex(row);
  1062.         checkColumnIndex(column);

  1063.         final int iBlock = row    / BLOCK_SIZE;
  1064.         final int jBlock = column / BLOCK_SIZE;
  1065.         final int k      = (row    - iBlock * BLOCK_SIZE) * blockWidth(jBlock) +
  1066.             (column - jBlock * BLOCK_SIZE);
  1067.         final T[] blockIJ = blocks[iBlock * blockColumns + jBlock];

  1068.         blockIJ[k] = blockIJ[k].multiply(factor);
  1069.     }

  1070.     /** {@inheritDoc} */
  1071.     @Override
  1072.     public FieldMatrix<T> transpose() {
  1073.         final int nRows = getRowDimension();
  1074.         final int nCols = getColumnDimension();
  1075.         final BlockFieldMatrix<T> out = new BlockFieldMatrix<>(getField(), nCols, nRows);

  1076.         // perform transpose block-wise, to ensure good cache behavior
  1077.         int blockIndex = 0;
  1078.         for (int iBlock = 0; iBlock < blockColumns; ++iBlock) {
  1079.             for (int jBlock = 0; jBlock < blockRows; ++jBlock) {

  1080.                 // transpose current block
  1081.                 final T[] outBlock = out.blocks[blockIndex];
  1082.                 final T[] tBlock   = blocks[jBlock * blockColumns + iBlock];
  1083.                 final int      pStart   = iBlock * BLOCK_SIZE;
  1084.                 final int      pEnd     = JdkMath.min(pStart + BLOCK_SIZE, columns);
  1085.                 final int      qStart   = jBlock * BLOCK_SIZE;
  1086.                 final int      qEnd     = JdkMath.min(qStart + BLOCK_SIZE, rows);
  1087.                 int k = 0;
  1088.                 for (int p = pStart; p < pEnd; ++p) {
  1089.                     final int lInc = pEnd - pStart;
  1090.                     int l = p - pStart;
  1091.                     for (int q = qStart; q < qEnd; ++q) {
  1092.                         outBlock[k] = tBlock[l];
  1093.                         ++k;
  1094.                         l+= lInc;
  1095.                     }
  1096.                 }

  1097.                 // go to next block
  1098.                 ++blockIndex;
  1099.             }
  1100.         }

  1101.         return out;
  1102.     }

  1103.     /** {@inheritDoc} */
  1104.     @Override
  1105.     public int getRowDimension() {
  1106.         return rows;
  1107.     }

  1108.     /** {@inheritDoc} */
  1109.     @Override
  1110.     public int getColumnDimension() {
  1111.         return columns;
  1112.     }

  1113.     /** {@inheritDoc} */
  1114.     @Override
  1115.     public T[] operate(final T[] v) throws DimensionMismatchException {
  1116.         if (v.length != columns) {
  1117.             throw new DimensionMismatchException(v.length, columns);
  1118.         }
  1119.         final T[] out = MathArrays.buildArray(getField(), rows);
  1120.         final T zero = getField().getZero();

  1121.         // perform multiplication block-wise, to ensure good cache behavior
  1122.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1123.             final int pStart = iBlock * BLOCK_SIZE;
  1124.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  1125.             for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  1126.                 final T[] block  = blocks[iBlock * blockColumns + jBlock];
  1127.                 final int      qStart = jBlock * BLOCK_SIZE;
  1128.                 final int      qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  1129.                 int k = 0;
  1130.                 for (int p = pStart; p < pEnd; ++p) {
  1131.                     T sum = zero;
  1132.                     int q = qStart;
  1133.                     while (q < qEnd - 3) {
  1134.                         sum = sum.
  1135.                               add(block[k].multiply(v[q])).
  1136.                               add(block[k + 1].multiply(v[q + 1])).
  1137.                               add(block[k + 2].multiply(v[q + 2])).
  1138.                               add(block[k + 3].multiply(v[q + 3]));
  1139.                         k += 4;
  1140.                         q += 4;
  1141.                     }
  1142.                     while (q < qEnd) {
  1143.                         sum = sum.add(block[k++].multiply(v[q++]));
  1144.                     }
  1145.                     out[p] = out[p].add(sum);
  1146.                 }
  1147.             }
  1148.         }

  1149.         return out;
  1150.     }

  1151.     /** {@inheritDoc} */
  1152.     @Override
  1153.     public T[] preMultiply(final T[] v) throws DimensionMismatchException {

  1154.         if (v.length != rows) {
  1155.             throw new DimensionMismatchException(v.length, rows);
  1156.         }
  1157.         final T[] out = MathArrays.buildArray(getField(), columns);
  1158.         final T zero = getField().getZero();

  1159.         // perform multiplication block-wise, to ensure good cache behavior
  1160.         for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  1161.             final int jWidth  = blockWidth(jBlock);
  1162.             final int jWidth2 = jWidth  + jWidth;
  1163.             final int jWidth3 = jWidth2 + jWidth;
  1164.             final int jWidth4 = jWidth3 + jWidth;
  1165.             final int qStart = jBlock * BLOCK_SIZE;
  1166.             final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  1167.             for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1168.                 final T[] block  = blocks[iBlock * blockColumns + jBlock];
  1169.                 final int      pStart = iBlock * BLOCK_SIZE;
  1170.                 final int      pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  1171.                 for (int q = qStart; q < qEnd; ++q) {
  1172.                     int k = q - qStart;
  1173.                     T sum = zero;
  1174.                     int p = pStart;
  1175.                     while (p < pEnd - 3) {
  1176.                         sum = sum.
  1177.                               add(block[k].multiply(v[p])).
  1178.                               add(block[k + jWidth].multiply(v[p + 1])).
  1179.                               add(block[k + jWidth2].multiply(v[p + 2])).
  1180.                               add(block[k + jWidth3].multiply(v[p + 3]));
  1181.                         k += jWidth4;
  1182.                         p += 4;
  1183.                     }
  1184.                     while (p < pEnd) {
  1185.                         sum = sum.add(block[k].multiply(v[p++]));
  1186.                         k += jWidth;
  1187.                     }
  1188.                     out[q] = out[q].add(sum);
  1189.                 }
  1190.             }
  1191.         }

  1192.         return out;
  1193.     }

  1194.     /** {@inheritDoc} */
  1195.     @Override
  1196.     public T walkInRowOrder(final FieldMatrixChangingVisitor<T> visitor) {
  1197.         visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
  1198.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1199.             final int pStart = iBlock * BLOCK_SIZE;
  1200.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  1201.             for (int p = pStart; p < pEnd; ++p) {
  1202.                 for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  1203.                     final int jWidth = blockWidth(jBlock);
  1204.                     final int qStart = jBlock * BLOCK_SIZE;
  1205.                     final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  1206.                     final T[] block = blocks[iBlock * blockColumns + jBlock];
  1207.                     int k = (p - pStart) * jWidth;
  1208.                     for (int q = qStart; q < qEnd; ++q) {
  1209.                         block[k] = visitor.visit(p, q, block[k]);
  1210.                         ++k;
  1211.                     }
  1212.                 }
  1213.              }
  1214.         }
  1215.         return visitor.end();
  1216.     }

  1217.     /** {@inheritDoc} */
  1218.     @Override
  1219.     public T walkInRowOrder(final FieldMatrixPreservingVisitor<T> visitor) {
  1220.         visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
  1221.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1222.             final int pStart = iBlock * BLOCK_SIZE;
  1223.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  1224.             for (int p = pStart; p < pEnd; ++p) {
  1225.                 for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  1226.                     final int jWidth = blockWidth(jBlock);
  1227.                     final int qStart = jBlock * BLOCK_SIZE;
  1228.                     final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  1229.                     final T[] block = blocks[iBlock * blockColumns + jBlock];
  1230.                     int k = (p - pStart) * jWidth;
  1231.                     for (int q = qStart; q < qEnd; ++q) {
  1232.                         visitor.visit(p, q, block[k]);
  1233.                         ++k;
  1234.                     }
  1235.                 }
  1236.              }
  1237.         }
  1238.         return visitor.end();
  1239.     }

  1240.     /** {@inheritDoc} */
  1241.     @Override
  1242.     public T walkInRowOrder(final FieldMatrixChangingVisitor<T> visitor,
  1243.                             final int startRow, final int endRow,
  1244.                             final int startColumn, final int endColumn)
  1245.         throws OutOfRangeException, NumberIsTooSmallException {
  1246.         checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
  1247.         visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
  1248.         for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
  1249.             final int p0     = iBlock * BLOCK_SIZE;
  1250.             final int pStart = JdkMath.max(startRow, p0);
  1251.             final int pEnd   = JdkMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
  1252.             for (int p = pStart; p < pEnd; ++p) {
  1253.                 for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) {
  1254.                     final int jWidth = blockWidth(jBlock);
  1255.                     final int q0     = jBlock * BLOCK_SIZE;
  1256.                     final int qStart = JdkMath.max(startColumn, q0);
  1257.                     final int qEnd   = JdkMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
  1258.                     final T[] block = blocks[iBlock * blockColumns + jBlock];
  1259.                     int k = (p - p0) * jWidth + qStart - q0;
  1260.                     for (int q = qStart; q < qEnd; ++q) {
  1261.                         block[k] = visitor.visit(p, q, block[k]);
  1262.                         ++k;
  1263.                     }
  1264.                 }
  1265.              }
  1266.         }
  1267.         return visitor.end();
  1268.     }

  1269.     /** {@inheritDoc} */
  1270.     @Override
  1271.     public T walkInRowOrder(final FieldMatrixPreservingVisitor<T> visitor,
  1272.                             final int startRow, final int endRow,
  1273.                             final int startColumn, final int endColumn)
  1274.         throws OutOfRangeException, NumberIsTooSmallException {
  1275.         checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
  1276.         visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
  1277.         for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
  1278.             final int p0     = iBlock * BLOCK_SIZE;
  1279.             final int pStart = JdkMath.max(startRow, p0);
  1280.             final int pEnd   = JdkMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
  1281.             for (int p = pStart; p < pEnd; ++p) {
  1282.                 for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) {
  1283.                     final int jWidth = blockWidth(jBlock);
  1284.                     final int q0     = jBlock * BLOCK_SIZE;
  1285.                     final int qStart = JdkMath.max(startColumn, q0);
  1286.                     final int qEnd   = JdkMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
  1287.                     final T[] block = blocks[iBlock * blockColumns + jBlock];
  1288.                     int k = (p - p0) * jWidth + qStart - q0;
  1289.                     for (int q = qStart; q < qEnd; ++q) {
  1290.                         visitor.visit(p, q, block[k]);
  1291.                         ++k;
  1292.                     }
  1293.                 }
  1294.              }
  1295.         }
  1296.         return visitor.end();
  1297.     }

  1298.     /** {@inheritDoc} */
  1299.     @Override
  1300.     public T walkInOptimizedOrder(final FieldMatrixChangingVisitor<T> visitor) {
  1301.         visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
  1302.         int blockIndex = 0;
  1303.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1304.             final int pStart = iBlock * BLOCK_SIZE;
  1305.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  1306.             for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  1307.                 final int qStart = jBlock * BLOCK_SIZE;
  1308.                 final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  1309.                 final T[] block = blocks[blockIndex];
  1310.                 int k = 0;
  1311.                 for (int p = pStart; p < pEnd; ++p) {
  1312.                     for (int q = qStart; q < qEnd; ++q) {
  1313.                         block[k] = visitor.visit(p, q, block[k]);
  1314.                         ++k;
  1315.                     }
  1316.                 }
  1317.                 ++blockIndex;
  1318.             }
  1319.         }
  1320.         return visitor.end();
  1321.     }

  1322.     /** {@inheritDoc} */
  1323.     @Override
  1324.     public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor<T> visitor) {
  1325.         visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
  1326.         int blockIndex = 0;
  1327.         for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
  1328.             final int pStart = iBlock * BLOCK_SIZE;
  1329.             final int pEnd   = JdkMath.min(pStart + BLOCK_SIZE, rows);
  1330.             for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
  1331.                 final int qStart = jBlock * BLOCK_SIZE;
  1332.                 final int qEnd   = JdkMath.min(qStart + BLOCK_SIZE, columns);
  1333.                 final T[] block = blocks[blockIndex];
  1334.                 int k = 0;
  1335.                 for (int p = pStart; p < pEnd; ++p) {
  1336.                     for (int q = qStart; q < qEnd; ++q) {
  1337.                         visitor.visit(p, q, block[k]);
  1338.                         ++k;
  1339.                     }
  1340.                 }
  1341.                 ++blockIndex;
  1342.             }
  1343.         }
  1344.         return visitor.end();
  1345.     }

  1346.     /** {@inheritDoc} */
  1347.     @Override
  1348.     public T walkInOptimizedOrder(final FieldMatrixChangingVisitor<T> visitor,
  1349.                                   final int startRow, final int endRow,
  1350.                                   final int startColumn, final int endColumn)
  1351.         throws OutOfRangeException, NumberIsTooSmallException {
  1352.         checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
  1353.         visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
  1354.         for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
  1355.             final int p0     = iBlock * BLOCK_SIZE;
  1356.             final int pStart = JdkMath.max(startRow, p0);
  1357.             final int pEnd   = JdkMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
  1358.             for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) {
  1359.                 final int jWidth = blockWidth(jBlock);
  1360.                 final int q0     = jBlock * BLOCK_SIZE;
  1361.                 final int qStart = JdkMath.max(startColumn, q0);
  1362.                 final int qEnd   = JdkMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
  1363.                 final T[] block = blocks[iBlock * blockColumns + jBlock];
  1364.                 for (int p = pStart; p < pEnd; ++p) {
  1365.                     int k = (p - p0) * jWidth + qStart - q0;
  1366.                     for (int q = qStart; q < qEnd; ++q) {
  1367.                         block[k] = visitor.visit(p, q, block[k]);
  1368.                         ++k;
  1369.                     }
  1370.                 }
  1371.             }
  1372.         }
  1373.         return visitor.end();
  1374.     }

  1375.     /** {@inheritDoc} */
  1376.     @Override
  1377.     public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor<T> visitor,
  1378.                                   final int startRow, final int endRow,
  1379.                                   final int startColumn, final int endColumn)
  1380.         throws OutOfRangeException, NumberIsTooSmallException {
  1381.         checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
  1382.         visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
  1383.         for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
  1384.             final int p0     = iBlock * BLOCK_SIZE;
  1385.             final int pStart = JdkMath.max(startRow, p0);
  1386.             final int pEnd   = JdkMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
  1387.             for (int jBlock = startColumn / BLOCK_SIZE; jBlock < 1 + endColumn / BLOCK_SIZE; ++jBlock) {
  1388.                 final int jWidth = blockWidth(jBlock);
  1389.                 final int q0     = jBlock * BLOCK_SIZE;
  1390.                 final int qStart = JdkMath.max(startColumn, q0);
  1391.                 final int qEnd   = JdkMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
  1392.                 final T[] block = blocks[iBlock * blockColumns + jBlock];
  1393.                 for (int p = pStart; p < pEnd; ++p) {
  1394.                     int k = (p - p0) * jWidth + qStart - q0;
  1395.                     for (int q = qStart; q < qEnd; ++q) {
  1396.                         visitor.visit(p, q, block[k]);
  1397.                         ++k;
  1398.                     }
  1399.                 }
  1400.             }
  1401.         }
  1402.         return visitor.end();
  1403.     }

  1404.     /**
  1405.      * Get the height of a block.
  1406.      * @param blockRow row index (in block sense) of the block
  1407.      * @return height (number of rows) of the block
  1408.      */
  1409.     private int blockHeight(final int blockRow) {
  1410.         return (blockRow == blockRows - 1) ? rows - blockRow * BLOCK_SIZE : BLOCK_SIZE;
  1411.     }

  1412.     /**
  1413.      * Get the width of a block.
  1414.      * @param blockColumn column index (in block sense) of the block
  1415.      * @return width (number of columns) of the block
  1416.      */
  1417.     private int blockWidth(final int blockColumn) {
  1418.         return (blockColumn == blockColumns - 1) ? columns - blockColumn * BLOCK_SIZE : BLOCK_SIZE;
  1419.     }
  1420. }