rachinations 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/README.md +184 -5
- data/lib/rachinations.rb +5 -4
- data/lib/rachinations/domain/diagrams/diagram.rb +35 -24
- data/lib/rachinations/domain/exceptions/bad_config.rb +1 -1
- data/lib/rachinations/domain/{exceptions → modules/common}/bad_options.rb +0 -0
- data/lib/rachinations/domain/modules/common/hash_init.rb +1 -0
- data/lib/rachinations/domain/modules/common/refiners/number_modifiers.rb +2 -1
- data/lib/rachinations/domain/modules/diagrams/verbose.rb +1 -1
- data/lib/rachinations/domain/nodes/gate.rb +23 -27
- data/lib/rachinations/domain/nodes/node.rb +4 -20
- data/lib/rachinations/domain/nodes/pool.rb +2 -3
- data/lib/rachinations/domain/nodes/resourceful_node.rb +4 -3
- data/lib/rachinations/domain/nodes/sink.rb +1 -1
- data/lib/rachinations/dsl/bad_dsl.rb +3 -0
- data/lib/rachinations/dsl/bootstrap.rb +10 -8
- data/lib/rachinations/dsl/diagram_shorthand_methods.rb +10 -5
- data/lib/rachinations/dsl/helpers/parser.rb +83 -49
- data/lib/rachinations/helpers/edge_helper.rb +105 -8
- data/lib/rachinations/utils/math_utils.rb +83 -0
- data/lib/rachinations/utils/string_utils.rb +8 -0
- data/lib/rachinations/version.rb +1 -1
- data/rachinations.gemspec +2 -1
- data/testing/spec/diagram_spec.rb +78 -17
- data/testing/spec/gate_spec.rb +223 -2
- data/testing/spec/non_deterministic_diagram_spec.rb +1 -1
- data/testing/spec/release1/dsl_spec.rb +100 -3
- data/testing/spec/release1/individual_cases_spec.rb +39 -0
- data/testing/spec/release1/monografia_spec.rb +69 -0
- metadata +24 -5
- data/lib/rachinations/utils/string_helper.rb +0 -7
@@ -1,12 +1,15 @@
|
|
1
|
+
require 'fraction'
|
2
|
+
|
1
3
|
module Helpers
|
2
4
|
module EdgeHelper
|
5
|
+
MathUtils = ::Utils::MathUtils
|
3
6
|
|
4
7
|
# Returns true if all edges passed as arguments can execute a push
|
5
8
|
# when called in the same round. The difference between calling this
|
6
9
|
# method and calling Edge#test_ping? on each edge is that this method
|
7
10
|
# knows that each edge removes at least one Resource when it performs
|
8
11
|
# a push.
|
9
|
-
# @param [
|
12
|
+
# @param [Enumerable<Edge>] edges the edges
|
10
13
|
# @param [Boolean] require_all whether to require that the maximum amount
|
11
14
|
# of resources supported by each edge (i.e. its label) be available in
|
12
15
|
# order to succeed.
|
@@ -17,24 +20,118 @@ module Helpers
|
|
17
20
|
number_of_resources: 0
|
18
21
|
}
|
19
22
|
|
20
|
-
raise NotImplementedError, 'only require_all is implemented so far' unless require_all
|
23
|
+
raise ::NotImplementedError, 'only require_all is implemented so far' unless require_all
|
24
|
+
|
25
|
+
final = edges.inject(initial) do |acc, elem|
|
26
|
+
|
27
|
+
edge = elem
|
21
28
|
|
22
|
-
|
23
|
-
accumulator[:number_of_resources] += edge.label
|
29
|
+
acc[:number_of_resources] += edge.label
|
24
30
|
|
25
31
|
resources_available = edge.from.resource_count(expr: edge.push_expression)
|
26
32
|
|
27
|
-
if resources_available >=
|
28
|
-
|
33
|
+
if resources_available >= acc[:number_of_resources]
|
34
|
+
acc[:partial_success] &&= true
|
29
35
|
else
|
30
|
-
|
36
|
+
acc[:partial_success] &&= false
|
31
37
|
end
|
32
|
-
|
38
|
+
acc
|
33
39
|
end
|
34
40
|
|
35
41
|
final[:partial_success]
|
36
42
|
|
37
43
|
end
|
38
44
|
|
45
|
+
# Validates that labels are consistent
|
46
|
+
def self.labels_valid?(edges)
|
47
|
+
all_labels_integer?(edges) || (all_labels_float?(edges) && float_labels_add_up_to_one?(edges))
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.all_labels_of_same_kind?(edges)
|
51
|
+
all_labels_integer?(edges) || all_labels_float?(edges)
|
52
|
+
end
|
53
|
+
|
54
|
+
# maybe returns an edge
|
55
|
+
def self.pick_one(edges:, mode:, index: nil)
|
56
|
+
raise ::RuntimeError.new('Labels must be of the same kind') unless all_labels_of_same_kind?(edges)
|
57
|
+
raise ::RuntimeError.new('Unknown mode') unless [:deterministic, :probabilistic].include?(mode)
|
58
|
+
|
59
|
+
if edges.empty?
|
60
|
+
nil
|
61
|
+
else
|
62
|
+
if mode === :deterministic
|
63
|
+
pick_next(edges, index)
|
64
|
+
elsif mode === :probabilistic
|
65
|
+
pick_any(edges)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def self.pick_next(edges, index)
|
73
|
+
raise ::ArgumentError.new('Invalid index: '+index) unless index.is_a?(Fixnum)
|
74
|
+
|
75
|
+
|
76
|
+
frequency_hash = edges.reduce(Hash.new) do |acc,elem|
|
77
|
+
acc[elem] = elem.label
|
78
|
+
acc
|
79
|
+
end
|
80
|
+
|
81
|
+
edge_lineup = MathUtils.get_cycle_lineup(frequency_hash)
|
82
|
+
normalized_index = index % edge_lineup.size
|
83
|
+
|
84
|
+
edge_lineup[normalized_index]
|
85
|
+
end
|
86
|
+
|
87
|
+
# pick one edge according to weights, if any
|
88
|
+
def self.pick_any(edges)
|
89
|
+
|
90
|
+
if all_labels_integer?(edges) && edges.all? { |e| e.label == 1 }
|
91
|
+
#edges is probably an enumerable but only arrays can be sample()'d
|
92
|
+
edges.to_a.sample
|
93
|
+
elsif all_labels_float?(edges)
|
94
|
+
#{edge=>weight} is the shape required by WeightedDistribution
|
95
|
+
weights = edges.reduce(Hash.new) { |acc, el| acc[el] = el.label; acc }
|
96
|
+
|
97
|
+
sum_of_labels = edges.reduce(0) { |acc, el| acc + el.label }
|
98
|
+
|
99
|
+
remaining = 1.0 - sum_of_labels
|
100
|
+
|
101
|
+
# chances that a resource 'vanishes'
|
102
|
+
weights[nil] = remaining
|
103
|
+
|
104
|
+
WeightedDistribution.new(weights).sample
|
105
|
+
|
106
|
+
elsif all_labels_integer?(edges)
|
107
|
+
|
108
|
+
sum_of_labels = edges.reduce(0) { |acc, el| acc + el.label }
|
109
|
+
|
110
|
+
# since labels are integers, we must normalize them to get probablities
|
111
|
+
weights = edges.reduce(Hash.new) { |acc, el| acc[el] = el.label/sum_of_labels; acc }
|
112
|
+
|
113
|
+
WeightedDistribution.new(weights).sample
|
114
|
+
|
115
|
+
else
|
116
|
+
raise RuntimeError.new('Invalid setup')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.all_labels_integer?(edges)
|
121
|
+
edges.all? { |e| e.label.is_a?(Fixnum) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.all_labels_float?(edges)
|
125
|
+
edges.all? { |e| e.label.is_a?(Float) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.float_labels_add_up_to_one?(edges)
|
129
|
+
|
130
|
+
labels_as_fractions = edges.reduce(Array.new){|acc,el| acc.push(el.label.to_fraction(100)); acc }
|
131
|
+
|
132
|
+
MathUtils.add_up_to_one?(labels_as_fractions)
|
133
|
+
|
134
|
+
end
|
135
|
+
|
39
136
|
end
|
40
137
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'fraction'
|
2
|
+
|
3
|
+
module Utils
|
4
|
+
module MathUtils
|
5
|
+
|
6
|
+
# Returns an array containing references to the objects, in the correct
|
7
|
+
# order, such that it can be used to represent the minimum possible cycle
|
8
|
+
# that can be implemented using these objects.
|
9
|
+
#
|
10
|
+
# @example Simple example
|
11
|
+
# {"foo"=>3,"bar"=>2} yields ["foo","foo","foo","bar","bar"]
|
12
|
+
# @param [Hash<Object,Numeric>] weights A hash where the keys are
|
13
|
+
# objects and the values their respective weights
|
14
|
+
# @return [Array[Object]] the lineup for the given weight hash
|
15
|
+
def self.get_cycle_lineup(weights)
|
16
|
+
raise ArgumentError.new('Weights should be supplied as a Hash') unless weights.is_a?(Hash)
|
17
|
+
raise ArgumentError.new('Weights should be numbers') unless weights.values.all?{|v| v.is_a?(Fixnum) || v.is_a?(Float) }
|
18
|
+
|
19
|
+
if weights.values.all?{|v| v.is_a?(Fixnum) }
|
20
|
+
get_integer_lineup(weights)
|
21
|
+
elsif weights.values.all?{|v| v.is_a?(Float) }
|
22
|
+
get_float_lineup(weights)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
# takes an enumerable of fractions
|
28
|
+
def self.add_up_to_one?(fractions)
|
29
|
+
|
30
|
+
numerators = fractions.map{|el| el[0] }
|
31
|
+
denominators = fractions.map{|el| el[1] }
|
32
|
+
|
33
|
+
# lowest_common_multiple
|
34
|
+
# lcm(denominators)
|
35
|
+
|
36
|
+
sum=numerators.map.with_index do |elem,i|
|
37
|
+
multiplier = (lcm(denominators) / denominators[i]).to_i
|
38
|
+
multiplier * elem
|
39
|
+
end.reduce(:+)
|
40
|
+
|
41
|
+
sum == lcm(denominators)
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# used when weights are integers
|
48
|
+
def self.get_integer_lineup(weights)
|
49
|
+
weights.reduce(Array.new) do |acc,(key,val)|
|
50
|
+
val.times{|i| acc.push(key) }
|
51
|
+
acc
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# used when weights are floats (will be turned to fractions)
|
56
|
+
def self.get_float_lineup(weights)
|
57
|
+
weights_as_fractions = weights.reduce(Hash.new){|acc,(key,val)| acc[key] = val.to_fraction(100); acc }
|
58
|
+
raise RuntimeError('Fractional weights must add up to one') unless add_up_to_one?(weights_as_fractions.values)
|
59
|
+
|
60
|
+
lcm = lcm(weights_as_fractions.values.map{|el| el[1] } )
|
61
|
+
|
62
|
+
# the higher its weight, the more times the object appears
|
63
|
+
weights_as_fractions.reduce(Array.new) do |acc,(key,val)|
|
64
|
+
numerator = val[0]
|
65
|
+
denominator = val[1]
|
66
|
+
error = val[2] # not used so far
|
67
|
+
appearances = ( (lcm / denominator) * numerator ).to_i
|
68
|
+
|
69
|
+
# one reference for each appearance
|
70
|
+
appearances.times{ acc.push(key) }
|
71
|
+
acc
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.lcm(integers)
|
77
|
+
integers.reduce do |acc,elem|
|
78
|
+
acc.to_i.lcm(elem)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/rachinations/version.rb
CHANGED
data/rachinations.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Felipe Almeida"]
|
10
10
|
spec.email = ["falmeida1988@gmail.com"]
|
11
11
|
spec.summary = %q{Ruby port for Dr. J. Dormans' Machinations Game Mechanics Diagrams.}
|
12
|
-
spec.description = %q{This project provides a Ruby-based
|
12
|
+
spec.description = %q{This project provides a Ruby-based Diagram to enable game designers to
|
13
13
|
design and also test tentative game designs and/or prototypes}
|
14
14
|
spec.homepage = "https://github.com/queirozfcom/rachinations"
|
15
15
|
spec.license = "MIT"
|
@@ -31,5 +31,6 @@ design and also test tentative game designs and/or prototypes}
|
|
31
31
|
spec.add_dependency "activesupport","3.0.0"
|
32
32
|
spec.add_dependency "i18n","0.6.11"
|
33
33
|
spec.add_dependency "weighted_distribution", "1.0.0"
|
34
|
+
spec.add_dependency 'fraction','0.3.2'
|
34
35
|
|
35
36
|
end
|
@@ -14,27 +14,27 @@ describe Diagram do
|
|
14
14
|
|
15
15
|
it 'should be created with a source and a pool and run n times with no errors' do
|
16
16
|
|
17
|
-
d=Diagram.new '
|
17
|
+
d=Diagram.new 'simples'
|
18
18
|
|
19
19
|
d.add_node! Source, {
|
20
|
-
:name => '
|
20
|
+
:name => 'fonte'
|
21
21
|
}
|
22
22
|
|
23
23
|
d.add_node! Pool, {
|
24
|
-
:name => '
|
24
|
+
:name => 'depósito',
|
25
25
|
:initial_value => 0
|
26
26
|
}
|
27
27
|
|
28
28
|
d.add_edge! Edge, {
|
29
|
-
:name => '
|
30
|
-
:from => '
|
31
|
-
:to => '
|
29
|
+
:name => 'conector',
|
30
|
+
:from => 'fonte',
|
31
|
+
:to => 'depósito'
|
32
32
|
}
|
33
33
|
|
34
34
|
d.run!(10)
|
35
35
|
|
36
36
|
expect(d.resource_count).to eq 10
|
37
|
-
expect(d.get_node('
|
37
|
+
expect(d.get_node('depósito').resource_count).to eq 10
|
38
38
|
|
39
39
|
end
|
40
40
|
|
@@ -459,11 +459,13 @@ describe Diagram do
|
|
459
459
|
|
460
460
|
d = Diagram.new
|
461
461
|
|
462
|
+
d.add_node! Sink, name: 'sink'
|
462
463
|
d.add_node! Source, name: 's'
|
463
464
|
d.add_node! Gate, name: 'g'
|
464
465
|
d.add_node! Pool, name: 'p'
|
465
466
|
d.add_edge! Edge, from: 's', to: 'g'
|
466
467
|
d.add_edge! Edge, from: 'g', to: 'p', label: 50.percent
|
468
|
+
d.add_edge! Edge, from: 'g', to: 'sink', label: 50.percent
|
467
469
|
|
468
470
|
d.run!(20)
|
469
471
|
|
@@ -472,8 +474,70 @@ describe Diagram do
|
|
472
474
|
end
|
473
475
|
end
|
474
476
|
|
477
|
+
describe 'stop conditions' do
|
478
|
+
|
479
|
+
it 'should stop when a stop condition is met before all turns run' do
|
475
480
|
|
476
|
-
|
481
|
+
d = Diagram.new
|
482
|
+
|
483
|
+
d.add_node! Source, name: 's'
|
484
|
+
d.add_node! Gate, name: 'g'
|
485
|
+
d.add_node! Pool, name: 'po'
|
486
|
+
d.add_edge! Edge, from: 's', to: 'g'
|
487
|
+
d.add_edge! Edge, from: 'g', to: 'po'
|
488
|
+
|
489
|
+
d.add_stop_condition! message: 'first', condition: expr { d.po.resource_count >= 5 }
|
490
|
+
|
491
|
+
# useless but still
|
492
|
+
d.add_stop_condition! message: 'second', condition: expr { d.po.resource_count >= 10 }
|
493
|
+
|
494
|
+
d.run!(20)
|
495
|
+
|
496
|
+
expect(d.po.resource_count).to eq 5
|
497
|
+
end
|
498
|
+
|
499
|
+
it 'should continue until the last round if no stop conditions are met' do
|
500
|
+
d = Diagram.new
|
501
|
+
|
502
|
+
d.add_node! Source, name: 's'
|
503
|
+
d.add_node! Gate, name: 'g'
|
504
|
+
d.add_node! Pool, name: 'po'
|
505
|
+
d.add_edge! Edge, from: 's', to: 'g'
|
506
|
+
d.add_edge! Edge, from: 'g', to: 'po'
|
507
|
+
|
508
|
+
d.add_stop_condition! message: 'first', condition: expr { d.po.resource_count > 99 }
|
509
|
+
|
510
|
+
# also useless but still
|
511
|
+
d.add_stop_condition! message: 'second', condition: expr { d.po.resource_count < -1 }
|
512
|
+
|
513
|
+
d.run!(20)
|
514
|
+
|
515
|
+
expect(d.po.resource_count).to eq 20
|
516
|
+
|
517
|
+
end
|
518
|
+
|
519
|
+
it 'complains' do
|
520
|
+
# when users try to add two stop conditions but give them no names
|
521
|
+
|
522
|
+
expect {
|
523
|
+
d = Diagram.new
|
524
|
+
|
525
|
+
d.add_node! Source, name: 's'
|
526
|
+
d.add_node! Gate, name: 'g'
|
527
|
+
d.add_node! Pool, name: 'po'
|
528
|
+
d.add_edge! Edge, from: 's', to: 'g'
|
529
|
+
d.add_edge! Edge, from: 'g', to: 'po'
|
530
|
+
|
531
|
+
d.add_stop_condition! condition: expr { d.po.resource_count > 99 }
|
532
|
+
d.add_stop_condition! condition: expr { d.po.resource_count < -1 }
|
533
|
+
|
534
|
+
}.to raise_error
|
535
|
+
|
536
|
+
end
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
describe 'Comprehensive examples' do
|
477
541
|
|
478
542
|
it 'example using pull_all and activators' do
|
479
543
|
|
@@ -485,14 +549,14 @@ describe Diagram do
|
|
485
549
|
d.add_node! Pool, name: 'p4', initial_value: 2
|
486
550
|
d.add_node! Pool, name: 'p5', activation: :automatic, initial_value: 6, mode: :push_any
|
487
551
|
|
488
|
-
d.add_edge! Edge, from: 'p2',to:'p1'
|
489
|
-
d.add_edge! Edge, from: 'p3',to:'p1'
|
490
|
-
d.add_edge! Edge, from: 'p4',to:'p1'
|
552
|
+
d.add_edge! Edge, from: 'p2', to: 'p1'
|
553
|
+
d.add_edge! Edge, from: 'p3', to: 'p1'
|
554
|
+
d.add_edge! Edge, from: 'p4', to: 'p1'
|
491
555
|
|
492
|
-
d.add_edge! Edge, from: 'p5',to:'p3'
|
493
|
-
d.add_edge! Edge, from: 'p5',to:'p4'
|
556
|
+
d.add_edge! Edge, from: 'p5', to: 'p3'
|
557
|
+
d.add_edge! Edge, from: 'p5', to: 'p4'
|
494
558
|
|
495
|
-
d.p5.attach_condition{ d.p3.resource_count < 1 && d.p4.resource_count < 1 }
|
559
|
+
d.p5.attach_condition { d.p3.resource_count < 1 && d.p4.resource_count < 1 }
|
496
560
|
|
497
561
|
d.run! 8
|
498
562
|
|
@@ -505,9 +569,6 @@ describe Diagram do
|
|
505
569
|
end
|
506
570
|
|
507
571
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
572
|
end
|
512
573
|
|
513
574
|
end
|
data/testing/spec/gate_spec.rb
CHANGED
@@ -2,6 +2,7 @@ require_relative 'spec_helper'
|
|
2
2
|
|
3
3
|
describe 'Diagrams with gates' do
|
4
4
|
|
5
|
+
|
5
6
|
context 'general tests' do
|
6
7
|
|
7
8
|
it 'works' do
|
@@ -20,12 +21,232 @@ describe 'Diagrams with gates' do
|
|
20
21
|
|
21
22
|
end
|
22
23
|
|
24
|
+
it 'has default settings' do
|
25
|
+
|
26
|
+
|
27
|
+
g = Gate.new name: 'g'
|
28
|
+
|
29
|
+
expect(g.mode).to eq(:deterministic)
|
30
|
+
expect(g.name).to eq('g')
|
31
|
+
expect(g.activation).to eq(:passive)
|
32
|
+
end
|
33
|
+
|
23
34
|
end
|
24
35
|
|
25
36
|
context 'specific features' do
|
26
37
|
|
27
|
-
|
28
|
-
|
38
|
+
# in other words,
|
39
|
+
# either all floats adding up to 1 (which will be interpreted as probabilities that one will be chosen)
|
40
|
+
# or all integers (which will be interpreted )
|
41
|
+
context 'outgoing edges' do
|
42
|
+
|
43
|
+
it 'errors when one is fraction and another integer' do
|
44
|
+
|
45
|
+
expect {
|
46
|
+
|
47
|
+
d = Diagram.new
|
48
|
+
|
49
|
+
d.add_node! Source, name: 's'
|
50
|
+
d.add_node! Gate, name: 'g'
|
51
|
+
d.add_node! Pool, name: 'p1'
|
52
|
+
d.add_node! Pool, name: 'p2'
|
53
|
+
|
54
|
+
|
55
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
56
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/2
|
57
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 4
|
58
|
+
|
59
|
+
d.run! 10
|
60
|
+
|
61
|
+
}.to raise_error(BadConfig)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'compiles when both are integer' do
|
65
|
+
|
66
|
+
expect {
|
67
|
+
|
68
|
+
d = Diagram.new
|
69
|
+
|
70
|
+
d.add_node! Source, name: 's'
|
71
|
+
d.add_node! Gate, name: 'g'
|
72
|
+
d.add_node! Pool, name: 'p1'
|
73
|
+
d.add_node! Pool, name: 'p2'
|
74
|
+
|
75
|
+
|
76
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
77
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 5
|
78
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 4
|
79
|
+
|
80
|
+
d.run! 10
|
81
|
+
|
82
|
+
}.not_to raise_error
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'compiles when both are fractions and add up to 1' do
|
87
|
+
|
88
|
+
# well-behaved labels
|
89
|
+
expect {
|
90
|
+
|
91
|
+
d = Diagram.new
|
92
|
+
|
93
|
+
d.add_node! Source, name: 's'
|
94
|
+
d.add_node! Gate, name: 'g'
|
95
|
+
d.add_node! Pool, name: 'p1'
|
96
|
+
d.add_node! Pool, name: 'p2'
|
97
|
+
|
98
|
+
|
99
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
100
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/2
|
101
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1/2
|
102
|
+
|
103
|
+
d.run! 10
|
104
|
+
|
105
|
+
}.not_to raise_error
|
106
|
+
|
107
|
+
# badly behaved labels
|
108
|
+
expect {
|
109
|
+
|
110
|
+
d = Diagram.new
|
111
|
+
|
112
|
+
d.add_node! Source, name: 's'
|
113
|
+
d.add_node! Gate, name: 'g'
|
114
|
+
d.add_node! Pool, name: 'p1'
|
115
|
+
d.add_node! Pool, name: 'p2'
|
116
|
+
|
117
|
+
|
118
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
119
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 3/21
|
120
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 18/21
|
121
|
+
|
122
|
+
d.run! 10
|
123
|
+
|
124
|
+
}.not_to raise_error
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
it "errors when fraction labels don't add up to one" do
|
129
|
+
# less than 1
|
130
|
+
expect{
|
131
|
+
d = Diagram.new
|
132
|
+
|
133
|
+
d.add_node! Source, name: 's'
|
134
|
+
d.add_node! Gate, name: 'g'
|
135
|
+
d.add_node! Pool, name: 'p1'
|
136
|
+
d.add_node! Pool, name: 'p2'
|
137
|
+
d.add_node! Pool, name: 'p3'
|
138
|
+
|
139
|
+
|
140
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
141
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/9
|
142
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 8/77
|
143
|
+
d.add_edge! Edge, name: 'e4', from: 'g', to: 'p3', label: 2/17
|
144
|
+
|
145
|
+
d.run! 10
|
146
|
+
}.to raise_error(BadConfig)
|
147
|
+
|
148
|
+
# greater than 1 but each one not greater than 1
|
149
|
+
expect{
|
150
|
+
d = Diagram.new
|
151
|
+
|
152
|
+
d.add_node! Source, name: 's'
|
153
|
+
d.add_node! Gate, name: 'g'
|
154
|
+
d.add_node! Pool, name: 'p1'
|
155
|
+
d.add_node! Pool, name: 'p2'
|
156
|
+
|
157
|
+
|
158
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
159
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 4/9
|
160
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 11/12
|
161
|
+
|
162
|
+
d.run! 10
|
163
|
+
}.to raise_error(BadConfig)
|
164
|
+
|
165
|
+
# greater than 1 but a single label is greater than 1
|
166
|
+
expect{
|
167
|
+
d = Diagram.new
|
168
|
+
|
169
|
+
d.add_node! Source, name: 's'
|
170
|
+
d.add_node! Gate, name: 'g'
|
171
|
+
d.add_node! Pool, name: 'p1'
|
172
|
+
d.add_node! Pool, name: 'p2'
|
173
|
+
|
174
|
+
|
175
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
176
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 22/9
|
177
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1/12
|
178
|
+
|
179
|
+
d.run! 10
|
180
|
+
}.to raise_error(BadConfig)
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'probabilistic gates send resources in a non-deterministic way' do
|
186
|
+
|
187
|
+
no_of_turns = 100
|
188
|
+
|
189
|
+
d = Diagram.new
|
190
|
+
|
191
|
+
d.add_node! Source, name: 's'
|
192
|
+
d.add_node! Gate, name: 'g', mode: :probabilistic
|
193
|
+
|
194
|
+
d.add_node! Pool, name: 'p1'
|
195
|
+
d.add_node! Pool, name: 'p2'
|
196
|
+
d.add_node! Pool, name: 'p3'
|
197
|
+
|
198
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
199
|
+
|
200
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 1/2
|
201
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1/4
|
202
|
+
d.add_edge! Edge, name: 'e4', from: 'g', to: 'p3', label: 1/4
|
203
|
+
|
204
|
+
d.run! no_of_turns
|
205
|
+
|
206
|
+
# have to deal with probabilities here
|
207
|
+
expect(d.get_node('p1').resource_count).to be_within(10).of(50)
|
208
|
+
expect(d.get_node('p2').resource_count).to be_within(10).of(25)
|
209
|
+
expect(d.get_node('p3').resource_count).to be_within(10).of(25)
|
210
|
+
|
211
|
+
# this should work in most cases
|
212
|
+
expect(d.get_node('p2').resource_count).not_to eq(d.get_node('p3').resource_count)
|
213
|
+
|
214
|
+
# the sum must remain constant
|
215
|
+
p1_res = d.get_node('p1').resource_count
|
216
|
+
p2_res = d.get_node('p2').resource_count
|
217
|
+
p3_res = d.get_node('p3').resource_count
|
218
|
+
|
219
|
+
sum_res = p1_res + p2_res + p3_res
|
220
|
+
expect(sum_res).to eq no_of_turns
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'deterministic gates always distribute resources in the same order (integer labels)' do
|
225
|
+
|
226
|
+
d = Diagram.new
|
227
|
+
|
228
|
+
d.add_node! Source, name: 's'
|
229
|
+
d.add_node! Gate, name: 'g', mode: :deterministic
|
230
|
+
|
231
|
+
d.add_node! Pool, name: 'p1'
|
232
|
+
d.add_node! Pool, name: 'p2'
|
233
|
+
d.add_node! Pool, name: 'p3'
|
234
|
+
|
235
|
+
d.add_edge! Edge, name: 'e1', from: 's', to: 'g'
|
236
|
+
|
237
|
+
d.add_edge! Edge, name: 'e2', from: 'g', to: 'p1', label: 2
|
238
|
+
d.add_edge! Edge, name: 'e3', from: 'g', to: 'p2', label: 1
|
239
|
+
d.add_edge! Edge, name: 'e4', from: 'g', to: 'p3', label: 1
|
240
|
+
|
241
|
+
# if resources are shared deterministically and the number of turns
|
242
|
+
# is a multiple of the number of outgoing edges then there should be
|
243
|
+
# a predictably defined number of resources in each pool
|
244
|
+
d.run! 12
|
245
|
+
|
246
|
+
expect(d.get_node('p1').resource_count).to eq(6)
|
247
|
+
expect(d.get_node('p2').resource_count).to eq(3)
|
248
|
+
expect(d.get_node('p3').resource_count).to eq(3)
|
249
|
+
|
29
250
|
end
|
30
251
|
|
31
252
|
end
|