This notebook demonstrates how to use a subset of the grammar that defines the programming language Rust in order to evolve different Rust number literals that all evaluate to the same decimal number.
Wikipedia
import os
import subprocess
import tempfile
import alogos as al
import unified_map as um
This is needed to use the Rust compiler rustc
and to execute the compiled binary file.
def run_shell_command(cmd, timeout_in_sec=1):
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
out, err = proc.communicate(timeout=timeout_in_sec)
return out.decode()[:-1]
rust_template = """
fn main() {{
let expr = {};
println!("{{}}", expr);
}}
"""
def compile_and_run_rust(code):
with tempfile.NamedTemporaryFile('w') as fp:
fp.write(code)
fp.flush()
src = fp.name
trg = src + '.out'
run_shell_command('rustc {} -o {} 2> /dev/null'.format(src, trg))
output = run_shell_command(trg)
os.remove(trg)
return output
def exec_rust_expression(expr):
code = rust_template.format(expr)
output = compile_and_run_rust(code)
return output
exec_rust_expression('0b10')
exec_rust_expression('0o10')
exec_rust_expression('10')
exec_rust_expression('0x10')
ebnf_template = """
EXPRESSION = {chosen_expression}
FLOAT_LITERAL = DEC_LITERAL "."
| DEC_LITERAL FLOAT_EXPONENT
| DEC_LITERAL "." DEC_LITERAL FLOAT_EXPONENT?
| DEC_LITERAL ("." DEC_LITERAL)? FLOAT_EXPONENT? FLOAT_SUFFIX
FLOAT_EXPONENT = ("e"|"E") ("+"|"-")? (DEC_DIGIT|"_")* DEC_DIGIT (DEC_DIGIT|"_")*
FLOAT_SUFFIX = "f32" | "f64"
INTEGER_LITERAL = ( DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) INTEGER_SUFFIX?
DEC_LITERAL = DEC_DIGIT (DEC_DIGIT|_)*
BIN_LITERAL = "0b" (BIN_DIGIT|_)* BIN_DIGIT (BIN_DIGIT|_)*
OCT_LITERAL = "0o" (OCT_DIGIT|_)* OCT_DIGIT (OCT_DIGIT|_)*
HEX_LITERAL = "0x" (HEX_DIGIT|_)* HEX_DIGIT (HEX_DIGIT|_)*
BIN_DIGIT = "0" | "1"
OCT_DIGIT = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
DEC_DIGIT = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
HEX_DIGIT = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" |
"a" | "b" | "c" | "d" | "e" | "f" |
"A" | "B" | "C" | "D" | "E" | "F"
INTEGER_SUFFIX = "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize"
"""
def generate_grammar(chosen_expression):
ebnf_text = ebnf_template.format(chosen_expression=chosen_expression)
grammar = al.Grammar(ebnf_text=ebnf_text)
return grammar
grammar_num = generate_grammar(chosen_expression='INTEGER_LITERAL | FLOAT_LITERAL')
grammar_float = generate_grammar(chosen_expression='FLOAT_LITERAL')
grammar_int = generate_grammar(chosen_expression='INTEGER_LITERAL')
grammar_dec = generate_grammar(chosen_expression='DEC_LITERAL')
grammar_bin = generate_grammar(chosen_expression='BIN_LITERAL')
grammar_oct = generate_grammar(chosen_expression='OCT_LITERAL')
grammar_hex = generate_grammar(chosen_expression='HEX_LITERAL')
The objective function gets a candidate solution (=a string of the grammar's language) and returns a fitness value for it. This is done by 1) inserting the Rust number literal into a Rust program that prints the number 2) compile and run that Rust program, and 3) compute how much it deviate from a target number.
target = 42
def objective_function(string):
number = float(exec_rust_expression(string))
return abs(number-target)
Check if grammar and objective function work as intended.
for _ in range(10):
random_string = grammar_num.generate_string()
print(random_string)
objective_function(random_string)
ea = al.EvolutionaryAlgorithm(
grammar_bin, objective_function, 'min', max_or_min_fitness=0.0, population_size=8, offspring_size=8,
verbose=True, evaluator=um.univariate.parallel.futures)
best_ind = ea.run()
string = best_ind.phenotype
print('Rust expression:', string)
program = rust_template.format(string)
print('Rust program:', program)
ea = al.EvolutionaryAlgorithm(
grammar_oct, objective_function, 'min', max_or_min_fitness=0.0, population_size=8, offspring_size=8,
verbose=True, evaluator=um.univariate.parallel.futures)
best_ind = ea.run()
string = best_ind.phenotype
print('Rust expression:', string)
program = rust_template.format(string)
print('Rust program:', program)
ea = al.EvolutionaryAlgorithm(
grammar_hex, objective_function, 'min', max_or_min_fitness=0.0, population_size=8, offspring_size=8,
verbose=True, evaluator=um.univariate.parallel.futures)
best_ind = ea.run()
string = best_ind.phenotype
print('Rust expression:', string)
program = rust_template.format(string)
print('Rust program:', program)