simulator 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +16 -0
- data/LICENSE +22 -0
- data/README.md +67 -0
- data/RELEASE.md +31 -0
- data/Rakefile +2 -0
- data/examples/drop/Gemfile +5 -0
- data/examples/drop/drop.png +0 -0
- data/examples/drop/drop.rb +70 -0
- data/examples/mortage/Gemfile +5 -0
- data/examples/mortage/mortgage.png +0 -0
- data/examples/mortage/mortgage.rb +91 -0
- data/features/equations.feature +17 -0
- data/features/physics.feature +22 -0
- data/features/savings_simulation.feature +17 -0
- data/features/step_definitions/equations_steps.rb +52 -0
- data/features/step_definitions/savings_simulation_steps.rb +40 -0
- data/lib/simulator.rb +14 -0
- data/lib/simulator/bound_variable.rb +18 -0
- data/lib/simulator/data.rb +23 -0
- data/lib/simulator/equation.rb +16 -0
- data/lib/simulator/model.rb +43 -0
- data/lib/simulator/period.rb +28 -0
- data/lib/simulator/run.rb +95 -0
- data/lib/simulator/sandbox.rb +31 -0
- data/lib/simulator/variable.rb +11 -0
- data/lib/simulator/variable_context.rb +66 -0
- data/lib/simulator/version.rb +3 -0
- data/simulator.gemspec +20 -0
- data/spec/beer_spec.rb +91 -0
- data/spec/context_spec.rb +38 -0
- data/spec/equation_spec.rb +24 -0
- data/spec/interest_spec.rb +79 -0
- data/spec/model_spec.rb +33 -0
- data/spec/period_spec.rb +28 -0
- data/spec/run_spec.rb +44 -0
- data/spec/sandbox_spec.rb +24 -0
- metadata +128 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jamie Ly
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Simulator
|
2
|
+
|
3
|
+
Use to easily create models and view results across periods.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'simulator'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install simulator
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
See the features for some examples.
|
22
|
+
|
23
|
+
bundle exec cucumber
|
24
|
+
|
25
|
+
See the specs for some simpler examples.
|
26
|
+
|
27
|
+
bundle exec rspec
|
28
|
+
|
29
|
+
See the `examples` subdirectory for examples as well. Including the one
|
30
|
+
excerpted below:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
# We create a model that simulates a ball drop
|
34
|
+
model = Simulator::Model.new do
|
35
|
+
name = "Ball drop model"
|
36
|
+
# create a couple static variables to represent acceleration with
|
37
|
+
# default values
|
38
|
+
var :ax, 0
|
39
|
+
var :ay, - 9.8
|
40
|
+
|
41
|
+
# create dynamic variables bound to some computation, with default
|
42
|
+
# values.
|
43
|
+
|
44
|
+
# velocity is affected by acceleration
|
45
|
+
eqtn(:vx, 20) { vx + ax }
|
46
|
+
eqtn(:vy, 50) { vy + ay }
|
47
|
+
|
48
|
+
# position is affected by velocity
|
49
|
+
eqtn(:x, 10) { x + vx }
|
50
|
+
eqtn(:y, 100) { y + vy }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Run the model 10 periods
|
54
|
+
model_run = model.new_run
|
55
|
+
10.times do
|
56
|
+
model_run.step
|
57
|
+
end
|
58
|
+
|
59
|
+
# retrieve the data
|
60
|
+
series = model_run.data.series :x, :y
|
61
|
+
puts series
|
62
|
+
# > [[30, 50, 70, 90, 110, 130, 150, 170, 190, 210, 210],
|
63
|
+
# > [140.2, 170.6, 191.2, 202.0, 203.0, 194.2, 175.6, 147.2, 108.99999999999999, 60.999999999999986, 60.999999999999986]]
|
64
|
+
|
65
|
+
# Then plot it or do whatever you like
|
66
|
+
```
|
67
|
+
|
data/RELEASE.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
v0.1.2
|
2
|
+
======
|
3
|
+
* Added detail to gemspec
|
4
|
+
|
5
|
+
v0.1.1
|
6
|
+
======
|
7
|
+
* Added mortgage example
|
8
|
+
|
9
|
+
v0.1.0
|
10
|
+
======
|
11
|
+
* First minor release
|
12
|
+
* Removes `puts` in `Model.rb`
|
13
|
+
* Alters beer_spec to use new var default syntax
|
14
|
+
|
15
|
+
v0.0.5
|
16
|
+
======
|
17
|
+
* Added "bounce" example
|
18
|
+
* Added arg to pass default value of variable with eqtn call
|
19
|
+
|
20
|
+
v0.0.4
|
21
|
+
======
|
22
|
+
|
23
|
+
v0.0.3
|
24
|
+
======
|
25
|
+
* Added block parameter to Model instantiation to more easily create
|
26
|
+
variables and equations.
|
27
|
+
* Added "interest" spec to test a simple interest model
|
28
|
+
* Added "beer" spec for a more complex model example
|
29
|
+
* Added `delay` function which allows equations to interact with past periods
|
30
|
+
|
31
|
+
|
data/Rakefile
ADDED
Binary file
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'chunky_png'
|
3
|
+
require 'simulator'
|
4
|
+
|
5
|
+
module Drop
|
6
|
+
class Program
|
7
|
+
attr_accessor :model, :steps_per_second
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
steps_per_second = 3.0
|
11
|
+
@steps_per_second = steps_per_second
|
12
|
+
@model = ::Simulator::Model.new do
|
13
|
+
name = "Drop model"
|
14
|
+
var :ax, 0
|
15
|
+
var :ay, - 9.8/steps_per_second
|
16
|
+
eqtn(:vx, 20) { vx + ax }
|
17
|
+
eqtn(:vy, 50) { vy + ay }
|
18
|
+
eqtn(:x, 10) { x + vx }
|
19
|
+
eqtn(:y, 100) { y + vy }
|
20
|
+
end
|
21
|
+
@height = 500
|
22
|
+
@width = 700
|
23
|
+
end
|
24
|
+
|
25
|
+
def model_data
|
26
|
+
# Run the model 30 steps
|
27
|
+
model_run = @model.new_run
|
28
|
+
30.times do
|
29
|
+
model_run.step
|
30
|
+
end
|
31
|
+
|
32
|
+
# get the data
|
33
|
+
model_run.data
|
34
|
+
end
|
35
|
+
|
36
|
+
def norm_data(data)
|
37
|
+
xs, ys = data.series :x, :y
|
38
|
+
|
39
|
+
# normalize
|
40
|
+
xs = xs.collect(&:round)
|
41
|
+
# Flip the y coordinates so we can draw
|
42
|
+
ys = ys.collect {|y| @height - y.round}
|
43
|
+
|
44
|
+
xs.zip(ys)
|
45
|
+
end
|
46
|
+
|
47
|
+
def plot_data(filename, pts)
|
48
|
+
# display the data in an image
|
49
|
+
image = ChunkyPNG::Image.new @width, @height,
|
50
|
+
ChunkyPNG::Color::BLACK
|
51
|
+
pts.each do |pt|
|
52
|
+
x, y = pt
|
53
|
+
image.circle x, y, 3, ChunkyPNG::Color('red')
|
54
|
+
end
|
55
|
+
image.save filename
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def run
|
60
|
+
data = model_data
|
61
|
+
pts = norm_data data
|
62
|
+
plot_data 'drop.png', pts
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Run the program
|
69
|
+
Drop::Program.new.run
|
70
|
+
|
Binary file
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'simulator'
|
2
|
+
require 'chunky_png'
|
3
|
+
|
4
|
+
module Mortgage
|
5
|
+
class Program
|
6
|
+
include Simulator
|
7
|
+
def model
|
8
|
+
@model = Model.new do
|
9
|
+
name = "Mortgage model"
|
10
|
+
|
11
|
+
# monthly steps
|
12
|
+
var :base_rate, 0.08
|
13
|
+
eqtn(:annual_rate) { base_rate + rand(10).to_f/1000 }
|
14
|
+
eqtn(:monthly_rate) { annual_rate / 12.0 }
|
15
|
+
var :payment, 2000
|
16
|
+
eqtn :balance, 250000 do
|
17
|
+
balance * (1 + monthly_rate) - payment
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def model_data
|
23
|
+
# fixed rate mortgage
|
24
|
+
fixed = @model.new_run
|
25
|
+
fixed.set payment: 2100
|
26
|
+
(30 * 12).times { fixed.step }
|
27
|
+
|
28
|
+
# balloon
|
29
|
+
balloon = @model.new_run
|
30
|
+
balloon.set payment: 1850
|
31
|
+
(30 * 12).times { balloon.step }
|
32
|
+
|
33
|
+
variable = @model.new_run
|
34
|
+
# first 10 years, stick with low payment
|
35
|
+
variable.set payment: 1800
|
36
|
+
(10 * 12).times do
|
37
|
+
variable.step
|
38
|
+
end
|
39
|
+
|
40
|
+
# subsequent years, balloon to higher payment until its paid
|
41
|
+
variable.set payment: 2100
|
42
|
+
(20 * 12).times {variable.step}
|
43
|
+
@balances = {
|
44
|
+
variable: variable.data.series(:balance),
|
45
|
+
fixed: fixed.data.series(:balance),
|
46
|
+
balloon: balloon.data.series(:balance)
|
47
|
+
}
|
48
|
+
|
49
|
+
@balances
|
50
|
+
end
|
51
|
+
def plot
|
52
|
+
@height = 500
|
53
|
+
@width = 800
|
54
|
+
|
55
|
+
# display the data in an image
|
56
|
+
image = ChunkyPNG::Image.new @width, @height,
|
57
|
+
ChunkyPNG::Color::BLACK
|
58
|
+
|
59
|
+
peg = @balances[:variable]
|
60
|
+
count = peg.length
|
61
|
+
max = peg.max.to_f
|
62
|
+
colors = {
|
63
|
+
variable: ChunkyPNG::Color('blue'),
|
64
|
+
fixed: ChunkyPNG::Color('yellow'),
|
65
|
+
balloon: ChunkyPNG::Color('red')
|
66
|
+
}
|
67
|
+
@balances.keys.each do |type|
|
68
|
+
balances = @balances[type]
|
69
|
+
color = colors[type]
|
70
|
+
|
71
|
+
balances.each_index do |i|
|
72
|
+
x = i/count.to_f * @width
|
73
|
+
y = @height - (balances[i]/max * @height).to_i
|
74
|
+
image.circle x, y, 3, color
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
image.save @filename
|
79
|
+
end
|
80
|
+
def run
|
81
|
+
model
|
82
|
+
model_data
|
83
|
+
@filename = "mortgage.png"
|
84
|
+
plot
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Mortgage::Program.new.run
|
91
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: equations
|
2
|
+
In order to create simulations
|
3
|
+
As a simulation author
|
4
|
+
I want to be able to create equations which model the behavior of a system
|
5
|
+
|
6
|
+
Scenario: adding equations to scenarios
|
7
|
+
Given a model
|
8
|
+
When I add a new equation
|
9
|
+
Then it should be accessible in the list of equations
|
10
|
+
|
11
|
+
Scenario: creating an equation based on the pythagorean equation
|
12
|
+
Given a variable context
|
13
|
+
And a value 3 bound to x
|
14
|
+
And a value 4 bound to z
|
15
|
+
When I create a new equation "Math.sqrt(x^2 + z^2)"
|
16
|
+
Then I get a value result 5
|
17
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Feature: Kinematics estimation
|
2
|
+
In order to determine the path of a ball
|
3
|
+
As someone studying physics
|
4
|
+
I want to be able model kinematics equations
|
5
|
+
|
6
|
+
Scenario: dropping a ball from rest
|
7
|
+
Given a model
|
8
|
+
And variable "velocity_y"
|
9
|
+
And variable "acceleration_y"
|
10
|
+
And variable "displacement_y"
|
11
|
+
And variable "t"
|
12
|
+
And equation "acceleration_y * t" bound to velocity_y
|
13
|
+
And equation "0.5 * acceleration_y * t**2" bound to displacement_y
|
14
|
+
And equation "t + 1" bound to t
|
15
|
+
And a new run of the model
|
16
|
+
And the value 0 bound to displacement_y
|
17
|
+
And the value 1 bound to t
|
18
|
+
And the value -9.8 bound to acceleration_y
|
19
|
+
When I step the run for 10 periods
|
20
|
+
Then the value -490 should be bound to displacement_y
|
21
|
+
And the value -98 should be bound to velocity_y
|
22
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: savings simulation
|
2
|
+
In order to simulate the outcome of savings rates across a number of periods
|
3
|
+
As a simulation runner
|
4
|
+
I want the simulation to output the outcome of the decisions made
|
5
|
+
|
6
|
+
Scenario: constant savings rate
|
7
|
+
Given a savings model
|
8
|
+
And variable "savings_rate"
|
9
|
+
And variable "savings"
|
10
|
+
And equation "savings * (1+savings_rate)" bound to savings
|
11
|
+
And a new run of the model
|
12
|
+
And the value 100 bound to savings
|
13
|
+
And the value 0.05 bound to savings_rate
|
14
|
+
When I step the run for 10 periods
|
15
|
+
Then the value 162.89 should be bound to savings
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'simulator'
|
2
|
+
include Simulator
|
3
|
+
|
4
|
+
Given /^a variable context$/ do
|
5
|
+
@context = VariableContext.new
|
6
|
+
end
|
7
|
+
|
8
|
+
Given /^a value (\d+) bound to (\w+)$/ do |value, name|
|
9
|
+
@var = Variable.new name.to_sym
|
10
|
+
@context.add_variables @var
|
11
|
+
@context.set name.to_sym => value.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
When /^I create a new equation "(.*?)"$/ do |arg1|
|
15
|
+
@pythagorean_eqtn = Equation.new @var do
|
16
|
+
Math.sqrt( x*x + z*z )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Given /^variables with values x=(\d+), and y=(\d+)$/ do |x_value, y_value|
|
21
|
+
@context = VariableContext.new
|
22
|
+
@context.add x: 3, z: 4
|
23
|
+
|
24
|
+
# set the values of the variables in this context
|
25
|
+
@context.set x: x_value.to_i, z: y_value.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
Then /^I get a value result (\d+)$/ do |equation_result|
|
29
|
+
# the context evaluates an equation in its own context
|
30
|
+
@pythagorean_eqtn.evaluate_in(@context).should eq equation_result.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^a bound variable "(.*?)" with value (\d+)$/ do |var_name, value|
|
34
|
+
@context.get(var_name.to_sym).value.should eq value.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
Given /^a model$/ do
|
38
|
+
@model = Model.new
|
39
|
+
end
|
40
|
+
|
41
|
+
When /^I add a new equation$/ do
|
42
|
+
@new_equation = Equation.new nil do
|
43
|
+
1 + 1
|
44
|
+
end
|
45
|
+
@model.add_equation @new_equation
|
46
|
+
end
|
47
|
+
|
48
|
+
Then /^it should be accessible in the list of equations$/ do
|
49
|
+
@model.equations.member?(@new_equation).should be true
|
50
|
+
end
|
51
|
+
|
52
|
+
|