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

Singleton parameterless controlled gates #10898

Merged
merged 13 commits into from
Oct 16, 2023

Conversation

mtreinish
Copy link
Member

@mtreinish mtreinish commented Sep 25, 2023

Summary

This commit adds a new class SingletonControlledGate which is very similar to the SingletonGate class, but instead can be used in place of the ControlledGate class as a parent class. This enables a controlled gate class to work as a singleton if there is no custom control state specified. This new class has a large amount of duplication with the SingletonGate class, but because of how the inheritance tree is built up here there is no avoiding this as we need to specialize for adding singleton support and SingletonGate is at the same level as ControlledGate.

With the introduction of this new class all the standard library ControlledGate instances are updated so they inherit from SingletonControlledGate instead of ControlledGate. This should significantly reduce the memory footprint of repeated instances of these gates in circuits.

Details and comments

TODO:

  • Fix text drawer tests with base gate labels
  • Add dedicated SingletonControlledGate test cases

This commit adds a new class SingletonControlledGate which is very
similar to the SingletonGate class, but instead can be used in place of
the ControlledGate class as a parent class. This enables a controlled
gate class to work as a singleton if there is no custom control state
specified. This new class has a large amount of duplication with the
SingletonGate class, but because of how the inheritance tree is built
up here there is no avoiding this as we need to specialize for adding
singleton support and SingletonGate is at the same level as
ControlledGate.

With the introduction of this new class all the standard library
ControlledGate instances are updated so they inherit from
SingletonControlledGate instead of ControlledGate. This should
significantly reduce the memory footprint of repeated instances of these
gates in circuits.
@mtreinish mtreinish added Changelog: New Feature Include in the "Added" section of the changelog Changelog: API Change Include in the "Changed" section of the changelog labels Sep 25, 2023
@mtreinish mtreinish added this to the 0.45.0 milestone Sep 25, 2023
@mtreinish mtreinish marked this pull request as ready for review October 3, 2023 20:40
@mtreinish mtreinish requested a review from a team as a code owner October 3, 2023 20:40
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia
  • @mtreinish
  • @nkanazawa1989

@mtreinish
Copy link
Member Author

Ok, this should be ready for review now. I fixed the last failing test cases (all of which were caused by extra state needed for controlled gate reconstruction getting lost through c_if or __deepcopy__) and also added dedicated tests for singleton controlled gates

@mtreinish
Copy link
Member Author

I ran a subset of ASV just as a sanity check locally and it didn't show any statistically significant change:

