<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>Structure &amp; Process</title>
 <link href="http://mark.reid.name/sap/atom.xml" rel="self"/>
 <link href="http://mark.reid.name/sap/"/>
 <updated>2009-12-05T11:04:15+11:00</updated>
 <id>http://mark.reid.name/sap/</id>
 <author>
   <name>Mark Reid</name>
   <email>mark@reid.name</email>
 </author>
 
 
 <entry>
   <title>Online Learning in Clojure</title>
   <link href="http://mark.reid.name/sap/online-learning-in-clojure.html"/>
   <updated>2009-07-14T00:00:00+10:00</updated>
   <id>id:/sap/online-learning-in-clojure</id>
   <content type="html">&lt;p&gt;&lt;a href='http://en.wikipedia.org/wiki/Online_machine_learning'&gt;Online Learning&lt;/a&gt; is a relatively old branch of machine learning that has recently regained favour for two reasons. Firstly, online learning algorithms such as &lt;a href='http://leon.bottou.org/research/stochastic'&gt;Stochastic Gradient Descent&lt;/a&gt; work extremely well on very large data sets which have become increasingly prevalent (and increasingly large!). Secondly, there has been a lot of &lt;a href='http://homes.dsi.unimi.it/~cesabian/predbook/'&gt;important theoretical steps&lt;/a&gt; made recently in understand the convergence behaviour of these algorithms and their &lt;a href='http://arxiv.org/abs/0903.5328'&gt;relationship&lt;/a&gt; to traditional Empirical Risk Minimisation (ERM) algorithms such as Support Vector Machines (SVMs).&lt;/p&gt;

&lt;p&gt;In order to understand these algorithms better, I implemented a recent one (Pegasos, described below) in &lt;a href='http://clojure.org'&gt;Clojure&lt;/a&gt;. This had the added advantage of seeing how well Clojure&amp;#8217;s performance held up when doing some serious number-crunching.&lt;/p&gt;

&lt;h2 id='online_learning'&gt;Online Learning&lt;/h2&gt;

&lt;p&gt;One very appealing property of online learning algorithms is that they are extremely simple. Here&amp;#8217;s what a general supervised online learning algorithm looks like. Given a loss function &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/7d2c0c2260079724d34361ae6d009baf.png' alt='equation' style='vertical-align: -0.0ex;height: 1.55555555555556ex;' /&gt;&lt;/span&gt; and a stream of examples &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/96d92e0e21719139e970d5033cc7879c.png' alt='equation' style='vertical-align: -0.0ex;height: 1.55555555555556ex;' /&gt;&lt;/span&gt; of the form &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/c7c3fb628888287d12be6b7f14590e0d.png' alt='equation' style='vertical-align: -0.555555555555556ex;height: 2.33333333333333ex;' /&gt;&lt;/span&gt;, do the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Initialise a starting model w
While there are more examples in S
    Get the next feature vector x
    Predict the label y&amp;#39; for x using the model w
    Get the true label y for x and incur a penaly L(y,y&amp;#39;)
    Update the model w if y ≠ y&amp;#39;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Models are usually represented as vectors of weights for the features used to represent the examples. For binary classification problems predictions involve looking at the sign of the innner product &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/718f0c38e4ad26984acf29858880c3cc.png' alt='equation' style='vertical-align: -0.555555555555556ex;height: 2.22222222222222ex;' /&gt;&lt;/span&gt; and the update step in line 2.3 modifies the current model by moving &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/c93165a40d5fe440ebce4dfb689ebb81.png' alt='equation' style='vertical-align: -0.0ex;height: 1.0ex;' /&gt;&lt;/span&gt; in the direction that most reduces the loss of the incorrect prediction: that is, in the direction given by the negative &lt;a href='http://en.wikipedia.org/wiki/Gradient'&gt;gradient&lt;/a&gt; of the loss.&lt;/p&gt;

&lt;h2 id='pegasos'&gt;Pegasos&lt;/h2&gt;

&lt;p&gt;One recent online algorithm (and the one I&amp;#8217;ve chosen to implement) is &lt;em&gt;&lt;a href='http://www.machinelearning.org/proceedings/icml2007/abstracts/587.htm'&gt;Pegasos: Primal Estimated sub-GrAdient SOlver for SVM&lt;/a&gt;&lt;/em&gt; (&lt;a href='http://www.machinelearning.org/proceedings/icml2007/papers/587.pdf'&gt;PDF&lt;/a&gt;) introduced by &lt;a href='http://ttic.uchicago.edu/~shai/'&gt;Shai Shalev-Shwartz&lt;/a&gt;, &lt;a href='http://www.cs.huji.ac.il/~singer/'&gt;Yoram Singer&lt;/a&gt; and &lt;a href='http://ttic.uchicago.edu/~nati/'&gt;Nathan Srebro&lt;/a&gt; at &lt;a href='http://oregonstate.edu/conferences/icml2007/'&gt;ICML 2007&lt;/a&gt; &lt;sup id='fnref:1'&gt;&lt;a href='#fn:1' rel='footnote'&gt;1&lt;/a&gt;&lt;/sup&gt;. As this is my programming blog (not my &lt;a href='/iem/'&gt;research blog&lt;/a&gt;) I&amp;#8217;ll just give enough of the detail of Pegasos so you can follow the implementation.&lt;/p&gt;

&lt;p&gt;Pegasos solves the same optimisation problem as &lt;a href='http://en.wikipedia.org/wiki/Support_vector_machine'&gt;support vector machines&lt;/a&gt;. That is, it minimises the empirical hinge loss with &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/fb2f9f87ab057adbea15fca9e859528d.png' alt='equation' style='vertical-align: -0.333333333333333ex;height: 1.88888888888889ex;' /&gt;&lt;/span&gt; regularisation:&lt;/p&gt;
&lt;div class='maruku-equation'&gt;&lt;img class='maruku-png' src='/images/latex/e13dddaf4b30e378472e9fe997c6a863.png' alt='equation' style='height: 5.66666666666667ex;' /&gt;&lt;div class='maruku-eq-tex'&gt;&lt;code style='display: none'&gt; \displaystyle	L(w,S) 
	= \frac{\lambda}{2}\|w\|^2 + \frac{1}{m} \sum_{(x,y)\in S} h(w; (x,y))

&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;where &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/96d92e0e21719139e970d5033cc7879c.png' alt='equation' style='vertical-align: -0.0ex;height: 1.55555555555556ex;' /&gt;&lt;/span&gt; is a set of training examples, &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/b1db39f24390dbd29a5ecc1f312b4e96.png' alt='equation' style='vertical-align: -0.555555555555556ex;height: 2.22222222222222ex;' /&gt;&lt;/span&gt; the &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/fb2f9f87ab057adbea15fca9e859528d.png' alt='equation' style='vertical-align: -0.333333333333333ex;height: 1.88888888888889ex;' /&gt;&lt;/span&gt; norm, &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/51c339488afbd74372e64f15a035283c.png' alt='equation' style='vertical-align: -0.0ex;height: 1.55555555555556ex;' /&gt;&lt;/span&gt; the regularisation constant and &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/0ae98142207eeccfcec4b191e8625743.png' alt='equation' style='vertical-align: -0.555555555555556ex;height: 2.33333333333333ex;' /&gt;&lt;/span&gt; is the hinge loss.&lt;/p&gt;

&lt;p&gt;The neat observation that allows optimisation problems like this to be cast as online learning problems is that the above loss can be computed using example-by-example updates rather than as a large sum. With a little care about how these updates are made fast convergence guarantees can be established.&lt;/p&gt;

&lt;p&gt;In the case of Pegasos, if &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/df6f17c38fd15eff6b8afabf9d1f741c.png' alt='equation' style='vertical-align: -0.333333333333333ex;height: 1.33333333333333ex;' /&gt;&lt;/span&gt; is the model after having seen &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/7ca2c7b5d8aded9e019a267b8ca1b94d.png' alt='equation' style='vertical-align: -0.0ex;height: 1.44444444444444ex;' /&gt;&lt;/span&gt; examples and &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/c7c3fb628888287d12be6b7f14590e0d.png' alt='equation' style='vertical-align: -0.555555555555556ex;height: 2.33333333333333ex;' /&gt;&lt;/span&gt; is an incorrectly predicted example, the (unnormalised) updated model is:&lt;/p&gt;
&lt;div class='maruku-equation'&gt;&lt;img class='maruku-png' src='/images/latex/796ef0ab493a68337f2c05edbbb791f9.png' alt='equation' style='height: 4.55555555555556ex;' /&gt;&lt;div class='maruku-eq-tex'&gt;&lt;code style='display: none'&gt; \displaystyle	w_{t+1} = (1-t^{-1})w_t + \frac{1}{\lambda t} yx.

&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If the new model is outside a ball of radius &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/3261f01857d3a2784c1117914540d504.png' alt='equation' style='vertical-align: -0.555555555555556ex;height: 2.55555555555556ex;' /&gt;&lt;/span&gt; it is projected back onto this ball.&lt;/p&gt;

&lt;h2 id='implementing_it_in_clojure'&gt;Implementing it in Clojure&lt;/h2&gt;

&lt;p&gt;Once I understood what it was doing, Pegasos struck me as a very simple algorithm so I was itching to implement it. As mentioned earler, I was also curious as to &lt;a href='http://clojure.org'&gt;Clojure&lt;/a&gt;&amp;#8217;s performance on number-crunching tasks like this, especially when the &lt;a href='http://jmlr.csail.mit.edu/papers/volume5/lewis04a/lyrl2004_rcv1v2_README.htm'&gt;canonical data set&lt;/a&gt; for online learning has over 700,000 examples and over 45,000 features.&lt;/p&gt;

&lt;p&gt;Represented as 45k entry feature vectors, the examples and models would quickly become unwieldy so the first order of business was to implement some sparse vector operations. Here I chose to represent vectors as hash maps where non-zero elements of a vector are stored with their index as a key and the value of the entry as value.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;add&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns the sparse sum of two sparse vectors x y&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt; &lt;span class='nv'&gt;y&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;merge-with &lt;/span&gt;&lt;span class='nv'&gt;+&lt;/span&gt; &lt;span class='nv'&gt;x&lt;/span&gt; &lt;span class='nv'&gt;y&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;inner&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Computes the inner product of the sparse vectors (hashes) x and y&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt; &lt;span class='nv'&gt;y&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;reduce &lt;/span&gt;&lt;span class='nv'&gt;+&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;*&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;get &lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt; &lt;span class='nv'&gt;%&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;get &lt;/span&gt;&lt;span class='nv'&gt;y&lt;/span&gt; &lt;span class='nv'&gt;%&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;keys &lt;/span&gt;&lt;span class='nv'&gt;y&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;norm&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns the l_2 norm of the (sparse) vector v&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/sqrt&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;inner&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;scale&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns the scalar product of the sparse vector v by the scalar a&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;a&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;zipmap &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;keys &lt;/span&gt;&lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='nv'&gt;*&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;vals &lt;/span&gt;&lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;repeat &lt;/span&gt;&lt;span class='nv'&gt;a&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;project&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns the projection of a parameter vector w onto the ball of radius r&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;w&lt;/span&gt; &lt;span class='nv'&gt;r&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;min &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='nv'&gt;r&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;norm&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The only slightly tricky thing here is the use of &lt;code&gt;zipmap&lt;/code&gt; to scale a sparse vector by mapping all the keys in the original vector to their values times a scalar multiple &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The other bit of framework code I required was to parse the training data. The format is a simple version of that used by &lt;a href='http://svmlight.joachims.org/'&gt;SVMlight&lt;/a&gt;. Each line of the text file containing the training data is of the form:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;y k_1:v_1 k_2:v_2 ... k_n:v_n&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where &lt;code&gt;y&lt;/code&gt; is the label (either &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;-1&lt;/code&gt;), each &lt;code&gt;k_i&lt;/code&gt; is an integer key representing a feature index, and each &lt;code&gt;v_i&lt;/code&gt; is a floating point value.&lt;/p&gt;

&lt;p&gt;The Clojure code to parse this format is a pretty straight-forward application of regular expressions:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;parse-feature&lt;/span&gt; 
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;string&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; 
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;_&lt;/span&gt; &lt;span class='nv'&gt;key&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;re-matches &lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;(\d+):(.*)&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;string&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
		&lt;span class='p'&gt;[(&lt;/span&gt;&lt;span class='nf'&gt;Integer/parseInt&lt;/span&gt; &lt;span class='nv'&gt;key&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Float/parseFloat&lt;/span&gt; &lt;span class='nv'&gt;val&lt;/span&gt;&lt;span class='p'&gt;)]))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;parse-features&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;string&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;into &lt;/span&gt;&lt;span class='p'&gt;{}&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='nv'&gt;parse-feature&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;re-seq &lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;[^\s]+&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;string&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;parse&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns a map {:y label, :x sparse-feature-vector} parsed from given line&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;line&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;_&lt;/span&gt; &lt;span class='nv'&gt;label&lt;/span&gt; &lt;span class='nv'&gt;features&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;re-matches &lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;^(-?\d+)(.*)$&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;line&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
		&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:y&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Float/parseFloat&lt;/span&gt; &lt;span class='nv'&gt;label&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;:x&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;parse-features&lt;/span&gt; &lt;span class='nv'&gt;features&lt;/span&gt;&lt;span class='p'&gt;)}))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The main parsing function &lt;code&gt;parse&lt;/code&gt; takes a whole line in this format as input and returns a hash map with key &lt;code&gt;:y&lt;/code&gt; giving the label of the example and &lt;code&gt;:x&lt;/code&gt; giving a hash map representing the feature vector.&lt;/p&gt;

