{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Perspectives on matrix multiplication\n", "\n", "\n", "Lots of you seem to have learned how to multiply matrices ([matrix multiplication](https://en.wikipedia.org/wiki/Matrix_multiplication)) in high school. \n", "We compute the product $C=AB$ of an $m \\times n$ matrix $A$ with an $n \\times p$ matrix $B$ to produce an $m \\times p$ matrix $C$.\n", "\n", "Did you ever wonder why \"matmul\" has such a fancy definition?\n", "\n", "When we add matrices we add elements. Why coudn't matmul be just as easy?\n", "\n", "## Compare Elementwise Multiply\n", "\n", "Of course the elementwise multiply is doable but never seems to be quite as important:\n", "\n", "(I'll bet your high school teacher never mentioned elementwise multiply!)\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A .* B = [7 4; 9 8]\n", "A * B = [13 6; 33 14]\n" ] } ], "source": [ "A=[1 2\n", " 3 4]\n", "B=[7 2\n", " 3 2]\n", "@show(A .* B) # Elementwise times is the \"dot star\"\n", "@show(A * B); # Matmul is just the \"star\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For square n x n matrices, elementwise multiply requires $n^2$ operations, while matmul requires about $2n^3$. (Think $n^2$ dot products, each requiring $n$ mults and almost $n$ adds.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Raising the Abstraction\n", "\n", "Why is matmul defined this way? We will find out later in the course when we begin to understand that a matrix represents a linear transformation, and matmul is the natural representation of the composition of transformations. It is only then you can understand the true nature of matrix multiplication. (Bet your high school teacher never told you that!)\n", "\n", "One of our goals in 18.06 is to sometimes stop thinking of matrices as arrays of numbers, and more as holistic objects.\n", "\n", "Abstractly, the rules for matrix multiplication are determined once you define how to multiply matrices by vectors $Ax$, the central [linear operation](https://en.wikipedia.org/wiki/Linear_map) of 18.06, by requiring that multiplication be [associative](https://en.wikipedia.org/wiki/Associative_property). That is, we require:\n", "$$\n", "A(Bx)=(AB)x\n", "$$\n", "for all matrices $A$ and $B$ and all vectors $x$. The expression $A(Bx)$ involves only matrix × vector (computing $y=Bx$ then $Ay$), and requiring this to equal $(AB)x$ actually uniquely defines the matrix–matrix product $AB$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Perspective 1 (high school!): rows × columns\n", "\n", "The most familar definition is that you take **dot products of rows of A with columns of B** to get the product $C$. For example:\n", "$$\n", "\\begin{pmatrix}\n", " -14 & 5 & 10 \\\\\n", " \\color{red}{-5} & -20 & 10 \\\\\n", " -6 & 10 & 6\n", "\\end{pmatrix} =\n", "\\begin{pmatrix}\n", " 2 & -1 & 5 \\\\\n", " \\color{red}{3} & \\color{red}{4} & \\color{red}{4} \\\\\n", " -4 & -2 & 0\n", "\\end{pmatrix}\n", "\\begin{pmatrix}\n", "\\color{red}{1} & 0 & -2 \\\\\n", " \\color{red}{1} & -5 & 1 \\\\\n", " \\color{red}{-3} & 0 & 3\n", "\\end{pmatrix}\n", "$$\n", "where we have highlighted the entry $\\color{red}{-5 = 3 \\times 1 + 4 \\times 1 + 4 \\times -3}$ (second row of $A$ ⋅ first column of $B$).\n", "\n", "This can be written out as the formula\n", "$$\n", "c_{ij} = \\sum_{k=1}^n a_{ik} b_{kj}\n", "$$\n", "in terms of the entries of the matrices, e.g. $c_{ij}$ is the entry in row $i$, column $j$ of $C$, assuming $A$ has $n$ columns and $B$ has $n$ rows.\n", "\n", "Essentially all matrix multiplications in practice are done with a version of this formula — at least, with the same operations, but often the *order* in which you multiply/add individual numbers is re-arranged.\n", "\n", "**In this notebook, we will explore several ways to *think* about these operations by re-arranging their order.**" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -14 5 10\n", " -5 -20 10\n", " -6 10 6" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A = [ 2 -1 5\n", " 3 4 4\n", " -4 -2 0]\n", "B = [ 1 0 -2\n", " 1 -5 1\n", " -3 0 3]\n", "C = A * B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because matrix multiplication is generally [not commutative](https://en.wikipedia.org/wiki/Commutative_property), $AB$ and $BA$ give *different* matrices:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 10 3 5\n", " -17 -23 -15\n", " -18 -3 -15" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B*A" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -24 2 5\n", " 12 3 25\n", " 12 13 21" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A*B - B*A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Though sometimes it can happen to be commutative." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Float64,2}:\n", " -138.0 -89.0 -147.0\n", " -101.0 -24.0 92.0\n", " 44.0 46.0 -132.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A*(A^2 + 2*A + inv(A)*10) " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Float64,2}:\n", " -138.0 -89.0 -147.0\n", " -101.0 -24.0 92.0\n", " 44.0 46.0 -132.0" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(A^2 + 2*A + inv(A)*10) * A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want, we can compute the individual dot products in Julia too. For example, let's compute $c_{2,1} = -5$ (the 2nd row and first column of $C$, or `C[2,1]` in Julia) by taking the dot product of the second row of $A$ with the first column of $B$.\n", "\n", "To extract rows and columns of a matrix, Julia supports a syntax for \"array slicing\" pioneered by APL. The second row of $A$ is `A[2,:]`, and the first column of `B` is `B[:,1]`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 2 -1 5\n", " 3 4 4\n", " -4 -2 0" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[2,3] # 2nd row, 3rd col" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Int64,1}:\n", " 3\n", " 4\n", " 4" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[2,:] # 2nd row of A" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 1 0 -2\n", " 1 -5 1\n", " -3 0 3" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Int64,1}:\n", " 1\n", " 1\n", " -3" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B[:,1] # 1st column of B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can compute $c_{2,1}$ by their dot product via the `dot` function:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "-5" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dot(A[2,:], B[:,1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also write the dot product with the $x \\dot y$ notation:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "-5" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[2,:] ⋅ B[:,1] # type \\cdot + tab" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "α = A[2,3] # \\alpha + tab" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This matches $c_{2,1}$ from above, or `C[2,1]` in Julia:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "-5" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C[2,1]" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "-5" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[2,:]' * B[:,1] # yet another way to get a dot product" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The summation $$c_{ij} = \\sum_{k=1}^n a_{ik} b_{kj}$$ directly in a triple loop code" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "matmul_ijk (generic function with 1 method)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function matmul_ijk(A,B)\n", " m,n = size(A)\n", " n2,p = size(B)\n", " if n≠n2 error(\"No good, n=$n ≠ n2=$(n2)\") end\n", " \n", " C = fill(0,m,p) # m x p \"zeros\" matrix\n", " \n", " for i=1:m, j=1:p, k=1:n\n", " C[i,j] += A[i,k]*B[k,j] # shorthand for C[i,j] = C[i,j] + A[i,k]*B[k,j] \n", " end\n", " \n", " return C \n", "end" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -14 5 10\n", " -5 -20 10\n", " -6 10 6" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "matmul_ijk(A,B)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Perspective 2: matrix × columns\n", "\n", "$AB$ can be viewed as multiplying $A$ on the *left* by each *column* of $B$.\n", "\n", "For example, let's multiply $A$ by the first column of $B$:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -14 5 10\n", " -5 -20 10\n", " -6 10 6" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A*B" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Int64,1}:\n", " 1\n", " 1\n", " -3" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B[:,1]" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Int64,1}:\n", " -14\n", " -5\n", " -6" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A * B[:,1]" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n" ], "text/plain": [ "HTML{String}(\" \\n\")" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "HTML{String}(\"\")" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "HTML{String}(\"\")" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ " \n" ], "text/plain": [ "HTML{String}(\" \\n\")" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "using Interact" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "
\n", "WebIO.mount(this.previousSibling,{"props":{},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[{"props":{"className":"field"},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[{"props":{},"nodeType":"Scope","type":"node","instanceArgs":{"imports":{"data":[{"name":"knockout","type":"js","url":"/assetserver/d6df0bcbf61025c952fa7d6260c0502952fc6253-knockout.js"},{"name":"knockout_punches","type":"js","url":"/assetserver/76b9e9b6191c21207f614aefd21121e841930334-knockout_punches.js"},{"name":null,"type":"js","url":"/assetserver/aee18abf5b39fa7dbdb49e13ea37aa8617a93fca-all.js"},{"name":null,"type":"css","url":"/assetserver/b8ef71fc8f8c937f705d60a54c85dd170cf7e73f-style.css"},{"name":null,"type":"css","url":"/assetserver/342c1e68f950ec6d9432ff00342c66dae6d08a65-main.css"}],"type":"async_block"},"id":"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09","handlers":{"_promises":{"importsLoaded":[function (ko, koPunches) {\n", " ko.punches.enableAll();\n", " ko.bindingHandlers.numericValue = {\n", " init : function(element, valueAccessor, allBindings, data, context) {\n", " var stringified = ko.observable(ko.unwrap(valueAccessor()));\n", " stringified.subscribe(function(value) {\n", " var val = parseFloat(value);\n", " if (!isNaN(val)) {\n", " valueAccessor()(val);\n", " }\n", " })\n", " valueAccessor().subscribe(function(value) {\n", " var str = JSON.stringify(value);\n", " if ((str == "0") && (["-0", "-0."].indexOf(stringified()) >= 0))\n", " return;\n", " if (["null", ""].indexOf(str) >= 0)\n", " return;\n", " stringified(str);\n", " })\n", " ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\n", " }\n", " };\n", " var json_data = JSON.parse("{\\"changes\\":0,\\"value\\":2}");\n", " var self = this;\n", " function AppViewModel() {\n", " for (var key in json_data) {\n", " var el = json_data[key];\n", " this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\n", " }\n", " \n", " [this["displayedvalue"]=ko.computed(function () {return this.value();},this)]\n", " [this["changes"].subscribe((function (val){!(this.valueFromJulia["changes"]) ? (WebIO.setval({"name":"changes","scope":"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09","id":"ob_02","type":"observable"},val)) : undefined; return this.valueFromJulia["changes"]=false}),self),this["value"].subscribe((function (val){!(this.valueFromJulia["value"]) ? (WebIO.setval({"name":"value","scope":"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09","id":"ob_01","type":"observable"},val)) : undefined; return this.valueFromJulia["value"]=false}),self)]\n", " \n", " }\n", " self.model = new AppViewModel();\n", " self.valueFromJulia = {};\n", " for (var key in json_data) {\n", " self.valueFromJulia[key] = false;\n", " }\n", " ko.applyBindings(self.model, self.dom);\n", "}\n", "]},"changes":[(function (val){return (val!=this.model["changes"]()) ? (this.valueFromJulia["changes"]=true, this.model["changes"](val)) : undefined})],"value":[(function (val){return (val!=this.model["value"]()) ? (this.valueFromJulia["value"]=true, this.model["value"](val)) : undefined})]},"systemjs_options":null,"observables":{"changes":{"sync":false,"id":"ob_02","value":0},"value":{"sync":true,"id":"ob_01","value":2}}},"children":[{"props":{"attributes":{"style":"display:flex; justify-content:center; align-items:center;"}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[{"props":{"attributes":{"style":"text-align:right;width:18%"}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[{"props":{"className":"interact ","style":{"padding":"5px 10px 0px 10px"}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"label"},"children":["j"]}]},{"props":{"attributes":{"style":"flex-grow:1; margin: 0 2%"}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[{"props":{"max":3,"min":1,"attributes":{"type":"range","data-bind":"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}"},"step":1,"className":"slider slider is-fullwidth","style":{}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"input"},"children":[]}]},{"props":{"attributes":{"style":"width:18%"}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[{"props":{"attributes":{"data-bind":"text: displayedvalue"}},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"p"},"children":[]}]}]}]}]},{"props":{},"nodeType":"Scope","type":"node","instanceArgs":{"imports":{"data":[],"type":"async_block"},"id":"scope-cf0ad405-f31b-41c5-8da2-3fac2f9d21a5","handlers":{"obs-output":[function (updated_htmlstr) {\n", " var el = this.dom.querySelector("#out");\n", " WebIO.propUtils.setInnerHtml(el, updated_htmlstr);\n", "}]},"systemjs_options":null,"observables":{"obs-output":{"sync":false,"id":"ob_06","value":"<div class='display:none'></div><unsafe-script style='display:none'>\\nWebIO.mount(this.previousSibling,{&quot;props&quot;:{&quot;attributes&quot;:{&quot;style&quot;:&quot;display:flex; justify-content:center; align-items:center;&quot;}},&quot;nodeType&quot;:&quot;DOM&quot;,&quot;type&quot;:&quot;node&quot;,&quot;instanceArgs&quot;:{&quot;namespace&quot;:&quot;html&quot;,&quot;tag&quot;:&quot;div&quot;},&quot;children&quot;:[{&quot;props&quot;:{&quot;setInnerHtml&quot;:&quot;&lt;pre&gt;3-element Array{Int64,1}:\\\\n 5\\\\n -20\\\\n 10&lt;/pre&gt;&quot;},&quot;nodeType&quot;:&quot;DOM&quot;,&quot;type&quot;:&quot;node&quot;,&quot;instanceArgs&quot;:{&quot;namespace&quot;:&quot;html&quot;,&quot;tag&quot;:&quot;div&quot;},&quot;children&quot;:[]}]})</unsafe-script>"}}},"children":[{"props":{"id":"out","setInnerHtml":"<div class='display:none'></div><unsafe-script style='display:none'>\\nWebIO.mount(this.previousSibling,{&quot;props&quot;:{&quot;attributes&quot;:{&quot;style&quot;:&quot;display:flex; justify-content:center; align-items:center;&quot;}},&quot;nodeType&quot;:&quot;DOM&quot;,&quot;type&quot;:&quot;node&quot;,&quot;instanceArgs&quot;:{&quot;namespace&quot;:&quot;html&quot;,&quot;tag&quot;:&quot;div&quot;},&quot;children&quot;:[{&quot;props&quot;:{&quot;setInnerHtml&quot;:&quot;&lt;pre&gt;3-element Array{Int64,1}:\\\\n 5\\\\n -20\\\\n 10&lt;/pre&gt;&quot;},&quot;nodeType&quot;:&quot;DOM&quot;,&quot;type&quot;:&quot;node&quot;,&quot;instanceArgs&quot;:{&quot;namespace&quot;:&quot;html&quot;,&quot;tag&quot;:&quot;div&quot;},&quot;children&quot;:[]}]})</unsafe-script>"},"nodeType":"DOM","type":"node","instanceArgs":{"namespace":"html","tag":"div"},"children":[]}]}]})\n", "
" ], "text/plain": [ "(div\n", " Widgets.Widget{:slider}(DataStructures.OrderedDict{Symbol,Any}(:changes=>Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(#= circular reference @-7 =#), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(\"ob_01\", 2, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37)), Observables.g]), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(#= circular reference @-8 =#), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(\"ob_01\", 2, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37)), Observables.g]), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.#37))]),:value=>Observables.Observable{Int64}(\"ob_01\", 2, Any[WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(#= circular reference @-7 =#), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(#= circular reference @-8 =#), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.#37)), Observables.g])), Observables.Observable{Int64}(\"ob_01\", 2, Any[WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(#= circular reference @-7 =#), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(#= circular reference @-8 =#), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.#37)), Observables.g]), Observables.Observable{Int64}(\"ob_01\", 2, Any[WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(#= circular reference @-7 =#), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.SyncCallback(WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(#= circular reference @-8 =#), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), WebIO.#37)), Observables.g]), WebIO.Scope(\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :label), Any[\"j\"], Dict{Symbol,Any}(Pair{Symbol,Any}(:className, \"interact \"),Pair{Symbol,Any}(:style, Dict{Any,Any}(Pair{Any,Any}(:padding, \"5px 10px 0px 10px\")))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"text-align:right;width:18%\"))), 2), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :input), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:max, 3),Pair{Symbol,Any}(:min, 1),Pair{Symbol,Any}(:attributes, Dict{Any,Any}(Pair{Any,Any}(:type, \"range\"),Pair{Any,Any}(Symbol(\"data-bind\"), \"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}\"))),Pair{Symbol,Any}(:step, 1),Pair{Symbol,Any}(:className, \"slider slider is-fullwidth\"),Pair{Symbol,Any}(:style, Dict{Any,Any}())), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"flex-grow:1; margin: 0 2%\"))), 1), WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :p), Any[], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"data-bind\"=>\"text: displayedvalue\"))), 0)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"width:18%\"))), 1)], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 7), Dict{String,Tuple{Observables.Observable,Union{Bool, Void}}}(Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"changes\", (Observables.Observable{Int64}(\"ob_02\", 0, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37))]), nothing)),Pair{String,Tuple{Observables.Observable,Union{Bool, Void}}}(\"value\", (Observables.Observable{Int64}(\"ob_01\", 2, Any[WebIO.SyncCallback(WebIO.Scope(#= circular reference @-7 =#), WebIO.SyncCallback(WebIO.Scope(#= circular reference @-8 =#), WebIO.#37)), Observables.g]), nothing))), Set{String}(), nothing, Any[\"knockout\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout.js\", \"knockout_punches\"=>\"/Users/stevenj/.julia/v0.6/Knockout/src/../assets/knockout_punches.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/all.js\", \"/Users/stevenj/.julia/v0.6/InteractBase/src/../assets/style.css\", \"/Users/stevenj/.julia/v0.6/InteractBulma/src/../assets/main.css\"], Dict{Any,Any}(Pair{Any,Any}(\"_promises\", Dict{Any,Any}(Pair{Any,Any}(\"importsLoaded\", Any[WebIO.JSString(\"function (ko, koPunches) {\\n ko.punches.enableAll();\\n ko.bindingHandlers.numericValue = {\\n init : function(element, valueAccessor, allBindings, data, context) {\\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\\n stringified.subscribe(function(value) {\\n var val = parseFloat(value);\\n if (!isNaN(val)) {\\n valueAccessor()(val);\\n }\\n })\\n valueAccessor().subscribe(function(value) {\\n var str = JSON.stringify(value);\\n if ((str == \\\"0\\\") && ([\\\"-0\\\", \\\"-0.\\\"].indexOf(stringified()) >= 0))\\n return;\\n if ([\\\"null\\\", \\\"\\\"].indexOf(str) >= 0)\\n return;\\n stringified(str);\\n })\\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\\n }\\n };\\n var json_data = JSON.parse(\\\"{\\\\\\\"changes\\\\\\\":0,\\\\\\\"value\\\\\\\":2}\\\");\\n var self = this;\\n function AppViewModel() {\\n for (var key in json_data) {\\n var el = json_data[key];\\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\\n }\\n \\n [this[\\\"displayedvalue\\\"]=ko.computed(function () {return this.value();},this)]\\n [this[\\\"changes\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"changes\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"changes\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_02\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"changes\\\"]=false}),self),this[\\\"value\\\"].subscribe((function (val){!(this.valueFromJulia[\\\"value\\\"]) ? (WebIO.setval({\\\"name\\\":\\\"value\\\",\\\"scope\\\":\\\"knockout-component-16694083-7eb6-4348-b89a-28d470b67f09\\\",\\\"id\\\":\\\"ob_01\\\",\\\"type\\\":\\\"observable\\\"},val)) : undefined; return this.valueFromJulia[\\\"value\\\"]=false}),self)]\\n \\n }\\n self.model = new AppViewModel();\\n self.valueFromJulia = {};\\n for (var key in json_data) {\\n self.valueFromJulia[key] = false;\\n }\\n ko.applyBindings(self.model, self.dom);\\n}\\n\")]))),Pair{Any,Any}(\"changes\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"changes\\\"]()) ? (this.valueFromJulia[\\\"changes\\\"]=true, this.model[\\\"changes\\\"](val)) : undefined})\")]),Pair{Any,Any}(\"value\", Any[WebIO.JSString(\"(function (val){return (val!=this.model[\\\"value\\\"]()) ? (this.valueFromJulia[\\\"value\\\"]=true, this.model[\\\"value\\\"](val)) : undefined})\")])), WebIO.ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:3), Set{WebIO.AbstractConnection}(), Channel{WebIO.AbstractConnection}(sz_max:32,sz_curr:0))), Widgets.#4, Base.#55)\n", " Observables.Observable{Any}(\"ob_05\", WebIO.Node{WebIO.DOM}(WebIO.DOM(:html, :div), Any[[5, -20, 10]], Dict{Symbol,Any}(Pair{Symbol,Any}(:attributes, Dict(\"style\"=>\"display:flex; justify-content:center; align-items:center;\"))), 1), Any[]))" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@manipulate for j=1:3\n", " A * B[:,j]\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the first column of $C$! If we do this to *all* the columns of $B$, we get $C$:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -14 5 10\n", " -5 -20 10\n", " -6 10 6" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ A*B[:,1] A*B[:,2] A*B[:,3] ]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -14 5 10\n", " -5 -20 10\n", " -6 10 6" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A*B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Equivalently, each column of $B$ specifies a [linear combination](https://en.wikipedia.org/wiki/Linear_combination) of *columns* of $A$ to produce the columns of $C$. So, **if you want to rearrange the *columns* of a matrix, multiply it by another matrix on the *right***.\n", "\n", "For example, let's do the transformation that *flips the sign of the first column of $A$* and *swaps the second and third columns*." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 2 -1 5\n", " 3 4 4\n", " -4 -2 0" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -2 5 -1\n", " -3 4 4\n", " 4 0 -2" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A * [ -1 0 0\n", " 0 0 1\n", " 0 1 0 ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As another example, let's swap the first two columns:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -1 2 5\n", " 4 3 4\n", " -2 -4 0" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A * [ 0 1 0\n", " 1 0 0\n", " 0 0 1 ]" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 2 -1 5\n", " 3 4 4\n", " -4 -2 0" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A lot of students are perplexed. They wonder how it could be legal to reorder in this way. \n", "It might take working through a few examples by hand to realize that from the perspective\n", "of C[i,j], the same sum is accumulated in the same order, but the order in which the different elements of C finish may vary. This little Julia demo may help with this understanding." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Perspective 3: rows × matrix\n", "\n", "$AB$ can be viewed as multiplying each *row* of $A$ by the matrix $B$ on the *right*. Multiplying a [row vector](https://en.wikipedia.org/wiki/Row_and_column_vectors) by a matrix on the right produces another row vector.\n", "\n", "For example, here is the first row of $A$:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Int64,1}:\n", " 2\n", " -1\n", " 5" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[1,:]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whoops, slicing a matrix in Julia produces a 1d array, which is interpreted as a column vector, no matter how you slice it. We can't multiply a column vector by a matrix $B$ on the *right* — that operation is not defined in linear algebra (the dimensions don't match up). Julia will give an error if we try it:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 2 -1 5\n", " 3 4 4\n", " -4 -2 0" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "\u001b[91mDimensionMismatch(\"matrix A has dimensions (3,1), matrix B has dimensions (3,3)\")\u001b[39m", "output_type": "error", "traceback": [ "\u001b[91mDimensionMismatch(\"matrix A has dimensions (3,1), matrix B has dimensions (3,3)\")\u001b[39m", "", "Stacktrace:", " [1] \u001b[1m_generic_matmatmul!\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::Array{Int64,2}, ::Char, ::Char, ::Array{Int64,2}, ::Array{Int64,2}\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./linalg/matmul.jl:492\u001b[22m\u001b[22m", " [2] \u001b[1mgeneric_matmatmul!\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::Array{Int64,2}, ::Char, ::Char, ::Array{Int64,2}, ::Array{Int64,2}\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./linalg/matmul.jl:483\u001b[22m\u001b[22m", " [3] \u001b[1m*\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::Array{Int64,1}, ::Array{Int64,2}\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./linalg/matmul.jl:87\u001b[22m\u001b[22m", " [4] \u001b[1minclude_string\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::String, ::String\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./loading.jl:522\u001b[22m\u001b[22m", " [5] \u001b[1mexecute_request\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::ZMQ.Socket, ::IJulia.Msg\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m/Users/stevenj/.julia/v0.6/IJulia/src/execute_request.jl:193\u001b[22m\u001b[22m", " [6] \u001b[1m(::Compat.#inner#14{Array{Any,1},IJulia.#execute_request,Tuple{ZMQ.Socket,IJulia.Msg}})\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m/Users/stevenj/.julia/v0.6/Compat/src/Compat.jl:332\u001b[22m\u001b[22m", " [7] \u001b[1meventloop\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::ZMQ.Socket\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m/Users/stevenj/.julia/v0.6/IJulia/src/eventloop.jl:8\u001b[22m\u001b[22m", " [8] \u001b[1m(::IJulia.##13#16)\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./task.jl:335\u001b[22m\u001b[22m" ] } ], "source": [ "A[1,:] * B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get a row vector we must [transpose](https://en.wikipedia.org/wiki/Transpose) the slice `A[1,:]`. In linear algebra, the transpose of a vector $x$ is usually denoted $x^T$. In Julia, the transpose is `x.'`.\n", "\n", "If we omit the `.` and just write `x'` it is the [complex-conjugate of the transpose](https://en.wikipedia.org/wiki/Conjugate_transpose), sometimes called the *adjoint*, often denoted $x^H$ (in matrix textbooks), $x^*$ (in pure math), or $x^\\dagger$ (in physics). For real-valued vectors (no complex numbers), the conjugate transpose is the same as the transpose, and correspondingly we usually just do `x'` for real vectors." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×3 RowVector{Int64,Array{Int64,1}}:\n", " 2 -1 5" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[1,:]'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's multiply this by $B$, which should give the first *row* of $C$:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×3 RowVector{Int64,Array{Int64,1}}:\n", " -14 5 10" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[1,:]' * B" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " -14 5 10\n", " -5 -20 10\n", " -6 10 6" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yup!\n", "\n", "Note that if we multiply a row vector by a matrix on the *left*, it doesn't really make sense. Julia will give an error:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "ename": "LoadError", "evalue": "\u001b[91mDimensionMismatch(\"matrix A has dimensions (3,3), matrix B has dimensions (1,3)\")\u001b[39m", "output_type": "error", "traceback": [ "\u001b[91mDimensionMismatch(\"matrix A has dimensions (3,3), matrix B has dimensions (1,3)\")\u001b[39m", "", "Stacktrace:", " [1] \u001b[1m_generic_matmatmul!\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::Array{Int64,2}, ::Char, ::Char, ::Array{Int64,2}, ::Array{Int64,2}\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./linalg/matmul.jl:492\u001b[22m\u001b[22m", " [2] \u001b[1mgeneric_matmatmul!\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::Array{Int64,2}, ::Char, ::Char, ::Array{Int64,2}, ::Array{Int64,2}\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./linalg/matmul.jl:483\u001b[22m\u001b[22m", " [3] \u001b[1mA_mul_Bc\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::Array{Int64,2}, ::Array{Int64,1}\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./linalg/matmul.jl:86\u001b[22m\u001b[22m", " [4] \u001b[1minclude_string\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::String, ::String\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./loading.jl:522\u001b[22m\u001b[22m", " [5] \u001b[1mexecute_request\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::ZMQ.Socket, ::IJulia.Msg\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m/Users/stevenj/.julia/v0.6/IJulia/src/execute_request.jl:193\u001b[22m\u001b[22m", " [6] \u001b[1m(::Compat.#inner#14{Array{Any,1},IJulia.#execute_request,Tuple{ZMQ.Socket,IJulia.Msg}})\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m/Users/stevenj/.julia/v0.6/Compat/src/Compat.jl:332\u001b[22m\u001b[22m", " [7] \u001b[1meventloop\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m::ZMQ.Socket\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m/Users/stevenj/.julia/v0.6/IJulia/src/eventloop.jl:8\u001b[22m\u001b[22m", " [8] \u001b[1m(::IJulia.##13#16)\u001b[22m\u001b[22m\u001b[1m(\u001b[22m\u001b[22m\u001b[1m)\u001b[22m\u001b[22m at \u001b[1m./task.jl:335\u001b[22m\u001b[22m" ] } ], "source": [ "B * A[1,:]'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we multiply $B$ on the right by *all* the rows of $A$, we get $C$ again:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ A[1,:]'*B \n", " A[2,:]'*B\n", " A[3,:]'*B ] == C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Equivalently, each row of $A$ specifies a linear combination of *rows* of $B$ to produce the rows of $C$. So, **if you want to rearrange the *rows* of a matrix, multiply it by another matrix on the *left***.\n", "\n", "For example, let's do the transformation that *adds two times the first row of $B$ to the third row, and leaves the other rows untouched*. This is resembles one of the steps of Gaussian elimination!" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 1 0 -2\n", " 1 -5 1\n", " -3 0 3" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "B" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 1 0 -2\n", " 1 -5 1\n", " -1 0 -1" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ 1 0 0\n", " 0 1 0\n", " 2 0 1 ] * B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In fact, we can write *exactly* one of the steps for Gaussian elimination on $B$ in this form:\n", "\n", "Let's do the transformation that *adds three times the first row of $B$ to the third row, subtracts the first row from the second, and leaves the first row untouched*. " ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 1 0 -2\n", " 0 -5 3\n", " 0 0 -3" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[ 1 0 0\n", " -1 1 0\n", " 3 0 1 ] * B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As desired, this \"eliminates\" the sub-diagonal entries of the first column of B. The nice thing about writing elimination algebraically in this way, instead of in words, is that it will allow us to *think* about the elimination process algebraically." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Perspective 4: columns × rows\n", "\n", "The key to this perspective is to observe:\n", "\n", "* elements in column $i$ of $A$ only multiply elements in row $j$ of $B$\n", "* a column times a row vector, sometimes denoted $xy^T$, is an [outer product](https://en.wikipedia.org/wiki/Outer_product) and produces a \"rank-1\" *matrix*\n", "\n", "(See [this excellent paper by Gil Strang](http://mth1007.mathappl.polymtl.ca/MultFactMatrStrang.pdf) for more on this perspective applied to linear algebra. You will be in a better position to understand this at the end of 18.06, however.)\n", "\n", "For example, here is column 1 of $A$ times row 1 of $B$:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×3 Array{Int64,2}:\n", " 2 0 -4\n", " 3 0 -6\n", " -4 0 8" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[:,1] * B[1,:]'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we do this for all three rows and columns and add them up, we get $C$:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[:,1] * B[1,:]' + A[:,2] * B[2,:]' + A[:,3] * B[3,:]' == C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, from this perspective, we could write:\n", "\n", "$$\n", "AB = \\sum_{k=1}^3 (\\mbox{column } k \\mbox{ of } A) (\\mbox{row } k \\mbox{ of } B) = \\sum_{k=1}^3 A[:,k] \\, B[k,:]^T\n", "$$\n", "\n", "where in the last expression we have used Julia notation for slices." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Perspective 5: submatrix blocks × blocks\n", "\n", "It turns out that all of the above are special cases of a more general rule, by which we can break up a matrix in to \"submatrix\" blocks and multiply the blocks. Rows, columns, etc. are just blocks of different shapes. We will do more of this later in the course." ] } ], "metadata": { "kernelspec": { "display_name": "Julia 0.6.2", "language": "julia", "name": "julia-0.6" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "0.6.3" } }, "nbformat": 4, "nbformat_minor": 2 }