Skip to content

Add lattice-utils to dwave-experimental#44

Open
SebastianGitt wants to merge 9 commits into
dwavesystems:mainfrom
SebastianGitt:add-lattice-utils
Open

Add lattice-utils to dwave-experimental#44
SebastianGitt wants to merge 9 commits into
dwavesystems:mainfrom
SebastianGitt:add-lattice-utils

Conversation

@SebastianGitt
Copy link
Copy Markdown
Contributor

Add a minimal version of LatQA to dwave-experimental as a submodule named lattice-utils. Currently supports examples of a 1D Ising Chain and a 2D dimerized triangular lattice. Tests still need to be added to the pull request.

-move initialization of num_spins to subclass
Copy link
Copy Markdown

@thisac thisac left a comment

Choose a reason for hiding this comment

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

Thanks @SebastianGitt! First pass of the lattice submodule.

  • I'd recommend running a formatter (black) since all of these files are new and there are a few places with incorrect spacing, etc.
  • I think the saving/loading of embeddings can be refined a bit, but I'll have a closer look at it later.

Comment on lines +15 to +19
# from latqa.experiment import *
# from latqa.lattice import *
# from latqa.observable import *
# from latqa.analysis import *
# from latqa._paths import *
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Forgotten? Should probably just be

import dwave.experimental.lattice_utils.experiment
import dwave.experimental.lattice_utils.lattice
...

unless there any any general functions that should be accessible directly from the dwave.experimental.lattice_utils namespace.

seed: int | None = None,
skipnan: bool = True,
) -> list[float]:
"""Compute bootstrap estimates of a statistic."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Expand on docstring, add args, return, etc.

repetitions: int,
seed: int | None = None,
) -> Iterator[NDArray]:
"""Generate resampled indices."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same here.



def confidence_interval(array: NDArray, width: float = 0.95) -> tuple[float, float, float]:
"""Ravel and take the quantiles; return median and error bar lengths."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

And here.

def generate_bootstrap_indices(
size: int,
repetitions: int,
seed: int | None = None,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Better to use NumPy's random.default_rng instead (see note on random.seed doc).

Suggested change
seed: int | None = None,
rng: np.random.Generator | None = None,

and then use rng.choice(...) below.

u: Hashable,
v: Hashable | None = None,
) -> Iterator[tuple[int, int]]:
"""Should also work for chains! These can be thought of as self-loops."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Update docstring and add args, returns.

if u == v or v is None:
# Interior chain connectivity.
# Generic version: add all possible edges.
return ((0, 1),)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why return tuple[tuple[int, int]] instead of just tuple[int, int]?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It represents a tuple of edges. In this case it's only a single edge but for other geometries it's expected that get_chain_connectivity() returns multiple edges

import os
from pathlib import Path
from collections.abc import Hashable
from numbers import Integral
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not used.

Suggested change
from numbers import Integral

Comment on lines +29 to +30
__all__ = ['Lattice']

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
__all__ = ['Lattice']
__all__ = ['Lattice']

Comment on lines +42 to +43
if not hasattr(self, "num_spins"):
raise AttributeError(f"{type(self).__name__} subclass must initialize self.num_spins")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This would make it pointless to instantiate Lattice directly, no? Why not make it into an abstract base-class instead, adding in potentially relevant abstract methods, etc.?

-added more comprehensive docstrings
-fixed formatting issues
-added more comprehensive docstrings
-fixed formatting issues
-removed spin reversal transform functionality
-uses automorphism module already in dwave-experimental
-uses data classes for experiment configs
-removed spin reversal transform functionality
-uses automorphism module already in dwave-experimental
-uses data classes for experiment configs
-formatted using black
@SebastianGitt SebastianGitt marked this pull request as ready for review May 14, 2026 17:50
Copy link
Copy Markdown

@andrew-d-king andrew-d-king left a comment

Choose a reason for hiding this comment

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

Looks generally good; I am not able to pore over it in detail but it all seems sensible.

response_dict = {}
call_dict = {}

for index, param in enumerate(parameter_list):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would like to see the progress bars restored here.

ret = parameter_list.copy()
for entry in ret:
if "anneal_time" in entry:
entry["anneal_time"] = np.round(entry["anneal_time"], 6)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Always nice to see smarter ways of doing things 👍 didn't know np.round took that parameter!


return sampler_call

def _format_parameter_list(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe not for this PR, but ultimately I think that a parameter class should be written, with a format parameter (basically sig figs, for float parameters). It's a bit ugly to do it ad hoc, as I've written it.

filename = title
for bad_symbol in "/: ;,":
filename = filename.replace(bad_symbol, "_")
fig.savefig(Path(os.getcwd()) / 'figures' / f"{filename}.png")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All the figures should be tight_layout(), then saved, then shown. It's inconsistent right now.

plt.show()

# Now we will analyze the kink-kink correlator for the fastest anneals (5ns)
fig3, ax3 = plt.subplots(1, 2, figsize=(10, 8))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It should be noted that running only five iterations will give poor results for ckk, which is fairly sensitive and requires a converged shim. It also requires more data. But we don't want to put too much QPU usage in a mere example, so I think it is best to just point this out clearly.


ax = axes[2, 0]
ax.plot(cshim[0])
ax.set_title(f"Coupler shim, t_a={ANNEAL_TIMES[0]:.3f}μs")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
ax.set_title(f"Coupler shim, t_a={ANNEAL_TIMES[0]:.3f}μs")
ax.set_title(f"Coupler shim, $t_a=${ANNEAL_TIMES[0]:.3f}μs")


ax = axes[2, 1]
ax.plot(cshim[1])
ax.set_title(f"Coupler shim, t_a={ANNEAL_TIMES[1]:.3f}μs")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
ax.set_title(f"Coupler shim, t_a={ANNEAL_TIMES[1]:.3f}μs")
ax.set_title(f"Coupler shim, $t_a=${ANNEAL_TIMES[1]:.3f}μs")


ax = axes[2, 2]
ax.plot(cshim[6])
ax.set_title(f"Coupler shim, t_a={ANNEAL_TIMES[-1]:.3f}μs")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
ax.set_title(f"Coupler shim, t_a={ANNEAL_TIMES[-1]:.3f}μs")
ax.set_title(f"Coupler shim, $t_a=${ANNEAL_TIMES[-1]:.3f}μs")

config = experiment.FastAnnealExperimentConfig(
energy_scale=energy_scale,
coupler_shim_step=0.05,
flux_bias_shim_step=1e-6,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
flux_bias_shim_step=1e-6,
flux_bias_shim_step=3e-6,

# Make parameter list. We will only vary anneal time.
parameter_list = [{"anneal_time": time} for time in ANNEAL_TIMES]

for _ in range(20): #TODO clean this up?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
for _ in range(20): #TODO clean this up?
while True:

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.

3 participants