&lt;p&gt;Finally, the code to perform a single update step for a model given an example is built using some helper functions. The loss is computed by &lt;code&gt;hinge-loss&lt;/code&gt;, the function &lt;code&gt;correct&lt;/code&gt; performs a single gradient descent step, and &lt;code&gt;report&lt;/code&gt; is just for diagnostics and prints some simple statistics about the model and its performance.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;hinge-loss&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns the hinge loss of the weight vector w on the given example&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;w&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;max &lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:y&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;inner&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:x&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;))))))&lt;/span&gt;
	
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;correct&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns a corrected version of the weight vector w&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;w&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:x&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;y&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:y&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;w1&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;eta&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='nv'&gt;lambda&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
		  &lt;span class='nv'&gt;r&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/sqrt&lt;/span&gt; &lt;span class='nv'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;))]&lt;/span&gt;
		&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;project &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='nv'&gt;w1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='nv'&gt;eta&lt;/span&gt; &lt;span class='nv'&gt;y&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='nv'&gt;r&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;report&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Prints some statistics about the given model at the specified interval&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;model&lt;/span&gt; &lt;span class='nv'&gt;interval&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;zero? &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;mod&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:step&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;interval&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
		&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:step&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		      &lt;span class='nv'&gt;size&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;count &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;keys &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:w&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
		      &lt;span class='nv'&gt;errors&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:errors&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
			&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;println &lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Step:&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; 
				 &lt;span class='s'&gt;&amp;quot;\t Features in w =&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;size&lt;/span&gt; 
				 &lt;span class='s'&gt;&amp;quot;\t Errors =&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;errors&lt;/span&gt; 
				 &lt;span class='s'&gt;&amp;quot;\t Accuracy =&amp;quot;&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;float &lt;/span&gt;&lt;span class='nv'&gt;errors&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;update&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns an updated model by taking the last model, the next training &lt;/span&gt;
&lt;span class='s'&gt;	 and applying the Pegasos update step&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;model&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;lambda&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:lambda&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;t&lt;/span&gt;      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:step&lt;/span&gt;   &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;w&lt;/span&gt;      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:w&lt;/span&gt;      &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;errors&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:errors&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
		  &lt;span class='nv'&gt;error&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;gt; &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;hinge-loss&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
		&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;do&lt;/span&gt; 
			&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;report&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt; &lt;span class='mi'&gt;100&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
			&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='nv'&gt;:w&lt;/span&gt;      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='nv'&gt;error&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;correct&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt; &lt;span class='nv'&gt;example&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;lambda&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;w&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; 
			  &lt;span class='nv'&gt;:lambda&lt;/span&gt; &lt;span class='nv'&gt;lambda,&lt;/span&gt; 
			  &lt;span class='nv'&gt;:step&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;inc &lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; 
			  &lt;span class='nv'&gt;:errors&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='nv'&gt;error&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;inc &lt;/span&gt;&lt;span class='nv'&gt;errors&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;errors&lt;/span&gt;&lt;span class='p'&gt;)}&lt;/span&gt; &lt;span class='p'&gt;)))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, this function returns a new, updated model as a hash that contains the feature weights &lt;code&gt;:w&lt;/code&gt; as well as several other useful bits of information including the culmulative number of errors (in &lt;code&gt;:errors&lt;/code&gt;) and the total number of update steps (in &lt;code&gt;:steps&lt;/code&gt;). The parameter &lt;span class='maruku-inline'&gt;&lt;img class='maruku-png' src='/images/latex/51c339488afbd74372e64f15a035283c.png' alt='equation' style='vertical-align: -0.0ex;height: 1.55555555555556ex;' /&gt;&lt;/span&gt; which controls the amount of regularisation is also passed along in the model (in &lt;code&gt;:lambda&lt;/code&gt;) for convenience.&lt;/p&gt;

&lt;p class='quiet'&gt;&lt;em&gt;A brief aside&lt;/em&gt;: If I have one criticism of Clojure as a language it&amp;#8217;s that implementing numerical procedures is a real pain. Prefix notation (while neatly side-stepping problems of operator precedence) is just a lot harder to read than the infix notation that many non-Lisp languages use.&lt;/p&gt;