Benchmarks that have stayed the same:

       before           after         ratio
     [dda063e4]       [6b575102]
     <main>       <singleton-controlled-gates>
      2.77±0.05ms      2.88±0.06ms     1.04  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(1, 'translator')
      2.78±0.01ms      2.89±0.07ms     1.04  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_parameterized_subroutine(11)
      19.8±0.09ms       20.5±0.3ms     1.03  circuit_construction.CircuitConstructionBench.time_circuit_construction(20, 2048)
         703±20μs         720±20μs     1.02  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_single_schedule(3)
         806±40μs         825±10μs     1.02  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_parameterized_subroutine(3)
       11.0±0.1ms      11.3±0.02ms     1.02  circuit_construction.CircuitConstructionBench.time_circuit_construction(1, 2048)
      1.01±0.04ms      1.03±0.02ms     1.02  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_through_pulse_gate(3)
        139±0.6ms        141±0.8ms     1.02  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm(1)
      1.39±0.01ms      1.41±0.01ms     1.01  circuit_construction.CircuitConstructionBench.time_circuit_construction(5, 128)
         86.9±1ms         88.1±4ms     1.01  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 32768)
          226±1ms          229±2ms     1.01  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm_backend_with_prop(2)
      9.65±0.05ms       9.77±0.1ms     1.01  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_through_pulse_gate(31)
      1.39±0.02ms      1.40±0.05ms     1.01  circuit_construction.CircuitConstructionBench.time_circuit_construction(14, 128)
        108±0.6ms        109±0.5ms     1.01  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm_backend_with_prop(3)
       26.9±0.2ms       27.1±0.2ms     1.01  circuit_construction.CircuitConstructionBench.time_circuit_copy(20, 8192)
          242±2ms          245±2ms     1.01  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm_backend_with_prop(1)
          631±2ms          637±2ms     1.01  passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['u', 'cx', 'id'])
       1.29±0.01s       1.31±0.01s     1.01  circuit_construction.CircuitConstructionBench.time_circuit_construction(2, 131072)
        239±0.4ms          241±2ms     1.01  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_qv_14_x_14(1)
          287±2ms          290±1ms     1.01  passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['rz', 'x', 'sx', 'cx', 'id'])
      2.75±0.01ms      2.78±0.05ms     1.01  pulse.schedule_construction.EchoedCrossResonanceConstructionBench.time_full_scratch
       19.9±0.2ms       20.0±0.3ms     1.01  circuit_construction.CircuitConstructionBench.time_circuit_construction(8, 2048)
          757±3ms          762±4ms     1.01  passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['rz', 'x', 'sx', 'cx', 'id'])
        885±0.8ms          891±5ms     1.01  passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['u', 'cx', 'id'])
          239±4ms          240±2ms     1.01  passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['u', 'cx', 'id'])
      10.7±0.05ms      10.8±0.04ms     1.01  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_single_schedule(51)
      2.64±0.02ms      2.65±0.03ms     1.01  pulse.schedule_construction.EchoedCrossResonanceConstructionBench.time_with_call
      6.56±0.02ms      6.60±0.01ms     1.01  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 2048)
        270±0.7ms        272±0.8ms     1.01  transpiler_levels.TranspilerLevelBenchmarks.time_schedule_qv_14_x_14(1)
          376±8ms          377±8ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 131072)
       57.5±0.1μs       57.7±0.5μs     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 8)
      1.38±0.02ms      1.38±0.01ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(8, 128)
          223±2ms          223±3ms     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm(2)
       20.2±0.3ms       20.2±0.3ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(5, 2048)
       1.30±0.02s       1.30±0.01s     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(14, 131072)
         787±20μs          789±1μs     1.00  pulse.schedule_construction.EchoedCrossResonanceConstructionBench.time_assign_later
        405±0.6ms          406±2ms     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_qv_14_x_14(2)
          1.05±0s          1.05±0s     1.00  passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['rz', 'x', 'sx', 'cx', 'id'])
       2.04±0.01s       2.04±0.01s     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_quantum_volume_transpile_50_x_20(2)
         80.3±2ms         80.4±1ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(5, 8192)
       79.0±0.7ms       79.1±0.7ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(20, 8192)
          180±3ms          180±1ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(1, 32768)
          165±1ms          165±1ms     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm_backend_with_prop(0)
       95.9±0.9μs       95.9±0.4μs     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(14, 8)
      19.5±0.09ms      19.4±0.09ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(14, 2048)
         78.5±1ms         78.5±1ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(14, 8192)
          344±1ms          344±1ms     1.00  passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id'])
      4.67±0.02ms      4.67±0.01ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 2048)
          483±1ms          483±2ms     1.00  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(14, 'translator')
       26.8±0.2ms       26.7±0.3ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(14, 8192)
          761±3μs          760±6μs     1.00  circuit_construction.CircuitConstructionBench.time_circuit_construction(1, 128)
          987±4ms          985±3ms     1.00  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(20, 'translator')
          502±5ms          501±7ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 131072)
        439±0.3μs          438±2μs     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 128)
          534±7ms          533±4ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(14, 131072)
       1.36±0.01s       1.36±0.01s     1.00  passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id'])
      6.56±0.09ms      6.54±0.09ms     1.00  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_single_schedule(31)
          922±4ms          919±8ms     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_quantum_volume_transpile_50_x_20(1)
        162±0.8ms        161±0.6ms     1.00  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(8, 'translator')
       3.17±0.01s       3.16±0.01s     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_quantum_volume_transpile_50_x_20(3)
          299±2μs        297±0.8μs     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 128)
       38.3±0.2μs       38.1±0.4μs     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(5, 8)
       16.1±0.4ms       16.0±0.2ms     1.00  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_through_pulse_gate(51)
       1.51±0.02s       1.51±0.01s     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_quantum_volume_transpile_50_x_20(0)
      3.47±0.02ms      3.45±0.03ms     1.00  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_through_pulse_gate(11)
      18.8±0.07ms      18.7±0.06ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 8192)
          717±1ms          714±1ms     1.00  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_qv_14_x_14(3)
       24.6±0.1ms      24.5±0.09ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 8192)
      6.53±0.07ms      6.49±0.03ms     1.00  circuit_construction.CircuitConstructionBench.time_circuit_copy(5, 2048)
      1.60±0.02ms      1.59±0.02ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(20, 128)
       26.9±0.3ms      26.8±0.08ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 8192)
      6.64±0.02ms      6.60±0.01ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(14, 2048)
          436±2μs          434±2μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(5, 128)
          330±2ms          328±4ms     0.99  transpiler_levels.TranspilerLevelBenchmarks.time_schedule_qv_14_x_14(0)
        106±0.4ms        105±0.3ms     0.99  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm(3)
       1.76±0.01s          1.75±0s     0.99  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(27, 'translator')
          970±5ms          964±6ms     0.99  passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id'])
      6.78±0.04ms      6.74±0.03ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(20, 2048)
       19.1±0.2ms       18.9±0.1ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(2, 2048)
          128±2ms          127±1ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(20, 32768)
      2.41±0.02ms      2.39±0.04ms     0.99  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_single_schedule(11)
       70.5±0.1ms       69.9±0.8ms     0.99  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_from_large_qasm(0)
          444±3μs          440±1μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(14, 128)
       65.1±0.5μs       64.5±0.2μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(1, 8)
          514±1μs          509±1μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(20, 128)
       26.4±0.2ms      26.2±0.08ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(5, 8192)
          127±2ms        126±0.8ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(5, 32768)
       27.6±0.4μs       27.4±0.4μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 8)
          334±2ms          330±4ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(8, 32768)
          109±3μs        107±0.6μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(2, 8)
          323±2ms          319±5ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(14, 32768)
          437±5μs          432±4μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(20, 8)
          391±2μs        387±0.9μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 128)
          129±1ms          127±1ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(14, 32768)
       44.2±0.3ms       43.7±0.4ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(1, 8192)
          326±2ms          322±4ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(20, 32768)
       13.5±0.2ms       13.3±0.1ms     0.99  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(2, 'translator')
          734±3ms          725±3ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(1, 131072)
       36.7±0.2μs       36.2±0.1μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 8)
          120±1ms        119±0.9ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 32768)
          537±8ms          531±4ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 131072)
        117±0.4μs        115±0.4μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(5, 8)
          136±1μs        134±0.9μs     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(20, 8)
       1.32±0.01s       1.30±0.02s     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(20, 131072)
      7.87±0.09ms      7.76±0.08ms     0.99  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_parameterized_subroutine(31)
      6.16±0.07ms      6.07±0.02ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 2048)
      1.29±0.01ms      1.27±0.01ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_construction(2, 128)
          129±3ms          127±1ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 32768)
          537±4ms          529±6ms     0.99  circuit_construction.CircuitConstructionBench.time_circuit_copy(5, 131072)
       12.7±0.1ms      12.5±0.04ms     0.98  pulse.schedule_construction.ParameterizedScheduleBench.time_assign_parameterized_subroutine(51)
          319±2ms          314±3ms     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(2, 32768)
       1.35±0.02s          1.33±0s     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(5, 131072)
          335±7ms          330±6ms     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(5, 32768)
         80.9±2ms         79.4±2ms     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(8, 8192)
         78.6±2ms       77.1±0.9ms     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(2, 8192)
       1.35±0.01s       1.32±0.02s     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(8, 131072)
          309±5μs          303±3μs     0.98  circuit_construction.CircuitConstructionBench.time_circuit_construction(14, 8)
         549±20ms          535±6ms     0.97  circuit_construction.CircuitConstructionBench.time_circuit_copy(20, 131072)
          184±2μs          180±1μs     0.97  circuit_construction.CircuitConstructionBench.time_circuit_construction(8, 8)
          279±5ms          269±2ms     0.97  transpiler_levels.TranspilerLevelBenchmarks.time_transpile_qv_14_x_14(0)
         56.9±1ms       53.9±0.5ms     0.95  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(5, 'translator')
         22.1±3ms       18.9±0.6ms    ~0.86  quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile(3, 'translator')

