Skip to content
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

Implement _act_on_ for SingleQubitCliffordGate #7069

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

daxfohl
Copy link
Collaborator

@daxfohl daxfohl commented Feb 15, 2025

Maybe fixes #5256

@BichengYing is this something like what you were looking for?

As background on the sim architecture, there's two levels: SimulationState, and QuantumStateRepresentation.

QuantumStateRepresentation is a container for the quantum state piece only, so there's implementations for BufferedStateVector, BufferedDensityMatrix, CliffordTableau, ChForm, and others. The latter two inherit from StabilizerState, which provides the interface apply_x(axis, exponent, phase), apply_y, etc that those two subclasses implement. This layer is also meant to be low-level and only works on axes/indexes and unitaries/channels/tableaux, not Qids or Gates.

SimulationState is a layer above that, and it contains a QuantumStateRepresentation, as well as the RNG, a place to store measurements that have been taken, and the qubit->index mapping. This layer has a parallel type hierarchy, StateVectorSimulationState, StabilizerSimulationState, CliffordTableauSimulationState, and so on, which I don't love, but was hard to avoid. This is the layer that _act_on_/_act_on_fallback_ works with. So that's why _act_on_ has the annoying sim_state._state.

There's also a Simulator layer on top of that, but that's out of scope for this topic.

_act_on_ then, is mostly used for delegation. If you search for def _act_on_(, you can see most of the implementations are for "composite" ops that delegate to sub-ops. CircuitOperation, ClassicallyControlledOperation, TaggedOperation, and so on. But in most cases, _act_on_fallback_ defined in the SimulationState itself knows how to handle the op efficiently. So for instance StateVectorSimulationState tries the apply_unitary protocol first, which XPowGate has an efficient implementation for, that's faster than generating the unitary matrix and applying it generically. So XPowGate does not need an explicit _act_on_ implementation to achieve this efficiency increase. So in general, we've tried to avoid having Gate classes need to be aware of any specific SimulationState classes, or do any if isinstance(sim_state, XYZSimState) in _act_on_ implementations.

I think SingleQubitCliffordGate is different in this regard: it's specifically designed to be used only in Clifford simulators, and so perhaps having an explicit isinstance(sim_state, ...) in an _act_on_ makes sense. That's what this PR does. Another approach could be to make the StabilizerSimulationState aware of those gates in _strat_apply_gate. That approach flips the dependency upside-down, so that the simulator is aware of the gate, rather than the gate being aware of the simulator. A third approach could be that we make CliffordGates the only thing that StabilizerSimulationState is aware of, remove the special-case handling of e.g. XPowGate, and change the _strat_decompose into _strat_decompose_into_cliffords, such that XPowGate gets decomposed into CliffordGate.X before it can be applied.

...Now that I think about it, the third strategy may make the most sense: have Clifford simulators only work with Clifford gates and things that decompose into Cliffords. That way we could maybe get rid of the apply_x, apply_y etc. of the StabilizerState interface and replace them all with something more generic. (I'm not all that familiar with Cliffords, so you'd probably know more about that than me).

Anyway, the current PR is the lowest-hanging quick fix for #5256. LMK if this is what you were looking for.

@CirqBot CirqBot added the size: S 10< lines changed <50 label Feb 15, 2025
Copy link

codecov bot commented Feb 15, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.15%. Comparing base (2e71950) to head (a44aa4d).
Report is 28 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7069      +/-   ##
==========================================
- Coverage   98.18%   98.15%   -0.03%     
==========================================
  Files        1089     1089              
  Lines       95208    95319     +111     
==========================================
+ Hits        93478    93563      +85     
- Misses       1730     1756      +26     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@daxfohl
Copy link
Collaborator Author

daxfohl commented Feb 15, 2025

Actually, what probably makes the most sense, is to define new protocols _apply_tableau_ and _apply_ch_. Then those can be defined on any gates that support them, and the CliffordTableauSimulationState and ChFormSimulationState can use those protocols as the primary strategy rather than depending on the specific gate types. That way we're not coupling gates to states or states to gates, and it'd allow both CliffordGate and pauli gates to work without requiring decomposition. It might even make sense to get rid of the StabilizerSimulationState superclass of those two at that point, and the corresponding StabilizerState quantum superclass, as I'm not sure if they'd still add any value.

This would also be more aligned with how the rest of the simulators work: other SimulationStates generally delegate everything to protocols, trying each from fastest to slowest, until finally decomposing as the last resort.

All that said, that's a project in its own right, going beyond the scope of the issue #5256, and would be its own issue, likely a good part-time project for somebody.

The current PR is more a direct fix for #5256, and will help speed up both of the Clifford simulators for these gates in the meantime. (Note: probably worth a perf test to confirm).

@daxfohl
Copy link
Collaborator Author

daxfohl commented Feb 15, 2025

probably worth a perf test to confirm

Oof, yeah, so in this PR, if we remove the implementation of SingleQubitCliffordGate._act_on_ entirely such that it falls back to CliffordGate._act_on_, it's about 15x slower than the special casing here. But the existing implementation of SingleQubitCliffordGate._act_on_ that overrides CliffordGate._act_on_ and returns NotImplemented causes decomposition, and is about 60x slower! So just removing the existing override would have provided a 4x speedup. That's for Tableau.

For CH form, there's no difference between the slow cases, as CliffordGate._act_on_ also always returns NotImplemented for CH. But the new SingleQubitCliffordGate._act_on_ implementation in this PR also provides about a 60x speedup versus decomposition on CH, at least for the "named" gates. For the unnamed gates, it falls back to decomp, so 60x slower. For Tableau unnamed gates, it depends on what we put for the default case. Currently this PR has it return NotImplemented which is 4x slower than if we return super()._act_on_(). I'll update the PR accordingly.

@daxfohl
Copy link
Collaborator Author

daxfohl commented Feb 15, 2025

Oh, wait. I see. The perf of CliffordGate._act_on_ gets incredibly slow beyond about 100 qubits. With decompose, you just pay the overhead of decomposition, which is relatively small when there are lots of qubits. It's only noticeable in narrow circuits.

I'll switch it back to returning NotImplemented in the "unnamed" case, to avoid the degenerate perf drop. Probably at some point, CliffordGate._act_on_ should count the qubits in sim_state and choose whether to apply the algorithm or decompose. But that's beyond the scope of this PR.

@daxfohl daxfohl marked this pull request as ready for review February 15, 2025 19:32
@daxfohl daxfohl requested review from vtomole and a team as code owners February 15, 2025 19:32
@daxfohl daxfohl requested a review from senecameeks February 15, 2025 19:32
@@ -143,10 +143,6 @@ def _gate_tableau(num_qubits: int, gate: raw_types.Gate) -> 'cirq.CliffordTablea
class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf):
"""A metaclass used to lazy initialize several common Clifford Gate as class attributes."""

# These are class properties so we define them as properties on a metaclass.
# Note that in python 3.9+ @classmethod can be used with @property, so these
# can be moved to CommonCliffordGates.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ability was removed again in 3.11, so deleting the comment. https://stackoverflow.com/questions/128573/using-property-on-classmethods/64738850#64738850

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: S 10< lines changed <50
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add explicit _act_on_ function for SingleQubitCliffordGate
2 participants