Exemplo n.º 1
0
def get_initial_pulse():
    m = 0.2908882086657218
    b = 0.02521031141769572

    max_rabi_rate = m * 0.7 + b  # rad / ns

    not_duration = np.pi / max_rabi_rate  # ns
    not_values = np.array([max_rabi_rate])
    h_duration = 3 * np.pi / (2 * max_rabi_rate)  # ns
    h_values = np.array([-1j * max_rabi_rate, max_rabi_rate, max_rabi_rate])

    not_pulse = get_pulse_plot_dict(name="$\Omega_{NOT}$",
                                    duration=not_duration,
                                    values=not_values)
    h_pulse = get_pulse_plot_dict(name="$\Omega_{H}$",
                                  duration=h_duration,
                                  values=h_values)
    both_pulses = {**not_pulse, **h_pulse}

    print(both_pulses)

    fig = plt.figure()
    plot_controls(fig, both_pulses, polar=False)
    plt.show()
Exemplo n.º 2
0
#
# where $\omega_C$ is the cavity transition frequency, $a$ is the annihilation operator of a cavity excitation, $b$ is the annihilation operator of the transmon system, $K$ is the Kerr coefficient, $\omega_T$ is the transmon frequency, $\chi$ is the dispersive shift and $\gamma(t)= I(t) + i Q(t)$ is the complex drive amplitude. The basis states in the Hilbert space will be denoted by $|i,j\rangle =|i\rangle_T \otimes |j\rangle_C$, for the transmon number state $|i\rangle_T$ and cavity number state $|j\rangle_C$.
#
# In the remainder of notebook, the transmon system will be operated as a qubit formed by the lowest two energy levels, while the cavity will be populated by the first $n_c$ Fock states. Note that in this configuration, the qubit will exhibit a spectrum of transition frequencies, set $\chi$ apart, each corresponding to a different excitation state of the cavity.

# ## Standard SNAP gate
#
# The objective of a SNAP gate is to impart a phase of $\theta$ to a target Fock state $j$ of the cavity $|j\rangle_C \rightarrow e^{i\theta} |j\rangle_C $. The [standard implementation](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.115.137002) of SNAP gates consists of two control $\pi$ pulses applied on the transmon qubit based on a target Fock state of the cavity. The pulses are performed around different axes, offset apart by an angle $\theta$. By the end of the gate,the qubit makes a net $2\pi$ rotation, disentangling from the targeted Fock state while imparting the desired phase $\theta$ to it.

# In[2]:

standard_SNAP_gate_rabi_rate = 0.2 * chi
controls["Standard"] = standard_SNAP_controls(standard_SNAP_gate_rabi_rate,
                                              theta)

plot_controls(plt.figure(), controls["Standard"], polar=False)

# The plots display the drive pulses $\gamma(t)$ applied on the transmon qubit. Note that since the standard SNAP gates demand high spectral selectivity, the qubit Rabi rates need to be smaller than the the qubit-cavity coupling $|\gamma| < \chi$, making the gate relatively long, (for example see [Heeres at al.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.115.137002)).

# ### Simulate the system evolution under the standard controls
#
# You'll begin by simulating the evolution of the system. For this purpose, the first four states of the cavity ($n_c=4$) will be evenly populated while the qubit will be initialized in the ground state $| 0\rangle_T$, making the total  initial state: $(|0, 0\rangle + |0, 1\rangle + |0, 2\rangle + |0, 3\rangle)/\sqrt{4}$.
#
# You'll target the first Fock state $|1\rangle_C$ with the SNAP gate, evolving the initial state into $(|0, 0\rangle + e^{i\theta}|0, 1\rangle + |0, 2\rangle + |0, 3\rangle)/\sqrt(4)$, for the gate angle $\theta$, here chosen to be $\theta=\pi/2$.

# In[3]:

# simulation parameters

sim_dimt = 2  # transmon dimensions
sim_dimc = 4  # cavity dimensions
else:
    result = load_var(
        "./resources/rydberg-atoms-generating-highly-entangled-states-in-large-atomic-arrays/Rydberg_6atoms_1optimization.json"
    )
    print("\n Optimization results with:")
    print("\t{} qubits".format(result["qubit_count"]))
    print("\t{} segments".format(result["segment_count"]))
    print("\t{} optimization count".format(result["optimization_count"]))
    print("Infidelity reached: {:.1e}".format(result["infidelity"]))

# In[6]:

