-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add source mapping for calibration expansion #370
Conversation
|
As much as I can read Rust, this is making sense to me. On the first point about a lurking bug, I wonder if |
…ly built when returned to the user
Hey, thanks @mhodson-rigetti . I've added python bindings and docs but no example (yet) - I'll have to think about how best to expose those direct mappings from source to target. Right now (following our chat a week ago) I made the structure recursive to model the recursive nature of calibration expansion, and that's the least intuitive part in practical use, because each level of expansion has its own index basis. There's an example describing that problem in the python docstrings now. You were right about The lingering bug though was actually fixed in this commit and was something prompted by our chat a week ago - certain instructions being yoinked out of the instruction body and thus (necessarily) out the source map. |
}) | ||
} | ||
} | ||
|
||
impl CalibrationSignature for Calibration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From @MarquessV : this should not be needed anymore if the CalibrationIdentifier
can serve as the signature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought here, CalibrationSignature
still seems necessary for CalibrationSet
to be generic, and it seems to make sense for impl CalibrationSignature for Calibration
to defer to impl CalibrationSignature for CalibrationIdentifier
. I have a feeling I'm being dense about this so lmk if you see otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is true, I don't know of a good way to make the generics work with just a CalibrationIdentifier
struct. I agree that it makes sense to formalize the concept of a calibration identifier w/ a struct and defer to it to implement the signature logic. They serve different purposes, so it's not entirely redundant, and the core logic doesn't need to be duplicated between either, so I'm good with this.
expansion_output | ||
.detail | ||
.remove_target_index(relative_target_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels very clumsy and likely inefficient, but no more clever way occurred to me. Suggestions welcome.
Basically, we determine whether an instruction is a "header" or "body" instruction by whether or not it made the instruction body longer, and then based on that we excise it from the source mapping so that the target_index
ranges stay accurate and don't map to a missing instruction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't necessarily have a better idea for this, but an observation that the distinction between "header" and "body" instruction comes up enough that maybe we should have a more concrete definition of it in the library? Like a method in Instruction
maybe? I know it's not a part of the spec, but it is a practical one that this library makes decisions around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @MarquessV is exactly right; this seems like it's going to be much cleaner if it never gets added than if it gets added and deleted. In particular, if the calibration expansion can check if something is a "header" instruction, then it can refrain from adding it to the expanded source, and then we don't need to do this fixup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the calibration expansion can check if something is a "header" instruction, then it can refrain from adding it to the expanded source, and then we don't need to do this fixup.
I don't see how this is possible. When the instruction is expanded, it does not have the scope of a program, and so it is just expanded into a Vec<Instruction>
. However, when we add that Vec to a program here, then some of its members are "hoisted" into data structures like memory region declarations and calibration declarations and such. Expanding the calibration in such a way that "predicted" how the program would handle it, without the use of an actual program, is something that could fall out of sync and requires extra care and maintenance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's statically knowable which instructions get hoisted and which ones don't: Instruction::CalibrationDefinition
does, Instruction::Gate
doesn't, etc. Expansion could return two vectors: Vec<HoistedInstruction>
and Vec<ComputationInstruction>
(new types optional, but perhaps clearer). Then anything working on the results of expansion wouldn't have to do its own classification.
expansion_output | ||
.detail | ||
.remove_target_index(relative_target_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't necessarily have a better idea for this, but an observation that the distinction between "header" and "body" instruction comes up enough that maybe we should have a more concrete definition of it in the library? Like a method in Instruction
maybe? I know it's not a part of the spec, but it is a practical one that this library makes decisions around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks overall good to me, but I think some details need to be hashed out before we merge this
expansion_output | ||
.detail | ||
.remove_target_index(relative_target_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @MarquessV is exactly right; this seems like it's going to be much cleaner if it never gets added than if it gets added and deleted. In particular, if the calibration expansion can check if something is a "header" instruction, then it can refrain from adding it to the expanded source, and then we don't need to do this fixup.
quil-rs/src/program/mod.rs
Outdated
@@ -693,14 +773,38 @@ impl ops::AddAssign<Program> for Program { | |||
} | |||
} | |||
|
|||
type InstructionIndex = usize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure we want this to be a type synonym and not a newtype? It's probably fine but I figure it's worth being explicit about that choice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize I had missed this comment on the last round, but it's a good idea. Changed that now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review, @antalsz
}) | ||
} | ||
} | ||
|
||
impl CalibrationSignature for Calibration { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought here, CalibrationSignature
still seems necessary for CalibrationSet
to be generic, and it seems to make sense for impl CalibrationSignature for Calibration
to defer to impl CalibrationSignature for CalibrationIdentifier
. I have a feeling I'm being dense about this so lmk if you see otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes look good, thanks! I have a few open threads that weren't addressed, but none I'd consider blockers (except for this one new comment). However, the discussion around header/body instruction distinction should wrap up before we merge this.
quil-rs/src/program/calibration.rs
Outdated
pub(crate) fn remove_target_index(&mut self, target_index: usize) { | ||
eprintln!("Removing target index {} from\n{self:#?}", target_index); | ||
|
||
// Adjust the start of the range if the target index is within the range |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Adjust the start of the range if the target index is within the range | |
// Adjust the start of the range if the target index is before the range |
expansion_output | ||
.detail | ||
.remove_target_index(relative_target_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's statically knowable which instructions get hoisted and which ones don't: Instruction::CalibrationDefinition
does, Instruction::Gate
doesn't, etc. Expansion could return two vectors: Vec<HoistedInstruction>
and Vec<ComputationInstruction>
(new types optional, but perhaps clearer). Then anything working on the results of expansion wouldn't have to do its own classification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said in my last review – now that I understand the negation change, I would not consider any of my remaining comments blocking. Approved!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 5d31908; a couple minor thoughts about the explanation, but the overall concept is great.
quil-py/quil/program/__init__.pyi
Outdated
# This is what we expect the expanded program to be. X and Y have been replaced by Z | ||
expected_program_text = inspect.cleandoc( | ||
\"\"\" | ||
DEFCAL X 0: | ||
Y 0 | ||
|
||
DEFCAL Y 0: | ||
Z 0 | ||
|
||
Z 0 # This instruction is index 0 | ||
Z 0 # This instruction is index 1 | ||
\"\"\" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this ought to have an assert. That raises questions of the precise whitespace, I know. Could we parse them both to Quil and assert the parsed programs are equal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the test did, I left it out here for readability but have restored it now
quil-py/quil/program/__init__.pyi
Outdated
# In order to discover _which_ calibration led to each Z in the resulting program, we | ||
# can interrogate the expansion source mapping: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this section a lot, but I found the specific comments a bit confusing. Perhaps we could revise this first comment to something like
"…can interrogate the expansion source mapping. For instance, The X at index 0 should have been replaced with a Z at index 0. Here's how we could confirm that: First, we…".
As it stands, the comment on line 46 made me think that we were also going to check the Y at index 1. (I think adding that would probably be redundant.)
Another possibility would be to drop the ellipses on lines 51 and 63; they're not following on from a previous sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, fixed
|
||
## Source Mapping for Calibration Expansion | ||
|
||
```py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this snippet run and tested? It would be nice if it were but I don't know enough about Python to tell if that's happening/how easy it would be to have that happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it's a test case, although not tested in situ unfortunately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a pointer to the test case it corresponds to?
58217f3
to
5c05993
Compare
Just wanted to check on the status of this - it's important for pulse plotting to work. |
I'm a little surprised by the extent of the changes in this PR. I was expecting something more along the lines of this: from quil.program import Program
from typing import Tuple, List
def expand_with_source_mapping(program: Program) -> Tuple[Program, List[int]]:
"""
Expand any instructions in the program which have a matching calibration, leaving the others
unchanged.
:param program: A quil.Program.
:return: A quil.Program with the instructions expanded and a source map.
The source map is a list with the length of the expanded instructions, and indicates the index
of the logical instruction in the original program from which the expanded instruction originated.
"""
instructions = program.body_instructions
calibrations = program.calibrations
expanded_program = program.clone_without_body_instructions()
source_map = []
expanded_instructions = []
for logical_index, inst in enumerate(instructions):
expanded_instruction = calibrations.expand(inst, [])
source_map += [logical_index]*len(expanded_instruction)
expanded_instructions += expanded_instruction
expanded_program.add_instructions(expanded_instructions)
return expanded_program, source_map
expanded_program, source_map = expand_with_source_mapping(quil_program)
default_expanded_program = quil_program.expand_calibrations()
assert expanded_program == default_expanded_program |
@bramathon that snippet doesn't:
Some of the diff, also, improves how calibrations are handled and matched |
Yup, that's true. I don't have any need for those things for my use case.
…On Fri, Oct 11, 2024, 20:00 Kalan ***@***.***> wrote:
I'm a little surprised by the extent of the changes in this PR. I was
expecting something more along the lines of this:
from quil.program import Programfrom typing import Tuple, List
def expand_with_source_mapping(program: Program) -> Tuple[Program, List[int]]:
""" Expand any instructions in the program which have a matching calibration, leaving the others unchanged. :param program: A quil.Program. :return: A quil.Program with the instructions expanded and a source map. The source map is a list with the length of the expanded instructions, and indicates the index of the logical instruction in the original program from which the expanded instruction originated. """
instructions = program.body_instructions
calibrations = program.calibrations
expanded_program = program.clone_without_body_instructions()
source_map = []
expanded_instructions = []
for logical_index, inst in enumerate(instructions):
expanded_instruction = calibrations.expand(inst, [])
source_map += [logical_index]*len(expanded_instruction)
expanded_instructions += expanded_instruction
expanded_program.add_instructions(expanded_instructions)
return expanded_program, source_map
expanded_program, source_map = expand_with_source_mapping(quil_program)default_expanded_program = quil_program.expand_calibrations()assert expanded_program == default_expanded_program
@bramathon <https://github.com/bramathon> that snippet doesn't:
- break out recursive calibrations
- capture which calibration was used to do the expansion
Some of the diff, also, improves how calibrations are handled and matched
—
Reply to this email directly, view it on GitHub
<#370 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEWA7XSPPA5YGRQB4JXV4DZ3AN5PAVCNFSM6AAAAABHGSS7PSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBXHE3TEMZVGI>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Over to you now @bramathon |
This PR allows the (Rust package) user to do the following:
Reviewer:
MeasureCalibrationDefinition
andCalibration
structs. This would make them somewhat more similar to DEFFRAME in that they would have identifiers that could be used to index them without respect to their instruction bodies. We could also take the chance to renameCalibration
toCalibrationDefinition
for consistency.@property
s are there, so this will only break users who construct and modify calibrations and measure calibrations. I could add additional setters to help migrate, but there's only one__new__
and it doesn't feel right to leave those behind.CalibrationSet#_expand
method and those nearby.CalibrationExpansion
when that is returned to the user; I first tried anexpansion: Option<&mut CalibrationExpansion>
argument, but that doesn't work well here because that doesn't have a sensible default.build_source_map
which skips most of the work involved in calibration expansion source mapping while still technically constructing one which is then discarded prior to return fromCalibrationSet.expand
.TODOs:
Closes #366