diff --git a/docs/source/figures/GUI.png b/docs/source/figures/GUI.png deleted file mode 100644 index 32d8406..0000000 Binary files a/docs/source/figures/GUI.png and /dev/null differ diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index dc1fcf4..355db27 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -44,6 +44,9 @@ stability diagram of a closed five dot quantum recreated with permission from `[ |recreations| +The code to reproduces these plots can be found in the examples folder of the QArray repository, named +figure_4b.py and figure_4d.py respectively. + .. |arXiv| image:: https://img.shields.io/badge/arXiv-2404.04994-Green.svg .. |PyPI| image:: https://img.shields.io/pypi/v/qarray .. |GitHub Workflow Status| image:: https://github.com/b-vanstraaten/qarray/actions/workflows/windows_tests.yaml//badge.svg diff --git a/examples/figure_3b.py b/examples/figure_4b.py similarity index 100% rename from examples/figure_3b.py rename to examples/figure_4b.py diff --git a/examples/figure_3d.py b/examples/figure_4d.py similarity index 100% rename from examples/figure_3d.py rename to examples/figure_4d.py diff --git a/examples/gui.py b/examples/gui.py index 7f692c6..0c8a940 100644 --- a/examples/gui.py +++ b/examples/gui.py @@ -18,10 +18,10 @@ ] Cgd = [ - [1., 0, 0, 0], - [0, 1., 0, 0.0], - [0, 0, 1., 0], - [0, 0, 0, 1] + [1., 0, 0, 0, 0], + [0, 1., 0, 0.0, 0], + [0, 0, 1., 0, 0], + [0, 0, 0, 1, 1] ] diff --git a/qarray/DotArrays/DotArray.py b/qarray/DotArrays/DotArray.py index 8972e89..bd622fb 100644 --- a/qarray/DotArrays/DotArray.py +++ b/qarray/DotArrays/DotArray.py @@ -6,7 +6,7 @@ from ._helper_functions import (check_algorithm_and_implementation, check_and_warn_user, convert_to_maxwell) from .ground_state import _ground_state_open, _ground_state_closed -from ..functions import _optimal_Vg, compute_threshold +from ..functions import _optimal_Vg, compute_threshold, compute_optimal_virtual_gate_matrix from ..latching_models import LatchingBaseModel from ..qarray_types import Cdd as CddType # to avoid name clash with dataclass cdd from ..qarray_types import CgdNonMaxwell, CddNonMaxwell, VectorList, Cgd_holes, Cgd_electrons, PositiveValuedMatrix, \ @@ -276,6 +276,18 @@ def do2d_closed(self, x_gate: int | str, x_min: float, x_max: float, x_points: i vg = self.gate_voltage_composer.do2d(x_gate, x_min, x_max, x_points, y_gate, y_min, y_max, y_points) return self.ground_state_closed(vg, n_charges) + def compute_optimal_virtual_gate_matrix(self): + """ + Computes the optimal virtual gate matrix for the dot array and sets it as the virtual gate matrix + in the gate voltage composer. + + The virtual gate matrix is computed as the pseudo inverse of the dot to dot capacitance matrix times the dot to gate capacitance matrix. + + returns np.ndarray: the virtual gate matrix + """ + virtual_gate_matrix = compute_optimal_virtual_gate_matrix(self.cdd_inv, self.cgd) + self.gate_voltage_composer.virtual_gate_matrix = virtual_gate_matrix + return virtual_gate_matrix def run_gui(self, port=27182, print_compute_time: bool = False): """ diff --git a/qarray/DotArrays/GateVoltageComposer.py b/qarray/DotArrays/GateVoltageComposer.py index 80d8b31..322363f 100644 --- a/qarray/DotArrays/GateVoltageComposer.py +++ b/qarray/DotArrays/GateVoltageComposer.py @@ -155,7 +155,7 @@ def meshgrid_virtual(self, dots: List[int | str], arrays: List[np.ndarray]) -> n V = np.meshgrid(*arrays) # setting the voltages - for dot in range(self.n_gate): + for dot in range(self.n_dot): # if the gate is not in the gates list then set it to the current voltage if dot not in dots: Vd[..., dot] = 0 diff --git a/qarray/__init__.py b/qarray/__init__.py index 6e639f5..1fc1cec 100644 --- a/qarray/__init__.py +++ b/qarray/__init__.py @@ -6,14 +6,14 @@ from .DotArrays import (DotArray, GateVoltageComposer, ChargeSensedDotArray) from .functions import (_optimal_Vg, dot_occupation_changes, charge_state_contrast, - charge_state_to_scalar) + charge_state_to_scalar, compute_optimal_virtual_gate_matrix) from .latching_models import * from .noise_models import * __all__ = [ 'DotArray', 'GateVoltageComposer', 'ChargeSensedDotArray', '_optimal_Vg', 'dot_occupation_changes', 'charge_state_contrast', - 'charge_state_to_scalar', + 'charge_state_to_scalar', 'compute_optimal_virtual_gate_matrix', ] submodules = ['latching_models', 'noise_models'] diff --git a/qarray/functions.py b/qarray/functions.py index c213fda..fe31625 100644 --- a/qarray/functions.py +++ b/qarray/functions.py @@ -9,6 +9,29 @@ Vector) +def compute_optimal_virtual_gate_matrix( + cdd_inv: CddInv, cgd: Cgd_holes, rcond: float = 1e-4) -> np.ndarray: + """ + Function to compute the optimal virtual gate matrix. + + :param cdd_inv: the inverse of the dot to dot capacitance matrix + :param cgd: the dot to gate capacitance matrix + :param rcond: the rcond parameter for the pseudo inverse + :return: the optimal virtual gate matrix + + """ + n_dot = cdd_inv.shape[0] + n_gate = cgd.shape[1] + virtual_gate_matrix = -np.linalg.pinv(cdd_inv @ cgd, rcond=rcond) + + # if the number of dots is less than the number of gates then we pad with zeros + if n_dot < n_gate: + virtual_gate_matrix = np.pad(virtual_gate_matrix, ((0, 0), (0, n_gate - n_dot)), mode='constant') + + return virtual_gate_matrix + + + def charge_state_to_scalar(n: Tetrad | np.ndarray) -> int: """ Function to convert the charge state to a unique index, using the binary representation. diff --git a/qarray/gui/gui.py b/qarray/gui/gui.py index 4746214..c508ca3 100644 --- a/qarray/gui/gui.py +++ b/qarray/gui/gui.py @@ -32,88 +32,122 @@ def run_gui(model, port=27182, run=True, print_compute_time=False): n_gate = model.n_gate # Create the gate options - gate_options = create_gate_options(model.n_gate) + gate_options = create_gate_options(model.n_gate, model.n_dot) # Convert the matrices to DataFrames for display in the tables Cdd = pd.DataFrame(model.Cdd, dtype=float, columns=[f'D{i + 1}' for i in range(n_dot)]) Cgd = pd.DataFrame(model.Cgd, dtype=float, columns=[f'P{i + 1}' for i in range(n_gate)]) + Cdd[''] = [f'D{i + 1}' for i in range(n_dot)] + Cgd[''] = [f'D{i + 1}' for i in range(n_dot)] + + # making the '' column the first column + Cdd = Cdd[[''] + [col for col in Cdd.columns if col != '']] + Cgd = Cgd[[''] + [col for col in Cgd.columns if col != '']] + + virtual_gate_matrix = model.compute_optimal_virtual_gate_matrix() + virtual_gate_matrix = np.round(virtual_gate_matrix, 3) + virtual_gate_matrix = pd.DataFrame(virtual_gate_matrix, dtype=float, columns=[f'vP{i + 1}' for i in range(n_gate)]) + app.layout = html.Div([ + # First Row: Tables html.Div([ html.Div([ html.H4("C dot-dot"), dash_table.DataTable( id='editable-table1', - columns=[{"name": i, "id": i, "type": "numeric"} for i in Cdd.columns], - data=Cdd.reset_index().astype(float).to_dict('records'), + columns=[{"name": i, "id": i} for i in Cdd.columns], + data=Cdd.reset_index().to_dict('records'), editable=True, + style_cell_conditional=[ + { + 'if': {'column_id': Cdd.columns[0]}, + 'backgroundColor': '#fafafa' # Light gray color for shading + } + ] ) - ], style={'display': 'inline-block', 'width': '40%', 'margin-right': '2%', 'vertical-align': 'top'}), + ], style={'width': '32%', 'margin-right': '2%'}), html.Div([ html.H4("C gate-dot"), dash_table.DataTable( id='editable-table2', - columns=[{"name": i, "id": i, "type": "numeric"} for i in Cgd.columns], - data=Cgd.reset_index().astype(float).to_dict('records'), + columns=[{"name": i, "id": i} for i in Cgd.columns], + data=Cgd.reset_index().to_dict('records'), + editable=True, + style_cell_conditional=[ + { + 'if': {'column_id': Cgd.columns[0]}, + 'backgroundColor': '#fafafa' # Light gray color for shading + } + ] + ) + ], style={'width': '32%', 'margin-right': '2%'}), + + html.Div([ + html.H4("Virtual gate matrix"), + dash_table.DataTable( + id='virtual-gate-matrix', + columns=[{"name": i, "id": i, "type": "numeric"} for i in virtual_gate_matrix.columns], + data=virtual_gate_matrix.reset_index().astype(float).to_dict('records'), editable=True ) - ], style={'display': 'inline-block', 'width': '40%', 'margin-right': '2%', 'vertical-align': 'top'}), + ], style={'width': '32%'}), - ], style={'text-align': 'left', 'margin-bottom': '20px', 'display': 'flex', - 'justify-content': 'space-between'}), + ], style={'display': 'flex', 'justify-content': 'space-between', 'margin-bottom': '20px'}), + # Second Row: Sweep Options and DAC values html.Div([ html.Div([ - html.H4("x sweep options"), + html.H4("X sweep options"), dcc.Dropdown( id='dropdown-menu-x', - placeholder='x gate', + placeholder='X gate', options=gate_options, value='P1' ), dcc.Input( id='input-scalar-x1', type='number', - placeholder='x_amplitude', + placeholder='X amplitude', value=5, style={'margin-left': '10px'} ), dcc.Input( id='input-scalar-x2', type='number', - placeholder='x_resolution', + placeholder='X resolution', value=200, style={'margin-left': '10px'} ), - ], style={'display': 'inline-block', 'width': '30%', 'margin-right': '2%', 'vertical-align': 'top'}), + ], style={'width': '24%', 'margin-right': '2%'}), html.Div([ - html.H4("y sweep options"), + html.H4("Y sweep options"), dcc.Dropdown( id='dropdown-menu-y', - placeholder='y gate', + placeholder='Y gate', options=gate_options, value=f"P{model.n_gate}" ), dcc.Input( id='input-scalar1', type='number', - placeholder='y_amplitude', + placeholder='Y amplitude', value=5, style={'margin-left': '10px'} ), dcc.Input( id='input-scalar2', type='number', - placeholder='y_resolution', + placeholder='Y resolution', value=200, style={'margin-left': '10px'} ), - ], style={'display': 'inline-block', 'width': '30%', 'margin-right': '2%', 'vertical-align': 'top'}), + ], style={'width': '24%', 'margin-right': '2%'}), html.Div([ - html.H4("Dac values"), + html.H4("DAC values"), *[ dcc.Input( id=f'dac_{i}', @@ -124,7 +158,19 @@ def run_gui(model, port=27182, run=True, print_compute_time=False): style={'margin-bottom': '10px', 'display': 'block'} ) for i in range(model.n_gate) ] - ], style={'display': 'inline-block', 'width': '30%', 'vertical-align': 'top'}), + ], style={'width': '24%'}), + + ], style={'display': 'flex', 'justify-content': 'space-between', 'margin-bottom': '20px'}), + + # Third Row: Plot Options and Heatmap + html.Div([ + + html.Div([ + dcc.Graph( + id='heatmap', + style={'width': '100%', 'margin-left': 'auto', 'margin-right': 'auto'} + ) + ], style={'width': '78%', 'text-align': 'center'}), html.Div([ html.H4("Open/Closed options"), @@ -134,46 +180,47 @@ def run_gui(model, port=27182, run=True, print_compute_time=False): options=n_charges_options, value='any' ), - html.H4("Plot options"), dcc.Dropdown( id='plot-options', - placeholder='Whether to plot changes or colours and if so the colour map', + placeholder='Select plot options', options=plot_options, value='changes' + ), + html.H4("Automatically update virtual gate matrix"), + dcc.Dropdown( + id='automatically-update-virtual-gate-matrix', + placeholder='Auto-update virtual gate matrix', + options=[ + {'label': 'True', 'value': 'True'}, + {'label': 'False', 'value': 'False'} + ], + value='True' ) + ], style={'width': '20%', 'margin-right': '2%'}), - ], style={'display': 'inline-block', 'width': '30%', 'margin-right': '2%', 'vertical-align': 'top'}), - - ], style={'text-align': 'left', 'margin-bottom': '20px', 'display': 'flex', - 'justify-content': 'space-between'}), - - html.Div([ - html.Div([ - dcc.Graph( - id='heatmap', - style={'width': '100%', 'display': 'block', 'margin-left': 'auto', 'margin-right': 'auto'} - ) - ], style={'text-align': 'center', 'margin-top': '20px'}) - ]) + ], style={'display': 'flex', 'justify-content': 'space-between', 'margin-top': '20px'}) ]) @app.callback( - Output('heatmap', 'figure'), - Input('editable-table1', 'data'), - Input('editable-table2', 'data'), - Input('dropdown-menu-x', 'value'), - Input('input-scalar-x1', 'value'), - Input('input-scalar-x2', 'value'), - Input('dropdown-menu-y', 'value'), - Input('input-scalar1', 'value'), - Input('input-scalar2', 'value'), - Input('dropdown-menu-n-charges', 'value'), - Input('plot-options', 'value'), - *[Input(f'dac_{i}', 'value') for i in range(model.n_gate)] + (Output('heatmap', 'figure'), + Output('virtual-gate-matrix', 'data')), + [Input('editable-table1', 'data'), + Input('editable-table2', 'data'), + Input('virtual-gate-matrix', 'data'), + Input('dropdown-menu-x', 'value'), + Input('input-scalar-x1', 'value'), + Input('input-scalar-x2', 'value'), + Input('dropdown-menu-y', 'value'), + Input('input-scalar1', 'value'), + Input('input-scalar2', 'value'), + Input('dropdown-menu-n-charges', 'value'), + Input('plot-options', 'value'), + Input('automatically-update-virtual-gate-matrix', 'value'), + *[Input(f'dac_{i}', 'value') for i in range(model.n_gate)]] ) - def update(Cdd, Cgd, x_gate, x_amplitude, x_resolution, y_gate, y_amplitude, y_resolution, - n_charges, plot_options, *dac_values): + def update(Cdd, Cgd, virtual_gate_matrix, x_gate, x_amplitude, x_resolution, y_gate, y_amplitude, y_resolution, + n_charges, plot_options, automatically_update_virtual_gate_matrix, *dac_values): """ Update the heatmap based on the input values. """ @@ -189,8 +236,8 @@ def update(Cdd, Cgd, x_gate, x_amplitude, x_resolution, y_gate, y_amplitude, y_r try: # Convert table data back to matrices - Cdd = pd.DataFrame(Cdd).set_index('index').astype(float) - Cgd = pd.DataFrame(Cgd).set_index('index').astype(float) + Cdd = pd.DataFrame(Cdd).drop(columns=['']).set_index('index').astype(float) + Cgd = pd.DataFrame(Cgd).drop(columns=['']).set_index('index').astype(float) except ValueError: print('Error the capacitance matrices cannot be converted to float. \n') return go.Figure() @@ -206,6 +253,18 @@ def update(Cdd, Cgd, x_gate, x_amplitude, x_resolution, y_gate, y_amplitude, y_r model.update_capacitance_matrices(Cdd=cdd_matrix, Cgd=Cgd.to_numpy()) + if automatically_update_virtual_gate_matrix == 'True': + + virtual_gate_matrix = model.compute_optimal_virtual_gate_matrix() + virtual_gate_matrix = np.round(virtual_gate_matrix, 3) + virtual_gate_matrix = pd.DataFrame(virtual_gate_matrix, dtype=float, + columns=[f'vP{i + 1}' for i in range(n_gate)]) + else: + virtual_gate_matrix = pd.DataFrame(virtual_gate_matrix) + + model.gate_voltage_composer.virtual_gate_matrix = virtual_gate_matrix.to_numpy()[:, :n_dot] + + vg = model.gate_voltage_composer.do2d( x_gate, -x_amplitude / 2, x_amplitude / 2, x_resolution, y_gate, -y_amplitude / 2, y_amplitude / 2, y_resolution @@ -281,7 +340,7 @@ def update(Cdd, Cgd, x_gate, x_amplitude, x_resolution, y_gate, y_amplitude, y_r height=600, ) - return fig + return fig, virtual_gate_matrix.to_dict('records') # Run the server if run: diff --git a/qarray/gui/helper_functions.py b/qarray/gui/helper_functions.py index cb5d2fc..cfc6078 100644 --- a/qarray/gui/helper_functions.py +++ b/qarray/gui/helper_functions.py @@ -34,11 +34,11 @@ def unique_last_axis(arr): return unique_arrays -def create_gate_options(N): - true_gates = [f'P{i + 1}' for i in range(N)] - virtual_gates = [f'vP{i + 1}' for i in range(N)] - e_gates = [f'e{i + 1}_{j + 1}' for i, j in combinations(range(N), 2)] - u_gates = [f'U{i + 1}_{j + 1}' for i, j in combinations(range(N), 2)] +def create_gate_options(n_gate, n_dot): + true_gates = [f'P{i + 1}' for i in range(n_gate)] + virtual_gates = [f'vP{i + 1}' for i in range(n_dot)] + e_gates = [f'e{i + 1}_{j + 1}' for i, j in combinations(range(n_dot), 2)] + u_gates = [f'U{i + 1}_{j + 1}' for i, j in combinations(range(n_dot), 2)] return [{'label': gate, 'value': gate} for gate in true_gates + virtual_gates + e_gates + u_gates]