&lt;p&gt;Now the update step is implemented, training a model online from a sequence of examples is a simple application of &lt;code&gt;reduce&lt;/code&gt;. The following code repeated calls &lt;code&gt;(update model example)&lt;/code&gt; where each example is taken from the sequence &lt;code&gt;examples&lt;/code&gt; and the model output by the last call to &lt;code&gt;update&lt;/code&gt; is used as input for the next.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;train&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Returns a model trained from the initial model on the given examples&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;initial&lt;/span&gt; &lt;span class='nv'&gt;examples&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;reduce &lt;/span&gt;&lt;span class='nv'&gt;update&lt;/span&gt; &lt;span class='nv'&gt;initial&lt;/span&gt; &lt;span class='nv'&gt;examples&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;All that&amp;#8217;s needed now is a main method to read examples from the standard input, parse them into vectors and train a model from some starting point:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;main&lt;/span&gt;
	&lt;span class='s'&gt;&amp;quot;Trains a model from the examples and prints out its weights&amp;quot;&lt;/span&gt;
	&lt;span class='p'&gt;[]&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;start&lt;/span&gt; 	&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;:lambda&lt;/span&gt; &lt;span class='mf'&gt;0.0001&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;:step&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;:w&lt;/span&gt; &lt;span class='p'&gt;{}&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;:errors&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; 
		  &lt;span class='nv'&gt;examples&lt;/span&gt; 	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='nv'&gt;parse&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;-&amp;gt; &lt;/span&gt;&lt;span class='nv'&gt;*in*&lt;/span&gt; &lt;span class='nv'&gt;BufferedReader&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt; &lt;span class='nv'&gt;line-seq&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; 
		  &lt;span class='nv'&gt;model&lt;/span&gt;     &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;train&lt;/span&gt; &lt;span class='nv'&gt;start&lt;/span&gt; &lt;span class='nv'&gt;examples&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
		&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;println &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;str&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;key &lt;/span&gt;&lt;span class='nv'&gt;%&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;:&amp;quot;&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;val &lt;/span&gt;&lt;span class='nv'&gt;%&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:w&lt;/span&gt; &lt;span class='nv'&gt;model&lt;/span&gt;&lt;span class='p'&gt;)))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When finished the &lt;code&gt;main&lt;/code&gt; function prints the weights for the final trained model to the command line in a format similar to the input data. The training starts with an empty model and a regularisation constant of 0.0001 (as was used in the paper describing Pegasos).&lt;/p&gt;

&lt;p&gt;The full version of the code is &lt;a href='http://github.com/mreid/injuce/'&gt;available at GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id='running_it'&gt;Running It&lt;/h2&gt;

&lt;p&gt;To see whether the algorithms (or at least my implementation of it) performs as advertised I ran it on the aforementioned RCV1 data set.&lt;/p&gt;

&lt;p&gt;This is a big data set.&lt;/p&gt;

&lt;p&gt;The gzipped version of the full data set weighs in at 423Mb. Understandably, I&amp;#8217;m not going to host a file that size so to get the full data set it you will have to follow the &lt;a href='http://leon.bottou.org/projects/sgd'&gt;instructions at Léon Bottou&amp;#8217;s SGD page&lt;/a&gt; and make it yourself. However, for the purposes of this blog post I&amp;#8217;ve created a 2,000 example version called &lt;code&gt;train2000.dat.gz&lt;/code&gt; that is checked into the &lt;a href='http://github.com/mreid/injuce/'&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the training data in hand I ran my implementation of Pegasos (in the file &lt;code&gt;sgd.clj&lt;/code&gt;) like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ zless train2000.dat.gz | clj sgd.clj &amp;gt; output.txt
Step: 100 	 Features in w = 2145 	 Errors = 64 	 Accuracy = 0.64
Step: 200 	 Features in w = 3333 	 Errors = 123 	 Accuracy = 0.615
Step: 300 	 Features in w = 4051 	 Errors = 175 	 Accuracy = 0.5833333
Step: 400 	 Features in w = 4755 	 Errors = 229 	 Accuracy = 0.5725
Step: 500 	 Features in w = 5236 	 Errors = 276 	 Accuracy = 0.552
Step: 600 	 Features in w = 5576 	 Errors = 318 	 Accuracy = 0.53
Step: 700 	 Features in w = 5870 	 Errors = 356 	 Accuracy = 0.50857145
Step: 800 	 Features in w = 6050 	 Errors = 388 	 Accuracy = 0.485
Step: 900 	 Features in w = 6325 	 Errors = 418 	 Accuracy = 0.46444446
Step: 1000 	 Features in w = 6578 	 Errors = 444 	 Accuracy = 0.444
Step: 1100 	 Features in w = 6747 	 Errors = 471 	 Accuracy = 0.42818183
Step: 1200 	 Features in w = 6934 	 Errors = 502 	 Accuracy = 0.41833332
Step: 1300 	 Features in w = 7109 	 Errors = 526 	 Accuracy = 0.40461537
Step: 1400 	 Features in w = 7300 	 Errors = 555 	 Accuracy = 0.39642859
Step: 1500 	 Features in w = 7515 	 Errors = 592 	 Accuracy = 0.39466667
Step: 1600 	 Features in w = 7655 	 Errors = 615 	 Accuracy = 0.384375
Step: 1700 	 Features in w = 7836 	 Errors = 644 	 Accuracy = 0.37882352
Step: 1800 	 Features in w = 8040 	 Errors = 672 	 Accuracy = 0.37333333
Step: 1900 	 Features in w = 8239 	 Errors = 697 	 Accuracy = 0.3668421
Step: 2000 	 Features in w = 8425 	 Errors = 718 	 Accuracy = 0.359&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see the algorithm slowly adds more and more features to the weight vector and, as a result, slowly improves the accuracy.&lt;/p&gt;

&lt;p&gt;The reported accuracy is simply the cumulative total number of errors divided by the number of steps. This is a fairly pessimistic take on how the later models are performing. In the last 100 examples the models made a combined total of only 19 mistakes so the final model accuracy is probably closer to 20% than 35%.&lt;/p&gt;

&lt;h2 id='performance'&gt;Performance&lt;/h2&gt;

&lt;p&gt;My biggest issue with my implementation of online learning in Clojure is that it is too slow. The 2,000 example test described above took about 40 seconds to complete.&lt;/p&gt;

&lt;p&gt;These algorithms are meant to be ridiculously fast. Léon Bottou &lt;a href='http://leon.bottou.org/projects/sgd'&gt;reports&lt;/a&gt; training times for his C++ stochastic gradient descent algorithm on the &lt;em&gt;full&lt;/em&gt; 780k example RCV1 data set of &lt;em&gt;1.4 seconds&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;Granted I was timing both the parsing and training of the data but on the other hand I&amp;#8217;m using less than 0.3% of the data. Indeed, a quick test shows that just parsing the 2,000 examples takes less than 2 seconds so just training on the 2,000 examples takes 35 seconds or more.&lt;/p&gt;

&lt;p&gt;Firing up the &lt;a href='http://www.fatvat.co.uk/2009/05/jvisualvm-and-clojure.html'&gt;JVisualVM&lt;/a&gt; to see where my code is spending most of its time reveals that a lot of time is spent getting variable values and looking up values in maps.&lt;/p&gt;

&lt;p&gt;The performance culprit then is very likely my hastily thrown together sparse vector &amp;#8220;library&amp;#8221; built from hash maps. Although hash maps are fast there is still a lot of overhead in packing float and integer values in and out of Java Objects and I suspect this is where most of the time is wasted.&lt;/p&gt;

&lt;p&gt;If I have time to write a next version, I&amp;#8217;ll make use of the sparse vector data structures in the Java &lt;a href='http://sites.google.com/site/piotrwendykier/software/parallelcolt'&gt;Parallel Colt&lt;/a&gt; library.&lt;/p&gt;

&lt;h2 id='conclusions'&gt;Conclusions&lt;/h2&gt;

&lt;p&gt;Despite its lack of speed, I was impressed with how easy it was to implement an online algorithm in Clojure. Minus the comments, the whole thing &amp;#8211; vector operations, reporting, data parsing and training &amp;#8211; weighs in at less than 100 lines of code.&lt;/p&gt;

&lt;p&gt;Given Clojure&amp;#8217;s ability to call high-performance Java libraries such as &lt;a href='http://sites.google.com/site/piotrwendykier/software/parallelcolt'&gt;Parallel Colt&lt;/a&gt;, I&amp;#8217;m optimistic that I can keep the terseness and transparency of the code and get performance comparable to the C++ implementations. I would also like to experiment with exploiting Clojure&amp;#8217;s concurrency features to chunk and parallelise the main training algorithm. I suspect that this will be relatively straight-forward and, with a bit of tuning I should get good performance on a multi-core machine.&lt;/p&gt;
&lt;div class='footnotes'&gt;&lt;hr /&gt;&lt;ol&gt;&lt;li id='fn:1'&gt;
&lt;p&gt;There&amp;#8217;s a good discussion of Pegasos over at the &lt;a href='http://lingpipe-blog.com/2009/04/08/convergence-relative-sgd-pegasos-liblinear-svmlight-svmper/'&gt;LingPipe blog&lt;/a&gt;.&lt;/p&gt;
&lt;a href='#fnref:1' rev='footnote'&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;</content>
 </entry>
 
 <entry>
   <title>Minilight in Clojure: Triangles</title>
   <link href="http://mark.reid.name/sap/minilight-clojure-triangles.html"/>
   <updated>2009-04-11T00:00:00+10:00</updated>
   <id>id:/sap/minilight-clojure-triangles</id>
   <content type="html">&lt;p&gt;Previously on &amp;#8220;Porting Minilight to Clojure&amp;#8221;, our intrepid programmer (a.k.a, yours truly) braved lists, cross products and testing suites to bring you a namespace full of &lt;a href='/sap/minilight-clojure-vectors.html'&gt;vector&lt;/a&gt; operations. In this, the nail-biting sequel, I use these to build up some simple geometry in the form of triangles.&lt;/p&gt;

&lt;h2 id='housekeeping'&gt;House-keeping&amp;#8230;&lt;/h2&gt;