BENCHMARKS NOT SIGNIFICANTLY CHANGED.

Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

Looks good so far. Just a few minor comments.

qiskit/circuit/singleton_gate.py Outdated Show resolved Hide resolved
qiskit/circuit/controlledgate.py Outdated Show resolved Hide resolved
qiskit/circuit/library/standard_gates/s.py Outdated Show resolved Hide resolved
qiskit/circuit/singleton_gate.py Outdated Show resolved Hide resolved
qiskit/circuit/singleton_gate.py Outdated Show resolved Hide resolved
qiskit/circuit/singleton_gate.py Outdated Show resolved Hide resolved
qiskit/circuit/singleton_gate.py Outdated Show resolved Hide resolved
releasenotes/notes/singletons-83782de8bd062cbc.yaml Outdated Show resolved Hide resolved
releasenotes/notes/singletons-83782de8bd062cbc.yaml Outdated Show resolved Hide resolved
test/python/circuit/test_singleton_gate.py Outdated Show resolved Hide resolved
Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

Overall LGTM!

My only suggestion would be that the release notes seem focused more heavily on users who are extending Qiskit with additional instructions and gates rather than on the user impact for building circuits. Specifically, I think we might want to communicate more concisely that gates in our circuit library not customized during construction no longer support mutation out of the box.


