/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.Arrays; import java.util.Comparator; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.Validate; import com.opengamma.financial.analytics.QuickSorter.ArrayQuickSorter; import com.opengamma.util.ArgumentChecker; /** * Represents a 3D labeled matrix. The dimensions are named X, Y, Z - values[Z][Y][X]. * * @param <KX> Key type for the X dimension * @param <KY> Key type for the Y dimension * @param <KZ> Key type for the Z dimension * @param <TX> Tolerance type for X key comparisons * @param <TY> Tolerance type for Y key comparisons * @param <TZ> Tolerance type for Z key comparisons * @param <SUBCLASS> Instantiating sub-class */ public abstract class LabelledMatrix3D<KX, KY, KZ, TX, TY, TZ, SUBCLASS> { private final KX[] _xKeys; private final Object[] _xLabels; private final KY[] _yKeys; private final Object[] _yLabels; private final KZ[] _zKeys; private final Object[] _zLabels; private final double[][][] _values; /** * Creates a new 3D labeled matrix. The labels are the {@link Object#toString} forms of the keys. * * @param xKeys keys for the X dimension * @param yKeys keys for the Y dimension * @param zKeys keys for the Z dimension * @param values values of the matrix in the shape [Z][Y][X] */ public LabelledMatrix3D(final KX[] xKeys, final KY[] yKeys, final KZ[] zKeys, final double[][][] values) { this(xKeys, LabelledMatrixUtils.toString(xKeys), yKeys, LabelledMatrixUtils.toString(yKeys), zKeys, LabelledMatrixUtils.toString(zKeys), values); } /** * Creates a new 3D labeled matrix. * * @param xKeys keys for the X dimension * @param xLabels labels for the X dimension * @param yKeys keys for the Y dimension * @param yLabels labels for the Y dimension * @param zKeys keys for the Z dimension * @param zLabels labels for the Z dimension * @param values values of the matrix in the shape [Z][Y][X] */ public LabelledMatrix3D(final KX[] xKeys, final Object[] xLabels, final KY[] yKeys, final Object[] yLabels, final KZ[] zKeys, final Object[] zLabels, final double[][][] values) { ArgumentChecker.notNull(xKeys, "xKeys"); ArgumentChecker.notNull(xLabels, "xLabels"); ArgumentChecker.notNull(yKeys, "yKeys"); ArgumentChecker.notNull(yLabels, "yLabels"); ArgumentChecker.notNull(zKeys, "zKeys"); ArgumentChecker.notNull(zLabels, "zLabels"); ArgumentChecker.notNull(values, "values"); final int x = xKeys.length; Validate.isTrue(xLabels.length == x, "invalid xLabels length"); final int y = yKeys.length; Validate.isTrue(yLabels.length == y, "invalid yLabels length"); final int z = zKeys.length; Validate.isTrue(zLabels.length == z, "invalid zLabels length"); Validate.isTrue(values.length == z, "invalid zKeys length"); _xKeys = Arrays.copyOf(xKeys, x); _xLabels = Arrays.copyOf(xLabels, x); _yKeys = Arrays.copyOf(yKeys, y); _yLabels = Arrays.copyOf(yLabels, y); _zKeys = Arrays.copyOf(zKeys, z); _zLabels = Arrays.copyOf(zLabels, z); _values = new double[z][y][x]; for (int iz = 0; iz < z; iz++) { Validate.isTrue(values[iz].length == y, "invalid yKeys length"); for (int iy = 0; iy < y; iy++) { Validate.isTrue(values[iz][iy].length == x, "invalid xKeys length"); System.arraycopy(values[iz][iy], 0, _values[iz][iy], 0, x); } } quickSortX(); quickSortY(); quickSortZ(); } /** * Creates a new labeled matrix instance. This is called at the end of operations so that the sub-class can return an object * of its own type. * * @param xKeys keys for the X dimension of the new matrix * @param xLabels labels for the X dimension of the new matrix * @param yKeys keys for the Y dimension of the new matrix * @param yLabels labels for the Y dimension of the new matrix * @param zKeys keys for the Z dimension of the new matrix * @param zLabels labels for the Z dimension of the new matrix * @param values values of the new matrix in the shape [Z][Y][X] * @return the new matrix */ protected abstract SUBCLASS create(final KX[] xKeys, final Object[] xLabels, final KY[] yKeys, final Object[] yLabels, final KZ[] zKeys, final Object[] zLabels, final double[][][] values); private void quickSortX() { (new ArrayQuickSorter<KX>(_xKeys) { private final TX _tolerance = getDefaultToleranceX(); @Override protected int compare(final KX first, final KX second) { return compareKeysX(first, second, _tolerance); } @Override protected void swap(final int first, final int second) { super.swap(first, second); swap(_xLabels, first, second); for (int iz = 0; iz < _zKeys.length; iz++) { for (int iy = 0; iy < _yKeys.length; iy++) { swap(_values[iz][iy], first, second); } } } }).sort(); } private void quickSortY() { (new ArrayQuickSorter<KY>(_yKeys) { private final TY _tolerance = getDefaultToleranceY(); @Override protected int compare(final KY first, final KY second) { return compareKeysY(first, second, _tolerance); } @Override protected void swap(final int first, final int second) { super.swap(first, second); swap(_yLabels, first, second); for (int iz = 0; iz < _zKeys.length; iz++) { swap(_values[iz], first, second); } } }).sort(); } private void quickSortZ() { (new ArrayQuickSorter<KZ>(_zKeys) { private final TZ _tolerance = getDefaultToleranceZ(); @Override protected int compare(final KZ first, final KZ second) { return compareKeysZ(first, second, _tolerance); } @Override protected void swap(final int first, final int second) { super.swap(first, second); swap(_zLabels, first, second); swap(_values, first, second); } }).sort(); } /** * Returns the default tolerance value for comparison of X dimension keys. This will be used for the initial sort * of a matrix, or if a tolerance is not specified for the other operations. It may be null if the * {@link #compareKeysX} method accepts it (i.e. ignores it). * * @return the default tolerance for X key comparisons */ public TX getDefaultToleranceX() { return null; } /** * Returns the default tolerance value for comparison of Y dimension keys. This will be used for the initial sort * of a matrix, or if a tolerance is not specified for the other operations. It may be null if the * {@link #compareKeysY} method accepts it (i.e. ignores it). * * @return the default tolerance for Y key comparisons */ public TY getDefaultToleranceY() { return null; } /** * Returns the default tolerance value for comparison of Z dimension keys. This will be used for the initial sort * of a matrix, or if a tolerance is not specified for the other operations. It may be null if the * {@link #compareKeysZ} method accepts it (i.e. ignores it). * * @return the default tolerance for Z key comparisons */ public TZ getDefaultToleranceZ() { return null; } /** * Compares two X dimension keys. * * @param key1 first key to compare * @param key2 second key to compare * @param tolerance comparison tolerance * @return negative if the first key is before the second, positive if after, zero if equal (given the tolerance) */ protected abstract int compareKeysX(final KX key1, final KX key2, final TX tolerance); /** * Returns a {@link Comparator} wrapping the {@link #compareKeysX} method for a given tolerance. * * @param tolerance comparison tolerance * @return a comparator */ protected Comparator<KX> compareKeysX(final TX tolerance) { return new Comparator<KX>() { @Override public int compare(final KX key1, final KX key2) { return compareKeysX(key1, key2, tolerance); } }; } /** * Compares two Y dimension keys. * * @param key1 first key to compare * @param key2 second key to compare * @param tolerance comparison tolerance * @return negative if the first key is before the second, positive if after, zero if equal (given the tolerance) */ protected abstract int compareKeysY(final KY key1, final KY key2, final TY tolerance); /** * Returns a {@link Comparator} wrapping the {@link #compareKeysY} method for a given tolerance. * * @param tolerance comparison tolerance * @return a comparator */ protected Comparator<KY> compareKeysY(final TY tolerance) { return new Comparator<KY>() { @Override public int compare(final KY key1, final KY key2) { return compareKeysY(key1, key2, tolerance); } }; } /** * Compares two Z dimension keys. * * @param key1 first key to compare * @param key2 second key to compare * @param tolerance comparison tolerance * @return negative if the first key is before the second, positive if after, zero if equal (given the tolerance) */ protected abstract int compareKeysZ(final KZ key1, final KZ key2, final TZ tolerance); /** * Returns a {@link Comparator} wrapping the {@link #compareKeysZ} method for a given tolerance. * * @param tolerance comparison tolerance * @return a comparator */ protected Comparator<KZ> compareKeysZ(final TZ tolerance) { return new Comparator<KZ>() { @Override public int compare(final KZ key1, final KZ key2) { return compareKeysZ(key1, key2, tolerance); } }; } /** * Adds a labeled matrix to this one to create a new matrix. * <p> * Each key triple in the other matrix is checked to see if it is in the current; if so, the value for that triple is added. If the key triple is * not present, the new key triple, labels and value are attached to the matrix. This method ignores the label - if there is a key already present * but the labels do not match, then the new label is the original. * * @param other Another labeled matrix * @param xTolerance tolerance for detecting a match on the X keys * @param yTolerance tolerance for detecting a match on the Y keys * @param zTolerance tolerance for detecting a match on the Z keys * @return The sum of the matrices */ public SUBCLASS addIgnoringLabel(final LabelledMatrix3D<KX, KY, KZ, TX, TY, TZ, ?> other, final TX xTolerance, final TY yTolerance, final TZ zTolerance) { return addImpl(other, xTolerance, yTolerance, zTolerance, true); } /** * Adds a labeled matrix to this one to create a new matrix. * <p> * Each key triple in the other matrix is checked to see if it is in the current; if so, the value for that triple is added. If the key triple is * not present, the new key triple, labels and value are attached to the matrix. This method ignores the label - if there is a key already present * but the labels do not match, then the new label is the original. * * @param other Another labeled matrix * @return The sum of the matrices */ public SUBCLASS addIgnoringLabel(final LabelledMatrix3D<KX, KY, KZ, TX, TY, TZ, ?> other) { return addIgnoringLabel(other, getDefaultToleranceX(), getDefaultToleranceY(), getDefaultToleranceZ()); } /** * Adds a labeled matrix to this one to create a new matrix. * <p> * Each key triple in the other matrix is checked to see if it is in the current; if so, the value for that triple is added. If the key triple is * not present, the new key triple, labels and value are attached to the matrix. If the key labels on the two matrices differ, an exception is thrown. * * @param other Another labeled matrix * @param xTolerance tolerance for detecting a match on the X keys * @param yTolerance tolerance for detecting a match on the Y keys * @param zTolerance tolerance for detecting a match on the Z keys * @return The sum of the matrices */ public SUBCLASS add(final LabelledMatrix3D<KX, KY, KZ, TX, TY, TZ, ?> other, final TX xTolerance, final TY yTolerance, final TZ zTolerance) { return addImpl(other, xTolerance, yTolerance, zTolerance, false); } /** * Adds a labeled matrix to this one to create a new matrix. * <p> * Each key triple in the other matrix is checked to see if it is in the current; if so, the value for that triple is added. If the key triple is * not present, the new key triple, labels and value are attached to the matrix. If the key labels on the two matrices differ, an exception is thrown. * * @param other Another labeled matrix * @return The sum of the matrices */ public SUBCLASS add(final LabelledMatrix3D<KX, KY, KZ, TX, TY, TZ, ?> other) { return add(other, getDefaultToleranceX(), getDefaultToleranceY(), getDefaultToleranceZ()); } protected SUBCLASS addImpl(final LabelledMatrix3D<KX, KY, KZ, TX, TY, TZ, ?> other, final TX xTolerance, final TY yTolerance, final TZ zTolerance, final boolean ignoreLabels) { Validate.notNull(other, "other"); // Check the new X keys and labels int iThis = 0, iOther = 0; ObjectArrayList<KX> newXKeys = null; ObjectArrayList<Object> newXLabels = null; final KX[] otherXKeys = other.getXKeys(); final Object[] otherXLabels = other.getXLabels(); final int[] otherXIndex = new int[otherXKeys.length]; while ((iThis < _xKeys.length) && (iOther < otherXKeys.length)) { final int cmp = compareKeysX(_xKeys[iThis], otherXKeys[iOther], xTolerance); if (cmp < 0) { iThis++; } else if (cmp > 0) { if (newXKeys == null) { newXKeys = ObjectArrayList.wrap(_xKeys); newXLabels = ObjectArrayList.wrap(_xLabels); } otherXIndex[iOther] = newXKeys.size(); newXKeys.add(otherXKeys[iOther]); newXLabels.add(otherXLabels[iOther]); iOther++; } else { if (!ignoreLabels && !ObjectUtils.equals(_xLabels[iThis], otherXLabels[iOther])) { throw new IllegalArgumentException("Label mismatch for X key " + _xKeys[iThis] + " - " + _xLabels[iThis] + " vs " + otherXLabels[iOther]); } otherXIndex[iOther] = iThis; iThis++; iOther++; } } if (iOther < otherXKeys.length) { if (newXKeys == null) { newXKeys = ObjectArrayList.wrap(_xKeys); newXLabels = ObjectArrayList.wrap(_xLabels); } do { otherXIndex[iOther] = newXKeys.size(); newXKeys.add(otherXKeys[iOther]); newXLabels.add(otherXLabels[iOther]); iOther++; } while (iOther < otherXKeys.length); } // Check the new Y keys and labels iThis = 0; iOther = 0; ObjectArrayList<KY> newYKeys = null; ObjectArrayList<Object> newYLabels = null; final KY[] otherYKeys = other.getYKeys(); final Object[] otherYLabels = other.getYLabels(); final int[] otherYIndex = new int[otherYKeys.length]; while ((iThis < _yKeys.length) && (iOther < otherYKeys.length)) { final int cmp = compareKeysY(_yKeys[iThis], otherYKeys[iOther], yTolerance); if (cmp < 0) { iThis++; } else if (cmp > 0) { if (newYKeys == null) { newYKeys = ObjectArrayList.wrap(_yKeys); newYLabels = ObjectArrayList.wrap(_yLabels); } otherYIndex[iOther] = newYKeys.size(); newYKeys.add(otherYKeys[iOther]); newYLabels.add(otherYLabels[iOther]); iOther++; } else { if (!ignoreLabels && !ObjectUtils.equals(_yLabels[iThis], otherYLabels[iOther])) { throw new IllegalArgumentException("Label mismatch for Y key " + _yKeys[iThis] + " - " + _yLabels[iThis] + " vs " + otherYLabels[iOther]); } otherYIndex[iOther] = iThis; iThis++; iOther++; } } if (iOther < otherYKeys.length) { if (newYKeys == null) { newYKeys = ObjectArrayList.wrap(_yKeys); newYLabels = ObjectArrayList.wrap(_yLabels); } do { otherYIndex[iOther] = newYKeys.size(); newYKeys.add(otherYKeys[iOther]); newYLabels.add(otherYLabels[iOther]); iOther++; } while (iOther < otherYKeys.length); } // Check the new Z keys and labels iThis = 0; iOther = 0; ObjectArrayList<KZ> newZKeys = null; ObjectArrayList<Object> newZLabels = null; final KZ[] otherZKeys = other.getZKeys(); final Object[] otherZLabels = other.getZLabels(); final int[] otherZIndex = new int[otherZKeys.length]; while ((iThis < _zKeys.length) && (iOther < otherZKeys.length)) { final int cmp = compareKeysZ(_zKeys[iThis], otherZKeys[iOther], zTolerance); if (cmp < 0) { iThis++; } else if (cmp > 0) { if (newZKeys == null) { newZKeys = ObjectArrayList.wrap(_zKeys); newZLabels = ObjectArrayList.wrap(_zLabels); } otherZIndex[iOther] = newZKeys.size(); newZKeys.add(otherZKeys[iOther]); newZLabels.add(otherZLabels[iOther]); iOther++; } else { if (!ignoreLabels && !ObjectUtils.equals(_zLabels[iThis], otherZLabels[iOther])) { throw new IllegalArgumentException("Label mismatch for Z key " + _zKeys[iThis] + " - " + _zLabels[iThis] + " vs " + otherZLabels[iOther]); } otherZIndex[iOther] = iThis; iThis++; iOther++; } } if (iOther < otherZKeys.length) { if (newZKeys == null) { newZKeys = ObjectArrayList.wrap(_zKeys); newZLabels = ObjectArrayList.wrap(_zLabels); } do { otherZIndex[iOther] = newZKeys.size(); newZKeys.add(otherZKeys[iOther]); newZLabels.add(otherZLabels[iOther]); iOther++; } while (iOther < otherZKeys.length); } // Build the new matrix final KX[] xKeys = (newXKeys != null) ? newXKeys.toArray(_xKeys) : _xKeys; final Object[] xLabels = (newXLabels != null) ? newXLabels.toArray(_xLabels) : _xLabels; final KY[] yKeys = (newYKeys != null) ? newYKeys.toArray(_yKeys) : _yKeys; final Object[] yLabels = (newYLabels != null) ? newYLabels.toArray(_yLabels) : _yLabels; final KZ[] zKeys = (newZKeys != null) ? newZKeys.toArray(_zKeys) : _zKeys; final Object[] zLabels = (newZLabels != null) ? newZLabels.toArray(_zLabels) : _zLabels; final double[][][] values = new double[zKeys.length][yKeys.length][xKeys.length]; for (int z = 0; z < _values.length; z++) { for (int y = 0; y < _values[z].length; y++) { System.arraycopy(_values[z][y], 0, values[z][y], 0, _values[z][y].length); } } final double[][][] otherValues = other.getValues(); for (int z = 0; z < otherValues.length; z++) { final int iz = otherZIndex[z]; for (int y = 0; y < otherValues[z].length; y++) { final int iy = otherYIndex[y]; for (int x = 0; x < otherValues[z][y].length; x++) { final int ix = otherXIndex[x]; values[iz][iy][ix] += otherValues[z][y][x]; } } } return create(xKeys, xLabels, yKeys, yLabels, zKeys, zLabels, values); } /** * Returns the keys for the X dimension. * * @return X keys */ public KX[] getXKeys() { return _xKeys; } /** * Returns the labels for the X dimension. * * @return X labels */ public Object[] getXLabels() { return _xLabels; } /** * Returns the keys for the Y dimension. * * @return Y keys */ public KY[] getYKeys() { return _yKeys; } /** * Returns the labels for the Y dimension. * * @return Y labels */ public Object[] getYLabels() { return _yLabels; } /** * Returns the keys for the Z dimension. * * @return Z keys */ public KZ[] getZKeys() { return _zKeys; } /** * Returns the labels for the Z dimension. * * @return Z labels */ public Object[] getZLabels() { return _zLabels; } /** * Returns the values of the matrix. * * @return the matrix values */ public double[][][] getValues() { return _values; } @Override public int hashCode() { int hc = 1; hc += (hc << 4) + Arrays.hashCode(_values); hc += (hc << 4) + Arrays.hashCode(_xKeys); hc += (hc << 4) + Arrays.hashCode(_xLabels); hc += (hc << 4) + Arrays.hashCode(_yKeys); hc += (hc << 4) + Arrays.hashCode(_yLabels); hc += (hc << 4) + Arrays.hashCode(_zKeys); hc += (hc << 4) + Arrays.hashCode(_zLabels); return hc; } @SuppressWarnings("rawtypes") @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!(obj instanceof LabelledMatrix3D)) { return false; } final LabelledMatrix3D other = (LabelledMatrix3D) obj; return Arrays.deepEquals(_values, other._values) && Arrays.equals(_xKeys, other._xKeys) && Arrays.equals(_xLabels, other._xLabels) && Arrays.equals(_yKeys, other._yKeys) && Arrays.equals(_yLabels, other._yLabels) && Arrays.equals(_zKeys, other._zKeys) && Arrays.equals(_zLabels, other._zLabels); } }