&lt;p&gt;Thanks to some &lt;a href='http://groups.google.com/group/clojure/browse_thread/thread/3f8283421219fef5/10de2f48fb48f6b8'&gt;feedback&lt;/a&gt; from the Clojure mailing list, I&amp;#8217;ve made a couple of small changes to the way I&amp;#8217;ve set up this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I now use of fully qualified names. That is, instead of a package called &lt;code&gt;vec&lt;/code&gt;, I will use the longer, Java-style &lt;code&gt;mreid.minilight.vec&lt;/code&gt; to avoid potential name collisions.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;The code now lives under a &lt;code&gt;src&lt;/code&gt; directory in folders corresponding to their fully-qualified package name. This meant adding a &lt;code&gt;.clojure&lt;/code&gt; file with the text &lt;code&gt;src&lt;/code&gt; in it so that my &lt;code&gt;clj&lt;/code&gt; script can find the code.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;The tests now define a class and are executed via a main method. This is so that IDE users won&amp;#8217;t have the tests run every time the &lt;code&gt;test.clj&lt;/code&gt; file is compiled.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests are now run from a simple shell script &lt;code&gt;runtests.sh&lt;/code&gt; which has the following contents:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;#!/bin/bash&lt;/span&gt;
&lt;span class='nv'&gt;JARS&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;$HOME&lt;/span&gt;/Library/Clojure/lib
&lt;span class='nv'&gt;CP&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;.:./src:&lt;span class='nv'&gt;$JARS&lt;/span&gt;/clojure.jar:&lt;span class='nv'&gt;$JARS&lt;/span&gt;/clojure-contrib.jar
&lt;span class='nv'&gt;TEST&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;src/mreid/minilight/test/all.clj&amp;quot;&lt;/span&gt;
&lt;span class='nv'&gt;MAIN&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;(mreid.minilight.test.all/-main)&amp;quot;&lt;/span&gt;

java -cp &lt;span class='nv'&gt;$CP&lt;/span&gt; clojure.main -i &lt;span class='nv'&gt;$TEST&lt;/span&gt; -e &lt;span class='s2'&gt;&amp;quot;$MAIN&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='and_a_bugfix'&gt;&amp;#8230;and a Bug-Fix&lt;/h2&gt;

&lt;p&gt;In the process of writing some new code I found a bug in my cross product implementation. After a bit of head-scratching, I realised that the form I use to extract a particular component from a vector assumes that the underlying data structure is a Clojure vector. I was using this &lt;code&gt;(v 0)&lt;/code&gt; to get at the x-coordinate of the vector &lt;code&gt;v&lt;/code&gt; which works fine for &lt;code&gt;([1 2 3] 1)&lt;/code&gt; but fails spectacularly when the vector is computed as a lazy sequence. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Clojure
user=&amp;gt; ((map - [1 2 3] [1 1 1]) 1)
java.lang.ClassCastException: clojure.lang.LazySeq cannot be cast to
clojure.lang.IFn (NO_SOURCE_FILE:0)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I used &lt;code&gt;map&lt;/code&gt; in several places to compute addition, subtraction and scaling of vectors so when these were passed into &lt;code&gt;cross&lt;/code&gt; they blew up.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve added some new tests to &lt;code&gt;test/vec.clj&lt;/code&gt; that test for this situation explicitly:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/test/vec.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;dyn100&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;dyn010&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-dynamic-add&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='nv'&gt;dyn100&lt;/span&gt; &lt;span class='nv'&gt;dyn010&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-dynamic-sub&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='nv'&gt;dyn100&lt;/span&gt; &lt;span class='nv'&gt;dyn010&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-dynamic-scale&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='nv'&gt;dyn100&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-dynamic-cross&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='nv'&gt;dyn100&lt;/span&gt; &lt;span class='nv'&gt;dyn010&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;and changed the definition of &lt;code&gt;cross&lt;/code&gt; to use the sequence friendly &lt;code&gt;nth&lt;/code&gt; instead:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/vec.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;cross&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns the cross product vector for the 3D vectors v1 and v2.&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; 
    &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt; &lt;span class='p'&gt;])&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I guess this is one of those instances where static typing would have helped me realise my mistake earlier.&lt;/p&gt;

&lt;h2 id='triangles'&gt;Triangles&lt;/h2&gt;

&lt;p&gt;Triangles are the simplest possible polygon. They are defined by three vectors, one for each vertex. As we will be using triangles to model surfaces in a ray-tracer, I will also need two other vectors to define how light is reflected and emitted from a surface. In Clojure, this is neatly captured using a &lt;code&gt;struct&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/triangle.clj ---&lt;/span&gt;
&lt;span class='c1'&gt;;; A structure and functions for defining and querying triangles.&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;ns&lt;/span&gt; &lt;span class='nv'&gt;mreid&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;minilight&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;mreid&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;minilight&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;vec&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
 
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defstruct &lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt;
  &lt;span class='nv'&gt;:vertices&lt;/span&gt; &lt;span class='c1'&gt;; Collection of 3 vectors&lt;/span&gt;
  &lt;span class='nv'&gt;:reflect&lt;/span&gt;  &lt;span class='c1'&gt;; Vector with all values in [0,1)&lt;/span&gt;
  &lt;span class='nv'&gt;:emit&lt;/span&gt;     &lt;span class='c1'&gt;; Vector with positive values&lt;/span&gt;
&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To interrogate the structure and calculate related quantities I will also need a number of simple functions:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;vertex&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns (the zero-indexed) vertex i in the triangle t&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;i&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nth &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:vertices&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;i&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;edge&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns the edge in the triangle t from vertex i to vertex j&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;i&lt;/span&gt; &lt;span class='nv'&gt;j&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;j&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;i&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;tangent&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns a unit vector tangent to the given triangle t&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normalise&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;normal&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns a vector normal to the given triangle t (edge01 x edge12)&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;unit-normal&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns a unit vector normal to the given triangle t&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normalise&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normal&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;area&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns the are of the given triangle t&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;norm&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normal&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is all very straight-forward and, once again, Clojure just &amp;#8220;gets out of the way&amp;#8221; and make it very easy to express each of these simple functions on a single line and with a minimum of fuss.&lt;/p&gt;

&lt;p&gt;Unit tests for these can be found in the file &lt;code&gt;test/triangle.clj&lt;/code&gt;. This consists of a few simple triangle structures and then a number of tests on them:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/test/triangle.clj ---&lt;/span&gt;
&lt;span class='c1'&gt;;; Tests for triangle.clj using the test-is library.&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;ns&lt;/span&gt; &lt;span class='nv'&gt;mreid&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;minilight&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;test&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;mreid&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;minilight&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;clojure&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;contrib&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;test-is&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;xytriangle&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;struct &lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt; 
     &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='c1'&gt;; Triangle in xy-plane&lt;/span&gt;
     &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;               &lt;span class='c1'&gt;; Reflectivity&lt;/span&gt;
     &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;                     &lt;span class='c1'&gt;; Emitivity &lt;/span&gt;
  &lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;y2ztriangle&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;struct &lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt; 
     &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='c1'&gt;; Triangle in xy-plane&lt;/span&gt;
     &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;               &lt;span class='c1'&gt;; Reflectivity&lt;/span&gt;
     &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;                     &lt;span class='c1'&gt;; Emitivity &lt;/span&gt;
  &lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;zxtriangle&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;struct &lt;/span&gt;&lt;span class='nv'&gt;triangle&lt;/span&gt; 
     &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-10&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;-10&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-9&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;-10&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-10&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;-9&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='c1'&gt;; Triangle parallel to zx-plane&lt;/span&gt;
     &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;                         &lt;span class='c1'&gt;; Reflectivity&lt;/span&gt;
     &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;                               &lt;span class='c1'&gt;; Emitivity &lt;/span&gt;
  &lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-vertex&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-edge&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;-2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-tangent&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;tangent&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;tangent&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-normal&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normal&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normal&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-unit-normal&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;unit-normal&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;unit-normal&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-area&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;area&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;area&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;There are three main functions left to port: one that computes a bounding-box for a triangle, one that tests whether a vector intersects a triangle, and one that randomly samples a point from a given triangle.&lt;/p&gt;

&lt;h2 id='bounding_boxes'&gt;Bounding Boxes&lt;/h2&gt;

&lt;p&gt;The following function creates the smallest box containing all the points belonging to a triangle and then slightly expands it. It is pretty straight-forward, the only slightly tricky part is the higher-order function &lt;code&gt;tweak&lt;/code&gt;. This takes in either a &lt;code&gt;+&lt;/code&gt; or a &lt;code&gt;-&lt;/code&gt; and will return a function that expands or shrinks &lt;em&gt;its&lt;/em&gt; input by the specified &lt;code&gt;TOLERANCE&lt;/code&gt;. This is used by &lt;code&gt;bounding-box&lt;/code&gt; to pull the point of a triangle closest to the origin about 0.1% closer and push the furthest point away by 0.1%.&lt;/p&gt;

