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

added _is_copyable to Parameter and updated AParametrizedComponent #447

Open
wants to merge 4 commits into
base: release/0.12.0
Choose a base branch
from

Conversation

oradomskyi
Copy link
Contributor

@oradomskyi oradomskyi commented Aug 4, 2024

Perceval allows to use same parameter object in multiple components of the circuit to give uniform access to the identical components in the circuit. But when copying the circuit, components are making copies of themselves and copying all of the existing parameters, as a result instead of reference to the common Parameter object each new component receives a unique copy of that, and assembly of the circuit runs into RuntimeError "Two parameters with the same name".

Better description in #414 (comment)

@oradomskyi
Copy link
Contributor Author

Failing tests on Windows are not related to the proposed changes

@oradomskyi oradomskyi changed the title added _is_copyable to Parameter and updated AbstractComponent added _is_copyable to Parameter and updated AParametrizedComponent Aug 5, 2024
@oradomskyi
Copy link
Contributor Author

oradomskyi commented Aug 8, 2024

Problem description

Let's consider several examples and see how they function with the current architecture of Perceval, gradually increasing the complexity of a circuit(s).

  1. Component with a Parameter
import perceval as pcvl
from math import pi
from perceval.rendering import format
from perceval.components.unitary_components import PS, BS, PERM

my_phi0 = pcvl.Parameter(name='phi_0')
my_PS0 = PS(phi=my_phi0) # phase shift

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_PS0, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_PS0, format.Format.MPLOT, recursive=True)

OK
image
image

  1. Component with two Parameters, being a reference to a single Parameter object
my_phi0 = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0, phi_bl=my_phi0) # beam splitter

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)

OK
image
image

  1. Component with two Parameters, each being a different object but with the same name
my_phi0 = pcvl.Parameter(name='phi_0')
my_XXXX = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0, phi_bl=my_XXXX) # beam splitter

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)

RuntimeError
image

  1. "Component with Parameter" "Component with Parameter" where Parameter is a reference to a common object
my_phi0 = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0) # beam splitter
my_BS1 = BS(phi_tl=my_phi0) # beam splitter

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)
pcvl.pdisplay(my_BS1, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)
pcvl.pdisplay(my_BS1, format.Format.MPLOT, recursive=True)

OK
image

  1. "Component with Parameter" "Component with Parameter" where two different Parameter objects
my_phi0 = pcvl.Parameter(name='phi_0')
my_XXXX = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0) # beam splitter
my_BS1 = BS(phi_tl=my_XXXX) # beam splitter

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)
pcvl.pdisplay(my_BS1, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_BS0, format.Format.MPLOT, recursive=True)
pcvl.pdisplay(my_BS1, format.Format.MPLOT, recursive=True)

OK
image

  1. Circuit [ "Component with Parameter" "Component with Parameter" ] where Parameter is a reference
my_phi0 = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0) # beam splitter
my_circuit = pcvl.Circuit(2, name="C0").add(0, my_BS0).add(0, my_BS0)

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

OK
We can reuse same parameter object Parameter for unified access to the identical components!
image
image

...few more beam splitters where parameter could be changed from one place
image

  1. Circuit [ "Component with Parameter" "Component with Parameter" ] with two different Parameter objects
my_phi0 = pcvl.Parameter(name='phi_0')
my_XXXX = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0) # beam splitter
my_XXX = BS(phi_tl=my_XXXX) # beam splitter
my_circuit = pcvl.Circuit(2, name="C0").add(0, my_BS0).add(0, my_XXX)

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

RuntimeError
image

  1. Two Circuits ["Component with Parameter"]-[ "Component with Parameter"] where Component [BS with Parameter] is a reference
my_phi0 = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0) # beam splitter
c0 = pcvl.Circuit(2, name="C0").add(0, my_BS0)
c1 = pcvl.Circuit(2, name="C1").add(0, my_BS0)
my_circuit = pcvl.Circuit(2, name="Root").add(0, c0).add(0,c1)

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

OK
image

4x4 with a single Parameter taken as a reference works as well

