Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pygsti/baseobjs/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import itertools as _itertools
import numbers as _numbers
import sys as _sys
import copy as _copy
import numpy as _np


Expand Down Expand Up @@ -138,7 +139,8 @@ def __new__(cls, name, state_space_labels=None, time=None, args=None):
return LabelStr.init(name, time)

else:
if args is not None: return LabelTupWithArgs.init(name, state_space_labels, time, args)
if args is not None:
return LabelTupWithArgs.init(name, state_space_labels, time, args)
else:
if time == 0.0:
return LabelTup.init(name, state_space_labels)
Expand Down Expand Up @@ -200,6 +202,8 @@ def is_simple(self):

return self.IS_SIMPLE

def copy(self):
return _copy.deepcopy(self)


class LabelTup(Label, tuple):
Expand Down
122 changes: 78 additions & 44 deletions pygsti/circuits/circuit.py

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions pygsti/forwardsims/forwardsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
from pygsti.baseobjs.resourceallocation import ResourceAllocation as _ResourceAllocation
from pygsti.baseobjs.nicelyserializable import NicelySerializable as _NicelySerializable
from pygsti.tools import slicetools as _slct
from typing import Union, Callable, Literal
from typing import Union, Callable, Literal, Optional, TYPE_CHECKING

if TYPE_CHECKING:
from pygsti.models.model import OpModel


class ForwardSimulator(_NicelySerializable):
Expand Down Expand Up @@ -96,7 +99,7 @@ def _array_types_for_method(cls, method_name):
return ('ep', 'ep') + cls._array_types_for_method('_bulk_fill_dprobs_block')
return ()

def __init__(self, model=None):
def __init__(self, model: Optional[OpModel] = None):
super().__init__()
#self.dim = model.dim
self.model = model
Expand Down Expand Up @@ -128,11 +131,11 @@ def __getstate__(self):
return state_dict

@property
def model(self):
def model(self) -> Union[OpModel, None]:
return self._model

@model.setter
def model(self, val):
def model(self, val: OpModel):
self._model = val
try:
evotype = None if val is None else self._model.evotype
Expand Down
2 changes: 1 addition & 1 deletion pygsti/layouts/evaltree.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def _get_start_indices(max_intersect):
(_time.time() - tm)); tm = _time.time()

#merge_method = "fast"
#Another possible algorith (but slower)
#Another possible algorithm (but slower)
#if merge_method == "best":
# while len(indicesLeft) > 0:
# iToMergeInto,_ = min(enumerate(map(len,subTreeSetList)),
Expand Down
10 changes: 6 additions & 4 deletions pygsti/layouts/prefixtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from math import ceil
from pygsti.baseobjs import Label as _Label
from pygsti.circuits.circuit import SeparatePOVMCircuit as _SeparatePOVMCircuit
from pygsti.tools.tqdm import our_tqdm


class PrefixTable(object):
Expand Down Expand Up @@ -689,7 +690,7 @@ def _cache_hits(circuit_reps, circuit_lengths):
cacheIndices = [] # indices into circuits_to_evaluate of the results to cache
cache_hits = [0]*len(circuit_reps)