&lt;p&gt;The reason for this function is that without it I would have have two copies of the code inside the function returned by &lt;code&gt;tweak&lt;/code&gt; with only a sign change. I figured this was a more &lt;a href='http://en.wikipedia.org/wiki/Don&amp;apos;t_repeat_yourself'&gt;DRY&lt;/a&gt; was of writing it.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;TOLERANCE&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mf'&gt;1.0&lt;/span&gt; &lt;span class='mf'&gt;1024.0&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;tweak&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns a function that adds or subtracts a small amount&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;add-or-sub&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fn &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add-or-sub&lt;/span&gt; &lt;span class='nv'&gt;x&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;+ &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/abs&lt;/span&gt; &lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mf'&gt;1.0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;bounding-box&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Computes the bounding box for a triangle t, returning the result as&lt;/span&gt;
&lt;span class='s'&gt;  a list of two vectors [lower-corner upper-corner].&amp;quot;&lt;/span&gt; 
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;vs&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:vertices&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;-&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;apply &lt;/span&gt;&lt;span class='nv'&gt;map&lt;/span&gt; &lt;span class='nv'&gt;min&lt;/span&gt; &lt;span class='nv'&gt;vs&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;+&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;apply &lt;/span&gt;&lt;span class='nv'&gt;map&lt;/span&gt; &lt;span class='nv'&gt;max&lt;/span&gt; &lt;span class='nv'&gt;vs&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;]))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The above code is definitely not the most efficient way of computing and expanding a bounding-box. Indeed, the Ruby code that I&amp;#8217;m cribbing this from explicitly optimises this using a single loop through each of a triangle&amp;#8217;s vectors. In the interests of simplicity, I&amp;#8217;ve opted for more two sweeps through each vector&amp;#8212;once for the &lt;code&gt;min&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt; and once for the &lt;code&gt;tweak&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When testing these functions, I&amp;#8217;ve opted for hand-building the correct values as constants defined in terms of the tolerance and then testing the output of &lt;code&gt;tweak&lt;/code&gt; and &lt;code&gt;bounding-box&lt;/code&gt; against these:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/test/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;tweak+0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;tweak-0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;tweak+1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;+ &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;tweak-1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;tweak+2&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;+ &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;tweak-2&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='nv'&gt;TOLERANCE&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-tweak&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='nv'&gt;tweak+0&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;+&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='nv'&gt;tweak-0&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;-&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='nv'&gt;tweak+1&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;+&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='nv'&gt;tweak-1&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;-&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='nv'&gt;tweak+2&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;+&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='nv'&gt;tweak-2&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='nf'&gt;tweak&lt;/span&gt; &lt;span class='nv'&gt;-&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-bounding-box&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[[&lt;/span&gt;&lt;span class='nv'&gt;tweak-0&lt;/span&gt; &lt;span class='nv'&gt;tweak-0&lt;/span&gt; &lt;span class='nv'&gt;tweak-0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;tweak+0&lt;/span&gt; &lt;span class='nv'&gt;tweak+2&lt;/span&gt; &lt;span class='nv'&gt;tweak+1&lt;/span&gt;&lt;span class='p'&gt;]]&lt;/span&gt; 
         &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;bounding-box&lt;/span&gt; &lt;span class='nv'&gt;y2ztriangle&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='apply_liberally'&gt;Apply Liberally&lt;/h2&gt;

&lt;p&gt;One discovery I made while attempting to write this was the &lt;code&gt;apply&lt;/code&gt; method. Its (very terse) documentation says&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;clojure.core/apply
([f args* argseq])
  Applies fn f to the argument list formed by prepending args to argseq.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In English, what it really does is treat a sequence of items as though they were passed in as arguments to the given function. In my case, I wanted to compute the point-wise minimum of three vectors. If these were, say, &lt;code&gt;[1 2 3]&lt;/code&gt;, &lt;code&gt;[0 5 5]&lt;/code&gt; and &lt;code&gt;[1 1 1]&lt;/code&gt; I would simply &lt;code&gt;map&lt;/code&gt; &lt;code&gt;min&lt;/code&gt; onto them as arguments:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Clojure
user=&amp;gt; (map min [1 2 3] [0 5 5] [1 1 1])
(0 1 1)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This works because &lt;code&gt;min&lt;/code&gt; can take an arbitrary number of arguments (&lt;em&gt;e.g.&lt;/em&gt;, &lt;code&gt;min 1 0 1&lt;/code&gt; gives 0) and &lt;code&gt;map&lt;/code&gt;, when given several sequences, will pull items from each in parallel and pass them to the function it is mapping.&lt;/p&gt;

&lt;p&gt;However, the problem I faced here was that the three vectors were inside a sequence and so naïvely trying the following gives the wrong result:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;user=&amp;gt; (map min [ [1 2 3] [0 5 5] [1 1 1] ])
([1 2 3] [0 5 5] [1 1 1])&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is where &lt;code&gt;apply&lt;/code&gt; comes in. It strips away the container and presents its contents to the function being applied as arguments, along with any extra arguments before the sequence:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;user=&amp;gt; (apply map min [ [1 2 3] [0 5 5] [1 1 1] ])
(0 1 1)&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id='intersection'&gt;Intersection&lt;/h2&gt;

&lt;p&gt;Much of the main loop of a ray-tracing algorithm involves checking whether a ray will hit a surface. Minilight is no exception, so we have the following method for testing whether a ray, represented as a starting point and a direction vector, will intersect with a given triangle:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;intersect&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Finds the intersection with the triangle t of the ray starting at r0 in &lt;/span&gt;
&lt;span class='s'&gt;  direction rd. The returned value is a positive number a such that r0 + a.rd &lt;/span&gt;
&lt;span class='s'&gt;  is contained within t, or nil if there is no such a.&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='nv'&gt;r0&lt;/span&gt; &lt;span class='nv'&gt;rd&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;e01&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='nv'&gt;e02&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='nv'&gt;p&lt;/span&gt;      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='nv'&gt;rd&lt;/span&gt; &lt;span class='nv'&gt;e02&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='nv'&gt;invdet&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;invdet&lt;/span&gt; &lt;span class='nv'&gt;e01&lt;/span&gt; &lt;span class='nv'&gt;rd&lt;/span&gt; &lt;span class='nv'&gt;e02&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;number?&lt;/span&gt; &lt;span class='nv'&gt;invdet&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;v0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
             &lt;span class='nv'&gt;tv&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='nv'&gt;r0&lt;/span&gt; &lt;span class='nv'&gt;v0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
             &lt;span class='nv'&gt;u&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='nv'&gt;tv&lt;/span&gt; &lt;span class='nv'&gt;p&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;invdet&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;and &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;gt;= &lt;/span&gt;&lt;span class='nv'&gt;u&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;lt;= &lt;/span&gt;&lt;span class='nv'&gt;u&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
          &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;q&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='nv'&gt;tv&lt;/span&gt; &lt;span class='nv'&gt;e01&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
                 &lt;span class='nv'&gt;v&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='nv'&gt;rd&lt;/span&gt; &lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;invdet&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
            &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;and &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;gt;= &lt;/span&gt;&lt;span class='nv'&gt;v&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;lt;= &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;+ &lt;/span&gt;&lt;span class='nv'&gt;u&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
              &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;a&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='nv'&gt;e02&lt;/span&gt; &lt;span class='nv'&gt;q&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;invdet&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
                &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;gt;= &lt;/span&gt;&lt;span class='nv'&gt;a&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;a&lt;/span&gt;&lt;span class='p'&gt;)))))))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I&amp;#8217;m least happy with the style of this code. The repeatedly nested &lt;code&gt;let&lt;/code&gt;/&lt;code&gt;if&lt;/code&gt; blocks seem ugly but I cannot see a more elegant way to do this sort of thing. Partly, it&amp;#8217;s because the series of conditional tests are essential to what is being computed, but I can&amp;#8217;t help but feel there is a lot of what Brook&amp;#8217;s calls &amp;#8221;&lt;a href='http://en.wikipedia.org/wiki/Accidental_complexity'&gt;accidental complexity&lt;/a&gt;&amp;#8221; in there as well.&lt;/p&gt;

&lt;p&gt;Suggestions on how to improve this function are very welcome.&lt;/p&gt;

&lt;p&gt;We can, at least, test that it is working though. Here I check whether hand-picked rays intersect on of the test triangles or not:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/test/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-intersect&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.9&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.1&lt;/span&gt; &lt;span class='mf'&gt;0.1&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-no-intersect&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nil? &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt; &lt;span class='c1'&gt;; Dir. is opposite&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nil? &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt; &lt;span class='c1'&gt;; Dir. is parallel&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nil? &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt; &lt;span class='c1'&gt;; Goes wide&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='sampling'&gt;Sampling&lt;/h2&gt;

&lt;p&gt;Finally, the renderer needs a way of choosing a random point uniformly from a given triangle. I&amp;#8217;ve lifted the formula for choosing the point at random directly from the Ruby code and translated as directly as possible into Clojure.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;rnd&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;java&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;util&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;Random&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;sample-point&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns a random point as a vector from inside the given triangle t&amp;quot;&lt;/span&gt;
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;sqr1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/sqrt&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;nextFloat&lt;/span&gt; &lt;span class='nv'&gt;rnd&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
         &lt;span class='nv'&gt;r2&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;nextFloat&lt;/span&gt; &lt;span class='nv'&gt;rnd&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='nv'&gt;a&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='nv'&gt;sqr1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
         &lt;span class='nv'&gt;b&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='nv'&gt;r2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;sqr1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vertex&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='nv'&gt;a&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; 
             &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='nv'&gt;b&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;edge&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;))))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By its very nature, &lt;code&gt;sample-point&lt;/code&gt; is non-deterministic. This makes testing a little unusual. Rather than testing whether a specific function call returns a specific value, I&amp;#8217;ve opted for testing an invariant. In particular, if I sample a point from a triangle and translate it one unit along its normal then a ray starting at that point which points back along the normal should intersect the original triangle:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- src/mreid/minilight/test/triangle.clj ---&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;random-ray&lt;/span&gt;
  &lt;span class='s'&gt;&amp;quot;Returns [r0 rd] where rd is the unit normal of the triangle t and r0 is a &lt;/span&gt;
