Push Instructions¶
A Push instruction is object that can manipulate the typed stacks of a PushState
. Typically, an instruction is defined by some function. When evaluated, the arguments to the instruction’s function are taken from the stacks of the PushInterpreter
’s PushState and the returned values are pushed to back to the stacks.
In PyshGP, there are a variety of classes that concretely implement the concept of an instruction. These definitions capture all the required information about an instruction. For example, the SimpleInstruction
class automatically handles the popping of arguments and pushing of returned values for functions that only require access to the top items of some stacks. Meanwhile, the more flexible StateToStateInstruction
only requires a function which takes the entire PushState and returns a PushState.
Every instruction must contain information on which stacks it manipulates. This is so that the PushInterpreter
will know which stacks are required to ensure a given collection of Instruction will be supportable. It also aids in filtering collections of instructions based on what types they manipulate.
Instructions also denote how many code blocks are expected to follow the instructions. This is used during Genome
to CodeBlock
translation to provide the nested structure of code blocks. For example, the exec_if
instruction should be followed by two code blocks: one for the “then” clause, one for the “else” clause.
With the exception of StateToStateInstruction
objects, the function passed to the f
argument of an Instruction must return a collection of values (either list
of tuple
). Each element of this collection will be pushed to its respective stack after evaluation of the instruction.
If an instruction is unable to be evaluated, it can return a Token.revert
object to indicate the instruction should be skipped and no change should be made to the PushState
. This ensures each instruction is an atomic transaction.
Core Instructions¶
PyshGP contains a collection of “core” instructions that work with the typical data types. These instructions include implementations of control structures such as if
and while
. The inclusion of these control structures is a key feature that makes PushGP unique compared to many other genetic programming systems.
To read about all the core instructions provided by PyshGP, see the following page.
Example Instruction Definitions¶
One way to extend the functionality of PyshGP`s Push interpreter is to define custom instructions. The following sections describe the variety of ways which Push instructions can be defined.
Simple Instruction¶
Used when the exact function signature is known. In other words, how many argument are needed and their types, as well as the number of returned values and their types.
def protected_divison(a, b):
if a == 0:
return Token.revert
return b / a,
int_div_instr = SimpleInstruction(
name="int_div",
f=protected_divison,
input_types=["int", "int"],
output_types=["int"],
code_blocks=0,
docstring="Divides the top two ints and pushes the result."
)
Produces Many Of Type Instruction¶
Same as SimpleInstruction
except that a variable number of returned values will be produced. All returned values will be of the same type, and thus are placed on the same stack.
def split_on_comma(s):
return s.split(",")
str_split_instr = ProducesManyOfTypeInstruction(
"str_split_on_comma",
split_on_comma,
input_stacks=["str"],
output_stack="str",
code_blocks=0,
docstring="Pushes multiple strs produced by splitting the top str on all commas.".format(t=push_type)
)
Takes State Instruction¶
Same as SimpleInstruction
except that the argument to the function is the entire PushState
. This is typically used when a variable number of elements are needed as input or one of the PushState
’s other attributes are needed to determine the result.
def bool_stack_depth(state):
return len(state["bool"]),
bool_depth_instr = TakesStateInstruction(
"bool_stack_depth",
bool_stack_depth,
output_stacks=["int"],
other_stacks=["bool"],
code_blocks=0,
docstring="Pushes the size of the bool stack to the int stack."
)
State-to-State Instruction¶
An Instruction that takes entire PushState
and returns entire PushState
.
def exec_when(state):
if state["exec"].is_empty() or state["bool"].is_empty():
return Token.revert
if not state["bool"].pop():
state["exec"].pop()
return state
exec_when_instr = StateToStateInstruction(
"exec_when",
exec_when,
stacks_used=["exec"],
code_blocks=1,
docstring="""Pops the next item on the exec stack without evaluating it
if the top bool is False. Otherwise, has no effect."""
)
Preparing Instruction Sets¶
To specify which instructions should be used during program synthesis (specifically during gene spawning, and genome variation), PyshGP utilizes an InstructionSet object. Instructions can be added, or “registered,” into an InsturctionSet
as long as the instructions name is unique across the set.
Subsets of the “core” instructions can easily be registered using the methods of the InstructionSet object.
instruction_set = (
InstructionSet()
.register_core_by_stack(
{"int", "bool", "stdout"}, # All core instructions that manipulate any of these types...
exclude={"str"} # ... but remove any instructions that use any of these types.
)
.register_core_by_name(".*_add") # All core instructions with name matching regex.
.register_n_inputs(2) # Register 2 input instructions when arity 2 programs are synthesized.
)
Registering Custom Instructions¶
If custom instructions are instantiated (either via a provided class or by extending the Instruction abstract base class) they can also be registered in an InstructionSet
via the .register()
and .register_list()
methods.
instruction_set.register(int_div_instr)
instruction_set.register_list([str_split_instr, bool_depth_instr, exec_when_instr])
To create instructions that manipulate custom stacks that hold values of a custom PushType
you will have to first define a PushTypeLibrary
that holds all PushTypes that will be used. Documentation of the PushTypeLibrary
can be found on its dedicated documentation page linked below.