Class: Ibex::Samples
- Inherits:
-
Object
- Object
- Ibex::Samples
- Defined in:
- lib/ibex/samples.rb,
sig/ibex/samples.rbs
Overview
Generates bounded terminal sentences from Grammar IR.
Constant Summary collapse
- DEFAULT_MAX_EXPANSIONS =
100_000
Instance Method Summary collapse
- #choose_production(nonterminal_id, budget, depth) ⇒ IR::Production
- #compute_minimum_costs ⇒ Hash[Integer, Integer?]
- #compute_minimum_heights ⇒ Hash[Integer, Integer?]
- #compute_minimum_values(terminal_value) {|arg0, arg1| ... } ⇒ Hash[Integer, Integer?]
- #expand(start, remaining_expansions) ⇒ [ Array[String], Integer ]
- #generate(count: 1) ⇒ Array[Array[String]]
-
#initialize(grammar, seed: 0, max_tokens: 32, max_depth: 16, max_expansions: DEFAULT_MAX_EXPANSIONS) ⇒ Samples
constructor
A new instance of Samples.
- #minimum_cost!(symbol_id) ⇒ Integer
- #production_cost(production) ⇒ Integer?
- #production_height(production) ⇒ Integer?
- #resolved_rhs_values(production, values) ⇒ Array[Integer]?
- #seed_terminal_values(values, terminal_value) ⇒ Array[Integer]
- #update_minimum(values, queue, symbol_id, candidate) ⇒ void
Constructor Details
#initialize(grammar, seed: 0, max_tokens: 32, max_depth: 16, max_expansions: DEFAULT_MAX_EXPANSIONS) ⇒ Samples
Returns a new instance of Samples.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/ibex/samples.rb', line 11 def initialize(grammar, seed: 0, max_tokens: 32, max_depth: 16, max_expansions: DEFAULT_MAX_EXPANSIONS) raise ArgumentError, "max_tokens must be positive" unless max_tokens.positive? raise ArgumentError, "max_depth must be positive" unless max_depth.positive? raise ArgumentError, "max_expansions must be positive" unless max_expansions.positive? @grammar = grammar @random = Random.new(seed) @max_tokens = max_tokens @max_depth = max_depth @max_expansions = max_expansions @productions = grammar.productions.group_by(&:lhs) @minimum_costs = compute_minimum_costs @minimum_heights = compute_minimum_heights end |
Instance Method Details
#choose_production(nonterminal_id, budget, depth) ⇒ IR::Production
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/ibex/samples.rb', line 152 def choose_production(nonterminal_id, budget, depth) productions = @productions[nonterminal_id] raise Ibex::Error, "(samples):1:1: no bounded derivation for symbol #{nonterminal_id}" unless productions candidates = productions.select do |production| cost = production_cost(production) cost && cost <= budget end raise Ibex::Error, "(samples):1:1: no bounded derivation for symbol #{nonterminal_id}" if candidates.empty? if depth >= @max_depth minimum = candidates.filter_map { |production| production_height(production) }.min candidates = candidates.select { |production| production_height(production) == minimum } end candidates.fetch(@random.rand(candidates.length)) end |
#compute_minimum_costs ⇒ Hash[Integer, Integer?]
51 52 53 54 55 56 |
# File 'lib/ibex/samples.rb', line 51 def compute_minimum_costs compute_minimum_values(1) do |production, values| rhs_values = resolved_rhs_values(production, values) rhs_values&.sum end end |
#compute_minimum_heights ⇒ Hash[Integer, Integer?]
59 60 61 62 63 64 |
# File 'lib/ibex/samples.rb', line 59 def compute_minimum_heights compute_minimum_values(0) do |production, values| rhs_values = resolved_rhs_values(production, values) rhs_values && ((rhs_values.max || 0) + 1) end end |
#compute_minimum_values(terminal_value) {|arg0, arg1| ... } ⇒ Hash[Integer, Integer?]
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/ibex/samples.rb', line 68 def compute_minimum_values(terminal_value) values = @grammar.symbols.to_h { |symbol| [symbol.id, nil] } #: Hash[Integer, Integer?] dependents = {} #: Hash[Integer, Array[IR::Production]] queue = seed_terminal_values(values, terminal_value) @grammar.productions.each do |production| update_minimum(values, queue, production.lhs, yield(production, values)) if production.rhs.empty? production.rhs.uniq.each do |symbol_id| (dependents[symbol_id] ||= []) << production end end index = 0 while index < queue.length dependents.fetch(queue.fetch(index), []).each do |production| update_minimum(values, queue, production.lhs, yield(production, values)) end index += 1 end values end |
#expand(start, remaining_expansions) ⇒ [ Array[String], Integer ]
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/ibex/samples.rb', line 121 def (start, remaining_expansions) result = [] #: Array[String] work = [[start.id, 0]] #: Array[[Integer, Integer]] pending_minimum = minimum_cost!(start.id) while (entry = work.pop) if remaining_expansions.zero? raise Ibex::Error, "(samples):1:1: expansion limit of #{@max_expansions} steps exceeded" end remaining_expansions -= 1 symbol_id, depth = entry pending_minimum -= minimum_cost!(symbol_id) symbol = @grammar.symbol_by_id(symbol_id) || raise(Ibex::Error, "(samples):1:1: missing symbol #{symbol_id}") if symbol.terminal? result << symbol.name next end budget = @max_tokens - result.length - pending_minimum production = choose_production(symbol_id, budget, depth) pending_minimum += production_cost(production) || raise(Ibex::Error, "(samples):1:1: no bounded derivation for symbol #{symbol_id}") production.rhs.reverse_each { |child_id| work << [child_id, depth + 1] } end [result, remaining_expansions] end |
#generate(count: 1) ⇒ Array[Array[String]]
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/ibex/samples.rb', line 27 def generate(count: 1) raise ArgumentError, "count must be positive" unless count.positive? start = @grammar.symbol(@grammar.start) || raise(Ibex::Error, "(samples):1:1: missing start symbol") minimum = @minimum_costs.fetch(start.id, nil) raise Ibex::Error, "(samples):1:1: start symbol #{@grammar.start} derives no terminal sentence" unless minimum if minimum > @max_tokens raise Ibex::Error, "(samples):1:1: minimum sentence needs #{minimum} tokens; limit is #{@max_tokens}" end if count > @max_expansions raise Ibex::Error, "(samples):1:1: count #{count} exceeds expansion limit #{@max_expansions}" end remaining_expansions = @max_expansions Array.new(count) do sample, remaining_expansions = (start, remaining_expansions) sample end end |
#minimum_cost!(symbol_id) ⇒ Integer
170 171 172 173 |
# File 'lib/ibex/samples.rb', line 170 def minimum_cost!(symbol_id) @minimum_costs.fetch(symbol_id) || raise(Ibex::Error, "(samples):1:1: no bounded derivation for symbol #{symbol_id}") end |
#production_cost(production) ⇒ Integer?
176 177 178 |
# File 'lib/ibex/samples.rb', line 176 def production_cost(production) resolved_rhs_values(production, @minimum_costs)&.sum end |
#production_height(production) ⇒ Integer?
181 182 183 184 |
# File 'lib/ibex/samples.rb', line 181 def production_height(production) child_heights = resolved_rhs_values(production, @minimum_heights) child_heights && ((child_heights.max || 0) + 1) end |
#resolved_rhs_values(production, values) ⇒ Array[Integer]?
113 114 115 116 117 118 |
# File 'lib/ibex/samples.rb', line 113 def resolved_rhs_values(production, values) rhs_values = production.rhs.map { |symbol_id| values.fetch(symbol_id) } return nil if rhs_values.any?(&:nil?) rhs_values.compact end |
#seed_terminal_values(values, terminal_value) ⇒ Array[Integer]
92 93 94 95 96 97 98 99 |
# File 'lib/ibex/samples.rb', line 92 def seed_terminal_values(values, terminal_value) @grammar.symbols.filter_map do |symbol| next unless symbol.terminal? && !symbol.reserved values[symbol.id] = terminal_value symbol.id end end |
#update_minimum(values, queue, symbol_id, candidate) ⇒ void
This method returns an undefined value.
102 103 104 105 106 107 108 109 110 |
# File 'lib/ibex/samples.rb', line 102 def update_minimum(values, queue, symbol_id, candidate) return unless candidate current = values.fetch(symbol_id) return if current && current <= candidate values[symbol_id] = candidate queue << symbol_id end |