for i in range(len(circuit_reps)):
for i in our_tqdm(range(len(circuit_reps)), 'Prefix table : _cache_hits '):
circuit = circuit_reps[i]
L = circuit_lengths[i] # can be a Circuit or a label tuple
for cached_index in reversed(cacheIndices):
Expand All @@ -707,10 +708,11 @@ def _build_table(sorted_circuits_to_evaluate, cache_hits, max_cache_size, circui

# Build prefix table: construct list, only caching items with hits > 0 (up to max_cache_size)
cacheIndices = [] # indices into circuits_to_evaluate of the results to cache
table_contents = [None]*len(sorted_circuits_to_evaluate)
num_sorted_circuits = len(sorted_circuits_to_evaluate)
table_contents = [None]*num_sorted_circuits
curCacheSize = 0
for j, (i, _) in zip(orig_indices,enumerate(sorted_circuits_to_evaluate)):

for i in our_tqdm(range(num_sorted_circuits), 'Prefix table : _build_table '):
j = orig_indices[i]
circuit_rep = circuit_reps[i]
L = circuit_lengths[i]

Expand Down
6 changes: 4 additions & 2 deletions pygsti/modelmembers/modelmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def relink_parent(self, parent):
assert(self._parent is None), "Cannot relink parent: parent is not None!"
self._parent = parent # assume no dependent objects

def unlink_parent(self, force=False):
def unlink_parent(self, force: bool=False):
"""
Remove the parent-link of this member.

Expand All @@ -330,12 +330,14 @@ def unlink_parent(self, force=False):
parent still contains references to it. If `False`, the parent
is only set to `None` when its parent contains no reference to it.

Passed through to unlink_parent calls for submembers of this model member.

Returns
-------
None
"""
for subm in self.submembers():
subm.unlink_parent()
subm.unlink_parent(force)

if (self.parent is not None) and (force or self.parent._obj_refcount(self) == 0):
self._parent = None
Expand Down
5 changes: 4 additions & 1 deletion pygsti/modelmembers/operations/opfactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
#***************************************************************************************************

from __future__ import annotations
import numpy as _np

from pygsti.modelmembers.operations.staticunitaryop import StaticUnitaryOp as _StaticUnitaryOp
Expand All @@ -24,9 +26,10 @@
from pygsti.baseobjs import basis as _basis
from pygsti.evotypes import Evotype as _Evotype
from pygsti.tools import optools as _ot
from pygsti.modelmembers.operations.linearop import LinearOperator as _LinearOperator


def op_from_factories(factory_dict, lbl):
def op_from_factories(factory_dict: dict[_Lbl, OpFactory], lbl: _Lbl) -> _LinearOperator:
"""
Create an operator for `lbl` from the factories in `factory_dict`.

Expand Down
9 changes: 5 additions & 4 deletions pygsti/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1806,17 +1806,18 @@ def complete_circuits(self, circuits, prep_lbl_to_prepend=None, povm_lbl_to_appe
already has a prep label this argument will be ignored.

povm_lbl_to_append : Label, optional (default None)
Optional user specified prep label to prepend. If not
Optional user specified povm label to prepend. If not
specified will use the default value as given by
:meth:_default_primitive_prep_layer_lbl. If the circuit
already has a prep label this argument will be ignored.
already has a povm label this argument will be ignored.

return_split : bool, optional (default False)
If True we additionally return a list of tuples of the form:
(prep_label, no_spam_circuit, povm_label)
for each circuit. This is of the same format returned by
:meth:split_circuits when using the kwarg combination:
erroron=('prep', 'povm'), split_prep=True, split_povm=True

Returns
-------
Circuit
Expand All @@ -1836,7 +1837,7 @@ def complete_circuits(self, circuits, prep_lbl_to_prepend=None, povm_lbl_to_appe

#precompute unique default povm labels.
unique_sslbls = set([ckt._line_labels for ckt in circuits])
default_povm_labels = {sslbls:(self._default_primitive_povm_layer_lbl(sslbls),) for sslbls in unique_sslbls}
default_povm_labels = {sslbls: (self._default_primitive_povm_layer_lbl(sslbls),) for sslbls in unique_sslbls}

comp_circuits = []
if return_split:
Expand Down
22 changes: 16 additions & 6 deletions pygsti/optimize/customsolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
_fastcalc = None


CUSTOM_SOLVE_THRESHOLD = 10_000


def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100):
"""
Simple parallel Gaussian Elimination with pivoting.
Expand Down Expand Up @@ -95,7 +98,7 @@ def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100):
return

#Just gather everything to one processor and compute there:
if comm.size < proc_threshold and a.shape[1] < 10000:
if comm.size < proc_threshold and a.shape[1] < CUSTOM_SOLVE_THRESHOLD:
# We're not exactly sure where scipy is better, but until we speed up / change gaussian-elim
# alg the scipy alg is much faster for small numbers of procs and so should be used unless
# A is too large to be gathered to the root proc.
Expand Down Expand Up @@ -163,9 +166,12 @@ def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100):
# Step 1: find the index of the row that is the best pivot.
# each proc looks for its best pivot (Note: it should not consider rows already pivoted on)
potential_pivot_indices = all_row_indices[potential_pivot_mask]
ibest_global, ibest_local, h, k = _find_pivot(a, b, icol, potential_pivot_indices, my_row_slice,
shared_floats, shared_ints, resource_alloc, comm, host_comm,
smbuf1, smbuf2, smbuf3, host_index_buf, host_val_buf)
ibest_global, ibest_local, h, k = _find_pivot(
a, icol, potential_pivot_indices,
my_row_slice, shared_floats, shared_ints, resource_alloc,
comm, host_comm, smbuf1, smbuf1b, smbuf2,
smbuf3, host_index_buf, host_val_buf
)

# Step 2: proc that owns best row (holds that row and is root of param-fine comm) broadcasts it
pivot_row, pivot_b = _broadcast_pivot_row(a, b, ibest_local, h, k, shared_rowb, local_pivot_rowb,
Expand Down Expand Up @@ -210,8 +216,12 @@ def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100):
return