my_phi0 = pcvl.Parameter(name='phi_0')
my_BS0 = BS(phi_tl=my_phi0) # beam splitter
c0 = pcvl.Circuit(2, name="C0").add(0, my_BS0).add(0, my_BS0).add(0, my_BS0).add(0, my_BS0)
c1 = pcvl.Circuit(2, name="C1").add(0, my_BS0).add(0, my_BS0).add(0, my_BS0).add(0, my_BS0)
my_circuit = pcvl.Circuit(8, name="Root").add(0, c0).add(2,c1).add(4,c0).add(6,c1)

my_phi0.set_value(pi/2)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

image
image

@oradomskyi
Copy link
Contributor Author

oradomskyi commented Aug 8, 2024

  1. Circuit with two Components and two Parameters having the same name, is expected to fail and it does
c0 = (pcvl.Circuit(2, name="C0")
    .add(0, BS(phi_tl=pcvl.P(name='phi_0')))
    .add(0, BS(phi_tl=pcvl.P(name='phi_0')))
) 

RuntimeError
image

  1. Taking Circuit as a reference does not produce RuntimeError, same as when taking Component as a reference. But it fails to make a copy.
c0 = pcvl.Circuit(2, name="C0").add(0, BS(phi_tl=pcvl.P(name='phi_0')))

my_circuit = (pcvl.Circuit(2, name="my_circuit")
    .add(0, c0)
    .add(0, c0)
)

pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

tmp = my_circuit.copy()
print(tmp)

OK on creating and running the circuit
RuntimeError on calling Circuit.copy()

image
image

  1. A bigger structure will perform in the same way - identical components having- or referring to- Parameters with the same names will work as long as these Parameters are
    a) unique to their parent circuit, but not the global structure,
    b) are not defined

OK circuit
RuntimeError copy()

my_phi0 = pcvl.Parameter(name='phi_0')
my_XXXX = pcvl.Parameter(name='phi_1')
my_BS0 = BS(phi_tl=my_phi0)
my_XXX = BS(phi_tl=my_XXXX)
c0 = pcvl.Circuit(2, name="C0").add(0, my_BS0)
c1 = pcvl.Circuit(2, name="C1").add(0, my_XXX)
my_circuit = pcvl.Circuit(2, name="Root").add(0, c0).add(0,c1).add(0, c0).add(0,c1)

my_phi0.set_value(pi/2)
#my_XXXX.set_value(3*pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
#my_XXXX.set_value(3*pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

tmp = my_circuit.copy()
print(tmp)

image

  1. But when parameters are defined it work fine, and is able to make a copy.
    OK circuit
    OK copy()
my_phi0 = pcvl.Parameter(name='phi_0')
my_XXXX = pcvl.Parameter(name='phi_1')
my_BS0 = BS(phi_tl=my_phi0)
my_XXX = BS(phi_tl=my_XXXX)
c0 = pcvl.Circuit(2, name="C0").add(0, my_BS0)
c1 = pcvl.Circuit(2, name="C1").add(0, my_XXX)
my_circuit = pcvl.Circuit(2, name="Root").add(0, c0).add(0,c1).add(0, c0).add(0,c1)

my_phi0.set_value(pi/2)
my_XXXX.set_value(3*pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)
my_phi0.set_value(pi/4)
my_XXXX.set_value(3*pi/4)
pcvl.pdisplay(my_circuit, format.Format.MPLOT, recursive=True)

tmp = my_circuit.copy()
print(tmp)

image

@oradomskyi
Copy link
Contributor Author

oradomskyi commented Aug 8, 2024

What would be the intended behavior of the Perceval in the cases similar to the above?

It could become much harder to fix later, especially if you would like to add a move constructor to the existing features.

I see two possible solutions:

  1. Allow the same name, forbid referencing and enforce copies, and iterate over the Parameter objects with the same name to set the new value.
    O(N) time, O(N) space, design gets simpler as it removes dependencies between objects at different levels of a nested structure

  2. Check the existing design to properly handle nested cases, and have the dedicated page in the documentation describing usage of the parameter control features.
    O(1) time, O(1) space, design gets more sophisticated as it requires reviewing existing dependencies between objects

Let me know what you think :)
Sasha

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.

2 participants