package mikera.vectorz.impl;
import mikera.indexz.Index;
import mikera.matrixx.AMatrix;
import mikera.matrixx.impl.AVectorMatrix;
import mikera.matrixx.impl.SparseRowMatrix;
import mikera.matrixx.impl.SparseColumnMatrix;
import mikera.vectorz.AVector;
import mikera.vectorz.Op;
import mikera.vectorz.Vector;
import mikera.vectorz.Vectorz;
import mikera.vectorz.util.DoubleArrays;
import mikera.vectorz.util.ErrorMessages;
import mikera.vectorz.util.IntArrays;
import mikera.vectorz.util.VectorzException;
/**
* Indexed sparse vector. Efficient for mutable, mostly sparse vectors.
*
* Maintains a indexed array of elements which may be non-zero. These values *can* also be zero.
*
* WARNING: individual updates of non-indexed elements are O(n) in the number of non-sparse elements. You should not normally
* perform element-wise mutation on a SparseIndexedVector if performance is a concern
*
* Index must be distinct and sorted.
*
* @author Mike
*
*/
public class SparseIndexedVector extends ASparseIndexedVector {
private static final long serialVersionUID = 750093598603613879L;
private Index index;
private double[] data;
private SparseIndexedVector(int length, Index index) {
this(length,index,new double[index.length()]);
}
private SparseIndexedVector(int length, Index index, double[] data) {
super(length);
this.index=index;
this.data=data;
}
private SparseIndexedVector(int length, Index index, AVector source) {
this(length,index,source.toDoubleArray());
}
/**
* Creates a SparseIndexedVector with the specified index and data values.
* Performs no checking - Index must be distinct and sorted.
*/
public static SparseIndexedVector wrap(int length, Index index, double[] data) {
assert(index.length()==data.length);
assert(index.isDistinctSorted());
return new SparseIndexedVector(length, index,data);
}
/**
* Creates a SparseIndexedVector with the specified index and data values.
* Performs no checking - Index must be distinct and sorted.
*/
public static SparseIndexedVector wrap(int length, int[] indices, double[] data) {
Index index=Index.wrap(indices);
assert(index.length()==data.length);
assert(index.isDistinctSorted());
return new SparseIndexedVector(length, index,data);
}
/**
* Creates a SparseIndexedVector using the given sorted Index to identify the indexes of non-zero values,
* and a double[] array to specify all the non-zero element values
*/
public static SparseIndexedVector create(int length, Index index, double[] data) {
if (!index.isDistinctSorted()) {
throw new VectorzException("Index must be sorted and distinct");
}
if (!(index.length()==data.length)) {
throw new VectorzException("Length of index: mismatch woth data");
}
return new SparseIndexedVector(length, index.clone(),DoubleArrays.copyOf(data));
}
/**
* Creates a sparse indexed vector using the specified indexed values from the source vector
*
* All non-indexed vales will be zero.
*
* @param v
* @param ixs
* @return
*/
public static AVector createWithIndices(AVector v, int[] ixs) {
int length=v.length();
int n=ixs.length;
double[] data = new double[n];
v.getElements(data,0,ixs);
return wrap(length,ixs,data);
}
public static SparseIndexedVector createLength(int length) {
return new SparseIndexedVector(length, Index.EMPTY,DoubleArrays.EMPTY);
}
/**
* Creates a SparseIndexedVector using the given sorted Index to identify the indexes of non-zero values,
* and a dense vector to specify all the non-zero element values
*/
public static SparseIndexedVector create(int length, Index index, AVector data) {
SparseIndexedVector sv= create(length, index, new double[index.length()]);
data.getElements(sv.data, 0);
return sv;
}
/**
* Creates a SparseIndexedVector from the given vector, ignoring the zeros in the source.
*
*/
public static SparseIndexedVector create(AVector source) {
if (source instanceof ASparseVector) return create((ASparseVector) source);
int srcLength = source.length();
if (srcLength==0) throw new IllegalArgumentException("Can't create a length 0 SparseIndexedVector");
int[] indexes=source.nonZeroIndices();
int len=indexes.length;
double[] vals=new double[len];
for (int i=0; i<len; i++) {
vals[i]=source.unsafeGet(indexes[i]);
}
return wrap(srcLength,Index.wrap(indexes),vals);
}
public static SparseIndexedVector create(ASparseVector source) {
int length = source.length();
if (length==0) throw new IllegalArgumentException("Can't create a length 0 SparseIndexedVector");
Index ixs=source.nonSparseIndex();
int n=ixs.length();
double[] vals=new double[n];
for (int i=0; i<n; i++) {
vals[i]=source.unsafeGet(ixs.unsafeGet(i));
}
return wrap(length,ixs,vals);
}
public static SparseIndexedVector create(SparseHashedVector source) {
int length = source.length();
if (length==0) throw new IllegalArgumentException("Can't create a length 0 SparseIndexedVector");
Index ixs=source.nonSparseIndex();
int n=ixs.length();
double[] vals=new double[n];
for (int i=0; i<n; i++) {
vals[i]=source.unsafeGet(ixs.unsafeGet(i));
}
return wrap(length,ixs,vals);
}
/** Creates a SparseIndexedVector from a row of an existing matrix */
public static AVector createFromRow(AMatrix m, int row) {
if (m instanceof AVectorMatrix) return create(m.getRow(row));
return create(m.getRow(row));
}
@Override
public int nonSparseElementCount() {
return data.length;
}
public AVector innerProduct(SparseRowMatrix m) {
int cc = m.columnCount();
int rc = m.rowCount();
checkLength(rc);
ASparseIndexedVector r = SparseIndexedVector.createLength(cc);
int n=nonSparseElementCount();
for (int ii = 0; ii < n; ii++) {
double value=data[ii];
if (value==0.0) continue; // skip zero values
int i=index.get(ii);
AVector row = m.unsafeGetVector(i);
if (row==null) continue; // skip zero rows
// TODO: we were casting to ASparseVector, necessary for speed??
// if (row instanceof ASparseVector)
// This vector could be of any type, such as Vector3.
// And some vectors don't have a sparseClone and instead return
// a reference to same instance!!!! (See Vector3...)
r.addMultiple(row, value);
}
return r;
}
public AVector innerProduct(SparseColumnMatrix m) {
int cc = m.columnCount();
int rc = m.rowCount();
checkLength(rc);
ASparseIndexedVector r = SparseIndexedVector.createLength(cc);
for (int i = 0; i < cc; i++) {
r.unsafeSet(i, this.dotProduct((ASparseVector)m.getColumn(i)));
}
return r;
}
@Override
public AVector innerProduct(AMatrix m) {
if (m instanceof SparseRowMatrix) {
return this.innerProduct((SparseRowMatrix)m);
}
if (m instanceof SparseColumnMatrix) {
return this.innerProduct((SparseColumnMatrix)m);
}
int cc=m.columnCount();
int rc=m.rowCount();
checkLength(rc);
AVector r = SparseIndexedVector.createLength(cc);
for (int i=0; i<cc; i++) {
r.unsafeSet(i,this.dotProduct(m.getColumn(i)));
}
return r;
}
@Override
public void add(AVector v) {
if (v instanceof ASparseVector) {
add((ASparseVector)v);
return;
}
includeIndices(v);
for (int i=0; i<data.length; i++) {
data[i]+=v.unsafeGet(index.get(i));
}
}
@Override
public void add(ADenseArrayVector v) {
includeIndices(v);
for (int i=0; i<data.length; i++) {
data[i]+=v.unsafeGet(index.get(i));
}
}
@Override
public void add(ASparseVector v) {
if (v instanceof ZeroVector) {
return;
}
includeIndices(v);
for (int i=0; i<data.length; i++) {
data[i]+=v.unsafeGet(index.get(i));
}
}
@Override
public void sub(AVector v) {
if (v instanceof ASparseVector) {
sub((ASparseVector)v);
return;
}
includeIndices(v);
for (int i=0; i<data.length; i++) {
data[i]-=v.unsafeGet(index.get(i));
}
}
public void sub(ASparseVector v) {
if (v instanceof ZeroVector) {
return;
}
includeIndices(v);
for (int i=0; i<data.length; i++) {
data[i]-=v.unsafeGet(index.get(i));
}
}
@Override
public void add(double[] src, int offset) {
add(Vectorz.wrap(src, offset, length));
}
@Override
public void multiply (double d) {
if (d==0.0) {
data=DoubleArrays.EMPTY;
index=Index.EMPTY;
} else {
DoubleArrays.multiply(data, d);
}
}
@Override
public void multiply(AVector v) {
if (v instanceof ADenseArrayVector) {
multiply((ADenseArrayVector)v);
return;
} else if (v instanceof ASparseVector) {
multiply((ASparseVector)v);
} else {
checkSameLength(v);
double[] data=this.data;
int[] ixs=index.data;
for (int i=0; i<data.length; i++) {
data[i]*=v.unsafeGet(ixs[i]);
}
}
}
public void multiply(ASparseVector v) {
checkSameLength(v);
int[] thisIndex=index.data;
int[] thatIndex=v.nonSparseIndex().data;
int[] tix=IntArrays.intersectSorted(thatIndex, thisIndex);
int n=tix.length;
double[] ndata=new double[n];
int i1=0;
int i2=0;
for (int i=0; i<n; i++) {
int ti=tix[i];
while (thatIndex[i1]!=ti) i1++;
while (thisIndex[i2]!=ti) i2++;
ndata[i]=v.unsafeGet(thatIndex[i1])*unsafeGet(thisIndex[i2]);
}
this.data=ndata;
this.index=Index.wrap(tix);
}
public void multiply(ADenseArrayVector v) {
multiply(v.getArray(),v.getArrayOffset());
}
@Override
public void multiply(double[] array, int offset) {
double[] data=this.data;
int[] ixs=index.data;
for (int i=0; i<data.length; i++) {
data[i]*=array[offset+ixs[i]];
}
}
@Override
public double maxAbsElement() {
double[] data=this.data;
double result=0.0;
for (int i=0; i<data.length; i++) {
double d=Math.abs(data[i]);
if (d>result) result=d;
}
return result;
}
@Override
public int maxElementIndex(){
double[] data=this.data;
if (data.length==0) return 0;
double result=data[0];
int di=0;
for (int i=1; i<data.length; i++) {
double d=data[i];
if (d>result) {
result=d;
di=i;
}
}
if (result<0.0) { // see if we can find a zero element
int ind=index.findMissing();
if (ind>0) return ind;
}
return index.get(di);
}
@Override
public int maxAbsElementIndex(){
double[] data=this.data;
if (data.length==0) return 0;
double result=data[0];
int di=0;
for (int i=1; i<data.length; i++) {
double d=Math.abs(data[i]);
if (d>result) {
result=d;
di=i;
}
}
return index.get(di);
}
@Override
public int minElementIndex(){
double[] data=this.data;
if (data.length==0) return 0;
double result=data[0];
int di=0;
for (int i=1; i<data.length; i++) {
double d=data[i];
if (d<result) {
result=d;
di=i;
}
}
if (result>0.0) { // see if we can find a zero element
int ind=index.findMissing();
if (ind>=0) return ind;
}
return index.get(di);
}
@Override
public void negate() {
DoubleArrays.negate(data);
}
@Override
public void applyOp(Op op) {
int dlen=data.length;
if ((dlen<length())&&(op.isStochastic()||(op.apply(0.0)!=0.0))) {
super.applyOp(op);
} else {
op.applyTo(data);
}
}
@Override
public void abs() {
DoubleArrays.abs(data);
}
@Override
public double get(int i) {
checkIndex(i);
int ip=index.indexPosition(i);
if (ip<0) return 0.0;
return data[ip];
}
@Override
public double unsafeGet(int i) {
int ip=index.indexPosition(i);
if (ip<0) return 0.0;
return data[ip];
}
@Override
public boolean isFullyMutable() {
return true;
}
@Override
public boolean isMutable() {
return length>0;
}
@Override
public void setElements(double[] array, int offset) {
int nz=DoubleArrays.nonZeroCount(array, offset, length);
int[] ixs=new int[nz];
double[] data=new double[nz];
this.data=data;
int di=0;
for (int i=0; i<length; i++) {
double x=array[offset+i];
if (x==0.0) continue;
ixs[di]=i;
data[di]=x;
di++;
}
index=Index.wrap(ixs);
}
@Override
public void setElements(int pos,double[] array, int offset, int length) {
if (length>=this.length) {
setElements(array,offset);
return;
}
int nz=DoubleArrays.nonZeroCount(array, offset, length);
int[] ixs=new int[nz];
double[] data=new double[nz];
this.data=data;
int di=pos;
for (int i=0; i<length; i++) {
double x=array[offset+i];
if (x==0.0) continue;
ixs[di]=i;
data[di]=x;
di++;
}
index=Index.wrap(ixs);
}
@Override
public void set(AVector v) {
checkSameLength(v);
if (v instanceof ADenseArrayVector) {
set((ADenseArrayVector)v);
return;
} else if (v instanceof ASparseVector) {
int[] nzi = v.nonZeroIndices();
index=Index.wrap(nzi);
if (nzi.length!=data.length) {
data=new double[nzi.length];
}
for (int i=0; i<index.length(); i++) {
double val=v.unsafeGet(index.get(i));
data[i]=val;
}
return;
} else {
double[] data=this.data;
int nz=(int) v.nonZeroCount();
if (nz!=data.length) {
data=new double[nz];
this.data=data;
index=Index.createLength(nz);
}
int di=0;
for (int i=0; i<nz; i++) {
double val=v.unsafeGet(i);
if (val!=0) {
data[di]=val;
index.set(di, i);
di++;
}
}
}
}
@Override
public void set(ADenseArrayVector v) {
checkSameLength(v);
setElements(v.getArray(),v.getArrayOffset());
}
@Override
public void set(int i, double value) {
checkIndex(i);
unsafeSet(i,value);
}
@Override
public void unsafeSet(int i, double value) {
int ip=index.indexPosition(i);
if (ip<0) {
if (value==0.0) return;
int npos=index.seekPosition(i);
data=DoubleArrays.insert(data,npos,value);
index=index.insert(npos,i);
} else {
data[ip]=value;
}
}
// TODO: consider a generic sparseApplyOp instead.
// keep in mind efficiency when randomly
// modifying index.
@Override
public ASparseVector roundToZero(double precision) {
int[] aboveInds = new int[data.length];
double[] aboveData = new double[data.length];
int ai = 0;
for (int i = 0; i < index.length(); i++) {
if (data[i] > precision) {
aboveInds[ai] = index.get(i);
aboveData[ai] = data[i];
ai++;
}
}
int[] newInds = new int[ai];
double[] newData = new double[ai];
System.arraycopy(aboveInds, 0, newInds, 0, ai);
System.arraycopy(aboveData, 0, newData, 0, ai);
return SparseIndexedVector.wrap(this.length, newInds, newData);
}
@Override
public void addAt(int i, double value) {
// worth checking for zero when dealing with sparse vectors
// can often avoid a relatively expensive index lookup
if (value==0.0) return;
int ip=index.indexPosition(i);
if (ip<0) {
unsafeSet(i,value);
} else {
data[ip]+=value;
}
}
@Override
public Vector nonSparseValues() {
return Vector.wrap(data);
}
@Override
public Index nonSparseIndex() {
return index;
}
@Override
public Vector toVector() {
Vector v=Vector.createLength(length);
double[] data=this.data;
int[] ixs=index.data;
for (int i=0; i<data.length; i++) {
v.unsafeSet(ixs[i],data[i]);
}
return v;
}
@Override
public SparseIndexedVector clone() {
return exactClone();
}
/**
* Include additional indices in the non-sparse index set of this vector.
*
* Useful to improve performance if subsequent operations will access these indices.
*
* @param ixs
*/
public void includeIndices(int[] ixs) {
int[] nixs = IntArrays.mergeSorted(index.data,ixs);
if (nixs.length==index.length()) return;
int nl=nixs.length;
double[] data=this.data;
double[] ndata=new double[nl];
int si=0;
for (int i=0; i<nl; i++) {
if (si>=data.length) break;
int z=index.data[si];
if (z==nixs[i]) {
ndata[i]=data[si];
si++;
}
}
this.data=ndata;
index=Index.wrap(nixs);
}
/**
* Include additional indices in the non-sparse index set of this vector.
*
* Useful to improve performance if subsequent operations will access these indices.
*
* @param ixs
*/
public void includeIndices(Index ixs) {
includeIndices(ixs.data);
}
/**
* Include additional indices in the non-sparse index set of this vector.
*
* Useful to improve performance if subsequent operations will access these indices.
*
* @param ixs
*/
public void includeIndices(AVector v) {
if (v instanceof ASparseIndexedVector) {
includeIndices((ASparseIndexedVector)v);
} else {
includeIndices(v.nonSparseIndex());
}
}
public void includeIndices(ASparseIndexedVector v) {
includeIndices(v.internalIndex());
}
@Override
public SparseIndexedVector sparseClone() {
return exactClone();
}
@Override
public SparseIndexedVector exactClone() {
return new SparseIndexedVector(length,index.clone(),data.clone());
}
@Override
public void validate() {
if (index.length()!=data.length) throw new VectorzException("Inconsistent data and index!");
if (!index.isDistinctSorted()) throw new VectorzException("Invalid index: "+index);
super.validate();
}
@Override
public AVector immutable() {
return SparseImmutableVector.create(this);
}
@Override
double[] internalData() {
return data;
}
@Override
Index internalIndex() {
return index;
}
}