Custom java library build for propane (or JRubyArt)

This library is taken from a neural net example in Dan Shiffmanns Nature of Code see original code, see propane version. In our case for convenience we are calling the java library xor.jar, and since this is a ruby project, we are making use of a polyglot maven build. See how easy it is to create a custom pom.rb build in ruby:-

project 'xor' do

  model_version '4.0.0'
  id 'nn:xor:1.0-SNAPSHOT'
  packaging 'jar'

  description 'neural net library for xor'

  developer 'shiffman' do
    name 'DanShiffman'
    roles 'developer'
  end

  properties( 'maven.compiler.source' => '1.8',
              'project.build.sourceEncoding' => 'UTF-8',
              'polyglot.dump.pom' => 'pom.xml',
              'xor.basedir' => '${project.basedir}',
              'maven.compiler.target' => '1.8' )

  overrides do
    plugin( :jar, '2.3.2',
            'outputDirectory' =>  '${xor.basedir}/library/xor' )
  end


  build do
    default_goal 'package'
    source_directory 'src'
    final_name 'xor'
  end
end

We dump pom.xml for reference, but is absolutely not required for the build which is as simple as:-

mvn package

NB: you do need to create a .mvn folder with an extension.xml file to define what flavor of polyglot maven you require see polyglot maven.

Here is the xor.rb code:-

require 'propane'
# The Nature of Code
# Daniel Shiffman
# https://natureofcode.com
# XOR Multi-Layered Neural Network Example
# Neural network code is all in the 'code' folder
class Xor < Propane::App
  load_library :xor

  require_relative './landscape'
  include_package 'nn'

  ITERATIONS_PER_FRAME = 5

  attr_reader :inputs, :nn, :count, :land, :theta, :f, :result, :known

  def setup
    sketch_title 'XOR'
    @theta = 0.0
    # Create a landscape object
    @land = Landscape.new(20, 300, 300)
    @f = create_font('Courier', 12, true)
    @nn = Network.new(2, 4)
    @count = 0
    # Create a list of 4 training inputs
    @inputs = []
    inputs << [1.0, 0]
    inputs << [0, 1.0]
    inputs << [1.0, 1.0]
    inputs << [0, 0.0]
  end

  def draw
    lights
    ITERATIONS_PER_FRAME.times do
      inp = inputs.sample
      # Compute XOR
      @known = ((inp[0] > 0.0 && inp[1] > 0.0) || (inp[0] < 1.0 && inp[1] < 1.0)) ? 0 : 1.0
      # Train that sucker!
      @result = nn.train(inp, known)
      @count += 1
    end
    # Ok, visualize the solution space
    background(175)
    push_matrix
    translate(width / 2, height / 2 + 20, -160)
    rotate_x(Math::PI / 3)
    rotate_z(theta)
    # Put a little BOX on screen
    push_matrix
    stroke(50)
    no_fill
    translate(-10, -10, 0)
    box(280)
    land.calculate(nn)
    land.render
    # Draw the landscape
    pop_matrix
    @theta += 0.0025
    pop_matrix
    # Display overal neural net stats
    network_status
  end

  def network_status
    mse = 0.0
    text_font(f)
    fill(0)
    text('Your friendly neighborhood neural network solving XOR.', 10, 20)
    text(format('Total iterations: %d', count), 10, 40)
    mse += (result - known) * (result - known)
    rmse = Math.sqrt(mse / 4.0)
    out = format('Root mean squared error: %.5f', rmse)
    hint DISABLE_DEPTH_SORT
    text(out, 10, 60)
    hint ENABLE_DEPTH_SORT
  end

  def settings
    size(400, 400, P3D)
  end
end

Xor.new

Here is the landscape.rb, note the use of a lambda function to simplify code, and also use the propane grid convenience method:-

# The Nature of Code
# Daniel Shiffman
# https://natureofcode.com

# "Landscape" example
class Landscape
  include Propane::Proxy

  attr_reader :scl, :w, :h, :rows, :cols, :z, :zoff

  def initialize(scl, w, h)
    @scl, @w, @h  = scl, w, h
    @cols = w / scl
    @rows = h / scl
    @z = Array.new(cols, Array.new(rows, 0.0))
    @zoff = 0
  end

  # Calculate height values (based off a neural network)
  def calculate(nn)
    val = lambda do |curr, net, x, y|
      curr * 0.95 + 0.05 * (net.feed_forward([x, y]) * 280.0 - 140.0)
    end
    @z = (0...cols).map do |i|
      (0...rows).map do |j|
        val.call(z[i][j], nn, i * 1.0 / cols, j * 1.0 / cols)
      end
    end
  end

  # Render landscape as grid of quads
  def render
    # Every cell is an individual quad
    # using the propane grid convenience function instead of a nested loop
    grid(z.size - 1, z[0].size - 1) do |x, y|
      # one quad at a time
      # each quad's color is determined by the height value at each vertex
      # (clean this part up)
      no_stroke
      push_matrix
      begin_shape(QUADS)
      translate(x * scl - w * 0.5, y * scl - h * 0.5, 0)
      fill(z[x][y] + 127, 220)
      vertex(0, 0, z[x][y])
      fill(z[x + 1][y] + 127, 220)
      vertex(scl, 0, z[x + 1][y])
      fill(z[x + 1][y + 1] + 127, 220)
      vertex(scl, scl, z[x + 1][y + 1])
      fill(z[x][y + 1] + 127, 220)
      vertex(0, scl, z[x][y + 1])
      end_shape
      pop_matrix
    end
  end
end