simulator 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|