{"action":"created","comment":{"url":"https://api.github.com/repos/kyonifer/golem/pulls/comments/57833620","id":57833620,"diff_hunk":"@@ -0,0 +1,256 @@\n+@file:JvmName(\"DimensionValidation\")\n+\n+package golem.util.validation\n+import golem.validateMatrices\n+import golem.matrix.Matrix\n+import kotlin.collections.MutableMap\n+import kotlin.collections.mutableMapOf\n+import kotlin.collections.MutableSet\n+import kotlin.collections.mutableSetOf\n+import java.util.HashSet\n+import java.io.ByteArrayOutputStream\n+import java.io.PrintStream\n+\n+private val ValidationContext.meta : DimensionValidator get() {\n+    return this.metadata(\"dim\") {\n+        val instance = DimensionValidator()\n+        addValidator(instance)\n+        instance\n+    }\n+}\n+\n+private class DimensionValidator: Validator {\n+    private val possibleValues = mutableMapOf<Char, MutableSet<Int>>()\n+    private val dimensions = mutableMapOf<Matrix<Double>, Pair<MutableSet<Int>, MutableSet<Int>>>()\n+    private val declared = mutableMapOf<String, Pair<String, String>>()\n+    private val seenInError = mutableSetOf<String>()\n+    private val transposable = mutableSetOf<Matrix<Double>>()\n+\n+    private fun inferValues(letter: Char, vararg dimensions: Int) : MutableSet<Int> {\n+        val possible = possibleValues.getOrPut(letter) { dimensions.toMutableSet() }\n+        possible.retainAll(dimensions.asIterable())\n+        return possible\n+    }\n+\n+    fun dim(name: String, matrix: Matrix<Double>, rows: Int, cols: Int) {\n+        declared[name] = Pair(\"${rows}\", \"${cols}\")\n+        dimensions[matrix] = Pair(mutableSetOf(rows), mutableSetOf(cols))\n+    }\n+\n+    fun dim(name: String, matrix: Matrix<Double>, rows: Int, cols: Char) {\n+        declared[name] = Pair(\"${rows}\", \"${cols}\")\n+        val inferenceSource = if (matrix.numCols() == rows) {\n+            matrix.numRows()\n+        } else {\n+            matrix.numCols()\n+        }\n+        dimensions[matrix] = Pair(mutableSetOf(rows), inferValues(cols, inferenceSource))\n+    }\n+\n+    fun dim(name: String, matrix: Matrix<Double>, rows: Char, cols: Int) {\n+        declared[name] = Pair(\"${rows}\", \"${cols}\")\n+        val inferenceSource = if (matrix.numRows() == cols) {\n+            matrix.numCols()\n+        } else {\n+            matrix.numRows()\n+        }\n+        dimensions[matrix] = Pair(inferValues(rows, inferenceSource), mutableSetOf(cols))\n+    }\n+\n+    fun dim(name: String, matrix: Matrix<Double>, rows: Char, cols: Char) {\n+        declared[name] = Pair(\"${rows}\", \"${cols}\")\n+        dimensions[matrix] = Pair(inferValues(rows, matrix.numRows(), matrix.numCols()),\n+                                  inferValues(cols, matrix.numRows(), matrix.numCols()))\n+    }\n+\n+    fun markTransposable(matrix: Matrix<Double>) {\n+        transposable.add(matrix)\n+    }\n+\n+    override public fun performValidation(context: ValidationContext) {\n+        val failed = mutableListOf<Pair<String, Matrix<Double>>>()\n+        for ((index, matrix) in context.matrices.withIndex()) {\n+            val matrixDimension = dimensions[matrix]\n+            if (matrixDimension != null) {\n+                val (expectedRows, expectedCols) = matrixDimension\n+                val actualRows = matrix.numRows()\n+                val actualCols = matrix.numCols()\n+                if (actualRows in expectedRows && actualCols in expectedCols) {\n+                    expectedRows.retainAll(arrayOf(actualRows))\n+                    expectedCols.retainAll(arrayOf(actualCols))\n+                } else if (actualRows in expectedCols\n+                        && actualCols in expectedRows\n+                        && matrix in transposable) {\n+                    expectedCols.retainAll(arrayOf(actualRows))\n+                    expectedRows.retainAll(arrayOf(actualCols))\n+                } else {\n+                    failed.add(Pair(context.matrixNames[index], matrix))\n+                }\n+            }\n+        }\n+        if (failed.size > 0) {\n+            throw IndexOutOfBoundsException(concoctExceptionMessage(context, failed))\n+        }\n+    }\n+\n+\n+    // Append text to the PrintStream, padded out to the given width with the given characters.\n+    private fun PrintStream.writeColumn(width: Int, text: String, lpad: Char? = null, rpad: Char? = ' ',\n+                                        gap: Char? = ' ') {\n+        val nrpad = if (rpad == null) 0 else (width - text.length) / (if (lpad == null) 1 else 2)\n+        val nlpad = if (lpad == null) 0 else (width - nrpad - text.length)\n+        if (lpad != null)\n+            for (x in 1..nlpad)\n+                append(lpad)\n+        append(text)\n+        if (rpad != null)\n+            for (x in 1..nrpad)\n+                append(rpad)\n+        if (gap != null)\n+            append(gap)\n+    }\n+\n+    // Build an english-description of numeric dimensions\n+    private fun concoctFixedDescription(rows: String, cols: String): String {\n+        if (rows == \"1\")\n+            return \"a ${cols}-vector\"\n+        if (cols == \"1\")\n+            return \"a ${rows}-vector\"\n+        return \"${rows} by ${cols}\"\n+    }\n+\n+    // Build a list of things compareTo needs to be the same as. Looks up other things that have\n+    // the same character-based dimension defined.\n+    private fun concoctComparison(name: String, compareTo: String): List<String> {\n+        val out = mutableListOf<String>()\n+        if (compareTo !in seenInError) {\n+            for ((name2, dims) in declared) {\n+                val (rows, cols) = dims\n+                if (name2 != name) {\n+                    if (rows == compareTo) out.add(\"as ${name2} has rows\")\n+                    if (cols == compareTo) out.add(\"as ${name2} has columns\")\n+                }\n+            }\n+            seenInError.add(compareTo)\n+        }\n+        return out\n+    }\n+\n+    // Build our pretty table of dimensions real vs actual.\n+    private fun concoctExceptionMessage(context: ValidationContext, failed: List<Pair<String,\n+    Matrix<Double>>>): String {\n+        val strings = mutableListOf<String>()\n+        for ((index, matrix) in context.matrices.withIndex()) {\n+            val name = context.matrixNames[index]\n+            val decl = declared[name]\n+            if (decl != null) {\n+                strings.add(name)\n+                val (r, c) = decl\n+                strings.add(\"${r}x${c}\")\n+                strings.add(\"${matrix.numRows()}x${matrix.numCols()}\")\n+            }\n+        }\n+        val headers = arrayOf(\"Matrix\", \"Required\", \"Actual\")\n+        val widths = mutableListOf(headers[0].length, headers[1].length, headers[2].length)\n+        for ((index, str) in strings.withIndex())\n+            if (str.length > widths[index % widths.size])\n+                widths[index % widths.size] = str.length\n+        val bstream = ByteArrayOutputStream()","path":"src/main/golem/util/validation/dimensions.kt","position":158,"original_position":158,"commit_id":"dd6992c4b4b0bb10b0e52f92c13682c5bc41b59d","original_commit_id":"dd6992c4b4b0bb10b0e52f92c13682c5bc41b59d","user":{"login":"kyonifer","id":980312,"avatar_url":"https://avatars.githubusercontent.com/u/980312?v=3","gravatar_id":"","url":"https://api.github.com/users/kyonifer","html_url":"https://github.com/kyonifer","followers_url":"https://api.github.com/users/kyonifer/followers","following_url":"https://api.github.com/users/kyonifer/following{/other_user}","gists_url":"https://api.github.com/users/kyonifer/gists{/gist_id}","starred_url":"https://api.github.com/users/kyonifer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kyonifer/subscriptions","organizations_url":"https://api.github.com/users/kyonifer/orgs","repos_url":"https://api.github.com/users/kyonifer/repos","events_url":"https://api.github.com/users/kyonifer/events{/privacy}","received_events_url":"https://api.github.com/users/kyonifer/received_events","type":"User","site_admin":false},"body":"All of this stream chaining might be unnecessary? Can probably just make a StringBuilder then use a chain of .append(..)/.appendln(..) followed by .toString().\r\n","created_at":"2016-03-30T03:40:02Z","updated_at":"2016-03-30T03:40:02Z","html_url":"https://github.com/kyonifer/golem/pull/2#discussion_r57833620","pull_request_url":"https://api.github.com/repos/kyonifer/golem/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/kyonifer/golem/pulls/comments/57833620"},"html":{"href":"https://github.com/kyonifer/golem/pull/2#discussion_r57833620"},"pull_request":{"href":"https://api.github.com/repos/kyonifer/golem/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/kyonifer/golem/pulls/2","id":64247168,"html_url":"https://github.com/kyonifer/golem/pull/2","diff_url":"https://github.com/kyonifer/golem/pull/2.diff","patch_url":"https://github.com/kyonifer/golem/pull/2.patch","issue_url":"https://api.github.com/repos/kyonifer/golem/issues/2","number":2,"state":"open","locked":false,"title":"Add a Matrix Validation Utility","user":{"login":"drmoose","id":5732028,"avatar_url":"https://avatars.githubusercontent.com/u/5732028?v=3","gravatar_id":"","url":"https://api.github.com/users/drmoose","html_url":"https://github.com/drmoose","followers_url":"https://api.github.com/users/drmoose/followers","following_url":"https://api.github.com/users/drmoose/following{/other_user}","gists_url":"https://api.github.com/users/drmoose/gists{/gist_id}","starred_url":"https://api.github.com/users/drmoose/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drmoose/subscriptions","organizations_url":"https://api.github.com/users/drmoose/orgs","repos_url":"https://api.github.com/users/drmoose/repos","events_url":"https://api.github.com/users/drmoose/events{/privacy}","received_events_url":"https://api.github.com/users/drmoose/received_events","type":"User","site_admin":false},"body":"Because there is no way to validate matrices at compile time, functions which take a `Matrix<Double>` as their parameter must inspect the dimensions, symmetry, and bounds of that matrix and throw a runtime exception if the given matrix is not valid for the context. This pull request provides a reusable validation DSL that generate detailed, consistent exceptions for matrix parameters in function calls. The functionality is documented in [the accompanying README](https://github.com/drmoose/golem/tree/validation/src/main/golem/util/validation).","created_at":"2016-03-26T19:49:51Z","updated_at":"2016-03-30T03:40:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"576b086a33250d819752b22ad1681785fc62a0c8","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/kyonifer/golem/pulls/2/commits","review_comments_url":"https://api.github.com/repos/kyonifer/golem/pulls/2/comments","review_comment_url":"https://api.github.com/repos/kyonifer/golem/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kyonifer/golem/issues/2/comments","statuses_url":"https://api.github.com/repos/kyonifer/golem/statuses/dd6992c4b4b0bb10b0e52f92c13682c5bc41b59d","head":{"label":"drmoose:validation","ref":"validation","sha":"dd6992c4b4b0bb10b0e52f92c13682c5bc41b59d","user":{"login":"drmoose","id":5732028,"avatar_url":"https://avatars.githubusercontent.com/u/5732028?v=3","gravatar_id":"","url":"https://api.github.com/users/drmoose","html_url":"https://github.com/drmoose","followers_url":"https://api.github.com/users/drmoose/followers","following_url":"https://api.github.com/users/drmoose/following{/other_user}","gists_url":"https://api.github.com/users/drmoose/gists{/gist_id}","starred_url":"https://api.github.com/users/drmoose/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drmoose/subscriptions","organizations_url":"https://api.github.com/users/drmoose/orgs","repos_url":"https://api.github.com/users/drmoose/repos","events_url":"https://api.github.com/users/drmoose/events{/privacy}","received_events_url":"https://api.github.com/users/drmoose/received_events","type":"User","site_admin":false},"repo":{"id":54782438,"name":"golem","full_name":"drmoose/golem","owner":{"login":"drmoose","id":5732028,"avatar_url":"https://avatars.githubusercontent.com/u/5732028?v=3","gravatar_id":"","url":"https://api.github.com/users/drmoose","html_url":"https://github.com/drmoose","followers_url":"https://api.github.com/users/drmoose/followers","following_url":"https://api.github.com/users/drmoose/following{/other_user}","gists_url":"https://api.github.com/users/drmoose/gists{/gist_id}","starred_url":"https://api.github.com/users/drmoose/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drmoose/subscriptions","organizations_url":"https://api.github.com/users/drmoose/orgs","repos_url":"https://api.github.com/users/drmoose/repos","events_url":"https://api.github.com/users/drmoose/events{/privacy}","received_events_url":"https://api.github.com/users/drmoose/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/drmoose/golem","description":"A scientific library for Kotlin.","fork":true,"url":"https://api.github.com/repos/drmoose/golem","forks_url":"https://api.github.com/repos/drmoose/golem/forks","keys_url":"https://api.github.com/repos/drmoose/golem/keys{/key_id}","collaborators_url":"https://api.github.com/repos/drmoose/golem/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/drmoose/golem/teams","hooks_url":"https://api.github.com/repos/drmoose/golem/hooks","issue_events_url":"https://api.github.com/repos/drmoose/golem/issues/events{/number}","events_url":"https://api.github.com/repos/drmoose/golem/events","assignees_url":"https://api.github.com/repos/drmoose/golem/assignees{/user}","branches_url":"https://api.github.com/repos/drmoose/golem/branches{/branch}","tags_url":"https://api.github.com/repos/drmoose/golem/tags","blobs_url":"https://api.github.com/repos/drmoose/golem/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/drmoose/golem/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/drmoose/golem/git/refs{/sha}","trees_url":"https://api.github.com/repos/drmoose/golem/git/trees{/sha}","statuses_url":"https://api.github.com/repos/drmoose/golem/statuses/{sha}","languages_url":"https://api.github.com/repos/drmoose/golem/languages","stargazers_url":"https://api.github.com/repos/drmoose/golem/stargazers","contributors_url":"https://api.github.com/repos/drmoose/golem/contributors","subscribers_url":"https://api.github.com/repos/drmoose/golem/subscribers","subscription_url":"https://api.github.com/repos/drmoose/golem/subscription","commits_url":"https://api.github.com/repos/drmoose/golem/commits{/sha}","git_commits_url":"https://api.github.com/repos/drmoose/golem/git/commits{/sha}","comments_url":"https://api.github.com/repos/drmoose/golem/comments{/number}","issue_comment_url":"https://api.github.com/repos/drmoose/golem/issues/comments{/number}","contents_url":"https://api.github.com/repos/drmoose/golem/contents/{+path}","compare_url":"https://api.github.com/repos/drmoose/golem/compare/{base}...{head}","merges_url":"https://api.github.com/repos/drmoose/golem/merges","archive_url":"https://api.github.com/repos/drmoose/golem/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/drmoose/golem/downloads","issues_url":"https://api.github.com/repos/drmoose/golem/issues{/number}","pulls_url":"https://api.github.com/repos/drmoose/golem/pulls{/number}","milestones_url":"https://api.github.com/repos/drmoose/golem/milestones{/number}","notifications_url":"https://api.github.com/repos/drmoose/golem/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/drmoose/golem/labels{/name}","releases_url":"https://api.github.com/repos/drmoose/golem/releases{/id}","deployments_url":"https://api.github.com/repos/drmoose/golem/deployments","created_at":"2016-03-26T14:22:00Z","updated_at":"2016-03-26T15:23:42Z","pushed_at":"2016-03-26T19:51:05Z","git_url":"git://github.com/drmoose/golem.git","ssh_url":"git@github.com:drmoose/golem.git","clone_url":"https://github.com/drmoose/golem.git","svn_url":"https://github.com/drmoose/golem","homepage":null,"size":336,"stargazers_count":0,"watchers_count":0,"language":"Kotlin","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"kyonifer:master","ref":"master","sha":"bfb8e9ea836d559d7a01d2c36c9985d4b29b036e","user":{"login":"kyonifer","id":980312,"avatar_url":"https://avatars.githubusercontent.com/u/980312?v=3","gravatar_id":"","url":"https://api.github.com/users/kyonifer","html_url":"https://github.com/kyonifer","followers_url":"https://api.github.com/users/kyonifer/followers","following_url":"https://api.github.com/users/kyonifer/following{/other_user}","gists_url":"https://api.github.com/users/kyonifer/gists{/gist_id}","starred_url":"https://api.github.com/users/kyonifer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kyonifer/subscriptions","organizations_url":"https://api.github.com/users/kyonifer/orgs","repos_url":"https://api.github.com/users/kyonifer/repos","events_url":"https://api.github.com/users/kyonifer/events{/privacy}","received_events_url":"https://api.github.com/users/kyonifer/received_events","type":"User","site_admin":false},"repo":{"id":52458035,"name":"golem","full_name":"kyonifer/golem","owner":{"login":"kyonifer","id":980312,"avatar_url":"https://avatars.githubusercontent.com/u/980312?v=3","gravatar_id":"","url":"https://api.github.com/users/kyonifer","html_url":"https://github.com/kyonifer","followers_url":"https://api.github.com/users/kyonifer/followers","following_url":"https://api.github.com/users/kyonifer/following{/other_user}","gists_url":"https://api.github.com/users/kyonifer/gists{/gist_id}","starred_url":"https://api.github.com/users/kyonifer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kyonifer/subscriptions","organizations_url":"https://api.github.com/users/kyonifer/orgs","repos_url":"https://api.github.com/users/kyonifer/repos","events_url":"https://api.github.com/users/kyonifer/events{/privacy}","received_events_url":"https://api.github.com/users/kyonifer/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kyonifer/golem","description":"A scientific library for Kotlin.","fork":false,"url":"https://api.github.com/repos/kyonifer/golem","forks_url":"https://api.github.com/repos/kyonifer/golem/forks","keys_url":"https://api.github.com/repos/kyonifer/golem/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kyonifer/golem/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kyonifer/golem/teams","hooks_url":"https://api.github.com/repos/kyonifer/golem/hooks","issue_events_url":"https://api.github.com/repos/kyonifer/golem/issues/events{/number}","events_url":"https://api.github.com/repos/kyonifer/golem/events","assignees_url":"https://api.github.com/repos/kyonifer/golem/assignees{/user}","branches_url":"https://api.github.com/repos/kyonifer/golem/branches{/branch}","tags_url":"https://api.github.com/repos/kyonifer/golem/tags","blobs_url":"https://api.github.com/repos/kyonifer/golem/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kyonifer/golem/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kyonifer/golem/git/refs{/sha}","trees_url":"https://api.github.com/repos/kyonifer/golem/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kyonifer/golem/statuses/{sha}","languages_url":"https://api.github.com/repos/kyonifer/golem/languages","stargazers_url":"https://api.github.com/repos/kyonifer/golem/stargazers","contributors_url":"https://api.github.com/repos/kyonifer/golem/contributors","subscribers_url":"https://api.github.com/repos/kyonifer/golem/subscribers","subscription_url":"https://api.github.com/repos/kyonifer/golem/subscription","commits_url":"https://api.github.com/repos/kyonifer/golem/commits{/sha}","git_commits_url":"https://api.github.com/repos/kyonifer/golem/git/commits{/sha}","comments_url":"https://api.github.com/repos/kyonifer/golem/comments{/number}","issue_comment_url":"https://api.github.com/repos/kyonifer/golem/issues/comments{/number}","contents_url":"https://api.github.com/repos/kyonifer/golem/contents/{+path}","compare_url":"https://api.github.com/repos/kyonifer/golem/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kyonifer/golem/merges","archive_url":"https://api.github.com/repos/kyonifer/golem/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kyonifer/golem/downloads","issues_url":"https://api.github.com/repos/kyonifer/golem/issues{/number}","pulls_url":"https://api.github.com/repos/kyonifer/golem/pulls{/number}","milestones_url":"https://api.github.com/repos/kyonifer/golem/milestones{/number}","notifications_url":"https://api.github.com/repos/kyonifer/golem/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kyonifer/golem/labels{/name}","releases_url":"https://api.github.com/repos/kyonifer/golem/releases{/id}","deployments_url":"https://api.github.com/repos/kyonifer/golem/deployments","created_at":"2016-02-24T16:42:40Z","updated_at":"2016-03-18T17:29:09Z","pushed_at":"2016-03-30T03:07:29Z","git_url":"git://github.com/kyonifer/golem.git","ssh_url":"git@github.com:kyonifer/golem.git","clone_url":"https://github.com/kyonifer/golem.git","svn_url":"https://github.com/kyonifer/golem","homepage":null,"size":336,"stargazers_count":2,"watchers_count":2,"language":"Kotlin","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":1,"forks":2,"open_issues":1,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kyonifer/golem/pulls/2"},"html":{"href":"https://github.com/kyonifer/golem/pull/2"},"issue":{"href":"https://api.github.com/repos/kyonifer/golem/issues/2"},"comments":{"href":"https://api.github.com/repos/kyonifer/golem/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/kyonifer/golem/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/kyonifer/golem/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kyonifer/golem/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/kyonifer/golem/statuses/dd6992c4b4b0bb10b0e52f92c13682c5bc41b59d"}}}}