&lt;span class='s'&gt;   random point on t translated by -rd&amp;quot;&lt;/span&gt; 
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;ray-direction&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;unit-normal&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sample-point&lt;/span&gt; &lt;span class='nv'&gt;t&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;ray-direction&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; 
      &lt;span class='nv'&gt;ray-direction&lt;/span&gt; &lt;span class='p'&gt;]))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-sample-point&lt;/span&gt;
  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;xy-random-ray&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;random-ray&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='nv'&gt;zx-random-ray&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;random-ray&lt;/span&gt; &lt;span class='nv'&gt;zxtriangle&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;apply &lt;/span&gt;&lt;span class='nv'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;xytriangle&lt;/span&gt; &lt;span class='nv'&gt;xy-random-ray&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;apply &lt;/span&gt;&lt;span class='nv'&gt;intersect&lt;/span&gt; &lt;span class='nv'&gt;zxtriangle&lt;/span&gt; &lt;span class='nv'&gt;zx-random-ray&lt;/span&gt;&lt;span class='p'&gt;)))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running this test on different occasions will check whether the invariant holds for different points. A down-side is that this test is not strictly repeatable and may fail very rarely. This may make tracking down a failing test difficult (even though the failure output will offer clues). The upside, however, is that the coverage for the &lt;code&gt;intersect&lt;/code&gt; function is increased.&lt;/p&gt;

&lt;h2 id='summary'&gt;Summary&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m definitely feeling more confident with Clojure even though I cannot instinctively count parentheses yet. Structures in Clojure feel very natural and are easy to work with and many of the functions I needed can still be expressed concisely. That said, the more mathematically involved functions didn&amp;#8217;t lend themselves to Clojure&amp;#8217;s prefix notation.&lt;/p&gt;

&lt;p&gt;I suspect I&amp;#8217;ll find these easier to read as time goes on but it is one case where simplicity of S-expressions falls short. Perhaps there are common ways of breaking up or otherwise formatting formulae that makes them easier to work with but they are far from obvious to me.&lt;/p&gt;

&lt;p&gt;As usual, the code is on GitHub, this time tagged with &lt;a href='http://github.com/mreid/minilight-clojure/tree/sap-2.1'&gt;sap-2.1&lt;/a&gt;. See you there!&lt;/p&gt;

&lt;h2 id='updates'&gt;Updates&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;15 April 2009&lt;/em&gt;: Fixed a bug spotted by HXA7241 in &lt;code&gt;sample-point&lt;/code&gt; as well as a related bug in &lt;code&gt;intersect&lt;/code&gt;. Updated the relevant tests to reproduce the bug.&lt;/li&gt;
&lt;/ul&gt;</content>
 </entry>
 
 <entry>
   <title>Minilight in Clojure: Vectors</title>
   <link href="http://mark.reid.name/sap/minilight-clojure-vectors.html"/>
   <updated>2009-04-05T00:00:00+11:00</updated>
   <id>id:/sap/minilight-clojure-vectors</id>
   <content type="html">&lt;p&gt;Now that I am happy with my Clojure &lt;a href='/sap/setting-up-clojure.html'&gt;set up&lt;/a&gt;, it is time to dive in and try to implement something using it. As it happens, I recently discovered the &lt;a href='http://www.hxa.name/minilight/'&gt;minilight&lt;/a&gt; project which aims to implement a &amp;#8220;minimal global illumination renderer&amp;#8221; in several languages including Ruby, Python, Java, Scala, and C++. I&amp;#8217;ve dabbled with 3D graphics in the past and since there is currently no implementation&lt;sup id='fnref:1'&gt;&lt;a href='#fn:1' rel='footnote'&gt;1&lt;/a&gt;&lt;/sup&gt; in Clojure I thought porting minilight to Clojure would be a great way to start.&lt;/p&gt;

&lt;p&gt;In this first part, I&amp;#8217;ll describe how I ported the Vector3fc class from the Ruby version of minilight. I am keeping all my code for this project over at &lt;a href='http://github.com/mreid/minilight-clojure/'&gt;GitHub&lt;/a&gt;. The code I&amp;#8217;ll be talking about here is tagged &amp;#8217;&lt;a href='http://github.com/mreid/minilight-clojure/tree/sap-1'&gt;sap-1&lt;/a&gt;&amp;#8217;.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m going to assume you have a basic familiarity with Clojure. Skimming over this overview of the &lt;a href='http://ociweb.com/jnb/jnbMar2009.html#Syntax'&gt;Clojure syntax&lt;/a&gt; would be a good place to start.&lt;/p&gt;

&lt;h2 id='100_functions_on_one_data_structure'&gt;100 Functions on One Data Structure&lt;/h2&gt;

&lt;p&gt;Vector manipulation is central to any 3D rendering code and Minilight is no exception. In object-oriented languages like Java, Ruby, Python and C++, the general practice is to create a new class for vectors and define methods for their manipulation.&lt;/p&gt;

&lt;p&gt;It is possible to do OO programming in LISP-like languages like Clojure but it is not idiomatic. As Alan Perlis put it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I&amp;#8217;ve instead opted for defining a namespace &lt;code&gt;vec&lt;/code&gt; along with a number of operations that treat lists of numbers as vectors and all live in the file &lt;code&gt;vec.clj&lt;/code&gt;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- vec.clj ---&lt;/span&gt;
&lt;span class='c1'&gt;;; A simple vector package that defines functions for working with geometrical &lt;/span&gt;
&lt;span class='c1'&gt;;; vectors.&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;ns&lt;/span&gt; &lt;span class='nv'&gt;vec&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='c1'&gt;; Constants&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;origin&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;    &lt;span class='c1'&gt;; Zero vector in 3D&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;def &lt;/span&gt;&lt;span class='nv'&gt;epsilon&lt;/span&gt; &lt;span class='mf'&gt;0.000001&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;  &lt;span class='c1'&gt;; Tolerance for equality&lt;/span&gt;

&lt;span class='c1'&gt;; Helper functions&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;approx0&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns true iff the value x is within epsilon of 0&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;&amp;lt; &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/abs&lt;/span&gt; &lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;epsilon&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;clamp&lt;/span&gt;
    &lt;span class='s'&gt;&amp;quot;Constrains all elements in v to be between vmin and vmax&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;vmin&lt;/span&gt; &lt;span class='nv'&gt;vmax&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fn &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;max &lt;/span&gt;&lt;span class='nv'&gt;vmin&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;min &lt;/span&gt;&lt;span class='nv'&gt;vmax&lt;/span&gt; &lt;span class='nv'&gt;x&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='c1'&gt;; Binary operators&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;dot&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns the value of dot product of the vectors v1 and v2&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;reduce &lt;/span&gt;&lt;span class='nv'&gt;+&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='nv'&gt;*&lt;/span&gt; &lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
    
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;add&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns a vector that is the sum of the vectors v1 and v2&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='nv'&gt;+&lt;/span&gt; &lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;sub&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns a vector that when added to v1 gives v2&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='nv'&gt;-&lt;/span&gt; &lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;cross&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns the cross product vector for the 3D vectors v1 and v2.&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; 
    &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
      &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;- &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;* &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;v2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt; &lt;span class='p'&gt;])&lt;/span&gt;

&lt;span class='c1'&gt;; Scalar operators&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;scale&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns the vector that is m times the vector v&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;m&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;map &lt;/span&gt;&lt;span class='o'&gt;#&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;*&lt;/span&gt; &lt;span class='nv'&gt;m&lt;/span&gt; &lt;span class='nv'&gt;%&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='c1'&gt;; Unary operators&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;norm&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns the (Euclidean) length of the vector v&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/sqrt&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;normalise&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns a vector of unit length in the same direction as v&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;norm&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='nv'&gt;v&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='c1'&gt;; Determinants&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;det&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;Returns the determinant of vectors v1 v2 and v3, i.e. v1.(v2 x v3)&amp;quot;&lt;/span&gt;    
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='nv'&gt;v3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='nv'&gt;v3&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;defn &lt;/span&gt;&lt;span class='nv'&gt;invdet&lt;/span&gt;
    &lt;span class='s'&gt;&amp;quot;Returns the inverse det. of v1, v2 and v3 or nil if it is too close to 0&amp;quot;&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='nv'&gt;v3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;let &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;d&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;det&lt;/span&gt; &lt;span class='nv'&gt;v1&lt;/span&gt; &lt;span class='nv'&gt;v2&lt;/span&gt; &lt;span class='nv'&gt;v3&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;if &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;approx0&lt;/span&gt; &lt;span class='nv'&gt;d&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
            &lt;span class='nv'&gt;nil&lt;/span&gt;
            &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mf'&gt;1.0&lt;/span&gt; &lt;span class='nv'&gt;d&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The string that appears after a function&amp;#8217;s name in a &lt;code&gt;defn&lt;/code&gt; form documents the function. You can view this documentation for any function using the &lt;code&gt;doc&lt;/code&gt; function. For example, we can see the documentation for &lt;code&gt;add&lt;/code&gt; like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Clojure
