| <!DOCTYPE html> |
| <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> |
| <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> |
| <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> |
| <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head> |
| <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='data science, groovy, matrices, ojalgo, commons math, nd4j, ejml, vector api, eclipse deeplearning4j'/><meta name='description' content='This post looks at using Groovy to write a number of applications involving matrices. It uses a number of open source matrix libraries.'/><title>The Apache Groovy programming language - Blogs - Matrix calculations with Groovy, Apache Commons Math, ojAlgo, Nd4j and EJML</title><link href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='stylesheet' type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet' type='text/css' href='../css/font-awesome.min.css'/><link rel='stylesheet' type='text/css' href='../css/style.css'/><link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css'/> |
| </head><body> |
| <div id='fork-me'> |
| <a href='https://github.com/apache/groovy'> |
| <img style='position: fixed; top: 20px; right: -58px; border: 0; z-index: 100; transform: rotate(45deg);' src='/img/horizontal-github-ribbon.png'/> |
| </a> |
| </div><div id='st-container' class='st-container st-effect-9'> |
| <nav class='st-menu st-effect-9' id='menu-12'> |
| <h2 class='icon icon-lab'>Socialize</h2><ul> |
| <li> |
| <a href='https://groovy-lang.org/mailing-lists.html' class='icon'><span class='fa fa-envelope'></span> Discuss on the mailing-list</a> |
| </li><li> |
| <a href='https://twitter.com/ApacheGroovy' class='icon'><span class='fa fa-twitter'></span> Groovy on Twitter</a> |
| </li><li> |
| <a href='https://groovy-lang.org/events.html' class='icon'><span class='fa fa-calendar'></span> Events and conferences</a> |
| </li><li> |
| <a href='https://github.com/apache/groovy' class='icon'><span class='fa fa-github'></span> Source code on GitHub</a> |
| </li><li> |
| <a href='https://groovy-lang.org/reporting-issues.html' class='icon'><span class='fa fa-bug'></span> Report issues in Jira</a> |
| </li><li> |
| <a href='http://stackoverflow.com/questions/tagged/groovy' class='icon'><span class='fa fa-stack-overflow'></span> Stack Overflow questions</a> |
| </li><li> |
| <a href='http://groovycommunity.com/' class='icon'><span class='fa fa-slack'></span> Slack Community</a> |
| </li> |
| </ul> |
| </nav><div class='st-pusher'> |
| <div class='st-content'> |
| <div class='st-content-inner'> |
| <!--[if lt IE 7]> |
| <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> |
| <![endif]--><div><div class='navbar navbar-default navbar-static-top' role='navigation'> |
| <div class='container'> |
| <div class='navbar-header'> |
| <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'> |
| <span class='sr-only'></span><span class='icon-bar'></span><span class='icon-bar'></span><span class='icon-bar'></span> |
| </button><a class='navbar-brand' href='../index.html'> |
| <i class='fa fa-star'></i> Apache Groovy |
| </a> |
| </div><div class='navbar-collapse collapse'> |
| <ul class='nav navbar-nav navbar-right'> |
| <li class=''><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li class=''><a href='/download.html'>Download</a></li><li class=''><a href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a href='/'>Contribute</a></li><li class=''><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a href='/blog'>Blog posts</a></li><li class=''><a href='https://groovy.apache.org/events.html'></a></li><li> |
| <a data-effect='st-effect-9' class='st-trigger' href='#'>Socialize</a> |
| </li><li class=''> |
| <a href='../search.html'> |
| <i class='fa fa-search'></i> |
| </a> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Matrix calculations with Groovy, Apache Commons Math, ojAlgo, Nd4j and EJML</a></li><li><a href='#_fibonacci' class='anchor-link'>Fibonacci</a></li><li><a href='#_leslie_matrices' class='anchor-link'>Leslie Matrices</a></li><li><a href='#_encryption_with_matrices' class='anchor-link'>Encryption with matrices</a></li><li><a href='#_shape_manipulation' class='anchor-link'>Shape manipulation</a></li><li><a href='#_language_and_tool_extensibility' class='anchor-link'>Language and tool extensibility</a></li><li><a href='#_further_information' class='anchor-link'>Further information</a></li><li><a href='#_conclusion' class='anchor-link'>Conclusion</a></li></ul><br/><ul class='nav-sidebar'><li style='padding: 0.35em 0.625em; background-color: #eee'><span>Related posts</span></li><li><a href='./classifying-iris-flowers-with-deep'>Classifying Iris Flowers with Deep Learning, Groovy and GraalVM</a></li><li><a href='./deep-learning-and-eclipse-collections'>Deep Learning and Eclipse Collections</a></li><li><a href='./fun-with-obfuscated-groovy'>Fun with obfuscated Groovy</a></li><li><a href='./whiskey-clustering-with-groovy-and'>Whiskey Clustering with Groovy and Apache Ignite</a></li><li><a href='./reading-and-writing-csv-files'>Reading and Writing CSV files with Groovy</a></li><li><a href='./using-groovy-with-apache-wayang'>Using Groovy with Apache Wayang and Apache Spark</a></li><li><a href='./detecting-objects-with-groovy-the'>Detecting objects with Groovy, the Deep Java Library (DJL), and Apache MXNet</a></li><li><a href='./fruity-eclipse-collections'>Fruity Eclipse Collections</a></li></ul></div><div class='col-lg-8 col-lg-pull-0'><a name='doc'></a><h1>Matrix calculations with Groovy, Apache Commons Math, ojAlgo, Nd4j and EJML</h1><p><span>Author: <i>Paul King</i></span><br/><span>Published: 2022-08-18 01:41PM</span></p><hr/><div id="preamble"> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>This blogs looks at performing matrix calculations with Groovy |
| using various libraries: |
| <a href="https://commons.apache.org/">Apache Commons</a> |
| <a href="https://commons.apache.org/proper/commons-math/">Math</a>, |
| <a href="https://www.ojalgo.org/">ojAlgo</a>, |
| <a href="https://ejml.org/">EJML</a>, and |
| <a href="https://deeplearning4j.konduit.ai/nd4j/tutorials/quickstart">Nd4j</a> |
| (part of Eclipse <a href="https://deeplearning4j.konduit.ai/">Deeplearning4j</a>). |
| We’ll also take a quick |
| look at using the incubating Vector API for matrix calculations |
| (JEPs <a href="https://openjdk.org/jeps/338">338</a>, |
| <a href="https://openjdk.org/jeps/414">414</a>, |
| <a href="https://openjdk.org/jeps/417">417</a>, |
| and <a href="https://openjdk.org/jeps/426">426</a>).</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_fibonacci">Fibonacci</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>The Fibonacci sequence has origins in India centuries earlier but |
| is named after the Italian author of the publication, |
| <a href="https://en.wikipedia.org/wiki/Fibonacci_number">The Book of Calculation</a>, published in 1202. In that publication, Fibonacci |
| proposed the sequence as a means to calculate the growth of |
| idealized (biologically unrealistic) rabbit populations. |
| He proposed that a newly born breeding pair of rabbits are |
| put in a field; each breeding pair mates at the age of one month, |
| and at the end of their second month they always produce another |
| pair of rabbits; and rabbits never die, but continue breeding |
| forever. Fibonacci posed the puzzle: how many pairs will there |
| be in one year? The sequence goes like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We can solve this problem using matrices. If we multiply the matrix <span class="image"><img src="img/FibMatrix.png" alt="fibonacci matrix" width="43"></span> by itself n times we get <span class="image"><img src="img/FibMatrixN.png" alt="fib n matrix" width="82"></span>. |
| This is an operation known as matrix exponentiation. |
| Let’s explore this problem using four of the most popular |
| and maintained matrix libraries.</p> |
| </div> |
| <div class="sect2"> |
| <h3 id="_apache_commons_math">Apache Commons Math</h3> |
| <div class="paragraph"> |
| <p>Let’s explore solving this problem using Apache Commons Math. |
| Apache Commons Math is a library of lightweight self-contained |
| mathematics and statics components. Matrices are part of the |
| linear algebra part of this library and for that context, |
| matrices of double values are relevant. So, we’ll represent |
| our Fibonacci numbers as double values.</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">double[][] data = [[1d, 1d], [1d, 0d]] |
| def m = MatrixUtils.createRealMatrix(data) |
| println m * m * m |
| println m**6</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Commons math has a factory method for creating matrixes from |
| double arrays. The names of the methods for multiplication and |
| exponentiation happen to align with Groovy’s methods available |
| for operator overloading, namely <code>multiply</code> and <code>power</code>, |
| so we can use Groovy’s convenient shorthands.</p> |
| </div> |
| <div class="paragraph"> |
| <p>When we run the script, the output looks like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Array2DRowRealMatrix{{3.0,2.0},{2.0,1.0}} |
| Array2DRowRealMatrix{{13.0,8.0},{8.0,5.0}}</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We could go a little further and print the values from the |
| matrix, but the result is clear enough. We see the values |
| in the Fibonacci sequence appearing in the output.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_ejml">EJML</h3> |
| <div class="paragraph"> |
| <p>EJML (Efficient Java Matrix Library) is a linear algebra library |
| for manipulating real, complex, dense, and sparse matrices. |
| It is also a 100% Java solution. It has some novel features |
| including import from Matlab and support for semirings |
| (<a href="https://en.wikipedia.org/wiki/GraphBLAS">GraphBLAS</a>) which |
| can be used for graph algorithms where a sparse matrix may |
| be used to represent a graph as an adjacency matrix or |
| incidence matrix.</p> |
| </div> |
| <div class="paragraph"> |
| <p>We can do the same calculation using EJML:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def m = new SimpleMatrix([[1d, 1d], [1d, 0d]] as double[][]) |
| def ans = m.mult(m).mult(m) |
| println ans |
| 6.times { ans = ans.mult(m) } |
| println ans</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The name of the multiply method differs from the one where we |
| can use automatic operator overloading shorthands, so we just |
| call the method that EJML provides. See a little later under |
| language extensibility on how we could in fact add support to |
| use the same shorthands as we saw for Commons Math.</p> |
| </div> |
| <div class="paragraph"> |
| <p>EJML doesn’t have an exponentiation method but we just call |
| multiply the requisite number of times to achieve the same effect. |
| Note that we bumped the number of iterations called for the second |
| matrix, to reveal the next bunch of elements from the Fibonacci |
| sequence.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Running this script has this output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Type = DDRM , rows = 2 , cols = 2 |
| 3.0000E+00 2.0000E+00 |
| 2.0000E+00 1.0000E+00 |
| |
| Type = DDRM , rows = 2 , cols = 2 |
| 5.5000E+01 3.4000E+01 |
| 3.4000E+01 2.1000E+01</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The first matrix has the same number as previously, and the second |
| reveals the next numbers in the Fibonacci sequence (21, 34, and 55).</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_nd4j">Nd4j</h3> |
| <div class="paragraph"> |
| <p>Nd4j provides functionality available on Python to Java users. |
| It contains a mix of numpy operations and tensorflow/pytorch |
| operations. Nd4l makes use of native backends to allow it to |
| work on different platforms and provide efficient operation when |
| scaling up.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The code for our Fibonacci solution is very similar to EJML:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def m = Nd4j.create([[1, 1], [1, 0]] as int[][]) |
| def ans = m.mmul(m).mmul(m) |
| println ans |
| 9.times { ans = ans.mmul(m) } |
| println ans</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>One feature that is different to the previous two libraries is |
| that Nd4j supports <em>integer</em> matrices (as well as doubles and |
| numerous other numerical and related types).</p> |
| </div> |
| <div class="paragraph"> |
| <p>Running the script gives the following output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre><span class="maroon">... |
| [main] INFO org.nd4j.linalg.factory.Nd4jBackend - Loaded [CpuBackend] backend |
| ... |
| [main] INFO org.nd4j.linalg.cpu.nativecpu.CpuNDArrayFactory - Binary level Generic x86 optimization level AVX/AVX2 |
| [main] INFO org.nd4j.linalg.api.ops.executioner.DefaultOpExecutioner - Blas vendor: [OPENBLAS] |
| ...</span> |
| [[ 3, 2], |
| [ 2, 1]] |
| [[ 233, 144], |
| [ 144, 89]]</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Again, the first matrix is the same as we have seen previously, |
| the second has been bumped along the Fibonacci sequence by |
| three more elements.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_ojalgo">ojAlgo</h3> |
| <div class="paragraph"> |
| <p>The next library we’ll look at is ojAlgo (oj! Algorithms). |
| It is an open source all-Java offering for mathematics, linear |
| algebra and optimisation, supporting data science, machine |
| learning and scientific computing. It claims to be the fastest |
| pure-Java linear algebra library available and the project |
| website provide links to numerous benchmarks backing that claim.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Here is the code for our Fibonacci example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def factory = Primitive64Matrix.FACTORY |
| def m = factory.rows([[1d, 1d], [1d, 0d]] as double[][]) |
| println m * m * m |
| println m**15</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We can see it supports the same operator overloading features we |
| saw for Commons Math.</p> |
| </div> |
| <div class="paragraph"> |
| <p>When we run the script, it has the following output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>org.ojalgo.matrix.Primitive64Matrix < 2 x 2 > |
| { { 3.0, 2.0 }, |
| { 2.0, 1.0 } } |
| org.ojalgo.matrix.Primitive64Matrix < 2 x 2 > |
| { { 987.0, 610.0 }, |
| { 610.0, 377.0 } }</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>As expected, the first matrix is as we’ve seen before, while the |
| second reveals the next three numbers in the sequence.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_exploring_the_vector_api_and_ejml">Exploring the Vector API and EJML</h3> |
| <div class="paragraph"> |
| <p>From JDK16, various versions (JEPs |
| <a href="https://openjdk.org/jeps/338">338</a>, |
| <a href="https://openjdk.org/jeps/414">414</a>, |
| <a href="https://openjdk.org/jeps/417">417</a>, |
| <a href="https://openjdk.org/jeps/426">426</a>) of the Vector API |
| have been available as an incubating preview feature. |
| The HotSpot compiler has previously already had some minimal |
| optimisations that can leverage vector hardware instructions |
| but the Vector API expands the scope of possible optimisations |
| considerably. We could look at writing our own code that might |
| make use of the Vector API and perhaps perform our matrix |
| multiplications ourselves. But, one of the libraries has already |
| done just that, so we’ll explore that path.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The main contributor to the EJML library has published a |
| <a href="https://github.com/lessthanoptimal/VectorPerformance">repo</a> |
| for the purposes of very early prototyping and benchmarking. |
| We’ll use the methods from one of its classes to explore use |
| of the vector API for our Fibonacci example. The |
| <code>MatrixMultiplication</code> class has three methods: |
| <code>mult_ikj_simple</code> is coded in the way any of us might write a |
| multiplication method as a first pass from its definition without |
| any attempts at optimisation, <code>mult_ikj</code> is coded in a |
| highly-optimised fashion and corresponds to the code EJML would |
| normally use, and <code>mult_ikj_vector</code> uses the Vector API. Note, you can think of these methods as "one layer down" from the <code>mult</code> |
| method we called in the previous example, i.e. the <code>mult</code> method |
| we used previously would be calling one of these under the covers. |
| That’s why we pass the internal "matrix" representation instead |
| of our <code>SimpleMatrix</code> instance.</p> |
| </div> |
| <div class="paragraph"> |
| <p>For our little calculations, the optimisations offered by the |
| Vector API would not be expected to be huge. However, we’ll do |
| our calculation for generating the matrix we did as a first step |
| for all of the libraries and we’ll do it in a loop with 1000 |
| iterations for each of the three methods (<code>mult_ikj_simple</code>, |
| <code>mult_ikj</code>, and <code>mult_ikj_vector</code>). The code looks like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def m = new SimpleMatrix([[1, 1], [1, 0]] as double[][]) |
| double[] expected = [3, 2, 2, 1] |
| def step1, result |
| |
| long t0 = System.nanoTime() |
| 1000.times { |
| step1 = new SimpleMatrix(2, 2) |
| result = new SimpleMatrix(2, 2) |
| MatrixMultiplication.mult_ikj_simple(m.matrix, m.matrix, step1.matrix) |
| MatrixMultiplication.mult_ikj_simple(step1.matrix, m.matrix, result.matrix) |
| assert result.matrix.data == expected |
| } |
| |
| long t1 = System.nanoTime() |
| 1000.times { |
| step1 = new SimpleMatrix(2, 2) |
| result = new SimpleMatrix(2, 2) |
| MatrixMultiplication.mult_ikj(m.matrix, m.matrix, step1.matrix) |
| MatrixMultiplication.mult_ikj(step1.matrix, m.matrix, result.matrix) |
| assert result.matrix.data == expected |
| } |
| |
| long t2 = System.nanoTime() |
| 1000.times { |
| step1 = new SimpleMatrix(2, 2) |
| result = new SimpleMatrix(2, 2) |
| MatrixMultiplication.mult_ikj_vector(m.matrix, m.matrix, step1.matrix) |
| MatrixMultiplication.mult_ikj_vector(step1.matrix, m.matrix, result.matrix) |
| assert result.matrix.data == expected |
| } |
| |
| long t3 = System.nanoTime() |
| printf "Simple: %6.2f ms\n", (t1 - t0)/1000_000 |
| printf "Optimized: %6.2f ms\n", (t2 - t1)/1000_000 |
| printf "Vector: %6.2f ms\n", (t3 - t2)/1000_000</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This example was run on JDK16 with the following VM options: |
| <code>–enable-preview –add-modules jdk.incubator.vector</code>.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The output looks like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre><span class="maroon">WARNING: Using incubator modules: jdk.incubator.vector</span> |
| Simple: 116.34 ms |
| Optimized: 34.91 ms |
| Vector: 21.94 ms</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We can see here that we have some improvement even for our trivial |
| little calculation. Certainly, for biggest problems, the benefit |
| of using the Vector API could be quite substantial.</p> |
| </div> |
| <div class="paragraph"> |
| <p>We should give a big disclaimer here. This little microbenchmark |
| using a loop of 1000 will give highly variable results and was |
| just done to give a very simple performance comparison. |
| For a more predictable comparison, consider running the |
| <a href="https://github.com/openjdk/jmh">jmh</a> benchmark in the aforementioned |
| <a href="https://github.com/lessthanoptimal/VectorPerformance">repo</a>. |
| And you may wish to wait until the Vector API is out of preview |
| before relying on it for any production code - but by all means, |
| consider trying it out now.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_leslie_matrices">Leslie Matrices</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>Earlier, we described the Fibonacci sequence as being for |
| <em>unrealistic</em> rabbit populations, where rabbits never died and |
| continued breeding forever. It turns out that Fibonacci matrices |
| are a special case of a more generalised model which can model |
| realistic rabbit populations (among other things). These are |
| <a href="https://en.wikipedia.org/wiki/Leslie_matrix">Leslie matrices</a>. |
| For Leslie matrices, populations are divided into classes, |
| and we keep track of birth rates and survival rates over a |
| particular period for each class. We store this information |
| in a matrix in a special form. The populations for each class |
| for the next period can be calculated from those for the current |
| period through multiplication by the Leslie matrix.</p> |
| </div> |
| <div class="paragraph"> |
| <p>This technique can be used for animal populations or human |
| population calculations. A Leslie matrix can help you find out |
| if there will be enough GenX, Millenials, and GenZ tax payers |
| to support an aging and soon retiring baby boomer population. |
| Sophisticated animal models might track populations for an animal |
| and for its predators or its prey. The survival and birth rates |
| might be adjusted based on such information. Given that only |
| females give birth, Leslie models will often be done only in |
| terms of the female population, with the total population |
| extrapolated from that.</p> |
| </div> |
| <div class="paragraph"> |
| <p>We’ll show an example for kangaroo population based on this |
| <a href="https://www.youtube.com/watch?v=I5WM2wdjr1M">video tutorial</a> |
| for Leslie matrices. It can help us find out if the kangaroo |
| population is under threat (perhaps drought, fires or floods |
| have impacted their feeding habitat) or if favorable conditions |
| are leading to overpopulation.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Following that example, we divide kangaroos into 3 population |
| classes: ages 0 to 3, 3 to 6, and 6 to 9. We are going to look |
| at the population every three years. The 0-3 year olds birthrate |
| (B1) is 0 since they are pre-fertile. The most fertile 3-6 year |
| olds birthrate (B2) is 2.3. The old roos (6-9) have a birthrate |
| (B3) of 0.4. We assume no kangaroos survive past 9 years old. |
| 60% (S1) of the young kangaroos survive to move into the next age |
| group. 30% (S2) of the middle-aged kangaroos survive into old age. |
| Initially, we have 400 kangaroos in each age group.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Here is what the code looks like for this model:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">double[] init = [400, // 0..<3 |
| 400, // 3..<6 |
| 400] // 6..9 |
| def p0 = MatrixUtils.createRealVector(init) |
| println "Initial populations: $p0" |
| |
| double[][] data = [ |
| [0 , 2.3, 0.4], // B1 B2 B3 |
| [0.6, 0, 0 ], // S1 0 0 |
| [0 , 0.3, 0 ] // 0 S2 0 |
| ] |
| def L = MatrixUtils.createRealMatrix(data) |
| def p1 = L.operate(p0) |
| println "Population after 1 round: $p1" |
| |
| def p2 = L.operate(p1) |
| println "Population after 2 rounds: $p2" |
| |
| def L10 = L ** 10 |
| println "Population after 10 rounds: ${L10.operate(p0).toArray()*.round()}"</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This code produces the following output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Initial populations: {400; 400; 400} |
| Population after 1 round: {1,080; 240; 120} |
| Population after 2 rounds: {600; 648; 72} |
| Population after 10 rounds: [3019, 2558, 365]</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>After the first round, we see many young roos but a worrying drop |
| off in the older age groups. After the second round, only the |
| oldest age group looks worryingly small. However, with the healthy |
| numbers in the young generation, we can see that after 10 |
| generations that indeed, the overall population is not at risk. |
| In fact, overpopulation might become a problem.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_encryption_with_matrices">Encryption with matrices</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>An early technique to encrypt a message was the |
| <a href="https://en.wikipedia.org/wiki/Caesar_cipher">Caesar cipher</a>. |
| It substitutes letters in the alphabet by the letter shifted a |
| certain amount along in the alphabet, e.g. "IBM" becomes "HAL" |
| if shifting to the previous letter and "VMS" becomes "WNT" if |
| shifting one letter forward in the alphabet. This kind of cipher |
| can be broken by looking at frequency analysis of letters or |
| pattern words.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The <a href="https://en.wikipedia.org/wiki/Hill_cipher">Hill cipher</a> |
| improves upon the Caesar cipher by factoring multiple letters |
| into each letter of the encrypted text. Using matrices made it |
| practical to look at three or more symbols at once. In general, |
| an N-by-N matrix (the key) is multiplied by an encoding of the |
| message in matrix form. The result is a matrix representation |
| (encoded form) of the encrypted text. We use the inverse matrix |
| of our key to decrypt or message.</p> |
| </div> |
| <div class="paragraph"> |
| <p>We need to have a way to convert our text message to and from |
| a matrix. A common scheme is to encode A as 1, B as 2, and so on. |
| We’ll just use the ascii value for each character. We define |
| <code>encode</code> and <code>decode</code> methods to do this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">double[][] encode(String s) { s.bytes*.intValue().collate(3) as double[][] } |
| String decode(double[] data) { data*.round() as char[] }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We’ll define a 2-by-2 matrix as our key and use it to encrypt. |
| We’ll find the inverse of our key and use that to decrypt. |
| If we wanted to, we could use a 3-by-3 key for improved security |
| at the cost of more processing time.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Our code looks like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def message = 'GROOVY' |
| def m = new SimpleMatrix(encode(message)) |
| println "Original: $message" |
| |
| def enKey = new SimpleMatrix([[1, 3], [-1, 2]] as double[][]) |
| def encrypted = enKey.mult(m) |
| println "Encrypted: ${decode(encrypted.matrix.data)}" |
| |
| def deKey = enKey.invert() |
| def decrypted = deKey.mult(encrypted) |
| println "Decrypted: ${decode(decrypted.matrix.data)}"</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>When run, it has the following output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Original: GROOVY |
| Encrypted: ĴŔŚWZc |
| Decrypted: GROOVY</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This offers far more security than the Caesar cipher, however, |
| given today’s computing availability, Hill ciphers can still |
| eventually be broken with sufficient brute force. For this reason, |
| Hill ciphers are seldom used on their own for encryption, but they |
| <em>are</em> often used in combination with other techniques to add |
| diffusion - strengthening the security offered by the other |
| techniques.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_shape_manipulation">Shape manipulation</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>Our final example looks at geometrically transforming shapes. |
| To do this, we represent the points of the shape as vectors and |
| multiply them using transforms represented as matrices. We need |
| only worry about the corners, since we’ll use Swing to draw our |
| shape, and it has a method for drawing a polygon by giving its |
| corners.</p> |
| </div> |
| <div class="paragraph"> |
| <p>First we’ll use Groovy’s <code>SwingBuilder</code> to set up our frame:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">new SwingBuilder().edt { |
| def frame = frame(title: 'Shapes', size: [420, 440], show: true, defaultCloseOperation: DISPOSE_ON_CLOSE) { |
| //contentPane.background = Color.WHITE |
| widget(new CustomPaintComponent()) |
| } |
| frame.contentPane.background = Color.WHITE |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>We aren’t really use much of SwingBuilder’s functionality here |
| but if we wanted to add more functionality, SwingBuilder would |
| make that task easier.</p> |
| </div> |
| <div class="paragraph"> |
| <p>We will actually draw our shapes within a custom component. |
| We’ll define a few color constants, a <code>drawPolygon</code> method which |
| given a matrix of points will draw those points as a polygon. |
| We’ll also define a <code>vectors</code> method to convert a list of points |
| (the corners) into vectors, and a <code>transform</code> method which is a |
| factory method for creating a transform matrix.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Here is the code:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">class CustomPaintComponent extends Component { |
| static final Color violet = new Color(0x67, 0x27, 0x7A, 127) |
| static final Color seaGreen = new Color(0x69, 0xCC, 0x67, 127) |
| static final Color crystalBlue = new Color(0x06, 0x4B, 0x93, 127) |
| static drawPolygon(Graphics g, List pts, boolean fill) { |
| def poly = new Polygon().tap { |
| pts.each { |
| addPoint(*it.toRawCopy1D()*.round()*.intValue().collect { it + 200 }) |
| } |
| } |
| fill ? g.fillPolygon(poly) : g.drawPolygon(poly) |
| } |
| |
| static List<Primitive64Matrix> vectors(List<Integer>... pts) { |
| pts.collect{ factory.column(*it) } |
| } |
| |
| static transform(List<Number>... lists) { |
| factory.rows(lists as double[][]) |
| } |
| |
| void paint(Graphics g) { |
| g.drawLine(0, 200, 400, 200) |
| g.drawLine(200, 0, 200, 400) |
| g.stroke = new BasicStroke(2) |
| |
| def triangle = vectors([-85, -150], [-145, -30], [-25, -30]) |
| g.color = seaGreen |
| drawPolygon(g, triangle, true) |
| // transform triangle |
| ... |
| |
| def rectangle = vectors([0, -110], [0, -45], [95, -45], [95, -110]) |
| g.color = crystalBlue |
| drawPolygon(g, rectangle, true) |
| // transform rectangle |
| ... |
| |
| def trapezoid = vectors([50, 50], [70, 100], [100, 100], [120, 50]) |
| g.color = violet |
| drawPolygon(g, trapezoid, true) |
| // transform trapezoid |
| ... |
| } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>When we run this code we see our three shapes:</p> |
| </div> |
| <div class="paragraph"> |
| <p><span class="image"><img src="img/MatrixShapes.png" alt="Shapes"></span></p> |
| </div> |
| <div class="paragraph"> |
| <p>We can now add our transforms. We’ll have one which rotate by 90 |
| degrees anti-clockwise. Another which enlarges a shape by 25% and |
| one that shrinks a shape by 25%. We can combine transforms simply |
| by multiplying them together. We’ll make two transformations of |
| our triangle. We’ll rotate in both cases but we’ll shrink one and |
| enlarge the other. We apply the transform simply by multiplying |
| each point by the transform matrix. Then we’ll draw both |
| transformed shapes as an outline rather than filled (to make it |
| easier to distinguish the original and transformed versions). |
| Here is the code:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def rot90 = transform([0, 1], [-1, 0]) |
| def bigger = transform([1.25, 0], [0, 1.25]) |
| def smaller = transform([0.75, 0], [0, 0.75]) |
| def triangle_ = triangle.collect{ rot90 * bigger * it } |
| drawPolygon(g, triangle_, false) |
| triangle_ = triangle.collect{ rot90 * smaller * it } |
| drawPolygon(g, triangle_, false)</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>For our rectangle, we’ll have one simple transform which flips |
| the shape in the vertical axis. A second transform combines |
| multiple changes in one transform. We could have split this into |
| smaller transforms and the multiplied them together - but here |
| they are all in one. We flip in the horizontal access and then |
| apply a shear. We then draw the transformed shapes as outlines:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def flipV = transform([1, 0], [0, -1]) |
| def rectangle_ = rectangle.collect{ flipV * it } |
| drawPolygon(g, rectangle_, false) |
| def flipH_shear = transform([-1, 0.5], [0, 1]) |
| rectangle_ = rectangle.collect{ flipH_shear * it } |
| drawPolygon(g, rectangle_, false)</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>For our trapezoid, we create a transform which rotates 45 degrees |
| anti-clockwise (recall sin 45° = cos 45° = 0.707). Then we create |
| 6 transforms rotating at 45, 90, 135 and so forth. We draw each |
| transformed shape as an outline:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">def rot45 = transform([0.707, 0.707], [-0.707, 0.707]) |
| def trapezoid_ |
| (1..6).each { z -> |
| trapezoid_ = trapezoid.collect{ rot45 ** z * it } |
| drawPolygon(g, trapezoid_, false) |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>When we run the entire example, here is what the output looks |
| like:</p> |
| </div> |
| <div class="paragraph"> |
| <p><span class="image"><img src="img/MatrixShapesTransformed.png" alt="Shapes transformed"></span></p> |
| </div> |
| <div class="paragraph"> |
| <p>We can see here that matrix transforms give us powerful ways to |
| manipulate images. We have used 2D images here, but the same |
| techniques would apply to 3D shapes.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_language_and_tool_extensibility">Language and tool extensibility</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>We saw earlier that some of the examples could make use of Groovy |
| operator shorthand syntax, while others couldn’t. Here is a |
| summary of some common methods in the libraries:</p> |
| </div> |
| <table class="tableblock frame-all grid-all stretch"> |
| <colgroup> |
| <col style="width: 20%;"> |
| <col style="width: 20%;"> |
| <col style="width: 20%;"> |
| <col style="width: 20%;"> |
| <col style="width: 20%;"> |
| </colgroup> |
| <tbody> |
| <tr> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>Groovy operator </em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>+</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>-</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>*</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>**</em></strong></p></td> |
| </tr> |
| <tr> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>Groovy method</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>plus</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>minus</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>multiply</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="blue"><em>power</em></strong></p></td> |
| </tr> |
| <tr> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="aqua"><em>Commons math</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>add</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>subtract</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>multiply</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>power</code></p></td> |
| </tr> |
| <tr> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="aqua"><em>EJML</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>plus</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>minus</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>mult</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock">-</p></td> |
| </tr> |
| <tr> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="aqua"><em>Nd4j</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>add</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>sub</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>mmul</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock">-</p></td> |
| </tr> |
| <tr> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><strong class="aqua"><em>ojAlgo</em></strong></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>add</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>subtract</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>multiply</code></p></td> |
| <td class="tableblock halign-left valign-top"><p class="tableblock"><code>power</code></p></td> |
| </tr> |
| </tbody> |
| </table> |
| <div class="paragraph"> |
| <p>Where the library used the same name as Groovy’s method, |
| the shorthand could be used.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Groovy offers numerous extensibility features. We won’t go into |
| all of the details but instead refer readers to the |
| <a href="https://dl.acm.org/doi/abs/10.1145/3386326">History of Groovy</a> |
| paper which gives more details.</p> |
| </div> |
| <div class="paragraph"> |
| <p>In summary, that paper defined the following extensions for |
| Commons Math matrices:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">RealMatrix.metaClass { |
| plus << { RealMatrix ma -> delegate.add(ma) } |
| plus << { double d -> delegate.scalarAdd(d) } |
| multiply { double d -> delegate.scalarMultiply(d) } |
| bitwiseNegate { -> new LUDecomposition(delegate).solver.inverse } |
| constructor = { List l -> MatrixUtils.createRealMatrix(l as double[][]) } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This fixes some of the method name discrepancies in the above |
| table. We can now use the operator shorthand for matrix and |
| scalar addition as well as scalar multiply. We can also use |
| the ~ (<code>bitwiseNegate</code>) operator when finding the inverse. |
| The mention of <code>double[][]</code> during matrix construction is |
| now also gone.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The paper goes on to discuss how to automatically add the |
| necessary imports for a matrix library and provide aliases if |
| needed. The imports aren’t shown for the code listings in this |
| blog but are in the listings in the sample code repo. |
| In any case, the imports can be "configured away" as the paper |
| discusses. The end result is our code in its entirety can look |
| like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">var m = [[1, 1], [1, 0]] as Matrix |
| m**6</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The paper also discusses tooling extensibility, in particular the |
| visualisation aspects of the GroovyConsole. It shows how to define |
| an output transform which renders any matrix result using a |
| jlatexmath library. So instead of seeing |
| <code>Array2DRowRealMatrix{{13.0,8.0},{8.0,5.0}}</code>, they will see |
| a graphical rendition of the matrix. So, the final end-user |
| experience when using the GroovyConsole looks like this:</p> |
| </div> |
| <div class="paragraph"> |
| <p><span class="image"><img src="img/GroovyConsoleOutputTransformsMatrix.png" alt="matrix output transforms in groovy console"></span></p> |
| </div> |
| <div class="paragraph"> |
| <p>When using in Jupyter style environments, other pretty output |
| styles may be supported.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_further_information">Further information</h2> |
| <div class="sectionbody"> |
| <div class="ulist"> |
| <ul> |
| <li> |
| <p>Apache Commons Math <a href="https://commons.apache.org/proper/commons-math/userguide/linear.html">User Guide</a> and <a href="https://github.com/apache/commons-math">GitHub mirror</a></p> |
| </li> |
| <li> |
| <p>ojAlgo <a href="https://www.ojalgo.org/">website</a> and <a href="https://github.com/optimatika/ojAlgo/">GitHub repo</a></p> |
| </li> |
| <li> |
| <p>EJML <a href="http://ejml.org/">website</a> and <a href="https://github.com/lessthanoptimal/ejml">GitHub repo</a></p> |
| </li> |
| <li> |
| <p>Nd4j <a href="https://deeplearning4j.konduit.ai/nd4j/tutorials/quickstart">documentation</a> and <a href="https://github.com/eclipse/deeplearning4j/tree/master/nd4j">GitHub repo</a></p> |
| </li> |
| <li> |
| <p><a href="https://github.com/paulk-asert/MatrixGroovy">Example code repo</a></p> |
| </li> |
| <li> |
| <p><a href="https://www.youtube.com/watch?v=I5WM2wdjr1M">Leslie Matrices for population age distribution modelling</a> video</p> |
| </li> |
| <li> |
| <p><a href="https://dl.acm.org/doi/abs/10.1145/3386326">A history of the Groovy programming language</a> paper</p> |
| </li> |
| <li> |
| <p><a href="https://medium.com/@Styp/java-18-vector-api-do-we-get-free-speed-up-c4510eda50d2">Java 18: Vector API — Do we get free speed-up?</a> blog post</p> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_conclusion">Conclusion</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>We have examined a number of simple applications of matrices using |
| the Groovy programming language and the Apache Commons Math, |
| ojAlgo, Nd4j, and EJML libraries. You should be convinced that |
| using matrices on the JVM isn’t hard, and you have numerous |
| options. We also saw a brief glimpse at the Vector API which |
| looks like an exciting addition to the JVM (hopefully arriving |
| soon in non-preview mode).</p> |
| </div> |
| </div> |
| </div></div></div></div></div><footer id='footer'> |
| <div class='row'> |
| <div class='colset-3-footer'> |
| <div class='col-1'> |
| <h1>Groovy</h1><ul> |
| <li><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a href='/download.html'>Download</a></li><li><a href='https://groovy-lang.org/support.html'>Support</a></li><li><a href='/'>Contribute</a></li><li><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a href='/blog'>Blog posts</a></li><li><a href='https://groovy.apache.org/events.html'></a></li> |
| </ul> |
| </div><div class='col-2'> |
| <h1>About</h1><ul> |
| <li><a href='https://github.com/apache/groovy'>Source code</a></li><li><a href='https://groovy-lang.org/security.html'>Security</a></li><li><a href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a href='https://groovy-lang.org/search.html'>Search</a></li> |
| </ul> |
| </div><div class='col-3'> |
| <h1>Socialize</h1><ul> |
| <li><a href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing-list</a></li><li><a href='https://twitter.com/ApacheGroovy'>Groovy on Twitter</a></li><li><a href='https://groovy-lang.org/events.html'>Events and conferences</a></li><li><a href='https://github.com/apache/groovy'>Source code on GitHub</a></li><li><a href='https://groovy-lang.org/reporting-issues.html'>Report issues in Jira</a></li><li><a href='http://stackoverflow.com/questions/tagged/groovy'>Stack Overflow questions</a></li><li><a href='http://groovycommunity.com/'>Slack Community</a></li> |
| </ul> |
| </div><div class='col-right'> |
| <p> |
| The Groovy programming language is supported by the <a href='http://www.apache.org'>Apache Software Foundation</a> and the Groovy community. |
| </p><div text-align='right'> |
| <img src='../img/asf_logo.png' title='The Apache Software Foundation' alt='The Apache Software Foundation' style='width:60%'/> |
| </div><p>Apache® and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p> |
| </div> |
| </div><div class='clearfix'>© 2003-2023 the Apache Groovy project — Groovy is Open Source: <a href='http://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2 License'>license</a>, <a href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy policy</a>.</div> |
| </div> |
| </footer></div> |
| </div> |
| </div> |
| </div> |
| </div><script src='../js/vendor/jquery-1.10.2.min.js' defer></script><script src='../js/vendor/classie.js' defer></script><script src='../js/vendor/bootstrap.js' defer></script><script src='../js/vendor/sidebarEffects.js' defer></script><script src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script src='../js/plugins.js' defer></script><script src='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script><script> |
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
| |
| ga('create', 'UA-257558-10', 'auto'); |
| ga('send', 'pageview'); |
| </script> |
| </body></html> |