def _find_pivot(a, b, icol, potential_pivot_inds, my_row_slice, shared_floats, shared_ints,
resource_alloc, comm, host_comm, buf1, buf1b, buf2, buf3, best_host_indices, best_host_vals):
def _find_pivot(
a, icol, potential_pivot_inds,
my_row_slice, shared_floats, shared_ints, resource_alloc,
comm, host_comm, buf1, buf1b,
buf2, buf3, best_host_indices, best_host_vals
):

#print(f'Length potential_pivot_inds {len(potential_pivot_inds)}')
#print(f'potential_pivot_inds: {potential_pivot_inds}')
Expand Down
6 changes: 1 addition & 5 deletions pygsti/optimize/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@
import numpy as _np
import scipy.optimize as _spo

try:
from scipy.optimize import Result as _optResult # for earlier scipy versions
except:
from scipy.optimize import OptimizeResult as _optResult # for later scipy versions

from scipy.optimize import OptimizeResult as _optResult
from pygsti.optimize.customcg import fmax_cg
from pygsti.baseobjs.verbosityprinter import VerbosityPrinter as _VerbosityPrinter

Expand Down
7 changes: 7 additions & 0 deletions pygsti/tools/tqdm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
try:
from tqdm import tqdm
def our_tqdm(iterator, message):
return tqdm(iterator, message)
except ImportError:
def our_tqdm(iterator, ignore):
return iterator
15 changes: 11 additions & 4 deletions test/unit/extras/ibmq/test_ibmqexperiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@
from pygsti.extras.devices.experimentaldevice import ExperimentalDevice
from pygsti.extras import ibmq
from pygsti.processors import CliffordCompilationRules as CCR
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit_ibm_runtime import QiskitRuntimeService

try:
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit_ibm_runtime import QiskitRuntimeService
HAVE_QISKIT = True
except ImportError:
HAVE_QISKIT = False
from pygsti.protocols import MirrorRBDesign as RMCDesign
from pygsti.protocols import PeriodicMirrorCircuitDesign as PMCDesign
from pygsti.protocols import ByDepthSummaryStatistics
from pygsti.modelpacks import smq1Q_XY
from pygsti.protocols import StandardGSTDesign
import numpy as np
import pytest

class IBMQExperimentTester():
@pytest.mark.skipif(not HAVE_QISKIT, reason="IBMQ experiment tests require qiskit.")
class IBMQExperimentTester:
@classmethod
def setup_class(cls):
cls.backend = GenericBackendV2(num_qubits=4)
Expand Down Expand Up @@ -144,4 +151,4 @@ def test_e2e_MCM_gst(self):
exp.transpile(self.backend)
exp.submit(self.backend)
exp.monitor()
exp.retrieve_results()
exp.retrieve_results()
4 changes: 4 additions & 0 deletions test/unit/mpi/run_me_with_mpiexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@

import pygsti
from pygsti.modelpacks import smq1Q_XYI as std
from pygsti.optimize import customsolve

customsolve.CUSTOM_SOLVE_THRESHOLD = 10
wcomm = MPI.COMM_WORLD
ALLOWED_MESSAGE = f'Running with CUSTOM_SOLVE_THRESHOLD = {customsolve.CUSTOM_SOLVE_THRESHOLD}'
print(ALLOWED_MESSAGE)


class ParallelTest(object):
Expand Down
16 changes: 15 additions & 1 deletion test/unit/mpi/test_mpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import pytest
import subprocess
import sys
import shutil

try:
from mpi4py import MPI
except (ImportError, RuntimeError):
MPI = None

from run_me_with_mpiexec import ALLOWED_MESSAGE

class MPITester:

Expand All @@ -23,9 +25,21 @@ def test_all(self, capfd: pytest.LogCaptureFixture):
if sys.platform == "darwin":
subprocess_args.insert(3, "-oversubscribe")

if shutil.which('mpiexec') is None:
msg = \
"""
mpi4py is installed, but mpiexec is not available. We're exitng this test
with an error.

If you think mpiexec should be available in this shell, perhaps you need to run
`module load mpi` or `spack load mpi` (or something similar) first.
"""
raise RuntimeError(msg)
result = subprocess.run(subprocess_args, capture_output=False, text=True)
out, err = capfd.readouterr()
if len(out) + len(err) > 0:
tmp = out + err
tmp = [t for t in tmp if t != ALLOWED_MESSAGE]
if len(tmp) > 0:
msg = out + '\n'+ 80*'-' + err
raise RuntimeError(msg)
return
Expand Down
Loading