plot_controls(
    plt.figure(),
    {
        "$\Omega$": result["omega_segments"],
        "$\Delta$": result["delta_segments"]
    },
)
plt.show()

# The above plots show the optimized pulses $\Omega (t)$ and $\Delta (t)$ that produce the final state.
#
# The next cell sets up a function to plot the optimized population dynamics and the final state. This involves simulating the dynamics using the full Hamiltonian, including optimized control terms.

# In[7]:


def plot_result(result):

    # Get physical parameters for the simulation
Exemplo n.º 4
0
# Optimize graph
optimization_result = qctrl.functions.calculate_optimization(
    cost_node_name="infidelity",
    output_node_names=["Omega"],
    graph=graph,
)

# In[6]:

print("Infidelity of gate: " + str(optimization_result.cost))

fig = plt.figure()
plot_controls(
    fig,
    controls={
        "$\\Omega$": optimization_result.output["Omega"],
    }, polar=False)
plt.show()

# In[7]:

# Test optimized pulse on more realistic qubit simulation

optimized_values = np.array([segment["value"] for segment in optimization_result.output["Omega"]])
result = simulate_more_realistic_qubit(duration=duration, values=optimized_values, shots=1024, repetitions=1)

# In[8]:
realized_not_gate = result["unitary"]
not_error = error_norm(realized_not_gate, ideal_not_gate)
Exemplo n.º 5
0
# In[4]:

controls = []
for k in range(control_count):
    # Create a random string of complex numbers for each controls.
    values = amplitudes * np.exp(1j * phases)

    controls.append({"duration": duration, "values": values})

# Plot the last control as an example.
plot_controls(
    figure=plt.figure(),
    controls={
        "$\Omega$": [{
            "duration": duration / segment_count,
            "value": value
        } for value in values]
    },
)

# We can now send those controls to the qubit and get back the results of applying each one of them. We put the returned object in `experiment_results`.

# In[5]:

# Obtain the results of the experiment.
experiment_results = qctrl.functions.calculate_qchack_measurements(
    controls=controls,
    shot_count=shot_count,
)
omega_max = 2 * np.pi * 1e6  # Hz
standard_duration = np.pi / omega_max  # s

standard_pulse_segments = [
    qctrl.types.ComplexSegmentInput(duration=standard_duration, value=omega_max),
]

plot_segments = {
    "$\Omega_Q$": [
        {"duration": segment.duration, "value": segment.value}
        for segment in standard_pulse_segments
    ]
}

qv.plot_controls(plt.figure(), plot_segments)
plt.show()


# ### Noise: Magnetic field with a 1/f spectrum
# 
# The noise part of the Hamiltonian is:
# \begin{align}
# H_{\rm noise}(t) = \eta(t) \sigma_z / 2.
# \end{align}
# 
# We treat the noisy magnetic field environment as a classical noise process $\eta(t)$ coupled to the quantum system with a noise operator $\sigma_z$. This approximate model is often reasonable for real quantum computing hardware when the decoherence time (T2) is the limiting factor, being much shorter than the relaxation time (T1) of the qubits. 
# 
# The noise process $\eta(t)$ is sampled from a noise spectral density that follows a power law: 
# \begin{align}
# S_{\eta}(\omega) = \frac{\omega_{\rm cutoff}^{a-1}}{\omega^a + \omega_{\rm cutoff}^a},
Exemplo n.º 7
0
values = np.array([-1, 3, 2, 3, -2, -1])


def get_pulse_plot_dict(name="default", duration=1, values=np.array([1.0])):
    segments = len(values)
    segment_durations = duration / segments
    pulse_plot_dict = {
        name: [{"duration": segment_durations, "value": v} for v in values]
    }
    return pulse_plot_dict


example_pulse = get_pulse_plot_dict(name="$\Omega$", duration=duration, values=values)

fig = plt.figure()
plot_controls(fig, example_pulse, polar=False)
plt.show()


# Now that we understand how the duration and values relate to the complex Rabi rate $\Omega(t)$, we can create and plot pulses that we think should implement ideal NOT and Hadamard gates.

# In[43]:


max_rabi_rate = 20 * 2 * np.pi  # MHz
not_duration = np.pi / (max_rabi_rate)  # us
not_values = np.array([max_rabi_rate])
h_duration = 3 * np.pi / (2 * max_rabi_rate)  # us
h_values = np.array([-1j * max_rabi_rate, max_rabi_rate, max_rabi_rate])