def __init__(
self,
num_ctrl_qubits: int,
dirty_ancillas: bool = False,
label: Optional[str] = None,
ctrl_state: Optional[Union[str, int]] = None,
_base_label=None,
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to have unit and duration as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

This one gets tricky based on the how MCXGate works. For some gates it calls __init__ from __new__ of MCXGate() and tracking the args gets confusing because of the layer of indirection. I'll double check these really quickly to see if they're missing something.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I just double checked for singleton gates in this PR I think we're ok. If the number of controls results in a discrete gate class like CXGate or CCXGate we already have the handling on __new__ which internally calls __init__ for the gate object in MCXGate.__new__ (which is the parent of this). If it's not then it's not a singleton because the gates are parameterized over the number of controls. There is a second PR #11013 which will cover this more broadly, so I'd say lets just save this for that PR.

Comment on lines 73 to 75
:class:`~.ControlledGate` and its direct subclasses, but mutation of these attributes
on :class:`~.SingletonControlledGate` and its subclasses is disallowed, and will raise
and exception. These attributes can be customized but only at creation time (i.e. via
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't quite right anymore since now SingletonControlledGate is a direct subclass of ControlledGate :3

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated this in: 3a108ab let me know if there is more to update there.

@mtreinish
Copy link
Member Author

My only suggestion would be that the release notes seem focused more heavily on users who are extending Qiskit with additional instructions and gates rather than on the user impact for building circuits. Specifically, I think we might want to communicate more concisely that gates in our circuit library not customized during construction no longer support mutation out of the box.

There was an existing section on the user upgrade impact of singleton gates from #10314 that I expanded in this PR. I added a bit more detail to it in 3a108ab. But I think we can save deeper tweaks for the final release notes PR, especially since we'll likely be putting singletons in the prelude.

Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks for the doc fixes.

@kevinhartman kevinhartman added this pull request to the merge queue Oct 16, 2023
Merged via the queue into Qiskit:main with commit 111fd64 Oct 16, 2023
13 checks passed
@mtreinish mtreinish deleted the singleton-controlled-gates branch October 16, 2023 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: API Change Include in the "Changed" section of the changelog Changelog: New Feature Include in the "Added" section of the changelog priority: high
Projects
Status: done
Development

Successfully merging this pull request may close these issues.

3 participants