rachinations 0.0.7 → 0.0.8
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.
- 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
|