def test_observe_stacks(self, state: PushState): state["int"].push(100) state["int"].push(-77) state["str"].push("Hello") state["str"].push("World") ouputs = state.observe_stacks(["int", "int", "str"]) assert ouputs == [-77, 100, "World"]
def evaluate(self, push_state: PushState, push_config: PushConfig = None) -> PushState: """Evaluate the instruction on the given PushState. Return mutated State. A ProducesManyOfTypeInstruction infers which values to pop from the stack based on `input_stacks` and pushes each output to the same stack based on `output_stack`. Parameters ---------- push_state : PushState Push state to modify with the Instruction. push_config : PushConfig Configuration of the interpreter. Used to get various limits. Returns ------- PushState Return the given state, possibly modified by the Instruction. """ # Pull args, if present. args = push_state.observe_stacks(self.input_stacks) if Token.no_stack_item in args: return push_state # Compute result, return if revert or response too big. result = self.f(*args) if result is Token.revert: return push_state if not isinstance(result, (list, tuple)): raise ValueError( "Instruction result must be a collection. {i} gave {t}.". format(i=self, t=type(result))) # Remove arguments, push results. push_state.pop_from_stacks(self.input_stacks) push_state.push_to_stacks(result, [self.output_stack] * len(result)) return push_state
def evaluate(self, push_state: PushState, interpreter_config): """Evaluate the instruction on the given PushState. Return mutated State. A ProducesManyOfTypeInstruction infers which values to pop from the stack based on `input_stacks` and pushes each output to the same stack based on `output_stack`. Parameters ---------- state : PushState Push state to modify with the Instruction. config : PushInterpreterConfig Configuration of the interpreter. Used to get various limits. Returns ------- PushState Return the given state, possibly modified by the Instruction. """ # Pull args, if present. args = push_state.observe_stacks(self.input_stacks) if Token.no_stack_item in args: return push_state # Compute result, return if revert or response too big. result = self.f(*args) if result is Token.revert: return push_state if not isinstance(result, (list, tuple)): raise ValueError("Instruction result must be a collection. {i} gave {t}.".format( i=self, t=type(result) )) # Remove arguments, push results. push_state.pop_from_stacks(self.input_stacks) push_state.push_to_stacks(result, [self.output_stack] * len(result)) return push_state
def evaluate(self, push_state: PushState, push_config: PushConfig = None) -> PushState: """Evaluate the instruction on the given PushState. Return mutated State. A SimpleInstruction infers which values to pop and push from the stack based on its `input_stacks` and `output_stacks`. Parameters ---------- push_state : PushState Push state to modify with the Instruction. push_config : pyshgp.push.interpreter.PushConfig Configuration of the interpreter. Used to get various limits. Returns ------- PushState Return the given state, possibly modified by the Instruction. """ # Pull args, if present. args = push_state.observe_stacks(self.input_stacks) if Token.no_stack_item in args: return push_state # Compute result, return if revert or response too big. result = self.f(*args) if result is Token.revert: return push_state _check_is_seq(result, self) # Remove arguments, push results. push_state.pop_from_stacks(self.input_stacks) push_state.push_to_stacks(result, self.output_stacks) return push_state
class PushInterpreter: """An interpreter capable of running Push programs. Parameters ---------- instruction_set : Union[InstructionSet, str], optional The ``InstructionSet`` to use for executing programs. Default is "core" which instantiates an ``InstructionSet`` using all the core instructions. Attributes ---------- instruction_set : InstructionSet The ``InstructionSet`` to use for executing programs. state : PushState The current ``PushState``. Contains one stack for each ``PushType`` mentioned by the instructions in the instruction set. status : PushInterpreterStatus A string denoting if the interpreter has encountered a situation where non-standard termination was required. """ def __init__(self, instruction_set: Union[InstructionSet, str] = "core", reset_on_run: bool = True): self.reset_on_run = reset_on_run # If no instruction set given, create one and register all instructions. if instruction_set == "core": self.instruction_set = InstructionSet(register_core=True) else: self.instruction_set = instruction_set self.type_library = self.instruction_set.type_library # Initialize the PushState and status self.state: PushState = None self.status: PushInterpreterStatus = None self._validate() def _validate(self): library_type_names = set(self.type_library.keys()) required_stacks = self.instruction_set.required_stacks() - { "stdout", "exec", "untyped" } if not required_stacks <= library_type_names: raise ValueError( "PushInterpreter instruction_set and type_library are incompatible. {iset} vs {tlib}. Diff: {d}" .format( iset=required_stacks, tlib=library_type_names, d=required_stacks - library_type_names, )) def _evaluate_instruction(self, instruction: Instruction, config: PushConfig): self.state = instruction.evaluate(self.state, config) def untyped_to_typed(self): """Infer ``PushType`` of items on state's untyped queue and push to corresponding stacks.""" while len(self.state.untyped) > 0: el = self.state.untyped.popleft() push_type = self.type_library.push_type_of(el, error_on_not_found=True) self.state[push_type.name].push(el) @tap def evaluate_atom(self, atom: Atom, config: PushConfig): """Evaluate an ``Atom``. Parameters ---------- atom : Atom The Atom (``Literal``, ``InstructionMeta``, ``Input``, or ``CodeBlock``) to evaluate against the current ``PushState``. config : PushConfig The configuration of the Push program being run. """ try: if isinstance(atom, InstructionMeta): self._evaluate_instruction(self.instruction_set[atom.name], config) elif isinstance(atom, Input): input_value = self.state.inputs[atom.input_index] self.state.untyped.append(input_value) elif isinstance(atom, CodeBlock): for a in atom[::-1]: self.state["exec"].push(a) elif isinstance(atom, Literal): self.state[atom.push_type.name].push(atom.value) elif isinstance(atom, Closer): raise PushError( "Closers should not be in push programs. Only genomes.") else: raise PushError( "Cannot evaluate {t}, require a subclass of Atom".format( t=type(atom))) self.untyped_to_typed() except Exception as e: err_type = type(e).__name__ err_msg = str(e) raise PushError( "{t} raised while evaluating {atom}. Original message: \"{m}\"" .format(t=err_type, atom=atom, m=err_msg)) @tap def run(self, program: Program, inputs: list, print_trace: bool = False) -> list: """Run a Push ``Program`` given some inputs and desired output ``PushTypes``. The general flow of this method is: 1. Create a new push state 2. Load the program and inputs. 3. If the exec stack is empty, return the outputs. 4. Else, pop the exec stack and process the atom. 5. Return to step 3. Parameters ---------- program : Program Program to run. inputs : list A sequence of values to use as inputs to the push program. print_trace : bool If True, each step of program execution will be summarized in stdout. Returns ------- Sequence A sequence of values pulled from the final push state. May contain pyshgp.utils.Token.no_stack_item if output stacks are empty. """ push_config = program.signature.push_config if self.reset_on_run or self.state is None: self.state = PushState(self.type_library, push_config) self.status = PushInterpreterStatus.normal # Setup self.state.load_code(program.code) self.state.load_inputs(inputs) stop_time = time.time() + push_config.runtime_limit steps = 0 if print_trace: print("Initial State:") self.state.pretty_print() # Iterate atom evaluation until entire program is evaluated. while len(self.state["exec"]) > 0: # Stopping conditions if steps > push_config.step_limit: self.status = PushInterpreterStatus.step_limit_exceeded break if time.time() > stop_time: self.status = PushInterpreterStatus.runtime_limit_exceeded break # Next atom in the program to evaluate. next_atom = self.state["exec"].pop() if print_trace: start = time.time() print("\nCurrent Atom: " + str(next_atom)) # Evaluate atom. old_size = self.state.size() self.evaluate_atom(next_atom, push_config) if self.state.size() > old_size + push_config.growth_cap: self.status = PushInterpreterStatus.growth_cap_exceeded break if print_trace: duration = time.time() - start print("Current State (step {step}):".format(step=steps)) self.state.pretty_print() print("Step duration:", duration) steps += 1 if print_trace: print("Finished program evaluation.") return self.state.observe_stacks(program.signature.output_stacks)
def test_observe_stacks_stdout(self, state: PushState): ouputs = state.observe_stacks(["stdout"]) assert ouputs[0] == ""
def test_observe_stacks_empty(self, state: PushState): ouputs = state.observe_stacks(["int", "int", "str"]) assert ouputs == [Token.no_stack_item] * 3
class PushInterpreter: """An interpreter capable of running Push programs. Parameters ---------- instruction_set : Union[InstructionSet, str], optional The InstructionSet to use for executing programs. Default is "core" which instansiates an InstructionSet using all the core instructions. config : PushInterpreterConfig, optional A PushInterpreterConfig specifying limits and early termination conditions. Default is None, which creates a config will all default values. Attributes ---------- instruction_set : InstructionSet The InstructionSet to use for executing programs. config : PushInterpreterConfig A PushInterpreterConfig specifying limits and early termination conditions. state : PushState The current PushState. Contains one stack for each PushType utilized mentioned by the instructions in the instruction set. status : PushInterpreterStatus A string denoting if the Interpreter has enountered a situation where non-standard termination was required. """ def __init__(self, instruction_set: Union[InstructionSet, str] = "core", config: PushInterpreterConfig = None): # If no instruction set given, create one and register all instructions. if instruction_set == "core": self.instruction_set = InstructionSet(register_all=True) else: self.instruction_set = instruction_set self._supported_types = self.instruction_set.supported_types() if config is None: self.config = PushInterpreterConfig() else: self.config = config # Initialize the PushState and status self.reset() def reset(self): """Reset the interpreter status and PushState.""" self.state = PushState(self._supported_types) self.status = PushInterpreterStatus.normal def _evaluate_instruction(self, instruction: Union[Instruction, JitInstructionRef]): self.state = instruction.evaluate(self.state, self.config) def evaluate_atom(self, atom: Atom): """Evaluate an Atom. Parameters ---------- atom : Atom The Atom (Literal, Instruction, JitInstructionRef, or CodeBlock) to evaluate against the current PushState. """ try: if isinstance(atom, Instruction): self._evaluate_instruction(atom) elif isinstance(atom, JitInstructionRef): self._evaluate_instruction(self.instruction_set[atom.name]) elif isinstance(atom, CodeBlock): for a in atom[::-1]: self.state["exec"].push(a) elif isinstance(atom, Literal): self.state[atom.push_type.name].push(atom.value) elif isinstance(atom, Closer): raise PushError( "Closers should not be in push programs. Only genomes.") else: raise PushError( "Cannont evaluate {t}, require a subclass of Atom".format( t=type(atom))) except Exception as e: err_type = type(e).__name__ err_msg = str(e) raise PushError( "{t} raised while evaluating {atom}. Origional mesage: \"{m}\"" .format(t=err_type, atom=atom, m=err_msg)) def run(self, program: CodeBlock, inputs: Sequence, output_types: Sequence[str], verbosity_config: VerbosityConfig = None): """Run a Push program given some inputs and desired output PushTypes. The general flow of this method is: 1. Create a new push state 2. Load the program and inputs. 3. If the exec stack is empty, return the outputs. 4. Else, pop the exec stack and process the atom. 5. Return to step 3. Parameters ---------- program Program to run. inputs A sequence of values to use as inputs to the push program. output_types A secence of values that denote the Pushtypes of the expected outputs of the push program. verbosity_config : VerbosityConfig, optional A VerbosityConfig controling what is logged during the execution of the program. Default is no verbosity. Returns ------- Sequence A sequence of values pulled from the final push state. May contain pyshgp.utils.Token.no_stack_item if needed stacks are empty. """ if self.config.reset_on_run: self.reset() self.state.load_program(program) self.state.load_inputs(inputs) stop_time = time.time() + self.config.runtime_limit steps = 0 if verbosity_config is None: verbosity_config = DEFAULT_VERBOSITY_LEVELS[0] verbose_trace = verbosity_config.program_trace if verbose_trace: verbose_trace("Initial State:") self.state.pretty_print(verbose_trace) while len(self.state["exec"]) > 0: if steps > self.config.atom_limit: self.status = PushInterpreterStatus.atom_limit_exceeded break if time.time() > stop_time: self.status = PushInterpreterStatus.runtime_limit_exceeded break next_atom = self.state["exec"].pop() if verbose_trace: verbose_trace("Current Atom: " + str(next_atom)) old_size = len(self.state) self.evaluate_atom(next_atom) if len(self.state) > old_size + self.config.growth_cap: self.status = PushInterpreterStatus.growth_cap_exceeded break if verbose_trace: verbose_trace("Current State:") self.state.pretty_print(verbose_trace) steps += 1 if verbose_trace: verbose_trace("Finished program evaluation.") return self.state.observe_stacks(output_types)
class PushInterpreter: """An interpreter capable of running Push programs. Parameters ---------- instruction_set : Union[InstructionSet, str], optional The InstructionSet to use for executing programs. Default is "core" which instansiates an InstructionSet using all the core instructions. config : PushInterpreterConfig, optional A PushInterpreterConfig specifying limits and early termination conditions. Default is None, which creates a config will all default values. verbosity_config : VerbosityConfig, optional A VerbosityConfig controling what is logged during the execution of the program. Default is no verbosity. Attributes ---------- instruction_set : InstructionSet The InstructionSet to use for executing programs. config : PushInterpreterConfig A PushInterpreterConfig specifying limits and early termination conditions. verbosity_config : VerbosityConfig, optional A VerbosityConfig controling what is logged during the execution of the program. Default is no verbosity. state : PushState The current PushState. Contains one stack for each PushType utilized mentioned by the instructions in the instruction set. status : PushInterpreterStatus A string denoting if the Interpreter has enountered a situation where non-standard termination was required. """ def __init__(self, instruction_set: Union[InstructionSet, str] = "core", config: PushInterpreterConfig = None, verbosity_config: VerbosityConfig = "default"): # If no instruction set given, create one and register all instructions. if instruction_set == "core": self.instruction_set = InstructionSet(register_core=True) else: self.instruction_set = instruction_set self.type_library = self.instruction_set.type_library if config is None: self.config = PushInterpreterConfig() else: self.config = config if verbosity_config == "default": self.verbosity_config = DEFAULT_VERBOSITY_LEVELS[0] else: self.verbosity_config = verbosity_config # Initialize the PushState and status self._validate() self.reset() def _validate(self): library_type_names = set(self.type_library.keys()) required_stacks = self.instruction_set.required_stacks() - {"stdout", "exec", "untyped"} if not required_stacks <= library_type_names: raise ValueError( "PushInterpreter instruction_set and type_library are incompatible. {iset} vs {tlib}. Diff: {d}".format( iset=required_stacks, tlib=library_type_names, d=required_stacks - library_type_names, )) def reset(self): """Reset the interpreter status and PushState.""" self.state = PushState(self.type_library) self.status = PushInterpreterStatus.normal self._verbose_trace = self.verbosity_config.program_trace self._log_fn_for_trace = log_function(self._verbose_trace) def _log_trace(self, msg=None, log_state=False): if msg is not None: self._log_fn_for_trace(msg) if log_state: self.state.pretty_print(self._log_fn_for_trace) def _evaluate_instruction(self, instruction: Union[Instruction, JitInstructionRef]): self.state = instruction.evaluate(self.state, self.config) def untyped_to_typed(self): """Infers PushType of items on state's untyped queue and pushes to corresponding stacks.""" while len(self.state.untyped) > 0: el = self.state.untyped.popleft() push_type = self.type_library.push_type_of(el, error_on_not_found=True) self.state[push_type.name].push(el) def evaluate_atom(self, atom: Atom): """Evaluate an Atom. Parameters ---------- atom : Atom The Atom (Literal, Instruction, JitInstructionRef, or CodeBlock) to evaluate against the current PushState. """ try: if isinstance(atom, Instruction): self._evaluate_instruction(atom) elif isinstance(atom, JitInstructionRef): self._evaluate_instruction(self.instruction_set[atom.name]) elif isinstance(atom, CodeBlock): for a in atom[::-1]: self.state["exec"].push(a) elif isinstance(atom, Literal): self.state[atom.push_type.name].push(atom.value) elif isinstance(atom, Closer): raise PushError("Closers should not be in push programs. Only genomes.") else: raise PushError("Cannont evaluate {t}, require a subclass of Atom".format(t=type(atom))) self.untyped_to_typed() except Exception as e: err_type = type(e).__name__ err_msg = str(e) raise PushError( "{t} raised while evaluating {atom}. Origional mesage: \"{m}\"".format( t=err_type, atom=atom, m=err_msg )) def run(self, program: CodeBlock, inputs: Sequence, output_types: Sequence[str]): """Run a Push program given some inputs and desired output PushTypes. The general flow of this method is: 1. Create a new push state 2. Load the program and inputs. 3. If the exec stack is empty, return the outputs. 4. Else, pop the exec stack and process the atom. 5. Return to step 3. Parameters ---------- program Program to run. inputs A sequence of values to use as inputs to the push program. output_types A secence of values that denote the Pushtypes of the expected outputs of the push program. Returns ------- Sequence A sequence of values pulled from the final push state. May contain pyshgp.utils.Token.no_stack_item if needed stacks are empty. """ if self.config.reset_on_run: self.reset() # Setup self.state.load_program(program) self.state.load_inputs(inputs) stop_time = time.time() + self.config.runtime_limit steps = 0 if self._verbose_trace >= self.verbosity_config.log_level: self._log_trace("Initial State:", True) # Iterate atom evaluation until entire program is evaluated. while len(self.state["exec"]) > 0: # Stopping conditions if steps > self.config.atom_limit: self.status = PushInterpreterStatus.atom_limit_exceeded break if time.time() > stop_time: self.status = PushInterpreterStatus.runtime_limit_exceeded break # Next atom in the program to evaluate. next_atom = self.state["exec"].pop() if self._verbose_trace >= self.verbosity_config.log_level: self._log_trace("Current Atom: " + str(next_atom)) # Evaluate atom. old_size = len(self.state) self.evaluate_atom(next_atom) if len(self.state) > old_size + self.config.growth_cap: self.status = PushInterpreterStatus.growth_cap_exceeded break if self._verbose_trace >= self.verbosity_config.log_level: self._log_trace("Current State:", True) steps += 1 if self._verbose_trace >= self.verbosity_config.log_level: self._log_trace("Finished program evaluation.") return self.state.observe_stacks(output_types)