Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolve "base class Generator" #14

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
lib
pascal
math
out/*
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
![pascal](/img/sample.png)
# Mathographix

A collection of functions that map mathematical objects to visuals. For example, a Pascal
triangle modulo a fixed number:

# pascal-cr
Pascal triangle visuals in [Crystal](https://crystal-lang.org/)
![pascal](/img/sample.png)

This is an update of [the slow Perl version](https://github.com/c-dilks/pascal).
The goal is to reproduce [images like these](https://github.com/c-dilks/pascal/tree/master/img)
This is an update and generalization of the [Perl Pascal triangle generator](https://github.com/c-dilks/pascal),
with the goal to reproduce [images like these](https://github.com/c-dilks/pascal/tree/master/img)
much more efficiently.

## Building
Both Ruby and Crystal are required. Additional dependencies can be installed
with `bundle` and `shards`:
Both Ruby and Crystal are required. Additional dependencies can be installed with `bundle` and `shards`:
```bash
bundle install # install ruby gems
shards install # install crystal shards
bundle install # install ruby gems
shards install # install crystal shards
```

Build by running one of the following:
```bash
./build.rb # standard build
./build.rb --release # optimized build
crystal run src/run.cr -- [args] # build and run `pascal` with paramters `[args]`
./build.rb # standard build
./build.rb --release # optimized build
```

## Usage
To generate triangles, run:
```bash
./pascal # run with default options
./pascal -h # show usage guide
./math # run with default options
./math -h # show usage guide
```

View output SVG files in a browser, or Eye of GNOME (`eog`), for example

If you edit the code, you can re-build and re-run with a single command:
```bash
crystal run src/app.cr -- [args] # `[args]` will be passed to `math`
```
4 changes: 2 additions & 2 deletions build.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env ruby

target = "pascal"
target = "math"

buildCommand = [
"crystal build",
ARGV.size>0 ? ARGV.join(' ') : "",
"-o #{target}",
"src/run.cr",
"src/app.cr",
].join " "

puts "buildCommand => #{buildCommand}"
Expand Down
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: pascal-cr
name: mathographix
version: 0.1.0

authors:
Expand Down
96 changes: 67 additions & 29 deletions src/run.cr → src/app.cr
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# generate pascal triangle
require "./pascal"
require "option_parser"
require "./generators/*"

# default settings
numRows = 17*17
modulus = 17
seed = [BigInt.new 1]
beginRow = 0
outBN = "output"
outDir = "out"
outFormats = [ :txt, :svg ]
Expand All @@ -18,26 +14,50 @@ outMode = outFormats.map{ |ext| [ext,false] }.to_h
sep = "-"*60
stopEarly = false

# list of supported generators
gen_list = [
Mathographix::Pascal,
]

# modifiers (todo)
modulus = 17

# parse options
gen = Mathographix::Generator.new
OptionParser.parse do |p|
p.banner = "
+=========================+
/ Pascal Triangle Generator \\
+=============================+
+============+
/ Mathographix \\
+================+

Usage: math [GENERATOR] [MODIFIER] [ARUMENTS]...
"
p.on "-h", "--help", "show help" do
puts p
exit
end
p.on "-H", "--dump", "show all settings and exit" { stopEarly = true }
p.on "-D", "--dry-run", "dry run, just show all settings and exit" { stopEarly = true }
p.separator sep
p.separator "generator settings:"
p.on "-n NUM_ROWS", "number of rows to generate" { |n| numRows = n.to_i }
p.on "-m MODULUS", "modulus" { |n| modulus = n.to_i }
p.on "-s SEED", "seed row number(s), e.g., `-s 1,2,3`" do |s|
seed = s.split(',').map{ |n| BigInt.new n }

# generator subcommand
p.separator "[GENERATOR]: choose one of the following generators:"
gen_list.each do |gen_class|
p.on( gen_class.subcommand, gen_class.description) do
gen = gen_class.new
p.separator sep
p.separator "#{gen_class.description} OPTIONS:"
gen.options.each do |opt|
p.on(opt.short_flag, opt.long_flag, opt.description) { |s| opt.action.call(s) }
end
end
end
p.on "-b BEGINROW", "row number to begin output on" { |n| beginRow = n.to_i }
p.separator "NOTE: run `math [GENERATOR] -h` to show generator-specific options"
p.separator sep

# modifier subcommand (todo)
p.on "-m MODULUS", "modulus" { |n| modulus = n.to_i }

# general options
p.separator sep
p.separator "output file directory and filename prefix:"
p.on "--outdir OUTDIR", "output directory" { |s| outDir = s.gsub(/\/$/,"") }
Expand All @@ -63,7 +83,9 @@ outMode[:svg] = true if outMode.values.find{|v|v}.nil?
# print settings
puts sep
puts "settings".upcase
p! numRows, modulus, seed, beginRow, outName, outMode, drawSize, colormap
p! modulus, outName, outMode, drawSize, colormap
puts sep
gen.print_settings
puts sep
exit if stopEarly

Expand All @@ -72,29 +94,45 @@ Dir.mkdir outDir unless Dir.exists? outDir
outTxt = File.new("#{outName}.txt","w") if outMode[:txt]
outSvg = File.new("#{outName}.svg","w") if outMode[:svg]

# set modifier
palette_range_min = 0
palette_range_max = 1
mod_function = "modulo"
case mod_function
when "modulo"
gen.mod_function = gen.modulo(modulus)
palette_range_max = modulus
when "modulo_row"
gen.mod_function = gen.modulo(modulus)
# gen.mod_function_update_mode = "modulo_row" # todo fix this
# palette_range_max = gen.size # todo fix this
else
STDERR.puts "ERROR: unknown modifier function '#{mod_function}'; using default"
end


# execution ---------------------------------------------------
svg = Celestine.draw do |ctx|

# start the triangle, given a seed row (default is `[1]`)
triangle = Pascal::Row.new numRows, seed, beginRow
triangle.drawSize = drawSize
triangle.palette.set_gradient colormap
triangle.palette.set_range 0, modulus-1
# common generator settings
gen.drawSize = drawSize
gen.palette.set_gradient colormap
gen.palette.set_range palette_range_min, palette_range_max

# skip to row number `beginRow`
beginRow.times.each do triangle.next end
# skip to iteration number `gen.first_iter`
gen.first_iter.times.each do gen.next end

# output proc
produce = -> {
triangle.modulo modulus
triangle.output outTxt.as(File) if outMode[:txt]
triangle.draw ctx if outMode[:svg]
gen.modify
gen.output outTxt.as(File) if outMode[:txt]
gen.draw ctx if outMode[:svg]
}
produce.call # seed row
produce.call

# loop over rows
numRows.times do |i|
triangle.next
gen.size.times do |i|
gen.next
produce.call
end

Expand Down
70 changes: 70 additions & 0 deletions src/generators/generator.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require "big"
require "celestine"
require "../colors"
require "../options"

module Mathographix

alias ModProc = BigInt -> BigInt

# base class for generators
class Generator
property size, first_iter, drawSize, palette, mod_function, mod_functions, options
@size : Int128
@first_iter : Int128
@mod_function : ModProc

# class variables
@@subcommand = String.new
@@description = String.new

def initialize
@size = 0
@first_iter = 0
@drawSize = 1.0
@palette = Colors::Palette.new
@mod_functions = ["modulo"]
@mod_function = ModProc.new{ |n| BigInt.new 1 }
@options = Array(Options::GeneratorOption).new
end

# print settings
def print_settings
p! @size, @first_iter, @drawSize
end

# call `@mod_function` on all elements of `values`
def modify(values)
values.map do |n|
@mod_function.call n
end
end

# class variable getters
def self.subcommand
@@subcommand
end
def self.description
@@description
end

# virtual methods
def next
end
def modify
end
def output(stream=STDOUT)
end
def draw(ctx)
end

### MODIFIER FUNCTIONS -------------------------------------------

def modulo(modulus)
ModProc.new do |n|
n.modulo modulus
end
end

end
end
98 changes: 98 additions & 0 deletions src/generators/pascal.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
require "./generator"

module Mathographix

# store one row of the pascal triangle
class Pascal < Generator
getter nums, mods
property mod_function_update_mode
@@subcommand = "pascal"
@@description = "Pasal Triangle"

def initialize
super()
# defaults
@size = Int128.new 17*17
@nums = [ BigInt.new 1 ]
@mods = @nums
@rowNum = 0
# modifiers
@mod_function_update_mode = "none"
@mod_functions += ["modulo-row"]
# options
@options += [
Options::GeneratorOption.new(
"-n NUM_ROWS",
"--num NUM_ROWS",
"number of rows to generate",
Options::OptionProc.new { |s| @size = s.to_i128 }
),
Options::GeneratorOption.new(
"-b BEGINROW",
"--begin-row BEGINROW",
"row number to begin output on",
Options::OptionProc.new { |s| @first_iter = s.to_i128 }
),
Options::GeneratorOption.new(
"-s SEED",
"--seed SEED",
"seed row number(s), e.g., `-s 1,2,3`",
Options::OptionProc.new { |s| @nums = s.split(',').map{ |n| BigInt.new n } }
),
]
end

def print_settings
puts "#{@@subcommand} generator settings".upcase
super
p! @nums
end

#################################################

# compute the next row of the triangle
def next
@nums = ([0]+@nums).zip(@nums+[0]).map &.sum
@rowNum += 1
end

# run a calculation on a row's `@nums`, store results in `@mods`
def modify
@mod_function = self.mod_function_update
@mods = super @nums
end

# update a modifier function
def mod_function_update
case @mod_function_update_mode
when "modulo_row"
@mod_function = modulo(@rowNum+1)
end
@mod_function
end

#################################################

# text output
def output(stream=STDOUT)
stream.puts @nums
# stream.puts @mods
end

# svg output
def draw(ctx)
@mods.each_with_index do |num,colNum|
ctx.rectangle do |rec|
offset = ( @size - @rowNum + @first_iter ) / 2
rec.x = ( offset + colNum ) * @drawSize
rec.y = ( @rowNum - @first_iter) * @drawSize
rec.width = @drawSize
rec.height = @drawSize
rec.fill = @palette.colorize num
rec
end
end
end

end
end
Loading