Skip to content

Refactor Gate class (1)#325

Merged
BoxiLi merged 48 commits intoqutip:gate_refactorfrom
Mayank447:refactor-gate-class
Mar 1, 2026
Merged

Refactor Gate class (1)#325
BoxiLi merged 48 commits intoqutip:gate_refactorfrom
Mayank447:refactor-gate-class

Conversation

@Mayank447
Copy link
Member

@Mayank447 Mayank447 commented Feb 22, 2026

  • Merge changes from master branch (Removed unused variables and minor corrections #311 and Replace n with num qubits #317)
  • Replaced get_compact_qobj with get_qobj and deprecated the former.
  • Added is_clifford, self_inverse class attributes to Gate class.
  • Moved num_ctrls class attribute, from Gate class to ControlledGate Gate class.
  • Renamed ParametrizedGate class to ParametricGate`.
  • Added num_params class attributes, validate_parameters method to Parametric Gate class.
  • Removed ControlledParamGate class and included that functionality in ControlledGate class itself.
  • Added a metaclass for Gate to disallow modification of Gate class attribution (since they are shared among all object instances).
  • Added __init_subclass__ in Gate, ParametricGate, ControlledGate class and several checks on class attribute type and value.
  • Rename gate factory functions to unitary_gate, controlled_gate.
  • Spilt standard gates into operations/std/ dir, later renamed to /operations/gates dir in Refactor Gate class (2) #328.
  • Deprecated redundant gates (Closes Depreciate SNOT and replace with H gate #296)
  • Deprecated string gate input, control_value, arg_value and arg_label in add_gate method.
  • Deprecated gates.py
  • Added typing.py
  • Minor fixes to qiskit backend, algorithms.

mudit06mah and others added 30 commits January 19, 2026 15:12
Removed unused variables and minor corrections in zzcrosstalk
Mayank447 and others added 9 commits February 10, 2026 19:17
**Changes:**
- Depreciated N in `QubitCircuit` and replaced it with `num_qubits` instead.
- Replaced `qc.N` with `qc.num_qubits` in the codebase
- Depreciated `N` in Gatecompiler.
- Replaced N with `num_qubits` in TexRenderer (N was not an input argument btw)
@Mayank447 Mayank447 changed the title Refactor gate class Refactor Gate class Feb 22, 2026
@BoxiLi
Copy link
Member

BoxiLi commented Feb 25, 2026

Glad to see this coming! It seems like a very big collection. Are there ways to make it smaller? Maybe only the parts that resolve the master branch conflict?

@Mayank447
Copy link
Member Author

Mayank447 commented Feb 25, 2026

Hi,

Yes, the number of commits did considerably exceed my expectation for this PR. And there is still a little work left.

I can split this PR into two (one before merge master) and one after, this should help with reviewing. Any changes as per the code review for the first half, I will do in the second PR to avoid any merge conflicts. How does it sound?

Also the major code changes are operations/, circuit.py and test_gates.py, it's better to review those files first.

@BoxiLi
Copy link
Member

BoxiLi commented Feb 25, 2026

Any changes as per the code review for the first half, I will do in the second PR to avoid any merge conflicts. How does it sound?

Yes, that sounds better. The merging one will require less code review.

@Mayank447
Copy link
Member Author

Mayank447 commented Feb 25, 2026

@BoxiLi I have splitted this PR into two. The failing tests in this PR are because of pytest raising error for DeprecationWarning, you can ignore that. Everything passes in #328 (the second PR).

This PR is ready for review.

@Mayank447 Mayank447 marked this pull request as ready for review February 25, 2026 09:45
@Mayank447 Mayank447 changed the title Refactor Gate class Refactor Gate class (1) Feb 25, 2026
Copy link
Member

@BoxiLi BoxiLi left a comment

Choose a reason for hiding this comment

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

I was wondering about the name std can we find something more user-friendly? Or simply encourage people to import from the gateclass module.

from qutip.operations import gateclass
gateclass.X

Though I still think it would be convenient to keep the string input.

Copy link
Member

@BoxiLi BoxiLi left a comment

Choose a reason for hiding this comment

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

Another thing I find slightly inconsistent is parameteric gates and std gates. For a parameteric gate, one calls parametricgate(argvalue).get_qobj(), for other gates like std, it is simply stdgate.get_qobj. A static method. I was wondering if it will cause confusion.

@Mayank447
Copy link
Member Author

Mayank447 commented Feb 27, 2026

Another thing I find slightly inconsistent is parameteric gates and std gates. For a parameteric gate, one calls parametricgate(argvalue).get_qobj(), for other gates like std, it is simply stdgate.get_qobj. A static method. I was wondering if it will cause confusion.

Ok there is a deeper reason for it. A Parametric Gate needs to be initialised with the parameter RX(0.5) only then can any gate method on it be called e.g. get_qobj, decomposition method etc. Actually the same holds true for the ControlledGate with control_value. But a gate like X doesn't need any argument or initialisation for that fact. So X() is unnecessary, it just creates more object instances even though all of them are same, and each still occupies a different memory segment. So X gate should not be instantiable, and in the next PR by default in __init__ we raise an error if someone tries to do so. __init__ is overwritten in case of ControlledGate, ParametricGate as they require arguments to mean a gate.

Also static method does mean a property so it is stdgate.get_qobj() not stdgate.get_qobj.

@Mayank447
Copy link
Member Author

Mayank447 commented Feb 27, 2026

I was wondering about the name std can we find something more user-friendly? Or simply encourage people to import from the gateclass module.

from qutip.operations import gateclass
gateclass.X

Yes, we can always rename std to something better (coming for a C/C++ background I agree even more). Unfortunately gateclass is a file so it won't work. How about we rename the following gates.py -> old_gates.py and std/ -> gates/, gateclass.py remains the way it is.

from qutip.operations import gates

qc = QubitCircuit(2)
qc.add_gate(gates.X, 0)

Though I still think it would be convenient to keep the string input.

I should have explained why I am in the favour of deprecating string input in the PR description. First this create inconsistency, "X" is fine but something like "RX" requires an arg_value which is really a property of the gate but is being handled in add_gate method, same holds true for control_value. Since we now allow custom gates via inheritance, user has the freedom to have his own arguments for the gate. So instead of us trying to handle these nuances in the add_gate method for string input, we make the user handle it himself. Also in my opinion, qc.add_gate(RX(0.5), 0) is cleaner than qc.add_gate("RX", 0, arg_value=0.5) (the user will have to use arg_value here because in the argument ordering controls comes before it).

Second thing, I agree string inputs are convenient. But now the user can do something like

import qutip_qip.operations.std as std

qc = QubitCircuit(2)
qc.add_gate(std.X, 0)
qc.add_gate(std.RX(0.5), 1)

so his string inputs only change from "gate" to std.gate.

Third thing, in the second PR I introduce the concept of namespaces. So each gate must belong to a namespace (default is std, not to be confused with the dir operations/std/). There are several reasons for it, first at several places we use gate.name for comparison. A user can define his own RX gate (since different conventions are still prevalent) and it will mess up our current decomposition routines entirely. A namespace will prevent any sort of name collision, he can still define his own RX but in a different namespace and we will compare RX wrt. the std namespace. Second the concept of namespaces is much broader and powerful, e.g. I can have std namespace for all these standard gates and another namespace for say noisy gates, or one for math components (Adders, Multipliers, etc.).

@BoxiLi
Copy link
Member

BoxiLi commented Feb 28, 2026

Yes, we can always rename std to something better (coming for a C/C++ background I agree even more). Unfortunately gateclass is a file so it won't work. How about we rename the following gates.py -> old_gates.py and std/ -> gates/, gateclass.py remains the way it is.

This might work, but let's make it in a separate PR at the end.

I should have explained why I am in the favour of deprecating string input in the PR description....

Ok, I am convinced. gates.X should be sufficiently simple. I agree. Just one thing. We need to keep the backward compatibility for this in this PR. I think there are quite a few people using this interface. We should raise a deprecation warning for this first.

@Mayank447
Copy link
Member Author

Mayank447 commented Feb 28, 2026

Ok, I am convinced. gates.X should be sufficiently simple. I agree. Just one thing. We need to keep the backward compatibility for this in this PR. I think there are quite a few people using this interface. We should raise a deprecation warning for this first.

Backward compatibility and the deprecation warning are already there in this PR.

@BoxiLi
Copy link
Member

BoxiLi commented Mar 1, 2026

Ok great, I missed this. If circuit.add_gate("X", ... ) still works for this version and raises a warning, we are good to go.

@Mayank447
Copy link
Member Author

@BoxiLi you can go ahead with the merge

@BoxiLi BoxiLi merged commit 979f94b into qutip:gate_refactor Mar 1, 2026
2 of 16 checks passed
BoxiLi added a commit that referenced this pull request Mar 11, 2026
The original PR #325 was split into 2, for the previous set of changes look at that PR description.

- Defined `__slots__` for all the Abstract Gate classes and standard gate classes for better memory efficiency and faster lookup time.

- Added boolean `is_controlled`, `is_parametric` methods to the `Gate` class.

- Added the concept and implementation of namespaces for operations. This is done because at several places earlier gate.name was being used for comparison. Each operation (gate for now) must now be defined in a namespace or if it is a temporary gate, namespace can be set to None. This allows the user to define a faulty version of standard gates or follow their own convention for certain Parametric gates. The concept of namespace is much broader. Currently the parent namespace is "std", under it only "gates" is set as of now. In future we can make other namespaces under "std" like for Arithmetic Ops, Ansatz Ops etc. For the idea of Ops look at #263 (comment) Though it hasn't been implemented yet. In a way std namespace will work like a standard library for different operations and the user can also define their own implementation for operations in a different namespace.

- Added caching in `get_qobj` method for standard gates. This is done for two reasons.
```
@staticmethod
def get_qobj() -> Qobj:
    return Qobj([[1, 0], [0, -1]])
```
If you run it multiple times and check `id()` for each obj, they will be different. This leads to higher memory consumption for large circuit simulations, even though all of them represent the Z gate's qobj in this case. Additonally different qobjs will each need to be garbage collected. Memory and lookup are an even larger bottleneck in VQA circuits where several times many gates share the same parameter.  

- Added `dtype` to `get_qobj(dtype="dense")` method, . This might be useful if the circuit simulation is run on a different backend like JAX, CUDA.

- Made `Gate` non initialised by default i.e. `__init__` raise will raise an Error. Obviously`ParametricGate` and its controlled versions are initialisable. This makes sense because target, controls have been removed from gates and `x1=X()`, `x2=X()` now represent the same thing. Also good from a memory point of view.

- Added `Sdag`, `Tdag`, `SQRTXdag`, `ISWAPdag`, `SQRTSWAPdag`, `BERKELEYdag` to the standard gates. They have been added to the documentation table and in the circuit draw (colour config for them).

- Added `inverse` method for `Gate` class. Also implemented inverse for all standard gates and tests for the same. For controlled gates, the inverse is auto calculated based on the target gate. Closes #301 

- Renamed `unitary_gate` to `get_unitary_gate` and added checks for all the input arguments. 

- The control value for a gate is now baked into the control gate itself. The user can always generate his own version of controlled gate e.g. `controlled(gates.X, num_ctrl_qubits=2, control_value=0b10)`. For more detail check out #308 (comment)

- For all Controlled gates, the (target_gate, num_ctrl_qubit, ctrl_value) is used as another key in the namespace to reference that controlled gate. This is done in order to prevent regeneration of the controlled gate class for a given target gate with known (num_ctrl_qubit, ctrl_value). Since the namespace is used by default, there is no overhead.

- Renamed `controlled_gate` to `controlled_gate_unitary` and resolved the bug which didn't allow for generating the correct qobj if number of controls > 1.

- Added gate equivalence checking for `ParametricGate`, `ControlledGate`. And added `__eq__`, `__hash__` in Gate metaclass as well. Replaced gate.name = "X" search with gate == X now.

- Corrected inconsistency between Gate and Type[Gate] from previous PRs. Gate refers to an instantiated object while type[Gate] refers to a Gate class or subclass in accordance with Python's typehinting.

- Added several more checks in `__init_subclass__`, `add_gate` method.

- Added tests in `test_gates.py` and `test_circuit.py` for mostly incorrect cases like num_qubits being negative etc., which should raise an Error. Since Gates are used throughout the codebase, their normal usage is already mostly covered.

**Maintainence and Bug Fixes:**

- `gate_product` had a clear bug that `tensor_list` was not defined but was being used, fixed that and also moved the entire `gate_product` logic to operations, which was split in circuit-simulator utils, operations but was always being imported from operations as per `__init__` even in `mat_mul_simulator.py`.

- Removed deprecated arguments and functions that were deprecated 3 years back. This was checked in the git commit history.

- Remove mutable default values from function arguments across the codebase, this is a common Python bug. Something like targets = [], can often lead to an error. For details check out this blog https://medium.com/@matbrizolla/beware-of-the-hidden-trap-understanding-mutable-default-arguments-in-python-d10e8b0a7b72

- Corrected several `isInstance` checks in the codebase. `Iterable` and `Sequence` were being used interchangeably at several places especially in qasm. This led to several unexpected behaviours and errors during gate refactoring.

- Moved pytest.ini to the root, this is what pytest themselves recommend https://docs.pytest.org/en/stable/explanation/goodpractices.html#choosing-a-test-layout-import-rules . The reason was that placing pytest.ini in `tests/` led to certain sys path issues, for reference check https://docs.pytest.org/en/stable/explanation/pythonpath.html. This caused an error when standard gates were designated to a namespace and pytest was trying to redefine them and reassign them to the same namespace.

*Edit: Some more changes*

- Separated IDLE (meant for Pulse level simulation) and Identity gate (standard Identity matrix).

- In Pulse compiler instead of `gate.name`, `gates` are themselves being used as keys.

- Removed expand argument from `inverse()` in Parametric Gates.

- Renamed `controlled` to `get_controlled_gate`.
@BoxiLi BoxiLi added this to the qutip-qip-0.5.0 milestone Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants