Building a Custom Java Library for a Propane Sketch
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