Climber
Climber holds the modules you picked.
A climber is a set of modules, and its arguments come in the order one step of a search runs
them. The selector picks the node the attempt starts from, or none. The policy names the operator
for it. The operator makes the attempt, reading what memory has to say. A tune trial of the result
may follow. Climber takes each of these as a name, a class or an instance, and anything you
leave out keeps its default.
from hillclimb import Climber
from hillclimb.memory import FilesMemory
from hillclimb.operators import Debug, Draft, Improve
from hillclimb.policies import Greedy
from hillclimb.selectors import MapElites
climber = Climber(
selector_policy=MapElites(num_islands=2, num_drafts=3), # which node, or none
operator_policy=Greedy(tune_budget=4), # which operator for it
operators=[Draft(retrieval=False), Debug(), Improve()], # how the attempt is made
tuner="optuna", # how a tune trial picks values
memory=FilesMemory(max_cards=1), # what the operator is told
name="mine", # just a label
)Each kind of module has a module to import it from: hillclimb.policies, hillclimb.selectors,
hillclimb.operators, hillclimb.tuners, hillclimb.memory and hillclimb.loops.
| part | what does it decide? | comes with |
|---|---|---|
selector_policy | which node the next attempt starts from, or none. It also carries the schedule: a failing tip first, roots until num_drafts, the final ensemble window | selectors.Best, selectors.MapElites |
operator_policy | which operator to apply to what the selector chose | policies.Greedy |
operators | how an attempt is made. The built-in ones are classes like any you write, so [Draft(), Debug(), Improve()] is the usual spelling; a name works too | operators.Draft, Debug, Improve, Ensemble |
tuner | how parameter values are chosen | tuners.RandomSearch, tuners.Optuna |
memory | what a search reads from earlier searches and leaves for later ones | memory.FilesMemory, memory.NoMemory |
loop | use this instead of a selector and a policy when the climber wants to own the whole loop | loops.GepaLoop |
params, selector_params | the policy's knobs and the selector's, as dicts | |
prompts | a folder of prompt templates that replace the built-in ones |
An instance is shorthand for a class plus its parameters. Best(num_drafts=3) means
selector_policy: best with selector_params: {num_drafts: 3}, and every search builds a fresh instance
from that. If you misspell a knob you find out right away. Greedy(tune_budge=3) raises a
TypeError before anything has been spent, and so does a schedule knob on the wrong module:
Greedy(num_drafts=3) tells you it belongs to the selector.
Presets
You can also start from a preset. Its name stands for a whole block, and the other arguments go on top.
Climber("greedy")
Climber("openevolve", params={"num_drafts": 5}) # greedy over a MAP-Elites archive
Climber(loop="gepa") # needs pip install 'hillclimb[gepa]'The block behind it
A climber built in Python and a climber: block in YAML are the same thing, and you can move
between them.
climber.to_spec().block() # the block as a dict
climber.write("climber.yaml") # the block as a file, ready for a run spec or runs/config.yaml
climber.sha256 # its identity, covering the block and every file it reachesYour own classes are written down in the most portable form available. A registry name if there
is one, then module:Class if the module can be imported, otherwise its_file.py:Class. That
last form means a search will import your script again, so keep the code that starts a search
under if __name__ == "__main__":. A class that only exists in the running process, say in a
notebook cell, still works with search and start. The climber is then not
portable, and to_spec(), hillclimb resume and detached engines tell you so instead of
trying.
What you can do with one
climber.search(problem, ...) | run one search and keep the result. See Search |
climber.start(problem, ...) | open the same search and step through it. See Stepping |
climber.best, climber.history, climber.solution, climber.to_frame() | what the search found. See Results |
climber.to_spec(), climber.write(path) | get the block |
climber.name, climber.sha256, climber.portable | its label, its identity, and whether it can be written down |