ScheduleBlock 4/4 - add ScheduleBlock#6158
Conversation
- update builder logic - deprecate call_schedule - deprecate pad context - remove auto padding of align_equispaced and align_func - move pad from alignment to canonicalization - add temp transform pass - unittest fix
lcapelluto
left a comment
There was a problem hiding this comment.
review IP. went through all the small files, now on to the main one: builder.py
| circ = circ.assign_parameters({alpha: freq - delta}) | ||
| cal_sched = list(circ.calibrations['custom'].values())[0] | ||
| self.assertEqual(cal_sched.instructions[0][1].frequency, freq - delta + beta) | ||
| self.assertEqual(cal_sched.instructions[0].frequency, freq - delta + beta) |
There was a problem hiding this comment.
Is the old instructions method being deprecated? Is it specifically for ScheduleBlocks? This looks like a breaking change to me
There was a problem hiding this comment.
This is only for ScheduleBlock since it doesn't have t0.
There was a problem hiding this comment.
This change would break user code, so it's a breaking interface change. Whenever possible, we should avoid this.
Perhaps for ScheduleBlock.instructions, we should try to convert to a Schedule, or print a warning, or error and ask users to use a different method. I'm not sure what the best, but we shouldn't expect them to make this change without some assistance. Do you have any ideas?
There was a problem hiding this comment.
I agree we want to avoid breaking changes for existing users when possible, which this likely is since we swapped out Schedule for ScheduleBlock. Ideally, we convert to a Schedule as @lcapelluto describes and warn that this method will be deprecated in favour of timed_instructions in a future PR and use another property (what should this be named?) instead that does not raise this deprecation warning for this behavior. We could always reintroduce instructions as an attribute after the deprecation period.
There was a problem hiding this comment.
Thanks. I updated the behavior of ScheduleBlock.instructions. Now it converts self into Schedule and call .instructions. The original method is renamed to ScheduleBlock.blocks. I don't think this change has impact to the user code. I'm not sure if we need to rename instructions to timed_instructions, since this causes tons of deprecation warnings in tutorials.
| if not isinstance(block, ScheduleBlock): | ||
| raise exceptions.PulseError(f'Input program {repr(block)} is not `ScheduleBlock`. ' | ||
| 'This cannot be a root context.') |
There was a problem hiding this comment.
Couldn't a schedule be trivially made into a ScheduleBlock? (This looks like a breaking change. If it's not, then it's safe to ignore this comment)
There was a problem hiding this comment.
Good question. The docstring of pulse.build says
schedule: A *mutable* pulse ``ScheduleBlock`` in which your pulse program will
be built.
I guess this indicates
my_sched1 = Schedule()
with build(schedule=my_sched1):
# do something
my_sched2 = Schedule()
with build() new_sched:
# do something
my_sched1 == my_sched2.append(new_sched)So the builder should operate on the input schedule in the mutable fashion. However we cannot append ScheduleBlock to Schedule because of the ambiguity of duration. Thus this will become breaking change. If there is any approach to avoid this, I'm happy to update the logic.
There was a problem hiding this comment.
This is what worked before:
with build(schedule=Schedule(...)) as new_schedule:
...
it looks like this would error now. My suggestion is to do something like:
if isinstance(block, Schedule):
tmp = ScheduleBlock()
tmp.append(block) # I forget what the interface is here. 'call'?
block = tmp
There was a problem hiding this comment.
Okey, so it seems like the docstring is not correct. block is not necessary to be mutable?
|
|
||
| @_compile_lazy_circuit_before | ||
| def pop_context(self): | ||
| """Pop the last context from the stack and append it to the parent.""" |
There was a problem hiding this comment.
The "and append to parent" seems uncommon for pop. Is there another term we can use for this?
This made compile below very difficult to understand:
while len(self._context_stack) > 1:
self.pop_context()
without reading the implementation of pop_context, it looks like this would just remove everything.
There was a problem hiding this comment.
Umm you're right. Any suggestion? pop_append_context?
There was a problem hiding this comment.
Yes, that's definitely better. 🤔 Not sure I have a better suggestion. process_last? @eggerdj, ideas?
There was a problem hiding this comment.
I lean toward updating the behavior of method. push & pop are standard terms for stack data structure and stick to these names would be better for developers. Instead of internally appending to parent, we can call builder.append_block method externally.
…9/qiskit-terra into issue-5679-4_schedule_block
…9/qiskit-terra into issue-5679-4_schedule_block
Co-authored-by: Thomas Alexander <[email protected]>
Co-authored-by: Thomas Alexander <[email protected]>
Co-authored-by: Thomas Alexander <[email protected]>
…9/qiskit-terra into issue-5679-4_schedule_block
|
@peendebak Thanks for testing the PR! I completely have forgotten this type checking in the instmap. The instmap is updated to allow the block programs. These blocks are internally converted into conventional schedule just before the job submission anyway, so you can use instmap without considering program types. @taalexander Thanks for the review! The PR is updated ;) |
…9/qiskit-terra into issue-5679-4_schedule_block
|
@nkanazawa1989 Thanks for the quick response. Adding to the |
|
Okey, I found the problem, however I feel it would be better to rewrite circuit scheduler to be based on the block representation, i.e. we can keep duration parametrized in the mapper. Could you please open an issue for the instmap after this PR is merged? This upgrade will be relatively major change, so I think it's better to separate PR. For now, you can schedule your circuit by using pulse gate: from qiskit import QuantumCircuit, schedule
from qiskit.test.mock import FakeAlmaden
from qiskit import pulse
from qiskit.circuit import Gate
from qiskit.pulse import DriveChannel
def build_schedule(duration, amplitude, frequency_offset, channel):
with pulse.build() as pulse_prog:
with pulse.frequency_offset(frequency_offset, channel):
pulse.play(pulse.library.Constant(duration=duration, amp=amplitude, name='genericX'), channel)
return pulse_prog
myschedule = build_schedule(10, .1, -.3, DriveChannel(0))
qc = QuantumCircuit(2)
gate =Gate('mygate', 1, [])
qc.append(gate, [0])
qc.add_calibration(gate, (0,), schedule=myschedule)
print(qc.draw())
backend = FakeAlmaden()
defaults = backend.defaults()
pulse_schedule = schedule(qc, backend) |
|
@nkanazawa1989 Thanks for fixing my issue. Is it still needed to open a bug report? I am not sure what to report, since the actual problem is not occuring any more. |
|
This fix is temporarily (block is converted forcibly into schedule now), so I plan to make another PR to keep blocks as-is. However if this fix solved your issue, you don't need to write an another issue. Thanks for finding and reporting it :) |
Summary
This is 4/4 step of
ScheduleBlockimplementation #5679Details and comments
This PR mainly aiming at replacing the output program of the builder context with
ScheduleBlock. The builder is now managed with stack data structure (FILO) to leverage the block representation.Since chained transformation appears everywhere, I added
qiskit/pulse/transforms/base_transforms.pyto define typical chain of transformation to generate an executable schedule. This will be replaced with proper transformation passes #6121 .In this PR unittests of the builder are temporarily performed by converting the output programs into schedule with above transformation (note that reference programs are currently written in
Schedulerepresentation). This may hurts CI performance, so these unittests will be replaced shortly #6106 .Some builder functions are deprecated
pulse.pad: No longer necessary and sometime requires quite complicated logic. For example,here the nested
padcontext is first evaluated, but delay in between pulses cannot be determined until the outer context (equispace) is evaluated. In this PR,padis move to transformation passes that is applied to the entire schedule.pulse.frequency_offset(compensate_phase=True): In the same reason, this requires complicated logic to evaluate duration of nested context. Currently no unittest for this situation exists. Instead, we can use signal formalism Support frames using signals #5977 -- this will calculate phase accumulation after all pulse locations are resolved.pulse.inline: I cannot find any practical use case for this though the implementation requires recursive instruction call for nested context (i.e. complicated and not good performance). Instead, we can just manually remove all alignment contexts within the inline context.