user=&amp;gt; (use &amp;#39;vec)
user=&amp;gt; (doc add)
-------------------------
vec/add
([v1 v2])
  Returns a vector that is the sum of the vectors v1 and v2&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here are some other observations about how I implemented these functions and some of the Clojure-related tricks I learnt along the way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Apart from the function &lt;code&gt;cross&lt;/code&gt;, all of these definitions are very concise thanks to the use of the higher-order functions &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;reduce&lt;/code&gt; and &lt;code&gt;every?&lt;/code&gt; which apply functions to sequences. Writing the functions this will almost certainly be slower than dealing with arrays of floats but I can always aim to improve efficiency later and thus heed Don Knuth&amp;#8217;s warning that &amp;#8220;premature optimization is the root of all evil (or at least most of it) in programming.&amp;#8221;&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;The function &lt;code&gt;Math/abs&lt;/code&gt; denotes a call to the static method &lt;code&gt;abs()&lt;/code&gt; in the class &lt;code&gt;Math&lt;/code&gt; that comes standard with Java.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;The form &lt;code&gt;#(* m %)&lt;/code&gt; in the definition of &lt;code&gt;scale&lt;/code&gt; is shorthand for &lt;code&gt;(fn [x] (* m x))&lt;/code&gt;&amp;#8212;that is, the function that multiplies its input by &lt;code&gt;m&lt;/code&gt;. The shorthand is known as a &lt;em&gt;reader macro&lt;/em&gt;. Clojure expands forms like these into their equivalent long version when it first encounters it.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;The &lt;code&gt;let&lt;/code&gt; form is Clojure&amp;#8217;s way of assigning local bindings. The first vector&lt;sup id='fnref:2'&gt;&lt;a href='#fn:2' rel='footnote'&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;code&gt;[d (det v1 v2 v3)]&lt;/code&gt; binds &lt;code&gt;d&lt;/code&gt; to the determinant of &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt; and &lt;code&gt;v3&lt;/code&gt;. In general, a &lt;code&gt;let&lt;/code&gt; can take many pairs of symbol/value bindings like so: &lt;code&gt;let [a 1 b 2 c3] (...)&lt;/code&gt;. The form after the binding is evaluated in the context of the bindings.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='testing_testing'&gt;Testing, testing&lt;/h2&gt;

&lt;p&gt;Writing tests for your code is almost always a good idea, especially if most of the code being tested does some kind of number-crunching. Since I plan to rewrite and optimise my vector functions later it is &lt;em&gt;definitely&lt;/em&gt; a good idea.&lt;/p&gt;

&lt;p&gt;Clojure comes with some built-in support for testing via its &lt;a href='http://clojure.org/special_forms'&gt;special form&lt;/a&gt; &lt;code&gt;:test&lt;/code&gt; which allows tests to sit right next to the code, much like documentation. However, I prefer separating tests into their own files so I&amp;#8217;ve been using &lt;a href='http://stuartsierra.com/'&gt;Stuart Sierra&lt;/a&gt;&amp;#8217;s test-is library that comes with the &lt;a href='http://code.google.com/p/clojure-contrib/'&gt;clojure-contrib&lt;/a&gt; package. It is a basic unit testing library that allows me to write simple tests for the above vector methods.&lt;/p&gt;

&lt;p&gt;Here is a small suite of tests for the vector functions. These appear in the file &lt;code&gt;test/vec.clj&lt;/code&gt; which means the appropriate namespace is &lt;code&gt;test.vec&lt;/code&gt;. Note the &lt;code&gt;:use&lt;/code&gt; form after the definition which imports all the functions in &lt;code&gt;vec&lt;/code&gt; and the &lt;code&gt;test-is&lt;/code&gt; library.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;;; --- test/vec.clj ---&lt;/span&gt;
&lt;span class='c1'&gt;;; Tests for vec.clj using the test-is library.&lt;/span&gt;
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;ns&lt;/span&gt; &lt;span class='nv'&gt;test&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;vec&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;vec&lt;/span&gt; &lt;span class='nv'&gt;clojure&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;contrib&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;test-is&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-approx0&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;approx0&lt;/span&gt; &lt;span class='mf'&gt;-0.000000001&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;not &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;approx0&lt;/span&gt; &lt;span class='mf'&gt;0.001&lt;/span&gt;&lt;span class='p'&gt;))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-clamp&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;       &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;clamp&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;clamp&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt; &lt;span class='mf'&gt;0.5&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-dot&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;dot&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='mi'&gt;-2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-add&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;origin&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='nv'&gt;origin&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;add&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-sub&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;origin&lt;/span&gt;&lt;span class='p'&gt;)))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-2&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;sub&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;
    
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-scale&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='mi'&gt;-2&lt;/span&gt; &lt;span class='mi'&gt;-3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;scale&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-cross&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-3&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt; &lt;span class='mi'&gt;-3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;cross&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-norm&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;norm&lt;/span&gt; &lt;span class='nv'&gt;origin&lt;/span&gt; &lt;span class='p'&gt;)))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/sqrt&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;norm&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;Math/sqrt&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;norm&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;-1&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-normalise&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mf'&gt;2.0&lt;/span&gt; &lt;span class='mf'&gt;7.0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mf'&gt;3.0&lt;/span&gt; &lt;span class='mf'&gt;7.0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mf'&gt;6.0&lt;/span&gt; &lt;span class='mf'&gt;7.0&lt;/span&gt;&lt;span class='p'&gt;)]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;normalise&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-det&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;det&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;det&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;det&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;
    
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-invdet&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;invdet&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;/ &lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;invdet&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
    &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;nil? &lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;invdet&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, these are fairly basic tests but they do exercise all of the important functions in &lt;code&gt;vec.clj&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The tests can be run from the interactive session of Clojure like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Clojure