not_pulse = get_pulse_plot_dict(
Exemplo n.º 8
0
# In[7]:

# Primitive
omega_1x_primitive = [{
    "duration": d["duration"] * micro,
    "value": d["value"] * Mega
} for d in result_primitive.output["drive_1x"]]
omega_1y_primitive = [{
    "duration": d["duration"] * micro,
    "value": d["value"] * Mega
} for d in result_primitive.output["drive_1y"]]

plot_controls(
    plt.figure(),
    {
        "$\\Omega_{x}$": omega_1x_primitive,
        "$\\Omega_{y}$": omega_1y_primitive,
    },
)
plt.suptitle("Primitive", fontsize=14)
plt.show()

# Optimized
omega_1x_optimized = [{
    "duration": d["duration"] * micro,
    "value": d["value"] * Mega
} for d in result_optimized.output["drive_1x"]]
omega_1y_optimized = [{
    "duration": d["duration"] * micro,
    "value": d["value"] * Mega
} for d in result_optimized.output["drive_1y"]]
    rabi_rotation=total_rotation,
    azimuthal_angle=azimuthal_angle,
    maximum_rabi_rate=omega_max,
    name=scheme,
)

# Export pulse segments
drive_segments = [{
    "duration": d,
    "value": v
} for d, v in zip(pulse.durations,
                  pulse.rabi_rates * np.exp(+1j * pulse.azimuthal_angles))]

# In[4]:

plot_controls(plt.figure(), {"$\gamma$": drive_segments})
plt.show()

# The figure is a visualization of the modulus $\Omega$ and phase (or angle) $\phi$ of the primitive drive $\gamma(t)$, which is a square pulse.

# ### Generating an optimized robust pulse
#
# To optimize your control `pulses`, you first need to parametrize them. In this optimization, each pulse is treated as a piecewise-constant function made of a set of `segments`. Each `segment` has a `duration` in time and a `real`/`complex` `value` that is held constant. You can also explore parametrization options in the [Optimization](https://docs.q-ctrl.com/boulder-opal/user-guides/optimization) User guide. After you set up the optimization specifications, such as a fixed modulus for the drive in the following cell, you can generate and visualize the optimized controls. A cost value close to zero indicates that the optimizer was able to find effective controls to perform the desired gate.

# In[5]:

# Optimization inputs
segment_count = 18
duration = 425.0e-6  # s

with qctrl.create_graph() as graph:
Exemplo n.º 10
0
#conduct search
best, score = search.genetic_gaussian_search(QCTRL_loss, seed, search_params,
                                             loss_params)
print('Done! THE BEST IS:')
print('f(%s \n) = %f' % (search.np_2d_print(best), score))
real_q.print_results_single(best, loss_params)

#output a json, normalize just in case
max_amp = max(best[:, 0])
best[:, 0] = best[:, 0] / max_amp
values = best[:, 0] * np.exp(1j * best[:, 1])

#build json and output
json_out = {"duration": loss_params["duration"], "values": values}
# print(json_out)
json_encode = jsonpickle.encode(json_out)
with open(gate_type.lower() + "_gate_pulse.json", 'w') as file:
    file.write(json_encode)

#plot our pulse!
plot_controls(
    figure=plt.figure(),
    controls={
        "$\Omega$": [{
            "duration": json_out["duration"] / segment_count / 1e9,
            "value": value
        } for value in json_out["values"]]
    },
)
plt.show()
Exemplo n.º 11
0
        name="infidelity",
    )

# Run the optimization
result = qctrl.functions.calculate_optimization(
    graph=graph,
    cost_node_name="infidelity",
    output_node_names=["$\\alpha$", "$L(\\alpha)$"],
    optimization_count=1,
)

print(f"Cost: {result.cost}")

# Visualize controls
print("\n\n")
plot_controls(plt.figure(), result.output)
plt.show()


# Unfiltered (top) and filtered (bottom) control amplitudes as a function of time.
# 
# In this example, we re-discretized the filtered pulse to plot it as a finely-grained piecewise-constant plot that can be compared to its unfiltered counterpart. The re-discretization can be skipped if plotting the filtered controls is not desired, as exemplified in the [Optimization user guide](https://docs.q-ctrl.com/boulder-opal/user-guides/optimization).

