Function style (@cm.declare)¶
@cm.declare turns a function into a model. The function receives
(args, epoch, ctx) and drives the whole run inside its body — you
iterate the epochs yourself.
import numpy as np
import cmenpy as cm
@cm.declare(x=cm.Argument[int, (0, 3), 2])
def my_algorithm(args, epoch, ctx):
pop = cm.PopulationSwarm(ctx.buff)
pop.compute << ctx.rng.uniform(ctx.bounds.low, ctx.bounds.up,
(pop.size, ctx.bounds.ndim))
for _ in epoch: # one epoch per iteration
pop.compute << np.clip(
~pop.extract
+ ctx.rng.uniform(-1, 1, (pop.size, ctx.bounds.ndim)) * args.x,
ctx.bounds.low, ctx.bounds.up,
)
The decorated name is the model (FunctionOptimizerModel).
The function contract¶
The function is called by keyword with exactly three parameters:
| Parameter | What it is |
|---|---|
args |
the bound field values (args.x, args.lr, …) |
epoch |
an EpochIteration — iterate it to run epochs |
ctx |
the per-run Context |
Name the parameters exactly args, epoch, ctx — the runner passes them
by keyword.
epoch¶
for _ in epoch: # yields 0, 1, ..., epochs-1
...
epoch.max # total epochs
epoch.current # index of the epoch most recently yielded
epoch.cpu_time # wall CPU time spent so far
The iterator tracks per-epoch CPU time, and with debug=True also records a
history snapshot per epoch.
ctx¶
One Context is built before your function runs and lives for the whole
run. ctx.epoch is therefore fixed at 0 in this style — read progress from
the epoch iterator instead.
Declaring and binding parameters¶
@cm.declare(
x=cm.Argument[int, (0, 3), 2], # range-checked with a default
lr=cm.Argument[float, (0.0, 1.0)], # required at binding time
)
def my_algorithm(args, epoch, ctx): ...
Declare fields by keyword — positional declarations passed to the decorator (or values passed positionally at binding) are ignored by the current implementation.
model = my_algorithm(seed=7, x=1, lr=0.1) # seed first, then fields
model.solve(f, bounds, epochs=100, pop_size=20)
seedis the first parameter of the binding call; it seeds the run'sctx.rng.- Every field with a range and no default must be bound; a field that was
declared but never bound raises
AttributeErrorwhen read throughargs. - Only
cm.Argumentis supported — see Fields.cm.Constantraises at declaration time (it is a class-style feature).
Errors at binding¶
| Error | When |
|---|---|
AttributeError: Cannot initialize 'y': It was not declared in MyArguments. |
binding an undeclared name |
TypeError: Invalid type in arguments: Invalid type for 'x'. Expected int, got str |
type mismatch |
ValueError: Invalid value in arguments: Value 5 for 'x' out of range [0, 3]. |
outside the range |
AttributeError: Attribute 'lr' has been declared but not initialized yet. |
reading an unbound field through args |
Range checks also cover arrays and collections (every element must be in range).
Running¶
best_fitness, best_x = model.solve(
f, bounds, epochs=100, pop_size=20,
pop_range=None, debug=False, sense="min",
)
model.alias # the function's __name__
model.tracker # [] unless solve(debug=True)
Each solve builds a fresh run (new buffer, new RNG, new tracker). Calling
solve again on the same model rebinds the seed/fields only if you call the
model again; the field values persist.
model = my_algorithm(x=1) # one configuration...
a = model.solve(f, bounds, 100, 20) # ...run against many objectives
b = model.solve(g, bounds, 200, 40)
Kernels and the function style¶
The function style has no compile-option channel: there is no
parallel/fastmath/cache to pass at binding time. Numba kernels used by
a @cm.declare model are declared at module level and compile with their
decorator's own options — see
Numba kernels and
Compilation options.
Limitations at a glance¶
- fields:
cm.Argumentonly (noConstant, noVariable); - state: locals/closures, no per-instance attributes;
- no
prepare()/ compile options; ctx.epochis fixed at 0.
If you need any of these, use the class style.
Full example¶
example/hello_world_v1.py is the complete, runnable version of the algorithm
above.