support `optimize` in builder
most of the optimization based problem
# f(report)
job.optimize(f, CMAES()).submit().report()
this should cover most of the optimization problem we have on pulses
I think we need to refactor the builder a bit to create an intermediate object that can be used to dynamically generate tasks/jobs and forward this from Emit. I think the following would be idea: the objects for generating jobs are constructed by methods from Emit, the argument of this method always being the static_assignments which correspond to the arguments of assign later the batch_assign arguments are passed in during the submit() method of this Job generator object.
The motivation for this is because in optimize I need to have some object that can generate tasks on the fly and I don't want to re-implement the same infrastructure.
job = (
Square(4, lattice_spacing="a")
.apply_defect_density(0.1)
.rydberg.detuning.uniform.piecewise_linear(
durations=[0.1, 3.8, 0.1], values=[-10, -10, "final_detuning", "final_detuning"]
)
.rabi.amplitude.uniform.piecewise_linear(
durations=[0.1, 3.8, 0.1], values=[0.0, 15.0, 15.0, 0.0]
)
.braket(**static_assignments)
.submit(100, **batch_assignments)
)
# optimization loop
optimized_results = (
Square(4, lattice_spacing="a")
.apply_defect_density(0.1)
.rydberg.detuning.uniform.piecewise_linear(
durations=[0.1, 3.8, 0.1], values=[-10, -10, "final_detuning", "final_detuning"]
)
.rabi.amplitude.uniform.piecewise_linear(
durations=[0.1, 3.8, 0.1], values=[0.0, 15.0, 15.0, 0.0]
)
.braket(**static_assignments)
.optimize(100, cost_function)
)
Oh one issue would be on how to deal with RunTimeVectors in the optimization step.