# ## Optimization with band-limited pulses
# 
# In this section, we show how to optimize a system in which the rates of change of the controls are limited. Using this constraint can help to ensure that optimized controls can be reliably implemented on physical hardware (which may be subject to bandwidth limits, for example). We consider a standard single-qubit system subject to dephasing noise:
# 
# \begin{align*}
# H(t) &= \frac{1}{2} \alpha_1(t)\sigma_{x} + \frac{1}{2} \alpha_2(t)\sigma_{z} + \beta(t) \sigma_{z}  \,, 
# \end{align*}
# 
Exemplo n.º 12
0
def filter_values(alpha_1_values=np.array([0.5]*50)):
    # Define standard matrices
    sigma_x = np.array([[0, 1], [1, 0]])
    sigma_z = np.array([[1, 0], [0, -1]])

    # Define physical constraints
    alpha_max = 2 * np.pi * 8.5e6  # Hz
    nu = 2 * np.pi * 6e6  # Hz
    sinc_cutoff_frequency = 2 * np.pi * 48e6  # Hz
    segment_count = 50
    duration = 250e-9  # s

    # Create graph object
    with qctrl.create_graph() as graph:
        # Create alpha_1(t) signal
        # alpha_1_values = qctrl.operations.bounded_optimization_variable(
        #     count=segment_count,
        #     lower_bound=-alpha_max,
        #     upper_bound=alpha_max,
        # )
        #alpha_1_values = np.array([0.5]*50)
        #print(alpha_1_values)
        #print(type(alpha_1_values))
        alpha_1 = qctrl.operations.pwc_signal(
            values=alpha_1_values,
            duration=duration,
            name="alpha_1",
        )
        # Create filtered signal
        alpha_1_filtered = qctrl.operations.convolve_pwc(
            alpha_1,
            qctrl.operations.sinc_integral_function(sinc_cutoff_frequency),
        )

        # Similarly, create filtered alpha_2(t) signal
        alpha_2_values = qctrl.operations.bounded_optimization_variable(
            count=segment_count,
            lower_bound=-alpha_max,
            upper_bound=alpha_max,
        )
        alpha_2 = qctrl.operations.pwc_signal(
            values=alpha_2_values,
            duration=duration,
            name="alpha_2",
        )
        alpha_2_filtered = qctrl.operations.convolve_pwc(
            alpha_2,
            qctrl.operations.sinc_integral_function(sinc_cutoff_frequency),
        )

        # Create drive term (note the use of STF functions instead of PWC functions,
        # because we are dealing with smooth signals instead of piecewise-constant
        # signals).
        drive = qctrl.operations.stf_operator(alpha_1_filtered, sigma_x / 2)
        # Create clock shift term
        shift = qctrl.operations.stf_operator(alpha_2_filtered, sigma_z / 2)

        # Create dephasing noise term
        dephasing = qctrl.operations.constant_stf_operator(sigma_z / duration)

        # Create target
        target_operator = qctrl.operations.target(operator=sigma_x)

        # Create infidelity (note that we pass an array of sample times, which
        # governs the granularity of the integration procedure)
        infidelity = qctrl.operations.infidelity_stf(
            np.linspace(0, duration, 150),
            qctrl.operations.stf_sum([drive, shift]),
            target_operator,
            [dephasing],
            name="infidelity",
        )

        # Sample filtered signals (to output and plot)
        alpha_1_smooth = qctrl.operations.discretize_stf(
            stf=alpha_1_filtered,
            duration=duration,
            segments_count=500,
            name="alpha_1_filtered",
        )
        alpha_2_smooth = qctrl.operations.discretize_stf(
            stf=alpha_2_filtered,
            duration=duration,
            segments_count=500,
            name="alpha_2_filtered",
        )

    # Run the optimization
    optimization_result = qctrl.functions.calculate_optimization(
        cost_node_name="infidelity",
        output_node_names=["alpha_1", "alpha_2", "alpha_1_filtered", "alpha_2_filtered"],
        graph=graph,
    )

    print("Optimized cost:\t", optimization_result.cost)

    # Plot the optimized controls
    plot_controls(
        plt.figure(),
        controls={
            "$\\alpha_1$": optimization_result.output["alpha_1"],
            "$\\alpha_2$": optimization_result.output["alpha_2"],
        },
    )
    plt.suptitle("Unfiltered pulses")

    plot_controls(
        plt.figure(),
        controls={
            "$L(\\alpha_1)$": optimization_result.output["alpha_1_filtered"],
            "$L(\\alpha_2)$": optimization_result.output["alpha_2_filtered"],
        },
    )
    plt.suptitle("Filtered pulses")

    plt.show()
    print(alpha_1_filtered)
    return(alpha_1_filtered)