user=&amp;gt; (use &amp;#39;test.vec &amp;#39;clojure.contrib.test-is)
user=&amp;gt; (run-tests &amp;#39;test.vec)                   

Testing test.vec

Ran 11 tests containing 28 assertions.
0 failures, 0 errors.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No failures. Excellent!&lt;/p&gt;

&lt;p&gt;To avoid having to run these tests manually, I&amp;#8217;ve set up a master test file in &lt;code&gt;test/test.clj&lt;/code&gt; that looks like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;ns&lt;/span&gt; &lt;span class='nv'&gt;test&lt;/span&gt; 
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;test&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;vec&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;clojure&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;contrib&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;test-is&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;run-tests&lt;/span&gt; &lt;span class='ss'&gt;&amp;#39;test&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;vec&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This will be make it easier to add new test suites as I write port more of Minilight and can be run as a script from the Terminal like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ clj test/test.clj 

Testing test.vec

Ran 11 tests containing 28 assertions.
0 failures, 0 errors.&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id='in_closing'&gt;In Closing&lt;/h2&gt;

&lt;p&gt;Well, that&amp;#8217;s covered the vector functions and their tests. In the next part I&amp;#8217;ll port the Triangle class which defines the geometry and properties of a triangle.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;d like to emphasise that I&amp;#8217;m very new to Clojure so if you see that I&amp;#8217;m doing something unidiomatic or just plain stupid in the above please let me know in the comments.&lt;/p&gt;
&lt;div class='footnotes'&gt;&lt;hr /&gt;&lt;ol&gt;&lt;li id='fn:1'&gt;
&lt;p&gt;Shortly after starting my port of minilight I discovered that &lt;a href='http://www.fatvat.co.uk/2009/04/implementing-minilight-in-clojure-2.html'&gt;someone else&lt;/a&gt; had the same idea and is at about the same stage. It will be interesting to compare our respective approaches as time goes on, and I cannot promise that I won&amp;#8217;t borrow an idea or two.&lt;/p&gt;
&lt;a href='#fnref:1' rev='footnote'&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;&lt;li id='fn:2'&gt;
&lt;p&gt;This is slightly confusing since Clojure sequences defined using square brackets (e.g., &lt;code&gt;[1 2 3]&lt;/code&gt;) are called &amp;#8220;vectors&amp;#8221; after the Java class of the same name.&lt;/p&gt;
&lt;a href='#fnref:2' rev='footnote'&gt;&amp;#8617;&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;</content>
 </entry>
 
 <entry>
   <title>Setting Up Clojure for Mac OS X Leopard</title>
   <link href="http://mark.reid.name/sap/setting-up-clojure.html"/>
   <updated>2009-03-29T00:00:00+11:00</updated>
   <id>id:/sap/setting-up-clojure</id>
   <content type="html">&lt;p&gt;&lt;a href='http://clojure.org/'&gt;Clojure&lt;/a&gt; is a fairly new &lt;a href='http://clojure.org/lisp'&gt;Lisp-like&lt;/a&gt;, &lt;a href='http://clojure.org/functional_programming'&gt;functional language&lt;/a&gt; that is &lt;a href='http://clojure.org/jvm_hosted'&gt;built on top of the JVM&lt;/a&gt;. It features great Java interoperability and is built from the ground up with &lt;a href='http://clojure.org/state'&gt;concurrency&lt;/a&gt; in mind.&lt;/p&gt;

&lt;p&gt;Below is a brief description of how to get Clojure up an running on Mac OS X Leopard. I also describe a small shell script that invokes Clojure and sets its classpath using a simple, project-specific configuration file.&lt;/p&gt;

&lt;p&gt;The first step is to create a Clojure directory in your Library folder that contains the subfolder &lt;code&gt;lib&lt;/code&gt;. Do this at your Terminal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ mkdir -p ~/Library/Clojure/lib
$ cd ~/Library/Clojure&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id='getting_clojure'&gt;Getting Clojure&lt;/h2&gt;

&lt;p&gt;The latest stable version of Clojure can be found &lt;a href='http://code.google.com/p/clojure/downloads/list'&gt;here&lt;/a&gt;. At the time of writing the latest stable version is the zip file for &lt;a href='http://clojure.googlecode.com/files/clojure_20090320.zip'&gt;Clojure release 20090320&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you&amp;#8217;ve downloaded it, copy &lt;code&gt;clojure.jar&lt;/code&gt; to the &lt;code&gt;~/Library/Clojure&lt;/code&gt; directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cp ~/Downloads/clojure/clojure.jar ~/Library/Clojure/lib/&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To make Clojure&amp;#8217;s interactive mode easier to you, you should grab the JLine library and install it as well.&lt;/p&gt;

&lt;p&gt;First download &lt;a href='http://downloads.sourceforge.net/jline/jline-0.9.94.zip'&gt;jline-0.9.94.zip&lt;/a&gt; from the &lt;a href='http://jline.sourceforge.net/'&gt;jline project site&lt;/a&gt; and then:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cp ~/Downloads/jline-0.9.94/jline-0.9.94.jar ~/Library/Clojure/lib/jline.jar&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id='startup_script'&gt;Startup Script&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve created a bash script called &lt;code&gt;clj&lt;/code&gt; that I put in &lt;code&gt;~/Library/Clojure&lt;/code&gt; and symbolically link to from somewhere in my path.&lt;/p&gt;

&lt;p&gt;This script sets up the Clojure classpath and, if it is present, also adds the contents of a &lt;code&gt;.clojure&lt;/code&gt; file to the classpath before executing Clojure. It also adds the current directory to the classpath for ease of use.&lt;/p&gt;

&lt;p&gt;You can &lt;a href='http://github.com/mreid/clojure-framework/blob/e1c80cc650f448713243be8272dba1fa3c1a7cea/clj'&gt;download the script&lt;/a&gt; from my &lt;a href='http://github.com/mreid/clojure-framework/tree'&gt;GitHub repository&lt;/a&gt;. Once you&amp;#8217;ve got it, copy it to the Clojure directory and make it executable:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cp ~/Downloads/clj ~/Library/Clojure/
$ chmod u+x ~/Library/Clojure/clj&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now you will want to symbolically link to it from a directory in your &lt;code&gt;$PATH&lt;/code&gt; Type &lt;code&gt;echo $PATH&lt;/code&gt; at the Terminal to see a list of options. I have a directory &lt;code&gt;~/bin/&lt;/code&gt; where I keep such things.&lt;/p&gt;

&lt;p&gt;To make the link use, for example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ln -s ~/Library/Clojure/clj ~/bin/clj&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now I can type &lt;code&gt;clj&lt;/code&gt; from any directory and see:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ clj
Clojure
user=&amp;gt; (= (* 6 8) 42)
false
user=&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can also run Clojure programs. For example, suppose I have the following file called &lt;code&gt;test.clj&lt;/code&gt; in the current directory:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;println &lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Hello, world!&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, when I run the following, I see:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ clj test.clj
Hello, world!&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id='extending_the_classpath'&gt;Extending the Classpath&lt;/h2&gt;

&lt;p&gt;If you need any project-specific jar files added to the classpath when running Clojure, this can be done by putting a &lt;code&gt;.clojure&lt;/code&gt; file in the same directory you will be running the project from.&lt;/p&gt;

&lt;p&gt;For example, suppose I have a program &lt;code&gt;~/code/cafe/macchiato.clj&lt;/code&gt; that requires class from the Java libraries &lt;code&gt;grinder.jar&lt;/code&gt; and &lt;code&gt;frother.jar&lt;/code&gt; in the &lt;code&gt;~/code/cafe/lib/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;I can easily create a file &lt;code&gt;.clojure&lt;/code&gt; that specifies where Clojure can find these extra libraries:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd ~/code/cafe
$ echo &amp;quot;lib/grinder.jar:lib/frother.jar&amp;quot; &amp;gt; .clojure&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then when I run:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ clj macchiato.clj&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;from the &lt;code&gt;~/code/cafe&lt;/code&gt; directory, the earlier &lt;code&gt;clj&lt;/code&gt; script will pick up the extra jar files from the &lt;code&gt;.clojure&lt;/code&gt; file and add them to the classpath before invoking Clojure.&lt;/p&gt;

&lt;h2 id='installing_and_using_clojurecontrib'&gt;Installing and Using clojure-contrib&lt;/h2&gt;

&lt;p&gt;This step is optional. You only need to do it if you wish to use some of the extra, community-contributed Clojure libraries.&lt;/p&gt;

&lt;p&gt;This step also requires &lt;a href='http://git-scm.com/'&gt;git&lt;/a&gt; which is not a standard part of OS X Leopard but a simple &lt;a href='http://code.google.com/p/git-osx-installer/'&gt;OS X installer&lt;/a&gt; is available.&lt;/p&gt;

&lt;p&gt;Once you have git installed, you can get the current version of the contributed libraries like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ git clone git://github.com/kevinoneill/clojure-contrib.git&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now build the jar file using &lt;code&gt;ant&lt;/code&gt; and copy it to the Clojure directory:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd clojure-contrib
$ ant -Dclojure.jar=$HOME/Library/Clojure/lib/clojure.jar
$ cp clojure-contrib.jar ~/Library/Clojure/lib/&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now you should be able to use things like Stuart Sierra&amp;#8217;s library. For example, suppose I was writing a simple vector library called &lt;code&gt;vec.clj&lt;/code&gt; and wanted to put the following tests in &lt;code&gt;test.clj&lt;/code&gt; in the same directory:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;ns&lt;/span&gt; &lt;span class='nv'&gt;test&lt;/span&gt; 
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:require&lt;/span&gt; &lt;span class='nv'&gt;vec&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;:use&lt;/span&gt; &lt;span class='nv'&gt;clojure&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;contrib&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nv'&gt;test-is&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
	
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;deftest&lt;/span&gt; &lt;span class='nv'&gt;test-cross-product&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;-3&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt; &lt;span class='mi'&gt;-3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vec/cross&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt; &lt;span class='mi'&gt;5&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;])))&lt;/span&gt;
	&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;is&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;= &lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;   &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;vec/cross&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]))))&lt;/span&gt;
	
&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nf'&gt;run-tests&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, because the &lt;code&gt;clojure-contrib.jar&lt;/code&gt; is on the classpath, I can run and see the following:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ clj test.clj

Testing test

Ran 1 tests containing 2 assertions.
0 failures, 0 errors.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Success!&lt;/p&gt;

&lt;h2 id='editing'&gt;Editing&lt;/h2&gt;

&lt;p&gt;I use &lt;a href='http://macromates.com/'&gt;TextMate&lt;/a&gt; as my Clojure editor along with the &lt;a href='http://github.com/stephenroller/clojure-tmbundle/tree/master'&gt;Clojure bundle&lt;/a&gt; created by &lt;a href='http://nullstyle.com/'&gt;nullstyle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It&amp;#8217;s probably overkill for what I need since that bundle includes a working installation of Clojure (&lt;em&gt;i.e.&lt;/em&gt;, it doesn&amp;#8217;t call the &lt;code&gt;clj&lt;/code&gt; I discuss here). I edit in TextMate and then run sessions and scripts from the Terminal. All I&amp;#8217;m really taking advantage of is the syntax-highlighting, auto-formatting and the online help (&lt;code&gt;^H&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;There are other options around such as Clojure modes for Vim and Emacs but I haven&amp;#8217;t tried them.&lt;/p&gt;

&lt;h2 id='in_closing'&gt;In Closing&lt;/h2&gt;

&lt;p&gt;I wrote these notes mainly to document the sometimes frustrating processes of getting a flexible, easy-to-use Clojure environment set up. The &amp;#8221;&lt;a href='http://clojure.org/getting_started'&gt;Getting Started&lt;/a&gt;&amp;#8221; page at the main &lt;a href='http://clojure.org/'&gt;Clojure&lt;/a&gt; site are great for getting a REPL up and running but didn&amp;#8217;t help me at all when it came to using other jars and clojure-contrib. Of course, if I&amp;#8217;m doing something here that is patently stupid, please let me know in the comments.&lt;/p&gt;

&lt;p&gt;I keep a shorter version of these notes with more concise step-by-step instructions at the &lt;a href='http://github.com/mreid/clojure-framework/tree'&gt;GitHub repository&lt;/a&gt; for this set up.&lt;/p&gt;

&lt;p&gt;Hopefully, this short introduction will make it easier for others to get up and running with this great new language.&lt;/p&gt;

&lt;h2 id='updates'&gt;Updates&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;1 April 2009&lt;/em&gt;: Added notes about the editor I use and link to my Github repository.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;em&gt;4 August 2009&lt;/em&gt;: Fixed some incorrect paths in the instructions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content>
 </entry>
 
 
</feed>