copy from prototype repo
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..27fbfa1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,424 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2017, Yahoo! Inc.
+ Licensed under the terms of the Apache License 2.0.
+ See LICENSE file at the project root for terms. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>svd</groupId>
+ <artifactId>svd</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <!-- Project Information -->
+ <name>${project.groupId}:${project.artifactId}</name>
+ <description>Sketches for matrix approximation.</description>
+ <url>https://datasketches.github.io/</url>
+ <inceptionYear>2017</inceptionYear>
+
+ <licenses>
+ <license>
+ <name>Apache License, Version 2.0</name>
+ <url>https://www.apache.org/licenses/LICENSE-2.0</url>
+ </license>
+ </licenses>
+
+ <organization>
+ <name>Yahoo! Inc.</name>
+ <url>https://www.yahoo.com</url>
+ </organization>
+
+ <developers>
+ <developer>
+ <name>Jon Malkin</name>
+ <roles>
+ <role>lead developer</role>
+ </roles>
+ <url>https://github.com/jmalkin</url>
+ </developer>
+ </developers>
+
+ <contributors>
+ <contributor>
+ <url>https://github.com/DataSketches/sketches-core/graphs/contributors</url>
+ </contributor>
+ </contributors>
+ <!-- End Project Information -->
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <argLine>-Xmx1024m -Duser.language=en -Duser.country=US -Dfile.encoding=UTF-8</argLine>
+ </properties>
+
+ <!-- Environment Settings -->
+ <issueManagement>
+ <system>GitHub Issues</system>
+ <url>https://github.com/DataSketches/sketches-core/issues</url>
+ </issueManagement>
+
+ <ciManagement>
+ <system>travis</system>
+ <url>https://travis-ci.org/DataSketches/sketches-core</url>
+ </ciManagement>
+
+ <mailingLists>
+ <mailingList>
+ <name>sketches-user</name>
+ <archive>https://groups.google.com/forum/#!forum/sketches-user</archive>
+ <subscribe>mailto:sketches-user%2Bsubscribe@googlegroups.com</subscribe>
+ <unsubscribe>mailto:sketches-user%2Bunsubscribe@googlegroups.com</unsubscribe>
+ <post>mailto:sketches-user@googlegroups.com</post>
+ </mailingList>
+ </mailingLists>
+
+ <scm>
+ <connection>scm:git:ssh://git@github.com/DataSketches/sketches-core.git</connection>
+ <developerConnection>scm:git:ssh://git@github.com/DataSketches/sketches-core.git</developerConnection>
+ <url>https://github.com/DataSketches/sketches-core.git</url>
+ <tag>HEAD</tag>
+ </scm>
+
+ <prerequisites>
+ <maven>3.0.4</maven>
+ </prerequisites>
+
+ <repositories>
+ <repository>
+ <id>jcenter</id>
+ <name>bintray</name>
+ <url>https://jcenter.bintray.com</url>
+ </repository>
+ </repositories>
+
+ <distributionManagement>
+ <repository>
+ <id>sonatype-nexus-staging</id>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ </distributionManagement>
+
+ <!-- This is optional -->
+ <profiles>
+ <profile>
+ <id>strict</id>
+ <build>
+ <pluginManagement>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ <compilerId>javac-with-errorprone</compilerId>
+ <forceJavacCompilerUse>true</forceJavacCompilerUse>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-compiler-javac-errorprone</artifactId>
+ <version>2.8.2</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+ </profiles>
+ <!-- End of Environment Settings -->
+
+ <dependencies>
+ <dependency>
+ <groupId>org.ojalgo</groupId>
+ <artifactId>ojalgo</artifactId>
+ <version>43.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.yahoo.datasketches</groupId>
+ <artifactId>memory</artifactId>
+ <version>0.10.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.pig</groupId>
+ <artifactId>pig</artifactId>
+ <version>0.16.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ <version>2.8.1</version>
+ <optional>true</optional>
+ <exclusions>
+ <exclusion>
+ <groupId>asm</groupId>
+ <artifactId>asm</artifactId>
+ </exclusion>
+ </exclusions>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Test Scope -->
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.11</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- run locally for now
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>findbugs</artifactId>
+ <version>3.0.1</version>
+ <scope>test</scope>
+ </dependency>
+ -->
+ </dependencies>
+
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <relocations>
+ <relocation>
+ <pattern>com.yahoo.memory</pattern>
+ <shadedPattern>shaded.com.yahoo.memory</shadedPattern>
+ </relocation>
+ </relocations>
+ <shadedArtifactAttached>true</shadedArtifactAttached>
+ <shadedClassifierName>with-shaded-memory</shadedClassifierName>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!--
+ <show>private</show>
+ -->
+ <stylesheetfile>src/main/javadoc/stylesheet.css</stylesheetfile>
+ <docfilessubdirs>true</docfilessubdirs>
+ </configuration>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ <goal>test-jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <serverId>sonatype-nexus-staging</serverId>
+ <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+ <autoReleaseAfterClose>false</autoReleaseAfterClose>
+ </configuration>
+ </plugin>
+
+ <!-- Code coverage plugin, generates coverage report to target/site/jacoco/
+ To skip coverage generation add -Djacoco.skip=true -->
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>report</id>
+ <phase>test</phase>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+
+ <pluginManagement>
+ <plugins>
+ <!-- ORG.APACHE.MAVEN -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.6.1</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>3.0.1</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.8.2</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.6</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-help-plugin</artifactId>
+ <version>2.2</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>2.5.2</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.10.4</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.5.3</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.0.2</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.0.1</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.20</version>
+ </plugin>
+
+ <!-- OTHER -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.6.0</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <version>1.13</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.7.9</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <version>1.6.8</version>
+ </plugin>
+
+ <plugin>
+ <groupId>com.versioneye</groupId>
+ <artifactId>versioneye-maven-plugin</artifactId>
+ <version>3.11.4</version>
+ </plugin>
+
+ </plugins>
+ </pluginManagement>
+ </build>
+</project>
diff --git a/src/main/java/com/yahoo/sketches/MatrixFamily.java b/src/main/java/com/yahoo/sketches/MatrixFamily.java
new file mode 100644
index 0000000..07b4674
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/MatrixFamily.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017, Yahoo, Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Defines the various families of sketch and set operation classes. A family defines a set of
+ * classes that share fundamental algorithms and behaviors. The classes within a family may
+ * still differ by how they are stored and accessed. For example, internally there may be separate
+ * classes for algorithms that operate on the Java heap and off-heap.
+ * Not all of these families have parallel forms on and off-heap but are included for completeness.
+ *
+ * <p>Family IDs start at 128 to allow separation from sketches-core for as long as possible without
+ * inducing a mutual dependency between packages.</p>
+ *
+ * @author Lee Rhodes
+ * @author Jon Malkin
+ */
+public enum MatrixFamily {
+ /**
+ * The Frequent Directions sketch is used for approximate Singular Value Decomposition (SVD) of a
+ * matrix.
+ */
+ MATRIX(128, "FrequentDirections", 2, 3),
+ FREQUENTDIRECTIONS(129, "FrequentDirections", 2, 4);
+
+
+ private static final Map<Integer, MatrixFamily> lookupID = new HashMap<>();
+ private static final Map<String, MatrixFamily> lookupFamName = new HashMap<>();
+ private int id_;
+ private String famName_;
+ private int minPreLongs_;
+ private int maxPreLongs_;
+
+ static {
+ for (MatrixFamily f : values()) {
+ lookupID.put(f.getID(), f);
+ lookupFamName.put(f.getFamilyName().toUpperCase(), f);
+ }
+ }
+
+ MatrixFamily(final int id, final String famName, final int minPreLongs, final int maxPreLongs) {
+ id_ = id;
+ famName_ = famName.toUpperCase();
+ minPreLongs_ = minPreLongs;
+ maxPreLongs_ = maxPreLongs;
+ }
+
+ /**
+ * Returns the byte ID for this family
+ * @return the byte ID for this family
+ */
+ public int getID() {
+ return id_;
+ }
+
+ /**
+ *
+ * @param id the given id, a value ≥ 128.
+ */
+ public void checkFamilyID(final int id) {
+ if (id != id_) {
+ throw new SketchesArgumentException(
+ "Possible Corruption: This Family " + this.toString()
+ + " does not match the ID of the given Family: " + idToFamily(id).toString());
+ }
+ }
+
+ /**
+ * Returns the name for this family
+ * @return the name for this family
+ */
+ public String getFamilyName() {
+ return famName_;
+ }
+
+ /**
+ * Returns the minimum preamble size for this family in longs
+ * @return the minimum preamble size for this family in longs
+ */
+ public int getMinPreLongs() {
+ return minPreLongs_;
+ }
+
+ /**
+ * Returns the maximum preamble size for this family in longs
+ * @return the maximum preamble size for this family in longs
+ */
+ public int getMaxPreLongs() {
+ return maxPreLongs_;
+ }
+
+ @Override
+ public String toString() {
+ return famName_;
+ }
+
+ /**
+ * Returns the Family given the ID
+ * @param id the given ID
+ * @return the Family given the ID
+ */
+ public static MatrixFamily idToFamily(final int id) {
+ final MatrixFamily f = lookupID.get(id);
+ if (f == null) {
+ throw new SketchesArgumentException("Possible Corruption: Illegal Family ID: " + id);
+ }
+ return f;
+ }
+
+ /**
+ * Returns the Family given the family name
+ * @param famName the family name
+ * @return the Family given the family name
+ */
+ public static MatrixFamily stringToFamily(final String famName) {
+ final MatrixFamily f = lookupFamName.get(famName.toUpperCase());
+ if (f == null) {
+ throw new SketchesArgumentException("Possible Corruption: Illegal Family Name: " + famName);
+ }
+ return f;
+ }
+
+ /**
+ * Returns the Family given one of the recognized class objects on one of the Families
+ * @param obj a recognized Family class object
+ * @return the Family given one of the recognized class objects on one of the Families
+ */
+ public static MatrixFamily objectToFamily(final Object obj) {
+ final String sname = obj.getClass().getSimpleName().toUpperCase();
+ for (MatrixFamily f : values()) {
+ if (sname.contains(f.toString())) {
+ return f;
+ }
+ }
+ throw new SketchesArgumentException("Possible Corruption: Unknown object");
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/SketchesArgumentException.java b/src/main/java/com/yahoo/sketches/SketchesArgumentException.java
new file mode 100644
index 0000000..7683696
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/SketchesArgumentException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015-16, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches;
+
+/**
+ * Illegal Arguments Exception class for the library
+ *
+ * @author Lee Rhodes
+ */
+public class SketchesArgumentException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ //other constructors to be added as needed.
+
+ /**
+ * Constructs a new runtime exception with the specified detail message. The cause is not
+ * initialized, and may subsequently be initialized by a call to
+ * Throwable.initCause(java.lang.Throwable).
+ *
+ * @param message the detail message. The detail message is saved for later retrieval by the
+ * Throwable.getMessage() method.
+ */
+ public SketchesArgumentException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/decomposition/FrequentDirections.java b/src/main/java/com/yahoo/sketches/decomposition/FrequentDirections.java
new file mode 100644
index 0000000..e1583d0
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/decomposition/FrequentDirections.java
@@ -0,0 +1,578 @@
+package com.yahoo.sketches.decomposition;
+
+import static com.yahoo.memory.UnsafeUtil.LS;
+import static com.yahoo.sketches.decomposition.PreambleUtil.EMPTY_FLAG_MASK;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractFamilyID;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractFlags;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractK;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractN;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractNumColumns;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractNumRows;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractSVAdjustment;
+import static com.yahoo.sketches.decomposition.PreambleUtil.extractSerVer;
+import static com.yahoo.sketches.decomposition.PreambleUtil.getAndCheckPreLongs;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertFamilyID;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertFlags;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertK;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertN;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertNumColumns;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertNumRows;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertPreLongs;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertSVAdjustment;
+import static com.yahoo.sketches.decomposition.PreambleUtil.insertSerVer;
+
+import org.ojalgo.array.Array1D;
+import org.ojalgo.matrix.decomposition.SingularValue;
+import org.ojalgo.matrix.store.MatrixStore;
+import org.ojalgo.matrix.store.PrimitiveDenseStore;
+import org.ojalgo.matrix.store.SparseStore;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+import com.yahoo.sketches.matrix.Matrix;
+import com.yahoo.sketches.matrix.MatrixBuilder;
+
+/**
+ * This class implements the Frequent Directions algorithm proposed by Edo Liberty in "Simple and
+ * Deterministic Matrix Sketches," KDD 2013. The sketch provides an approximation to the singular
+ * value decomposition of a matrix with deterministic error bounds on the error between the
+ * approximation and the optimal rank-k matrix decomposition.
+ *
+ * @author Jon Malkin
+ */
+public final class FrequentDirections {
+ private final int k_;
+ private final int l_;
+ private final int d_;
+ private long n_;
+
+ private double svAdjustment_;
+
+ private PrimitiveDenseStore B_;
+ transient private int nextZeroRow_;
+
+ transient private final double[] sv_; // pre-allocated to fetch singular values
+ transient private final SparseStore<Double> S_; // to hold singular value matrix
+
+ /**
+ * Creates a new instance of a Frequent Directions sketch.
+ * @param k Number of dimensions (rows) in the sketch output
+ * @param d Number of dimensions per input vector (columns)
+ * @return An empty Frequent Directions sketch
+ */
+ public static FrequentDirections newInstance(final int k, final int d) {
+ return new FrequentDirections(k, d);
+ }
+
+ /**
+ * Instantiates a Frequent Directions sketch from a serialized image.
+ * @param srcMem Memory containing the serialized image of a Frequent Directions sketch
+ * @return A Frequent Directions sketch
+ */
+ public static FrequentDirections heapify(final Memory srcMem) {
+ final int preLongs = getAndCheckPreLongs(srcMem);
+ final int serVer = extractSerVer(srcMem);
+ if (serVer != PreambleUtil.SER_VER) {
+ throw new SketchesArgumentException("Invalid serialization version: " + serVer);
+ }
+
+ final int family = extractFamilyID(srcMem);
+ if (family != MatrixFamily.FREQUENTDIRECTIONS.getID()) {
+ throw new SketchesArgumentException("Possible corruption: Family id (" + family + ") "
+ + "is not a FrequentDirections sketch");
+ }
+
+ final int k = extractK(srcMem);
+ final int numRows = extractNumRows(srcMem);
+ final int d = extractNumColumns(srcMem);
+ final boolean empty = (extractFlags(srcMem) & EMPTY_FLAG_MASK) > 0;
+
+ if (empty) {
+ return new FrequentDirections(k, d);
+ }
+
+ final long offsetBytes = preLongs * Long.BYTES;
+ final long mtxBytes = srcMem.getCapacity() - offsetBytes;
+ final Matrix B = Matrix.heapify(srcMem.region(offsetBytes, mtxBytes), MatrixBuilder.Algo.OJALGO);
+ assert B != null;
+
+ final FrequentDirections fd
+ = new FrequentDirections(k, d, (PrimitiveDenseStore) B.getRawObject());
+ fd.n_ = extractN(srcMem);
+ fd.nextZeroRow_ = numRows;
+ fd.svAdjustment_ = extractSVAdjustment(srcMem);
+
+ return fd;
+ }
+
+ private FrequentDirections(final int k, final int d) {
+ this(k, d, null);
+ }
+
+ private FrequentDirections(final int k, final int d, final PrimitiveDenseStore B) {
+ if (k < 1) {
+ throw new IllegalArgumentException("Number of projected dimensions must be at least 1");
+ }
+ if (d < 1) {
+ throw new IllegalArgumentException("Number of feature dimensions must be at least 1");
+ }
+
+ k_ = k;
+ l_ = 2 * k;
+ d_ = d;
+
+ if (d_ < l_) {
+ throw new IllegalArgumentException("Running with d < 2k not yet supported");
+ }
+
+ svAdjustment_ = 0.0;
+
+ nextZeroRow_ = 0;
+ n_ = 0;
+
+ if (B == null) {
+ B_ = PrimitiveDenseStore.FACTORY.makeZero(l_, d_);
+ } else {
+ B_ = B;
+ }
+
+ final int svDim = Math.min(l_, d_);
+ sv_ = new double[svDim];
+ S_ = SparseStore.makePrimitive(svDim, svDim);
+ }
+
+ /**
+ * Update sketch with a dense input vector of exactly d dimensions.
+ * @param vector A dense input vector representing one row of the input matrix
+ */
+ public void update(final double[] vector) {
+ if (vector == null) {
+ return;
+ }
+
+ if (vector.length != d_) {
+ throw new IllegalArgumentException("Input vector has too few dimensions. Expected " + d_
+ + "; found " + vector.length);
+ }
+
+ if (nextZeroRow_ == l_) {
+ reduceRank();
+ }
+
+ // dense input so set all values
+ for (int i = 0; i < vector.length; ++i) {
+ B_.set(nextZeroRow_, i, vector[i]);
+ }
+
+ ++n_;
+ ++nextZeroRow_;
+ }
+
+ /**
+ * Merge a Frequent Directions sketch into the current one.
+ * @param fd A Frequent Direction sketch to be merged.
+ */
+ public void update(final FrequentDirections fd) {
+ if (fd == null || fd.nextZeroRow_ == 0) {
+ return;
+ }
+
+ if ((fd.d_ != d_) || (fd.k_ < k_)) {
+ throw new SketchesArgumentException("Incoming sketch must have same number of dimensions "
+ + "and no smaller a value of k");
+ }
+
+ for (int m = 0; m < fd.nextZeroRow_; ++m) {
+ if (nextZeroRow_ == l_) {
+ reduceRank();
+ }
+
+ final Array1D<Double> rv = fd.B_.sliceRow(m);
+ for (int i = 0; i < rv.count(); ++i) {
+ B_.set(nextZeroRow_, i, rv.get(i));
+ }
+
+ ++nextZeroRow_;
+ }
+
+ n_ += fd.n_;
+ svAdjustment_ += fd.svAdjustment_;
+ }
+
+ /**
+ * Checks if the sketch is empty, specifically whether it has processed any input data.
+ * @return True if hte sketch has not yet processed any input
+ */
+ public boolean isEmpty() {
+ return n_ == 0;
+ }
+
+ /**
+ * Returns the target number of dimensions, k, for this sketch.
+ * @return The sketch's configured k value
+ */
+ public int getK() { return k_; }
+
+ /**
+ * Returns the number of dimensions per input vector, d, for this sketch.
+ * @return The sketch's configured number of dimensions per input
+ */
+ public int getD() { return d_; }
+
+ /**
+ * Returns the total number of items this sketch has seen.
+ * @return The number of items processed by the sketch.
+ */
+ public long getN() { return n_; }
+
+ /**
+ * Returns the singular values of the sketch, adjusted for the mass subtracted off during the
+ * algorithm.
+ * @return An array of singular values.
+ */
+ public double[] getSingularValues() {
+ return getSingularValues(true);
+ }
+
+ /**
+ * Returns the singular values of the sketch, optionally adjusting for any mass subtracted off
+ * during the algorithm.
+ * @param compensative If true, adjusts for mass subtracted during the algorithm, otherwise
+ * uses raw singular values.
+ * @return As array of singular values.
+ */
+ public double[] getSingularValues(final boolean compensative) {
+ final SingularValue<Double> svd = SingularValue.make(B_);
+ svd.compute(B_);
+ svd.getSingularValues(sv_);
+
+ double medianSVSq = sv_[k_ - 1]; // (l_/2)th item, not yet squared
+ medianSVSq *= medianSVSq;
+ final double tmpSvAdj = svAdjustment_ + medianSVSq;
+ final double[] svList = new double[k_];
+
+ for (int i = 0; i < k_ - 1; ++i) {
+ final double val = sv_[i];
+ double adjSqSV = val * val - medianSVSq;
+ if (compensative) { adjSqSV += tmpSvAdj; }
+ svList[i] = adjSqSV < 0 ? 0.0 : Math.sqrt(adjSqSV);
+ }
+
+ return svList;
+ }
+
+ /**
+ * Returns an orthonormal projection Matrix that can be use to project input vectors into the
+ * k-dimensional space represented by the sketch.
+ * @return An orthonormal Matrix object
+ */
+ public Matrix getProjectionMatrix() {
+ final SingularValue<Double> svd = SingularValue.make(B_);
+ svd.compute(B_);
+ final MatrixStore<Double> m = svd.getQ2().transpose();
+
+ // not super efficient...
+ final Matrix result = Matrix.builder().build(k_, d_);
+ for (int i = 0; i < k_ - 1; ++i) { // last SV is 0
+ result.setRow(i, m.sliceRow(i).toRawCopy1D());
+ }
+
+ return result;
+ }
+
+ /**
+ * Calls <tt>getResult(true, false)</tt>
+ * @return A Matrix representing the data in this sketch
+ */
+ public Matrix getResult() {
+ return getResult(true, false);
+ }
+
+ /**
+ * Returns a Matrix with the sketch's estimate of the SVD of the input data.
+ * @param compress If true, force compression down to no more than k vectors
+ * @param compensative If true, applies adjustment to singular values based on the cumulative
+ * weight subtracted off
+ * @return A Matrix representing the data in this sketch
+ */
+ public Matrix getResult(final boolean compress, final boolean compensative) {
+ if (isEmpty()) {
+ return null;
+ }
+
+ if (compress && nextZeroRow_ > k_) {
+ reduceRank();
+ }
+
+ final PrimitiveDenseStore result;
+
+ if (compensative) {
+ // in the event we just called reduceRank(), the high rows are already zeroed out so no need
+ // to do so again
+ final SingularValue<Double> svd = SingularValue.make(B_);
+ svd.compute(B_);
+ svd.getSingularValues(sv_);
+
+ for (int i = 0; i < k_ - 1; ++i) {
+ final double val = sv_[i];
+ final double adjSV = Math.sqrt(val * val + svAdjustment_);
+ S_.set(i, i, adjSV);
+ }
+ for (int i = k_ - 1; i < S_.countColumns(); ++i) {
+ S_.set(i, i, 0.0);
+ }
+
+ //result = PrimitiveDenseStore.FACTORY.makeZero(l_, d_);
+ result = PrimitiveDenseStore.FACTORY.makeZero(nextZeroRow_, d_);
+ S_.multiply(svd.getQ2().transpose(), result);
+ } else {
+ result = PrimitiveDenseStore.FACTORY.makeZero(nextZeroRow_, d_);
+ for (int i = 0; i < nextZeroRow_; ++i) {
+ int j = 0;
+ for (double d : B_.sliceRow(i)) {
+ result.set(i, j++, d);
+ }
+ }
+ }
+
+ return Matrix.wrap(result);
+ }
+
+ /**
+ * Resets the sketch to its virgin state.
+ */
+ public void reset() {
+ n_ = 0;
+ nextZeroRow_ = 0;
+ }
+
+ /**
+ * Returns a serialized representation of the sketch. Equivalent to calling <tt>toByteArray
+ * (true)</tt>.
+ * <p>Note: May modify sketch state. If the sketch would store more than k rows, applies SVD to
+ * compress the sketch to examply k rows.</p>
+ * @return A serialized representation of the sketch.
+ */
+ public byte[] toByteArray() {
+ return toByteArray(true);
+ }
+
+ /**
+ * Returns a serialized representation of the sketch.
+ * <p>Note: If compress is true, will modify sketch state if the sketch would store more than k
+ * rows by applying SVD to compress the sketch to examply k rows.</p>
+ * @param compress If true, compresses teh sketch to no more than k rows.
+ * @return A serialized representation of the sketch.
+ */
+ public byte[] toByteArray(final boolean compress) {
+ final boolean empty = isEmpty();
+ final int serVer = 1;
+ final int familyId = MatrixFamily.FREQUENTDIRECTIONS.getID();
+
+ final Matrix wrapB = Matrix.wrap(B_);
+
+ // project down to k rows to serialize, chasing the 2GB byte[] limit
+ if (compress && nextZeroRow_ > k_) {
+ reduceRank();
+ }
+
+ final int preLongs = empty
+ ? MatrixFamily.FREQUENTDIRECTIONS.getMinPreLongs()
+ : MatrixFamily.FREQUENTDIRECTIONS.getMaxPreLongs();
+
+ final int mtxBytes = empty ? 0 : wrapB.getCompactSizeBytes(nextZeroRow_, d_);
+ final int outBytes = (preLongs * Long.BYTES) + mtxBytes;
+
+ final byte[] outArr = new byte[outBytes];
+ final WritableMemory memOut = WritableMemory.wrap(outArr);
+ final Object memObj = memOut.getArray();
+ final long memAddr = memOut.getCumulativeOffset(0L);
+
+ insertPreLongs(memObj, memAddr, preLongs);
+ insertSerVer(memObj, memAddr, serVer);
+ insertFamilyID(memObj, memAddr, familyId);
+ insertFlags(memObj, memAddr, (empty ? EMPTY_FLAG_MASK : 0));
+ insertK(memObj, memAddr, k_);
+ insertNumRows(memObj, memAddr, nextZeroRow_);
+ insertNumColumns(memObj, memAddr, d_);
+
+ if (empty) {
+ return outArr;
+ }
+
+ insertN(memObj, memAddr, n_);
+ insertSVAdjustment(memObj, memAddr, svAdjustment_);
+
+ memOut.putByteArray(preLongs * Long.BYTES,
+ wrapB.toCompactByteArray(nextZeroRow_, d_), 0, mtxBytes);
+
+ return outArr;
+ }
+
+ @Override
+ public String toString() {
+ return toString(false, false, false);
+ }
+
+ /**
+ * Returns a human-readable summary of the sketch and, optionally, prints the raw data.
+ * @param printMatrix If true, prints sketch's data matrix
+ * @return A String representation of the sketch.
+ */
+ public String toString(final boolean printMatrix) {
+ return toString(printMatrix, false, false);
+ }
+
+ /**
+ * Returns a human-readable summary of the sketch, optionally printing either the filled
+ * or complete sketch matrix, and also optionally adjusting the singular values based on the
+ * total weight subtacted during the algorithm.
+ * @param printMatrix If true, prints the sketch's data matrix
+ * @param fullMatrix If true, prints all rows; if false, only non-empty rows
+ * @param applyCompensation If true, prints adjusted singular values
+ * @return A String representation of the sketch.
+ */
+ public String toString(final boolean printMatrix, final boolean fullMatrix,
+ final boolean applyCompensation) {
+ final StringBuilder sb = new StringBuilder();
+
+ final String thisSimpleName = this.getClass().getSimpleName();
+
+ sb.append(LS);
+ sb.append("### ").append(thisSimpleName).append(" INFO: ").append(LS);
+ if (applyCompensation) {
+ sb.append("Applying compensative adjustments to matrix values").append(LS);
+ }
+ sb.append(" k : ").append(k_).append(LS);
+ sb.append(" d : ").append(d_).append(LS);
+ sb.append(" l : ").append(l_).append(LS);
+ sb.append(" n : ").append(n_).append(LS);
+ sb.append(" numRows : ").append(nextZeroRow_).append(LS);
+ sb.append(" SV adjustment: ").append(svAdjustment_).append(LS);
+
+ if (!printMatrix) {
+ return sb.toString();
+ }
+
+ sb.append(" Singular Vals: ")
+ .append(applyCompensation ? "(adjusted)" : "(unadjusted)").append(LS);
+ final double[] sv = getSingularValues(applyCompensation);
+ for (int i = 0; i < Math.min(k_, n_); ++i) {
+ if (sv[i] > 0.0) {
+ double val = sv[i];
+ if (val > 0.0 && applyCompensation) {
+ val = Math.sqrt(val * val + svAdjustment_);
+ }
+
+ sb.append(" \t").append(i).append(":\t").append(val).append(LS);
+ }
+ }
+
+ final Matrix mtx = Matrix.wrap(B_);
+
+ final int tmpRowDim = fullMatrix ? nextZeroRow_ : Math.min(k_, nextZeroRow_);
+ final int tmpColDim = (int) mtx.getNumColumns();
+
+ sb.append(" Matrix data :").append(LS);
+ sb.append(mtx.getClass().getName());
+ sb.append(" < ").append(tmpRowDim).append(" x ").append(tmpColDim).append(" >");
+
+ // First element
+ sb.append("\n{ { ").append(mtx.getElement(0, 0));
+
+ // Rest of the first row
+ for (int j = 1; j < tmpColDim; j++) {
+ sb.append(",\t").append(mtx.getElement(0, j));
+ }
+
+ // For each of the remaining rows
+ for (int i = 1; i < tmpRowDim; i++) {
+
+ // First column
+ sb.append(" },\n{ ").append(mtx.getElement(i, 0));
+
+ // Remaining columns
+ for (int j = 1; j < tmpColDim; j++) {
+ sb.append(",\t").append(mtx.getElement(i, j));
+ }
+ }
+
+ // Finish
+ sb.append(" } }").append(LS);
+ sb.append("### END SKETCH SUMMARY").append(LS);
+
+ return sb.toString();
+ }
+
+ int getNumRows() { return nextZeroRow_; }
+
+ // exists for testing
+ double getSvAdjustment() { return svAdjustment_; }
+
+ private void reduceRank() {
+ //++numReduce;
+
+ final SingularValue<Double> svd = SingularValue.make(B_);
+ svd.compute(B_);
+ svd.getSingularValues(sv_);
+
+ if (sv_.length >= k_) {
+ double medianSVSq = sv_[k_ - 1]; // (l_/2)th item, not yet squared
+ medianSVSq *= medianSVSq;
+ svAdjustment_ += medianSVSq; // always track, even if not using compensative mode
+ for (int i = 0; i < k_ - 1; ++i) {
+ final double val = sv_[i];
+ final double adjSqSV = val * val - medianSVSq;
+ S_.set(i, i, adjSqSV < 0 ? 0.0 : Math.sqrt(adjSqSV));
+ }
+ for (int i = k_ - 1; i < S_.countColumns(); ++i) {
+ S_.set(i, i, 0.0);
+ }
+ nextZeroRow_ = k_;
+ } else {
+ for (int i = 0; i < sv_.length; ++i) {
+ S_.set(i, i, sv_[i]);
+ }
+ for (int i = sv_.length; i < S_.countColumns(); ++i) {
+ S_.set(i, i, 0.0);
+ }
+ nextZeroRow_ = sv_.length;
+ throw new RuntimeException("Running with d < 2k not yet supported");
+ }
+
+ S_.multiply(svd.getQ2().transpose()).supplyTo(B_);
+ }
+
+ /*
+ private static double computeFrobNorm(final MatrixStore<Double> M) {
+ double sum = 0.0;
+ for (double d : M) {
+ sum += d * d;
+ }
+ return Math.sqrt(sum);
+ }
+
+ private static double computeFrobNorm(final MatrixStore<Double> M, final int k) {
+ double sum = 0.0;
+ for (int i = 0; i < k; ++i) {
+ for (double d : M.sliceRow(i)) {
+ sum += d * d;
+ }
+ }
+ return Math.sqrt(sum);
+ }
+
+ private static MatrixStore<Double> getKRows(final MatrixStore<Double> M, final int k) {
+ PrimitiveDenseStore result = PrimitiveDenseStore.FACTORY.makeZero(k, M.countColumns());
+ for (int i = 0; i < k; ++i) {
+ int j = 0;
+ for (double d : M.sliceRow(i)) {
+ result.set(i, j++, d);
+ }
+ }
+ return result;
+ }
+ */
+}
diff --git a/src/main/java/com/yahoo/sketches/decomposition/PreambleUtil.java b/src/main/java/com/yahoo/sketches/decomposition/PreambleUtil.java
new file mode 100644
index 0000000..a1d14f4
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/decomposition/PreambleUtil.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2017, Yahoo, Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.decomposition;
+
+import static com.yahoo.memory.UnsafeUtil.unsafe;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+
+/**
+ * This class defines the preamble items structure and provides basic utilities for some of the key fields.
+ *
+ * <p>
+ * The low significance bytes of this <tt>long</tt> items structure are on the right. Multi-byte
+ * integers (<tt>int</tt> and <tt>long</tt>) are stored in native byte order. All <tt>byte</tt>
+ * values are treated as unsigned.</p>
+ *
+ * <p>An empty Frequent Directions sketch requires 16 bytes. A non-empty sketch requires 32 bytes
+ * of preamble. The matrix is dense and is expected to dominate storage.</p>
+ *
+ * <pre>
+ * Long || Start Byte Adr:
+ * Adr:
+ * || 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ * 0 ||----------Sketch Size (k)----------| Flags | FamID | SerVer | Preamble_Longs |
+ *
+ * || 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
+ * 1 ||-----------Num. Columns------------|------Current Num Rows-------------------------|
+ *
+ * || 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
+ * 2 ||-----------------------------Total Records Seen (n)--------------------------------|
+ *
+ * || 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 |
+ * 3 ||------------------------------Total SV Adjustment----------------------------------|
+ * </pre>
+ *
+ * @author Jon Malkin
+ */
+public final class PreambleUtil {
+
+ /**
+ * The java line separator character as a String.
+ */
+ public static final String LS = System.getProperty("line.separator");
+
+ private PreambleUtil() {}
+
+ // ###### DO NOT MESS WITH THIS FROM HERE ...
+ // Preamble byte Addresses
+ static final int PREAMBLE_LONGS_BYTE = 0;
+ static final int SER_VER_BYTE = 1;
+ static final int FAMILY_BYTE = 2;
+ static final int FLAGS_BYTE = 3;
+ static final int K_INT = 4;
+ static final int NUM_ROWS_INT = 8;
+ static final int NUM_COLUMNS_INT = 12;
+ static final int N_LONG = 16;
+ static final int SV_ADJUSTMENT_DOUBLE = 24;
+
+ // flag bit masks
+ static final int EMPTY_FLAG_MASK = 4;
+ static final int COMPENSATIVE_FLAG_MASK = 128;
+
+ // Other constants
+ static final int SER_VER = 1;
+
+
+ /**
+ * Returns a human readable string summary of the preamble state of the given Memory.
+ * Note: other than making sure that the given Memory size is large
+ * enough for just the preamble, this does not do much value checking of the contents of the
+ * preamble as this is primarily a tool for debugging the preamble visually.
+ *
+ * @param mem the given Memory.
+ * @return the summary preamble string.
+ */
+ public static String preambleToString(final Memory mem) {
+
+ final int preLongs = getAndCheckPreLongs(mem); // make sure we can get the assumed preamble
+ final MatrixFamily family = MatrixFamily.idToFamily(extractFamilyID(mem));
+
+ final int serVer = extractSerVer(mem);
+ final int flags = extractFlags(mem);
+ final String flagsStr = Integer.toBinaryString(flags) + ", " + flags;
+ final boolean isEmpty = (flags & EMPTY_FLAG_MASK) > 0;
+
+ final int k = extractK(mem);
+ final int d = extractNumColumns(mem);
+ final int numRows = extractNumRows(mem);
+
+ long n = 0;
+ double svAdjustment = 0.0;
+ if (!isEmpty) {
+ n = extractN(mem);
+ svAdjustment = extractSVAdjustment(mem);
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(LS)
+ .append("### START ")
+ .append(family.getFamilyName().toUpperCase())
+ .append(" PREAMBLE SUMMARY").append(LS)
+ .append("Byte 0: Preamble Longs : ").append(preLongs).append(LS)
+ .append("Byte 1: Serialization Version: ").append(serVer).append(LS)
+ .append("Byte 2: Family : ").append(family.toString()).append(LS)
+ .append("Byte 3: Flags Field : ").append(flagsStr).append(LS)
+ .append(" EMPTY : ").append(isEmpty).append(LS)
+ .append("Bytes 4-7 : Sketch Size (k) : ").append(k).append(LS)
+ .append("Bytes 8-11: Num Rows : ").append(numRows).append(LS)
+ .append("Bytes 12-15: Num Dimensions : ").append(d).append(LS);
+
+ if (!isEmpty) {
+ sb.append("Bytes 16-23: Items Seen(n) : ").append(n).append(LS);
+ sb.append("Bytes 24-31: SV Adjustment : ").append(svAdjustment).append(LS);
+ }
+
+ final long numBytes = numRows * d * Double.BYTES;
+ sb.append("TOTAL Sketch Bytes : ").append(mem.getCapacity()).append(LS)
+ .append(" Preamble Bytes : ").append(preLongs << 3).append(LS)
+ .append(" Data Bytes : ").append(numBytes).append(LS)
+ .append("### END ")
+ .append(family.getFamilyName().toUpperCase())
+ .append(" PREAMBLE SUMMARY").append(LS);
+ return sb.toString();
+ }
+
+ // Extraction methods
+
+ static int extractPreLongs(final Memory mem) {
+ return mem.getInt(PREAMBLE_LONGS_BYTE) & 0xFF;
+ }
+
+ static int extractSerVer(final Memory mem) {
+ return mem.getInt(SER_VER_BYTE) & 0xFF;
+ }
+
+ static int extractFamilyID(final Memory mem) {
+ return mem.getByte(FAMILY_BYTE) & 0xFF;
+ }
+
+ static int extractFlags(final Memory mem) {
+ return mem.getByte(FLAGS_BYTE) & 0xFF;
+ }
+
+ static int extractK(final Memory mem) {
+ return mem.getInt(K_INT);
+ }
+
+ static int extractNumRows(final Memory mem) {
+ return mem.getInt(NUM_ROWS_INT);
+ }
+
+ static int extractNumColumns(final Memory mem) {
+ return mem.getInt(NUM_COLUMNS_INT);
+ }
+
+ static long extractN(final Memory mem) {
+ return mem.getLong(N_LONG);
+ }
+
+ static double extractSVAdjustment(final Memory mem) {
+ return mem.getDouble(SV_ADJUSTMENT_DOUBLE);
+ }
+
+
+ // Insertion methods
+
+ static void insertPreLongs(final Object memObj, final long memAddr, final int preLongs) {
+ unsafe.putByte(memObj, memAddr + PREAMBLE_LONGS_BYTE, (byte) preLongs);
+ }
+
+ static void insertSerVer(final Object memObj, final long memAddr, final int serVer) {
+ unsafe.putByte(memObj, memAddr + SER_VER_BYTE, (byte) serVer);
+ }
+
+ static void insertFamilyID(final Object memObj, final long memAddr, final int matrixFamId) {
+ unsafe.putByte(memObj, memAddr + FAMILY_BYTE, (byte) matrixFamId);
+ }
+
+ static void insertFlags(final Object memObj, final long memAddr, final int flags) {
+ unsafe.putByte(memObj, memAddr + FLAGS_BYTE, (byte) flags);
+ }
+
+ static void insertK(final Object memObj, final long memAddr, final int k) {
+ unsafe.putInt(memObj, memAddr + K_INT, k);
+ }
+
+ static void insertNumRows(final Object memObj, final long memAddr, final int numRows) {
+ unsafe.putInt(memObj, memAddr + NUM_ROWS_INT, numRows);
+ }
+
+ static void insertNumColumns(final Object memObj, final long memAddr, final int numColumns) {
+ unsafe.putInt(memObj, memAddr + NUM_COLUMNS_INT, numColumns);
+ }
+
+ static void insertN(final Object memObj, final long memAddr, final long n) {
+ unsafe.putLong(memObj, memAddr + N_LONG, n);
+ }
+
+ static void insertSVAdjustment(final Object memObj, final long memAddr, final double adj) {
+ unsafe.putDouble(memObj, memAddr + SV_ADJUSTMENT_DOUBLE, adj);
+ }
+
+
+ /**
+ * Checks Memory for capacity to hold the preamble and returns the extracted preLongs.
+ * @param mem the given Memory
+ * @return the extracted prelongs value.
+ */
+ static int getAndCheckPreLongs(final Memory mem) {
+ final long cap = mem.getCapacity();
+ if (cap < 8) { throwNotBigEnough(cap, 8); }
+ final int preLongs = extractPreLongs(mem);
+ final int required = Math.max(preLongs << 3, 8);
+ if (cap < required) { throwNotBigEnough(cap, required); }
+ return preLongs;
+ }
+
+ private static void throwNotBigEnough(final long cap, final int required) {
+ throw new SketchesArgumentException(
+ "Possible Corruption: Size of byte array or Memory not large enough: Size: " + cap
+ + ", Required: " + required);
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/decomposition/package-info.java b/src/main/java/com/yahoo/sketches/decomposition/package-info.java
new file mode 100644
index 0000000..b51f02d
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/decomposition/package-info.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root
+ * for terms.
+ */
+
+/**
+ * <p>This package is dedicated to streaming algorithms that enable approximate matrix
+ * decompositions.</p>
+ *
+ * <p>These sketches are mergeable and can be serialized and deserialized to/from a compact
+ * form.</p>
+ */
+package com.yahoo.sketches.decomposition;
\ No newline at end of file
diff --git a/src/main/java/com/yahoo/sketches/matrix/Matrix.java b/src/main/java/com/yahoo/sketches/matrix/Matrix.java
new file mode 100644
index 0000000..e91a69b
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/matrix/Matrix.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root
+ * for terms.
+ */
+
+package com.yahoo.sketches.matrix;
+
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.LS;
+
+import org.ojalgo.matrix.store.PrimitiveDenseStore;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+
+
+/**
+ * Provides an implementation-agnostic wrapper around Matrix classes.
+ *
+ * @author Jon Malkin
+ */
+public abstract class Matrix {
+ int numRows_;
+ int numCols_;
+
+ /**
+ * Loads matrix from srcMem, assuming storage in column-major order to ensure portability.
+ * Does not necessarily encode matrix size; do not expect size checks based on passed-in
+ * parameters.
+ *
+ * @param srcMem Memory wrapping the matrix
+ * @param type Matrix implementation type to use
+ * @return The heapified matrix
+ */
+ public static Matrix heapify(final Memory srcMem, final MatrixBuilder.Algo type) {
+ switch (type) {
+ case OJALGO:
+ return MatrixImplOjAlgo.heapifyInstance(srcMem);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Wraps an object without allocating memory. This method will throw an exception if the mtx
+ * Object is not of the same type as the implementing class's native format.
+ * @param mtx Matrix object to wrap
+ * @return A Matrix object
+ */
+ public static Matrix wrap(final Object mtx) {
+ if (mtx == null) {
+ return null;
+ } else if (mtx instanceof PrimitiveDenseStore) {
+ return MatrixImplOjAlgo.wrap((PrimitiveDenseStore) mtx);
+ }
+ else {
+ throw new SketchesArgumentException("wrap() does not currently support "
+ + mtx.getClass().toString());
+ }
+ }
+
+ /**
+ * Gets a builder to be able to create instances of Matrix objects
+ * @return a MatrixBuilder object
+ */
+ public static MatrixBuilder builder() {
+ return new MatrixBuilder();
+ }
+
+ /**
+ * Returns the raw data object backing this Matrix, as an Object. Must be cast to the
+ * appropriate type (assuming knowledge of the implementation) to be used.
+ * @return An Object pointing to the raw data backing this Matrix
+ */
+ public abstract Object getRawObject();
+
+ /**
+ * Serializes the Matrix in a custom format as a byte array
+ * @return A byte[] conttaining a serialized Matrix
+ */
+ public abstract byte[] toByteArray();
+
+ /**
+ * Serializes a sub-Matrix by storing only the first numRows and numCols rows and columns,
+ * respsectively.
+ * @param numRows Number of rows to write
+ * @param numCols Number of columns to write
+ * @return A byte[] containing the serialized sub-Matrix.
+ */
+ public abstract byte[] toCompactByteArray(int numRows, int numCols);
+
+ /**
+ * Returns a single element from the Matrix
+ * @param row Row index of target element (0-based)
+ * @param col Column index of target elemtn (0-based)
+ * @return Matrix value at (row, column)
+ */
+ public abstract double getElement(int row, int col);
+
+ /**
+ * Returns a copy of an entire row of the Matrix
+ * @param row Row index to return (0-based)
+ * @return A double[] representing the Matrix row
+ */
+ public abstract double[] getRow(int row);
+
+ /**
+ * Returns a copy of an entire column of the Matrix
+ * @param col Column index to return (0-based)
+ * @return A double[] representing the Matrix column
+ */
+ public abstract double[] getColumn(int col);
+
+ /**
+ * Sets a single element inthe Matrix
+ * @param row Row index of target element (0-based)
+ * @param col Column index of target element (0-based)
+ * @param value The value to insert into the Matrix at (row, column)
+ */
+ public abstract void setElement(int row, int col, double value);
+
+ /**
+ * Sets an entire row of the Matrix, by copying data from the input
+ * @param row Target row index (0-based)
+ * @param values Array of values to write into the Matrix
+ */
+ public abstract void setRow(int row, double[] values);
+
+ /**
+ * Sets an entire column of the Matrix, by copying data from the input
+ * @param column Target column index (0-based)
+ * @param values Array of values to write into the Matrix
+ */
+ public abstract void setColumn(int column, double[] values);
+
+ /**
+ * Gets the number of rows in the Matrix
+ * @return Configured number of rows in the Matrix
+ */
+ public long getNumRows() { return numRows_; }
+
+ /**
+ * Gets the number of columns in the Matrix
+ * @return Configured number of columns in the Matrix
+ */
+ public long getNumColumns() { return numCols_; }
+
+ /**
+ * Gets serialized size of the Matrix, in bytes.
+ * @return Number of bytes needed for a serialized Matrix
+ */
+ public int getSizeBytes() {
+ final int preBytes = MatrixFamily.MATRIX.getMinPreLongs() * Long.BYTES;
+ final int mtxBytes = (numRows_ * numCols_) * Double.BYTES;
+ return preBytes + mtxBytes;
+ }
+
+ /**
+ * Gets serialized size of the Matrix in cmpact form, in bytes.
+ * @param rows Number of rows to select for writing
+ * @param cols Number of columns to select for writing
+ * @return Number of bytes needed to serialize the first (rows, cols) of this Matrix
+ */
+ public int getCompactSizeBytes(final int rows, final int cols) {
+ final int nRows = Math.min(rows, numRows_);
+ final int nCols = Math.min(cols, numCols_);
+
+ if (nRows < 1 || nCols < 1) {
+ return MatrixFamily.MATRIX.getMinPreLongs() * Long.BYTES;
+ } else if (nRows == numRows_ && nCols == numCols_) {
+ return getSizeBytes();
+ }
+
+ final int preBytes = MatrixFamily.MATRIX.getMaxPreLongs() * Long.BYTES;
+ final int mtxBytes = (nRows * nCols) * Double.BYTES;
+ return preBytes + mtxBytes;
+ }
+
+ /**
+ * Writes information about this Matrix to a String.
+ * @return A human-readable representation of a Matrix
+ */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append(" Matrix data :").append(LS);
+ sb.append(this.getClass().getName());
+ sb.append(" < ").append(numRows_).append(" x ").append(numCols_).append(" >");
+
+ // First element
+ sb.append("\n{ { ").append(getElement(0, 0));
+
+ // Rest of the first row
+ for (int j = 1; j < numCols_; j++) {
+ sb.append(",\t").append(getElement(0, j));
+ }
+
+ // For each of the remaining rows
+ for (int i = 1; i < numRows_; i++) {
+
+ // First column
+ sb.append(" },\n{ ").append(getElement(i, 0));
+
+ // Remaining columns
+ for (int j = 1; j < numCols_; j++) {
+ sb.append(",\t").append(getElement(i, j));
+ }
+ }
+
+ // Finish
+ sb.append(" } }").append(LS);
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/matrix/MatrixBuilder.java b/src/main/java/com/yahoo/sketches/matrix/MatrixBuilder.java
new file mode 100644
index 0000000..b21f063
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/matrix/MatrixBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root
+ * for terms.
+ */
+
+package com.yahoo.sketches.matrix;
+
+import com.yahoo.sketches.SketchesArgumentException;
+
+/**
+ * Provides a builder for Matrix objects.
+ */
+public class MatrixBuilder {
+ public enum Algo {
+ OJALGO(1, "ojAlgo"),
+ NATIVE(2, "native");
+
+ private int id_;
+ private String name_;
+
+ Algo(final int id, final String name) {
+ id_ = id;
+ name_ = name;
+ }
+
+ public int getId() { return id_; }
+
+ public String getName() { return name_; }
+
+ @Override
+ public String toString() { return name_; }
+ }
+
+ private Algo type_ = Algo.OJALGO; // default type
+
+ MatrixBuilder() {}
+
+ /**
+ * Sets the underlying type of object to use with any Matrix objects created.
+ * @param type One of the supported types
+ * @return This MatrixBuilder object
+ */
+ public MatrixBuilder setType(final Algo type) {
+ type_ = type;
+ return this;
+ }
+
+ /**
+ * Returns a value from an enum definig the type of object backing any Matrix objects created.
+ * @return An item from the Algo enum.
+ */
+ public Algo getFamily() {
+ return type_;
+ }
+
+ /**
+ * Instantiates a new, empty matrix of the target size
+ *
+ * @param numRows Number of rows in matrix
+ * @param numCols Number of columns in matrix
+ * @return An empty matrix of the requested size
+ */
+ public Matrix build(final int numRows, final int numCols) {
+ switch (type_) {
+ case OJALGO:
+ return MatrixImplOjAlgo.newInstance(numRows, numCols);
+
+ case NATIVE:
+ default:
+ throw new SketchesArgumentException("Only Algo.OJALGO is currently supported Matrix type");
+ }
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/matrix/MatrixImplOjAlgo.java b/src/main/java/com/yahoo/sketches/matrix/MatrixImplOjAlgo.java
new file mode 100644
index 0000000..590002b
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/matrix/MatrixImplOjAlgo.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root
+ * for terms.
+ */
+
+package com.yahoo.sketches.matrix;
+
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.COMPACT_FLAG_MASK;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractFamilyID;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractFlags;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractNumColumns;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractNumColumnsUsed;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractNumRows;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractNumRowsUsed;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractPreLongs;
+import static com.yahoo.sketches.matrix.MatrixPreambleUtil.extractSerVer;
+
+import org.ojalgo.matrix.store.PrimitiveDenseStore;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+
+public final class MatrixImplOjAlgo extends Matrix {
+ private PrimitiveDenseStore mtx_;
+
+ private MatrixImplOjAlgo(final int numRows, final int numCols) {
+ mtx_ = PrimitiveDenseStore.FACTORY.makeZero(numRows, numCols);
+ numRows_ = numRows;
+ numCols_ = numCols;
+ }
+
+ private MatrixImplOjAlgo(final PrimitiveDenseStore mtx) {
+ mtx_ = mtx;
+ numRows_ = (int) mtx.countRows();
+ numCols_ = (int) mtx.countColumns();
+ }
+
+ static Matrix newInstance(final int numRows, final int numCols) {
+ return new MatrixImplOjAlgo(numRows, numCols);
+ }
+
+ static Matrix heapifyInstance(final Memory srcMem) {
+ final int minBytes = MatrixFamily.MATRIX.getMinPreLongs() * Long.BYTES;
+ final long memCapBytes = srcMem.getCapacity();
+ if (memCapBytes < minBytes) {
+ throw new SketchesArgumentException("Source Memory too small: " + memCapBytes
+ + " < " + minBytes);
+ }
+
+ final int preLongs = extractPreLongs(srcMem);
+ final int serVer = extractSerVer(srcMem);
+ final int familyID = extractFamilyID(srcMem);
+
+ if (serVer != 1) {
+ throw new SketchesArgumentException("Invalid SerVer reading srcMem. Expected 1, found: "
+ + serVer);
+ }
+ if (familyID != MatrixFamily.MATRIX.getID()) {
+ throw new SketchesArgumentException("srcMem does not point to a Matrix");
+ }
+
+ final int flags = extractFlags(srcMem);
+ final boolean isCompact = (flags & COMPACT_FLAG_MASK) > 0;
+
+ int nRows = extractNumRows(srcMem);
+ int nCols = extractNumColumns(srcMem);
+
+ final MatrixImplOjAlgo matrix = new MatrixImplOjAlgo(nRows, nCols);
+ if (isCompact) {
+ nRows = extractNumRowsUsed(srcMem);
+ nCols = extractNumColumnsUsed(srcMem);
+ }
+
+ int memOffset = preLongs * Long.BYTES;
+ for (int c = 0; c < nCols; ++c) {
+ for (int r = 0; r < nRows; ++r) {
+ matrix.mtx_.set(r, c, srcMem.getDouble(memOffset));
+ memOffset += Double.BYTES;
+ }
+ }
+
+ return matrix;
+ }
+
+ static Matrix wrap(final PrimitiveDenseStore mtx) {
+ return new MatrixImplOjAlgo(mtx);
+ }
+
+ public Object getRawObject() {
+ return mtx_;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ final int preLongs = 2;
+ final long numElements = mtx_.count();
+ assert numElements == mtx_.countColumns() * mtx_.countRows();
+
+ final int outBytes = (int) ((preLongs * Long.BYTES) + (numElements * Double.BYTES));
+ final byte[] outByteArr = new byte[outBytes];
+ final WritableMemory memOut = WritableMemory.wrap(outByteArr);
+ final Object memObj = memOut.getArray();
+ final long memAddr = memOut.getCumulativeOffset(0L);
+
+ MatrixPreambleUtil.insertPreLongs(memObj, memAddr, preLongs);
+ MatrixPreambleUtil.insertSerVer(memObj, memAddr, MatrixPreambleUtil.SER_VER);
+ MatrixPreambleUtil.insertFamilyID(memObj, memAddr, MatrixFamily.MATRIX.getID());
+ MatrixPreambleUtil.insertFlags(memObj, memAddr, 0);
+ MatrixPreambleUtil.insertNumRows(memObj, memAddr, (int) mtx_.countRows());
+ MatrixPreambleUtil.insertNumColumns(memObj, memAddr, (int) mtx_.countColumns());
+ memOut.putDoubleArray(preLongs << 3, mtx_.data, 0, (int) numElements);
+
+ return outByteArr;
+ }
+
+ @Override
+ public byte[] toCompactByteArray(final int numRows, final int numCols) {
+ // TODO: row/col limit checks
+
+ final int preLongs = 3;
+
+ // for non-compact we can do an array copy, so save as non-compact if using the entire matrix
+ final long numElements = numRows * numCols;
+ final boolean isCompact = numElements < mtx_.count();
+ if (!isCompact) {
+ return toByteArray();
+ }
+
+ assert numElements < mtx_.count();
+
+ //final boolean isEmpty = (numRows == 0) || (numColumns == 0);
+ //final int flags = COMPACT_FLAG_MASK | (isEmpty ? EMPTY_FLAG_MASK : 0);
+
+ final int outBytes = (int) ((preLongs * Long.BYTES) + (numElements * Double.BYTES));
+ final byte[] outByteArr = new byte[outBytes];
+ final WritableMemory memOut = WritableMemory.wrap(outByteArr);
+ final Object memObj = memOut.getArray();
+ final long memAddr = memOut.getCumulativeOffset(0L);
+
+ MatrixPreambleUtil.insertPreLongs(memObj, memAddr, preLongs);
+ MatrixPreambleUtil.insertSerVer(memObj, memAddr, MatrixPreambleUtil.SER_VER);
+ MatrixPreambleUtil.insertFamilyID(memObj, memAddr, MatrixFamily.MATRIX.getID());
+ MatrixPreambleUtil.insertFlags(memObj, memAddr, COMPACT_FLAG_MASK);
+ MatrixPreambleUtil.insertNumRows(memObj, memAddr, (int) mtx_.countRows());
+ MatrixPreambleUtil.insertNumColumns(memObj, memAddr, (int) mtx_.countColumns());
+ MatrixPreambleUtil.insertNumRowsUsed(memObj, memAddr, numRows);
+ MatrixPreambleUtil.insertNumColumnsUsed(memObj, memAddr, numCols);
+
+ // write elements in column-major order
+ long offsetBytes = preLongs * Long.BYTES;
+ for (int c = 0; c < numCols; ++c) {
+ for (int r = 0; r < numRows; ++r) {
+ memOut.putDouble(offsetBytes, mtx_.get(r, c));
+ offsetBytes += Double.BYTES;
+ }
+ }
+
+ return outByteArr;
+ }
+
+ @Override
+ public double getElement(final int row, final int col) {
+ return mtx_.get(row, col);
+ }
+
+ @Override
+ public double[] getRow(final int row) {
+ final int cols = (int) mtx_.countColumns();
+ final double[] result = new double[cols];
+ for (int c = 0; c < cols; ++c) {
+ result[c] = mtx_.get(row, c);
+ }
+ return result;
+ }
+
+ @Override
+ public double[] getColumn(final int col) {
+ final int rows = (int) mtx_.countRows();
+ final double[] result = new double[rows];
+ for (int r = 0; r < rows; ++r) {
+ result[r] = mtx_.get(r, col);
+ }
+ return result;
+ }
+
+ @Override
+ public void setElement(final int row, final int col, final double value) {
+ mtx_.set(row, col, value);
+ }
+
+ @Override
+ public void setRow(final int row, final double[] values) {
+ if (values.length != mtx_.countColumns()) {
+ throw new SketchesArgumentException("Invalid number of elements for row. Expected "
+ + mtx_.countColumns() + ", found " + values.length);
+ }
+
+ for (int i = 0; i < mtx_.countColumns(); ++i) {
+ mtx_.set(row, i, values[i]);
+ }
+ }
+
+ @Override
+ public void setColumn(final int column, final double[] values) {
+ if (values.length != mtx_.countRows()) {
+ throw new SketchesArgumentException("Invalid number of elements for column. Expected "
+ + mtx_.countRows() + ", found " + values.length);
+ }
+
+ for (int i = 0; i < mtx_.countRows(); ++i) {
+ mtx_.set(i, column, values[i]);
+ }
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/matrix/MatrixPreambleUtil.java b/src/main/java/com/yahoo/sketches/matrix/MatrixPreambleUtil.java
new file mode 100644
index 0000000..9a5af74
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/matrix/MatrixPreambleUtil.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2017, Yahoo, Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms.
+ */
+
+package com.yahoo.sketches.matrix;
+
+import static com.yahoo.memory.UnsafeUtil.unsafe;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+
+/**
+ * This class defines the preamble items structure and provides basic utilities for some of the
+ * key fields for a Matrix
+ *
+ * <p>
+ * The low significance bytes of this <tt>long</tt> items structure are on the right. Multi-byte
+ * integers (<tt>int</tt> and <tt>long</tt>) are stored in native byte order. All <tt>byte</tt>
+ * values are treated as unsigned.</p>
+ *
+ * <p>An empty or non-compact Matrix requires 16 bytes. A compact under-full matrix requires
+ * 24 bytes of preamble.</p>
+ *
+ * <pre>
+ * Long || Start Byte Adr:
+ * Adr:
+ * || 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ * 0 ||-------------Reserved--------------| Flags | FamID | SerVer | Preamble_Longs |
+ *
+ * || 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |
+ * 1 ||-----------Num. Columns------------|-------------Num. Rows-------------------------|
+ *
+ * || 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
+ * 2 ||---------Num. Columns Used---------|----------Num. Rows Used-----------------------|
+ * </pre>
+ *
+ * @author Jon Malkin
+ */
+public final class MatrixPreambleUtil {
+
+ /**
+ * The java line separator character as a String.
+ */
+ public static final String LS = System.getProperty("line.separator");
+
+ private MatrixPreambleUtil() {}
+
+ // ###### DO NOT MESS WITH THIS FROM HERE ...
+ // Preamble byte Addresses
+ private static final int PREAMBLE_LONGS_BYTE = 0;
+ private static final int SER_VER_BYTE = 1;
+ private static final int FAMILY_BYTE = 2;
+ private static final int FLAGS_BYTE = 3;
+ private static final int NUM_ROWS_INT = 8;
+ private static final int NUM_COLUMNS_INT = 12;
+ private static final int ROWS_USED_INT = 16;
+ private static final int COLS_USED_INT = 20;
+
+ // flag bit masks
+ //static final int EMPTY_FLAG_MASK = 4;
+ static final int COMPACT_FLAG_MASK = 8;
+
+ // Other constants
+ static final int SER_VER = 1;
+
+
+
+ /**
+ * Returns a human readable string summary of the preamble state of the given byte array.
+ * Used primarily in testing.
+ *
+ * @param byteArr the given byte array.
+ * @return the summary preamble string.
+ */
+ public static String preambleToString(final byte[] byteArr) {
+ final Memory mem = Memory.wrap(byteArr);
+ return preambleToString(mem);
+ }
+
+ /**
+ * Returns a human readable string summary of the preamble state of the given Memory.
+ * Note: other than making sure that the given Memory size is large
+ * enough for just the preamble, this does not do much value checking of the contents of the
+ * preamble as this is primarily a tool for debugging the preamble visually.
+ *
+ * @param mem the given Memory.
+ * @return the summary preamble string.
+ */
+ private static String preambleToString(final Memory mem) {
+
+ final int preLongs = getAndCheckPreLongs(mem); // make sure we can get the assumed preamble
+ final MatrixFamily family = MatrixFamily.idToFamily(mem.getByte(FAMILY_BYTE));
+ if (family != MatrixFamily.MATRIX) {
+ throw new SketchesArgumentException("Invalid family in memory region. Expected Matrix; "
+ + "Found: " + family.getFamilyName());
+ }
+
+ final int serVer = extractSerVer(mem);
+ if (serVer != SER_VER) {
+ throw new SketchesArgumentException("Invalid serialization version in memory region. "
+ + "Found: " + serVer);
+ }
+
+ final int flags = extractFlags(mem);
+ final String flagsStr = Integer.toBinaryString(flags) + ", " + flags;
+ //final boolean isEmpty = (flags & EMPTY_FLAG_MASK) > 0;
+ final boolean isCompact = (flags & COMPACT_FLAG_MASK) > 0;
+
+ final int numRows = extractNumRows(mem);
+ final int numCols = extractNumColumns(mem);
+
+ int numRowsUsed = numRows;
+ int numColsUsed = numCols;
+ if (isCompact) {
+ numRowsUsed = extractNumRowsUsed(mem);
+ numColsUsed = extractNumColumnsUsed(mem);
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(LS)
+ .append("### START ")
+ .append(family.getFamilyName().toUpperCase())
+ .append(" PREAMBLE SUMMARY").append(LS)
+ .append("Byte 0: Preamble Longs : ").append(preLongs).append(LS)
+ .append("Byte 1: Serialization Version: ").append(serVer).append(LS)
+ .append("Byte 2: Family : ").append(family.toString()).append(LS)
+ .append("Byte 3: Flags Field : ").append(flagsStr).append(LS)
+ //.append(" EMPTY : ").append(isEmpty).append(LS)
+ .append(" COMPACT : ").append(isCompact).append(LS)
+ .append("Bytes 8-11: Num Rows : ").append(numRows).append(LS)
+ .append("Bytes 12-15: Num Columns : ").append(numCols).append(LS);
+
+ if (isCompact) {
+ sb.append("Bytes 16-23: Num Rows Used : ").append(numRowsUsed).append(LS);
+ sb.append("Bytes 24-31: Num Columns Used : ").append(numColsUsed).append(LS);
+ }
+
+ return sb.toString();
+ }
+
+ // Extraction methods
+
+ static int extractPreLongs(final Memory mem) {
+ return mem.getInt(PREAMBLE_LONGS_BYTE) & 0xFF;
+ }
+
+ static int extractSerVer(final Memory mem) {
+ return mem.getInt(SER_VER_BYTE) & 0xFF;
+ }
+
+ static int extractFamilyID(final Memory mem) {
+ return mem.getByte(FAMILY_BYTE) & 0xFF;
+ }
+
+ static int extractFlags(final Memory mem) {
+ return mem.getByte(FLAGS_BYTE) & 0xFF;
+ }
+
+ static int extractNumRows(final Memory mem) {
+ return mem.getInt(NUM_ROWS_INT);
+ }
+
+ static int extractNumColumns(final Memory mem) {
+ return mem.getInt(NUM_COLUMNS_INT);
+ }
+
+ static int extractNumRowsUsed(final Memory mem) {
+ return mem.getInt(ROWS_USED_INT);
+ }
+
+ static int extractNumColumnsUsed(final Memory mem) {
+ return mem.getInt(COLS_USED_INT);
+ }
+
+ // Insertion methods
+
+ static void insertPreLongs(final Object memObj, final long memAddr, final int preLongs) {
+ unsafe.putByte(memObj, memAddr + PREAMBLE_LONGS_BYTE, (byte) preLongs);
+ }
+
+ static void insertSerVer(final Object memObj, final long memAddr, final int serVer) {
+ unsafe.putByte(memObj, memAddr + SER_VER_BYTE, (byte) serVer);
+ }
+
+ static void insertFamilyID(final Object memObj, final long memAddr, final int matrixFamId) {
+ unsafe.putByte(memObj, memAddr + FAMILY_BYTE, (byte) matrixFamId);
+ }
+
+ static void insertFlags(final Object memObj, final long memAddr, final int flags) {
+ unsafe.putByte(memObj, memAddr + FLAGS_BYTE, (byte) flags);
+ }
+
+ static void insertNumRows(final Object memObj, final long memAddr, final int numRows) {
+ unsafe.putInt(memObj, memAddr + NUM_ROWS_INT, numRows);
+ }
+
+ static void insertNumColumns(final Object memObj, final long memAddr, final int numColumns) {
+ unsafe.putInt(memObj, memAddr + NUM_COLUMNS_INT, numColumns);
+ }
+
+ static void insertNumRowsUsed(final Object memObj, final long memAddr, final int rowsUsed) {
+ unsafe.putInt(memObj, memAddr + ROWS_USED_INT, rowsUsed);
+ }
+
+ static void insertNumColumnsUsed(final Object memObj, final long memAddr, final int columnsUsed) {
+ unsafe.putInt(memObj, memAddr + COLS_USED_INT, columnsUsed);
+ }
+
+
+ /**
+ * Checks Memory for capacity to hold the preamble and returns the extracted preLongs.
+ * @param mem the given Memory
+ * @return the extracted prelongs value.
+ */
+ static int getAndCheckPreLongs(final Memory mem) {
+ final long cap = mem.getCapacity();
+ if (cap < 8) { throwNotBigEnough(cap, 8); }
+ final int preLongs = mem.getByte(0) & 0x3F;
+ final int required = Math.max(preLongs << 3, 8);
+ if (cap < required) { throwNotBigEnough(cap, required); }
+ return preLongs;
+ }
+
+ private static void throwNotBigEnough(final long cap, final int required) {
+ throw new SketchesArgumentException(
+ "Possible Corruption: Size of byte array or Memory not large enough: Size: " + cap
+ + ", Required: " + required);
+ }
+}
diff --git a/src/main/java/com/yahoo/sketches/matrix/package-info.java b/src/main/java/com/yahoo/sketches/matrix/package-info.java
new file mode 100644
index 0000000..ec7459c
--- /dev/null
+++ b/src/main/java/com/yahoo/sketches/matrix/package-info.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017, Yahoo! Inc.
+ * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root
+ * for terms.
+ */
+
+/**
+ * <p>This package contains a Matrix class that wraps one of several underlying matrix
+ * implementations. It can be used to provide a stable public API independent of the
+ * specific linear algebra package used for computation.</p>
+ *
+ * <p>These Matrix objects can be serialized and deserialized to/from a compact form.</p>
+ */
+package com.yahoo.sketches.matrix;
\ No newline at end of file
diff --git a/src/test/java/com/yahoo/sketches/decomposition/FrequentDirectionsTest.java b/src/test/java/com/yahoo/sketches/decomposition/FrequentDirectionsTest.java
new file mode 100644
index 0000000..dc515f2
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/decomposition/FrequentDirectionsTest.java
@@ -0,0 +1,337 @@
+package com.yahoo.sketches.decomposition;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.Arrays;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+import com.yahoo.sketches.matrix.Matrix;
+import org.testng.annotations.Test;
+
+public class FrequentDirectionsTest {
+ @Test
+ public void instantiateFD() {
+ final int k = 32;
+ final int d = 256;
+ final FrequentDirections fd = FrequentDirections.newInstance(k, d);
+
+ assertNotNull(fd);
+ assertTrue(fd.isEmpty());
+ assertEquals(fd.getK(), k);
+ assertEquals(fd.getD(), d);
+ assertEquals(fd.getN(), 0);
+ assertNull(fd.getResult());
+
+ // error conditions
+ // d = 0
+ try {
+ FrequentDirections.newInstance(k, 0);
+ fail();
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+
+ // k = -1
+ try {
+ FrequentDirections.newInstance(-1, d);
+ fail();
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+
+ // d < 2k (not handled in reduceRank()
+ try {
+ FrequentDirections.newInstance(d, d);
+ fail();
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void checkUpdate() {
+ final int k = 4;
+ final int d = 16; // should be > 2k
+ final FrequentDirections fd = FrequentDirections.newInstance(k, d);
+
+ // creates matrix with increasing values along diagonal
+ final double[] input = new double[d];
+ for (int i = 0; i < 2 * k; ++i) {
+ if (i > 0) {
+ input[i - 1] = 0.0;
+ }
+ //input[i] = (2 * k) - (i * 1.0);
+ input[i] = i * 1.0;
+ fd.update(input);
+ }
+ fd.update((double[]) null); // should be a no-op and not impact next lines
+ assertEquals(fd.getNumRows(), 2 * k);
+ assertEquals(fd.getN(), 2 * k);
+
+ input[(2 * k) - 1] = 0.0;
+ input[2 * k] = 2.0 * k;
+ fd.update(input); // trigger reduceRank(), then add 1 more row
+ assertEquals(fd.getNumRows(), k + 1);
+
+ fd.reset();
+ assertTrue(fd.isEmpty());
+
+ println(fd.toString());
+ println(fd.toString(true));
+ }
+
+ @Test
+ public void updateWithTooFewDimensions() {
+ final int k = 4;
+ final int d = 16; // should be > 2k
+ final FrequentDirections fd = FrequentDirections.newInstance(k, d);
+
+ final double[] input = new double[d - 3];
+ try {
+ fd.update(input);
+ fail();
+ } catch (final IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void mergeSketches() {
+ final int k = 5;
+ final int d = 12; // should be > 2k
+ final int initialRows = 7;
+ final FrequentDirections fd1 = FrequentDirections.newInstance(k, d);
+ final FrequentDirections fd2 = FrequentDirections.newInstance(k, d);
+
+ // two diagonal matrices
+ final double[] input = new double[d];
+ for (int i = 0; i < initialRows; ++i) {
+ if (i > 0) {
+ input[i - 1] = 0.0;
+ }
+ //input[i] = (2 * k) - (i * 1.0);
+ input[i] = i * 1.0;
+ fd1.update(input);
+
+ input[i] = (i * 1.0) - (2 * k);
+ fd2.update(input);
+ }
+
+ // the next two lines are no-ops
+ fd1.update((FrequentDirections) null);
+ fd1.update(FrequentDirections.newInstance(k, d));
+ assertEquals(fd1.getNumRows(), initialRows);
+ assertEquals(fd1.getN(), initialRows);
+
+ assertEquals(fd2.getNumRows(), initialRows);
+ assertEquals(fd2.getN(), initialRows);
+
+ fd1.update(fd2);
+ final int expectedRows = ((2 * initialRows) % k) + k; // assumes 2 * initialRows > k
+ assertEquals(fd1.getNumRows(), expectedRows);
+ assertEquals(fd1.getN(), 2 * initialRows);
+
+ Matrix result = fd1.getResult(false, false);
+ assertNotNull(result);
+ assertEquals(result.getNumRows(), expectedRows);
+
+ result = fd1.getResult(true, true);
+ assertNotNull(result);
+ assertEquals(result.getNumRows(), k);
+
+ println(fd1.toString(true, true, true));
+ }
+
+ @Test
+ public void checkCompensativeResult() {
+ final int k = 4;
+ final int d = 10; // should be > 2k
+ final FrequentDirections fd = FrequentDirections.newInstance(k, d);
+
+ // diagonal matrix for easy checking
+ final double[] input = new double[d];
+ for (int i = 0; i < k + 1; ++i) {
+ if (i > 0) {
+ input[i - 1] = 0.0;
+ }
+ input[i] = (i + 1) * 1.0;
+ fd.update(input);
+ }
+
+ Matrix m = fd.getResult(false, false);
+ for (int i = 0; i < k + 1; ++i) {
+ assertEquals(m.getElement(i,i), 1.0 * (i + 1), 1e-6);
+ }
+
+ final Matrix p = fd.getProjectionMatrix();
+ double[] sv = fd.getSingularValues(false);
+
+ // without compensation, and check projection at the same time
+ m = fd.getResult(true, false);
+ for (int i = k; i > 1; --i) {
+ final double val = Math.abs(m.getElement(k - i, i));
+ final double expected = Math.sqrt(((i + 1) * (i + 1)) - fd.getSvAdjustment());
+ assertEquals(val, expected, 1e-6);
+ assertEquals(sv[k - i], expected, 1e-10);
+ assertEquals(Math.abs(p.getElement(k - i, i)), 1.0, 1e-6);
+ }
+ assertEquals(m.getElement(k, 1), 0.0);
+ assertEquals(p.getElement(k, 1), 0.0);
+
+ // with compensation
+ m = fd.getResult(true, true);
+ sv = fd.getSingularValues();
+ for (int i = k; i > 1; --i) {
+ final double val = Math.abs(m.getElement(k - i, i));
+ assertEquals(val, i + 1.0, 1e-6);
+ assertEquals(sv[k - i], i + 1.0, 1e-10);
+ }
+ assertEquals(m.getElement(k, 1), 0.0);
+ }
+
+ @Test
+ public void mergeIncompatibleSketches() {
+ final int k = 5;
+ final int d = 12; // should be > 2k
+ final FrequentDirections fd1 = FrequentDirections.newInstance(k, d);
+
+ final double[] input = new double[d];
+ input[0] = 1.0;
+ fd1.update(input);
+
+ // merge in smaller k
+ FrequentDirections fd2 = FrequentDirections.newInstance(k - 1, d);
+ fd2.update(input);
+ try {
+ fd1.update(fd2);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+
+ // mismatch in d
+ fd2 = FrequentDirections.newInstance(k, d - 1);
+ fd2.update(new double[d - 1]);
+ try {
+ fd1.update(fd2);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void checkSerialization() {
+ final int k = 7;
+ final int d = 20;
+ final FrequentDirections fd = FrequentDirections.newInstance(k, d);
+
+ byte[] sketchBytes = fd.toByteArray(false);
+ assertEquals(sketchBytes.length,
+ MatrixFamily.FREQUENTDIRECTIONS.getMinPreLongs() * Long.BYTES);
+ Memory mem = Memory.wrap(sketchBytes);
+ FrequentDirections rebuilt = FrequentDirections.heapify(mem);
+ assertTrue(rebuilt.isEmpty());
+ assertEquals(rebuilt.getD(), fd.getD());
+ assertEquals(rebuilt.getK(), fd.getK());
+
+ // creates matrix with increasing values along diagonal
+ // k rows, so shouldn't compress yet
+ final double[] input = new double[d];
+ for (int i = 0; i < k; ++i) {
+ if (i > 0) {
+ input[i - 1] = 0.0;
+ }
+ //input[i] = (2 * k) - (i * 1.0);
+ input[i] = i * 1.0;
+ fd.update(input);
+ }
+ sketchBytes = fd.toByteArray();
+ mem = Memory.wrap(sketchBytes);
+ rebuilt = FrequentDirections.heapify(mem);
+ assertEquals(rebuilt.getN(), fd.getN());
+ assertEquals(rebuilt.getD(), fd.getD());
+ assertEquals(rebuilt.getK(), fd.getK());
+
+ // add another k rows and serialize, compressing this time
+ for (int i = k; i < 2 * k - 1; ++i) {
+ input[i] = i * 1.0;
+ fd.update(input);
+ }
+ assertEquals(fd.getNumRows(), 2 * k - 1);
+ sketchBytes = fd.toByteArray(true);
+ assertEquals(fd.getNumRows(), k);
+ mem = Memory.wrap(sketchBytes);
+ rebuilt = FrequentDirections.heapify(mem);
+ assertEquals(rebuilt.getN(), fd.getN());
+ assertEquals(rebuilt.getNumRows(), k);
+
+ println(PreambleUtil.preambleToString(mem));
+ }
+
+ @Test
+ public void checkCorruptedHeapify() {
+ final int k = 50;
+ final int d = 250;
+ final FrequentDirections fd = FrequentDirections.newInstance(k, d);
+ byte[] sketchBytes = fd.toByteArray();
+ WritableMemory mem = WritableMemory.wrap(sketchBytes);
+
+ FrequentDirections rebuilt = FrequentDirections.heapify(mem);
+ assertTrue(rebuilt.isEmpty());
+ println(PreambleUtil.preambleToString(mem));
+
+ // corrupt the serialization version
+ mem.putByte(PreambleUtil.SER_VER_BYTE, (byte) 0);
+ try {
+ FrequentDirections.heapify(mem);
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+
+ // corrupt the family ID, after grabbing fresh bytes
+ sketchBytes = fd.toByteArray();
+ mem = WritableMemory.wrap(sketchBytes);
+ mem.putByte(PreambleUtil.FAMILY_BYTE, (byte) 0);
+ try {
+ FrequentDirections.heapify(mem);
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void checkInsufficientMemory() {
+ // no capacity
+ byte[] bytes = new byte[0];
+ Memory mem = Memory.wrap(bytes);
+ try {
+ FrequentDirections.heapify(mem);
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+
+ // capacity smaller than prelongs size
+ final FrequentDirections fd = FrequentDirections.newInstance(10, 50);
+ bytes = fd.toByteArray();
+ bytes = Arrays.copyOf(bytes, bytes.length - 1);
+ mem = Memory.wrap(bytes);
+ try {
+ FrequentDirections.heapify(mem);
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+
+ private void println(final String msg) {
+ //System.out.println(msg);
+ }
+}
diff --git a/src/test/java/com/yahoo/sketches/matrix/MatrixImplOjAlgoTest.java b/src/test/java/com/yahoo/sketches/matrix/MatrixImplOjAlgoTest.java
new file mode 100644
index 0000000..442d674
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/matrix/MatrixImplOjAlgoTest.java
@@ -0,0 +1,206 @@
+package com.yahoo.sketches.matrix;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.memory.WritableMemory;
+import com.yahoo.sketches.SketchesArgumentException;
+import org.ojalgo.matrix.store.PrimitiveDenseStore;
+import org.testng.annotations.Test;
+
+public class MatrixImplOjAlgoTest {
+ @Test
+ public void checkInstantiation() {
+ final int nRows = 10;
+ final int nCols = 15;
+ final Matrix m = MatrixImplOjAlgo.newInstance(nRows, nCols);
+ assertEquals(m.getNumRows(), nRows);
+ assertEquals(m.getNumColumns(), nCols);
+
+ final PrimitiveDenseStore pds = (PrimitiveDenseStore) m.getRawObject();
+ assertEquals(pds.countRows(), nRows);
+ assertEquals(pds.countColumns(), nCols);
+
+ final Matrix wrapped = Matrix.wrap(pds);
+ MatrixTest.checkMatrixEquality(wrapped, m);
+ assertEquals(wrapped.getRawObject(), pds);
+ }
+
+ @Test
+ public void updateAndQueryValues() {
+ final int nRows = 5;
+ final int nCols = 5;
+ final Matrix m = generateIncreasingEye(nRows, nCols); // tests setElement() in method
+
+ for (int i = 0; i < nRows; ++i) {
+ for (int j = 0; j < nCols; ++j) {
+ double val = m.getElement(i, j);
+ if (i == j) {
+ assertEquals(val, i + 1.0);
+ } else {
+ assertEquals(val, 0.0);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void checkStandardSerialization() {
+ final int nRows = 3;
+ final int nCols = 7;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+
+ final byte[] mtxBytes = m.toByteArray();
+ assertEquals(mtxBytes.length, m.getSizeBytes());
+
+ final Memory mem = Memory.wrap(mtxBytes);
+ final Matrix tgt = MatrixImplOjAlgo.heapifyInstance(mem);
+ MatrixTest.checkMatrixEquality(tgt, m);
+ }
+
+ @Test
+ public void checkCompactSerialization() {
+ final int nRows = 4;
+ final int nCols = 7;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+
+ byte[] mtxBytes = m.toCompactByteArray(nRows - 1, 7);
+ assertEquals(mtxBytes.length, m.getCompactSizeBytes(nRows - 1, 7));
+
+ Memory mem = Memory.wrap(mtxBytes);
+ Matrix tgt = MatrixImplOjAlgo.heapifyInstance(mem);
+ for (int c = 0; c < nCols; ++c) {
+ for (int r = 0; r < nRows - 1; ++r) {
+ assertEquals(tgt.getElement(r, c), m.getElement(r, c)); // equal here
+ }
+ // assuming nRows - 1 so check only the last row as being 0
+ assertEquals(tgt.getElement(nRows - 1, c), 0.0);
+ }
+
+ // test without compacting
+ mtxBytes = m.toCompactByteArray(nRows, nCols);
+ assertEquals(mtxBytes.length, m.getSizeBytes());
+ mem = Memory.wrap(mtxBytes);
+ tgt = MatrixImplOjAlgo.heapifyInstance(mem);
+ MatrixTest.checkMatrixEquality(tgt, m);
+ }
+
+ @Test
+ public void matrixRowOperations() {
+ final int nRows = 7;
+ final int nCols = 5;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+
+ final int tgtCol = 2;
+ final double[] v = m.getRow(tgtCol); // diagonal matrix, so this works ok
+ for (int i = 0; i < v.length; ++i) {
+ assertEquals(v[i], (i == tgtCol ? i + 1.0 : 0.0));
+ }
+
+ assertEquals(m.getElement(6, tgtCol), 0.0);
+ m.setRow(6, v);
+ assertEquals(m.getElement(6, tgtCol), tgtCol + 1.0);
+ }
+
+ @Test
+ public void matrixColumnOperations() {
+ final int nRows = 9;
+ final int nCols = 4;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+
+ final int tgtRow = 3;
+ final double[] v = m.getColumn(tgtRow); // diagonal matrix, so this works ok
+ for (int i = 0; i < v.length; ++i) {
+ assertEquals(v[i], (i == tgtRow ? i + 1.0 : 0.0));
+ }
+
+ assertEquals(m.getElement(tgtRow, 0), 0.0);
+ m.setColumn(0, v);
+ assertEquals(m.getElement(tgtRow, 0), tgtRow + 1.0);
+ }
+
+ @Test
+ public void invalidRowColumnOperations() {
+ final int nRows = 9;
+ final int nCols = 4;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+
+ final double[] shortRow = new double[nCols - 2];
+ try {
+ m.setRow(1, shortRow);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+
+ final double[] longColumn = new double[nRows + 2];
+ try {
+ m.setColumn(1, longColumn);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void invalidSerVer() {
+ final int nRows = 3;
+ final int nCols = 3;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+ final byte[] sketchBytes = m.toByteArray();
+ final WritableMemory mem = WritableMemory.wrap(sketchBytes);
+ MatrixPreambleUtil.insertSerVer(mem.getArray(), mem.getCumulativeOffset(0L), 0);
+
+ try {
+ MatrixImplOjAlgo.heapifyInstance(mem);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void invalidFamily() {
+ final int nRows = 3;
+ final int nCols = 3;
+ final Matrix m = generateIncreasingEye(nRows, nCols);
+ final byte[] sketchBytes = m.toByteArray();
+ final WritableMemory mem = WritableMemory.wrap(sketchBytes);
+ MatrixPreambleUtil.insertFamilyID(mem.getArray(), mem.getCumulativeOffset(0L), 0);
+
+ try {
+ MatrixImplOjAlgo.heapifyInstance(mem);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void insufficientMemoryCapacity() {
+ final byte[] bytes = new byte[6];
+ final Memory mem = Memory.wrap(bytes);
+ try {
+ MatrixImplOjAlgo.heapifyInstance(mem);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Creates a scaled I matrix, where the diagonal consists of increasing integers,
+ * starting with 1.0.
+ * @param nRows number of rows
+ * @param nCols number of columns
+ * @return PrimitiveDenseStore, suitable for direct use or wrapping
+ */
+ private Matrix generateIncreasingEye(final int nRows, final int nCols) {
+ final Matrix m = MatrixImplOjAlgo.newInstance(nRows, nCols);
+ for (int i = 0; i < nRows && i < nCols; ++i) {
+ m.setElement(i, i, 1.0 + i);
+ }
+ return m;
+ }
+}
diff --git a/src/test/java/com/yahoo/sketches/matrix/MatrixTest.java b/src/test/java/com/yahoo/sketches/matrix/MatrixTest.java
new file mode 100644
index 0000000..742626d
--- /dev/null
+++ b/src/test/java/com/yahoo/sketches/matrix/MatrixTest.java
@@ -0,0 +1,94 @@
+package com.yahoo.sketches.matrix;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import com.yahoo.memory.Memory;
+import com.yahoo.sketches.MatrixFamily;
+import com.yahoo.sketches.SketchesArgumentException;
+import org.testng.annotations.Test;
+
+public class MatrixTest {
+
+ @Test
+ public void checkHeapify() {
+ final Matrix m = Matrix.builder().setType(MatrixBuilder.Algo.OJALGO).build(3, 3);
+ final byte[] bytes = m.toByteArray();
+ final Memory mem = Memory.wrap(bytes);
+
+ Matrix tgt = Matrix.heapify(mem, MatrixBuilder.Algo.OJALGO);
+ assertTrue(tgt instanceof MatrixImplOjAlgo);
+ checkMatrixEquality(m, tgt);
+
+ tgt = Matrix.heapify(mem, MatrixBuilder.Algo.NATIVE);
+ assertNull(tgt);
+ }
+
+ @Test
+ public void checkWrap() {
+ assertNull(Matrix.wrap(null));
+
+ final Matrix src = Matrix.builder().setType(MatrixBuilder.Algo.OJALGO).build(3, 3);
+ final Object obj = src.getRawObject();
+ final Matrix tgt = Matrix.wrap(obj);
+ assertTrue(tgt instanceof MatrixImplOjAlgo);
+ checkMatrixEquality(src, tgt);
+
+ try {
+ final Object notAMatrix = 1.0;
+ Matrix.wrap(notAMatrix);
+ fail();
+ } catch (final SketchesArgumentException e) {
+ // expected
+ }
+
+ assertNotNull(src.toString());
+ }
+
+ @Test
+ public void checkSize() {
+ final int nRow = 7;
+ final int nCol = 3;
+ final Matrix m = Matrix.builder().build(nRow, nCol);
+
+ int expectedSize = (MatrixFamily.MATRIX.getMinPreLongs() * Long.BYTES)
+ + (nRow * nCol * Double.BYTES);
+ assertEquals(m.getSizeBytes(), expectedSize);
+
+ // this should redirect call to getSizeBytes()
+ assertEquals(m.getCompactSizeBytes(nRow, nCol), expectedSize);
+
+ // degenerate cases
+ expectedSize = (MatrixFamily.MATRIX.getMinPreLongs() * Long.BYTES);
+ assertEquals(m.getCompactSizeBytes(0, nCol), expectedSize);
+ assertEquals(m.getCompactSizeBytes(nRow, 0), expectedSize);
+
+ // matrix subsets
+ expectedSize = (MatrixFamily.MATRIX.getMaxPreLongs() * Long.BYTES)
+ + (5 * 3) * Double.BYTES;
+ assertEquals(m.getCompactSizeBytes(5, 3), expectedSize);
+
+ expectedSize = (MatrixFamily.MATRIX.getMaxPreLongs() * Long.BYTES)
+ + (7 * 2) * Double.BYTES;
+ assertEquals(m.getCompactSizeBytes(7, 2), expectedSize);
+
+ expectedSize = (MatrixFamily.MATRIX.getMaxPreLongs() * Long.BYTES)
+ + (2 * 2) * Double.BYTES;
+ assertEquals(m.getCompactSizeBytes(2, 2), expectedSize);
+ }
+
+ static void checkMatrixEquality(final Matrix m1, final Matrix m2) {
+ assertEquals(m1.numRows_, m2.numRows_);
+ assertEquals(m1.numCols_, m2.numCols_);
+
+ for (int i = 0; i < m1.numRows_; ++i) {
+ for (int j = 0; j < m1.numCols_; ++j) {
+ assertEquals(m1.getElement(i, j), m2.getElement(i, j),
+ "Mismatch at (" + i + ", " + j + ")");
+ }
+ }
+ }
+}
diff --git a/tools/CloverConfig.txt b/tools/CloverConfig.txt
new file mode 100644
index 0000000..9e0ec43
--- /dev/null
+++ b/tools/CloverConfig.txt
@@ -0,0 +1,33 @@
+Clover Config for Eclipse:
+
+At Project Level Properties:
+Clover:
+ Instrumentation:
+ Initstring: Default value
+ Output Folder: ...project output dir(s)
+ Flush Policy: At JVM shutdown ...
+ Misc: Fully qualify ... , Instrument and compile at statement level
+ Contexts:
+ Check: assert statements
+ Add Custom Coverage Context Filter:
+ private-constructor: also see link below
+ Method
+ (.* )?private +[a-zA-Z0-9_$]+ *\( *\).*
+ Source Files
+ Only look ...
+ [check] src/main/java[includes=**/*.java][excludes=]
+ [check] src/test/java[includes=**/*.java][excludes=]
+ Test Classes
+ Assume all source in the specified folders are tests or test utility classes
+ [check] src/test/java
+
+At Clover "down-triangle" menu:
+ Columns:
+ Element
+ % TOTAL Coverage
+ Uncovered Elements: Custom: %UncoveredElements * TotalElements / 100
+ Total Elements
+
+
+
+http://alexruizlog.blogspot.com/2009/04/how-to-make-clover-ignore-private_21.html
\ No newline at end of file
diff --git a/tools/FindBugsExcludeFilter.xml b/tools/FindBugsExcludeFilter.xml
new file mode 100644
index 0000000..16e5fa3
--- /dev/null
+++ b/tools/FindBugsExcludeFilter.xml
@@ -0,0 +1,90 @@
+<FindBugsFilter> <!-- sketches-core -->
+
+ <!-- Too many false positives to be useful. I could not make it happy :( -->
+ <Match>
+ <Bug pattern="SF_SWITCH_NO_DEFAULT" />
+ </Match>
+
+ <!-- False positive. FindBugs complains if DQS_RESIZE_THRESHOLD == REBUILD_THRESHOLD,
+ but this allows us to tune these constants for different sketches. -->
+ <Match>
+ <Bug pattern="DB_DUPLICATE_BRANCHES" />
+ <Class name="com.yahoo.sketches.theta.DirectQuickSelectSketch" />
+ <Method name="setHashTableThreshold" />
+ </Match>
+
+ <!-- False positive. In this case we want to ignore the exceptions -->
+ <Match>
+ <Bug pattern="DE_MIGHT_IGNORE" />
+ <Class name="com.yahoo.sketches.theta.PairwiseCornerCasesTest" />
+ <Method name="checkNotOrdered" />
+ </Match>
+
+ <Match> <!-- Exclude for test classes; too many False Positives. -->
+ <Bug pattern="NP_NULL_PARAM_DEREF_NONVIRTUAL" />
+ <Class name="~.*\.*Test" />
+ </Match>
+
+ <Match> <!-- Exclude for test classes; too many False Positives. -->
+ <Bug pattern="DLS_DEAD_LOCAL_STORE" />
+ <Class name="~.*\.*Test" />
+ </Match>
+
+</FindBugsFilter>
+
+
+
+<!-- Examples: -->
+
+<!-- Exclude java.* classes -->
+ <!--
+ <Match>
+ <Package name="java\.*" />
+ </Match>
+-->
+
+ <!-- Exclude test classes -->
+<!--
+ <Match>
+ <Class name="~.*\.*Test" />
+ </Match>
+-->
+
+<!--
+ <Match>
+ <Class name="com.foobar.ClassNotToBeAnalyzed" />
+ </Match>
+-->
+<!--
+ <Match>
+ <Class name="com.foobar.ClassWithSomeBugsMatched" />
+ <Bug code="DE,UrF,SIC" />
+ </Match>
+-->
+ <!-- Match all XYZ violations. -->
+<!--
+ <Match>
+ <Bug code="XYZ" />
+ </Match>
+-->
+ <!-- Match all doublecheck violations in these methods of "AnotherClass". -->
+<!--
+ <Match>
+ <Class name="com.foobar.AnotherClass" />
+ <Or>
+ <Method name="nonOverloadedMethod" />
+ <Method name="frob" params="int,java.lang.String" returns="void" />
+ <Method name="blat" params="" returns="boolean" />
+ </Or>
+ <Bug code="DC" />
+ </Match>
+-->
+ <!-- A method with a dead local store false positive (medium priority). -->
+<!--
+ <Match>
+ <Class name="com.foobar.MyClass" />
+ <Method name="someMethod" />
+ <Bug pattern="DLS_DEAD_LOCAL_STORE" />
+ <Priority value="2" />
+ </Match>
+-->
diff --git a/tools/SketchesCheckstyle.xml b/tools/SketchesCheckstyle.xml
new file mode 100644
index 0000000..aaebec2
--- /dev/null
+++ b/tools/SketchesCheckstyle.xml
@@ -0,0 +1,373 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> <!-- does not work with https -->
+
+<!--
+ SketchesCheckstyle.xml for sketches-core
+
+ Checkstyle is very configurable. Be sure to read the documentation at
+ http://checkstyle.sourceforge.net (or in your downloaded distribution). Note: Does not work with https.
+
+ To completely disable a check, just comment it out or delete it from the file.
+
+ Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
+-->
+
+<module name = "Checker">
+ <property name="charset" value="UTF-8"/>
+ <property name="severity" value="warning"/>
+ <property name="fileExtensions" value="java, properties, xml"/>
+
+ <module name="FileTabCharacter">
+ <property name="eachLine" value="true"/>
+ </module>
+
+ <module name="JavadocPackage"/>
+
+ <module name="NewlineAtEndOfFile">
+ <property name="lineSeparator" value="lf"/>
+ </module>
+
+ <module name="TreeWalker">
+
+ <!-- Enable suppression using comments: //CHECKSTYLE.OFF "RULE" and //CHECKSTYLE.ON "RULE"
+ You must specify the specific rule, as in: //CHECKSTYLE.OFF: LineLength -->
+ <module name="SuppressionCommentFilter">
+ <property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)"/>
+ <property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)"/>
+ <property name="checkFormat" value="$1"/>
+ </module>
+
+ <!-- Annotations -->
+ <module name="AnnotationLocation">
+ <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
+ </module>
+
+ <module name="AnnotationLocation">
+ <property name="tokens" value="VARIABLE_DEF"/>
+ <property name="allowSamelineMultipleAnnotations" value="true"/>
+ </module>
+
+ <!-- Block Checks -->
+ <module name="EmptyBlock">
+ <property name="severity" value="warning"/>
+ <property name="option" value="TEXT"/>
+ <property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="EmptyCatchBlock">
+ <property name="severity" value="ignore"/>
+ <property name="exceptionVariableName" value="expected"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="LeftCurly">
+ <!-- doesn't allow for if (n == 0) { return 0.0; }, which is readable and not corruptable -->
+ <property name="severity" value="ignore"/>
+ <property name="maxLineLength" value="100"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="NeedBraces">
+ <property name="allowSingleLineStatement" value="false"/> <!-- default = false -->
+ <property name="allowEmptyLoopBody" value="false"/> <!-- default = false -->
+ </module>
+
+ <module name="RightCurly">
+ <!-- requires right curly start a new line; alone: on a line alone -->
+ <property name="severity" value="ignore"/>
+ <property name="option" value="alone"/>
+ <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <!-- Coding -->
+ <module name="FallThrough">
+ <property name="severity" value="warning"/>
+ <property name="reliefPattern" value="fallthru|falls? ?through|FALL[- ]?THROUGH"/>
+ <!-- <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/> -->
+ </module>
+
+ <module name="FinalLocalVariable">
+ <property name="severity" value="warning"/>
+ <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/>
+ </module>
+
+ <module name="IllegalTokenText">
+ <property name="severity" value="warning"/>
+ <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
+ <property name="format" value="\\u00(08|09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
+ <property name="message" value="Avoid using corresponding octal or Unicode escape."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="MissingSwitchDefault">
+ <!-- Too many false positives -->
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="MultipleVariableDeclarations">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="NoFinalizer">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="OneStatementPerLine">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="OverloadMethodsDeclarationOrder">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="VariableDeclarationUsageDistance">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <!-- Class Design -->
+ <module name="OneTopLevelClass"/>
+
+ <module name="FinalClass"/>
+
+
+ <!-- Imports -->
+ <module name="AvoidStarImport"/>
+
+ <module name="CustomImportOrder">
+ <property name="specialImportsRegExp" value="org"/>
+ <property name="sortImportsInGroupAlphabetically" value="true"/>
+ <property name="separateLineBetweenGroups" value="true"/>
+ <!-- Google: "STATIC###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE" -->
+ <property name="customImportOrderRules" value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/>
+ </module>
+
+ <module name="RedundantImport"/>
+
+ <module name="UnusedImports"/>
+
+ <!-- Javadoc Comments -->
+ <!-- JavadocPackage under Checker -->
+ <module name="AtclauseOrder">
+ <property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
+ <property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
+ </module>
+
+ <module name="JavadocMethod">
+ <property name="scope" value="public"/>
+ <property name="allowMissingParamTags" value="false"/>
+ <property name="allowMissingThrowsTags" value="true"/>
+ <property name="allowMissingReturnTag" value="false"/>
+ <property name="minLineCount" value="2"/>
+ <property name="allowedAnnotations" value="Override, Test"/>
+ <property name="allowThrowsTagsForSubclasses" value="true"/>
+ </module>
+
+ <module name="JavadocParagraph"/>
+
+ <module name="JavadocTagContinuationIndentation">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="NonEmptyAtclauseDescription"/>
+
+ <module name="SingleLineJavadoc">
+ <property name="ignoreInlineTags" value="false"/>
+ </module>
+
+ <module name="SummaryJavadocCheck">
+ <property name="severity" value="ignore"/>
+ <property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <!-- Miscellaneous -->
+ <module name="ArrayTypeStyle">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="AvoidEscapedUnicodeCharacters">
+ <property name="severity" value="warning"/>
+ <property name="allowEscapesForControlCharacters" value="true"/>
+ <property name="allowByTailComment" value="true"/>
+ <property name="allowNonPrintableEscapes" value="true"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="CommentsIndentation">
+ <property name="severity" value="warning"/>
+ <!-- <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/> -->
+ </module>
+
+ <module name="FileContentsHolder"/> <!-- Used with SuppressionCommentFilter -->
+
+ <module name="Indentation">
+ <property name="severity" value="ignore"/>
+ <property name="basicOffset" value="2"/>
+ <property name="braceAdjustment" value="0"/>
+ <property name="caseIndent" value="2"/>
+ <property name="throwsIndent" value="4"/>
+ <property name="lineWrappingIndentation" value="4"/>
+ <property name="arrayInitIndent" value="2"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="OuterTypeFilename">
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="TodoComment">
+ <property name="severity" value="ignore"/>
+ <property name="format" value="(//TODO)|(//FIXME)"/>
+ </module>
+
+ <module name="UpperEll">
+ <property name="severity" value="warning"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <!-- Modifiers -->
+ <module name="ModifierOrder">
+ <property name="severity" value="ignore"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <!-- Naming Conventions -->
+ <module name="AbbreviationAsWordInName">
+ <property name="severity" value="ignore"/>
+ <property name="ignoreFinal" value="false"/>
+ <property name="allowedAbbreviationLength" value="1"/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="ClassTypeParameterName">
+ <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
+ <message key="name.invalidPattern"
+ value="Class type name ''{0}'' must match pattern ''{1}''."/>
+ </module>
+
+ <module name="LocalVariableName">
+ <property name="severity" value="ignore"/>
+ <property name="tokens" value="VARIABLE_DEF"/>
+ <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
+ <property name="allowOneCharVarInForLoop" value="true"/>
+ <message key="name.invalidPattern"
+ value="Local variable name ''{0}'' must match pattern ''{1}''."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="MemberName">
+ <property name="severity" value="ignore"/>
+ <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
+ <message key="name.invalidPattern"
+ value="Member name ''{0}'' must match pattern ''{1}''."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="MethodName">
+ <property name="severity" value="ignore"/>
+ <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
+ <message key="name.invalidPattern"
+ value="Method name ''{0}'' must match pattern ''{1}''."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="MethodTypeParameterName">
+ <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
+ <message key="name.invalidPattern"
+ value="Method type name ''{0}'' must match pattern ''{1}''."/>
+ </module>
+
+ <module name="PackageName">
+ <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
+ <message key="name.invalidPattern"
+ value="Package name ''{0}'' must match pattern ''{1}''."/>
+ </module>
+
+ <module name="ParameterName">
+ <property name="severity" value="ignore"/>
+ <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
+ <message key="name.invalidPattern"
+ value="Parameter name ''{0}'' must match pattern ''{1}''."/>
+ <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
+ </module>
+
+ <module name="TypeName">
+ <message key="name.invalidPattern"
+ value="Type name ''{0}'' must match pattern ''{1}''."/>
+ </module>
+
+ <!-- Regexp -->
+ <module name="Regexp">
+ <property name="severity" value="ignore"/>
+ <property name="format" value="[ \t]+$"/>
+ <property name="illegalPattern" value="true"/>
+ <property name="message" value="Trailing whitespace"/>
+ <property name="ignoreComments" value="true"/>
+ </module>
+
+ <!-- Size Violations -->
+ <module name="LineLength">
+ <property name="severity" value="warning"/>
+ <property name="max" value="110"/>
+ <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
+ <!-- <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/> -->
+ </module>
+
+ <!-- Whitespace -->
+ <module name="EmptyLineSeparator">
+ <property name="allowNoEmptyLineBetweenFields" value="true"/>
+ </module>
+
+ <module name="GenericWhitespace">
+ <message key="ws.followed"
+ value="GenericWhitespace ''{0}'' is followed by whitespace."/>
+ <message key="ws.preceded"
+ value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
+ <message key="ws.illegalFollow"
+ value="GenericWhitespace ''{0}'' should followed by whitespace."/>
+ <message key="ws.notPreceded"
+ value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
+ </module>
+
+ <module name="NoLineWrap"/> <!-- Only for import and package statements -->
+
+ <module name="MethodParamPad"/>
+
+ <module name="OperatorWrap">
+ <property name="option" value="NL"/>
+ <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
+ </module>
+
+ <module name="SeparatorWrap">
+ <property name="tokens" value="DOT"/>
+ <property name="option" value="nl"/>
+ </module>
+
+ <module name="SeparatorWrap">
+ <property name="tokens" value="COMMA"/>
+ <property name="option" value="EOL"/>
+ </module>
+
+ <module name="WhitespaceAround">
+ <property name="allowEmptyConstructors" value="true"/>
+ <property name="allowEmptyMethods" value="true"/>
+ <property name="allowEmptyTypes" value="true"/>
+ <property name="allowEmptyLoops" value="true"/>
+ <message key="ws.notPreceded" value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
+ <message key="ws.notFollowed" value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
+ </module>
+
+ </module> <!-- End of TreeWalker -->
+</module>