Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
889298e
feat: Move the package to use poetry for installation.
ErikKiss-FunnyFox Jan 27, 2025
80abf92
refactor: Convert to base class and subclasses to implement different…
ErikKiss-FunnyFox Jan 27, 2025
2b6a68f
changed get_embeddings to have data_dir as input parameter
Jan 29, 2025
3bdd1b8
added self.j_map to base Adjudicator __init__ and use it in _game_sta…
Jan 29, 2025
71d861a
changed to compare old and new; quantum_annealing not added yet
Jan 29, 2025
07c0fa5
added 'data_dir' kwarg requirement, and requirement to always load/co…
Jan 29, 2025
86047af
PEP
Jan 29, 2025
d5b363d
temporarily set USE_QC and USE_MOCK_DWAVE_SAMPLER to True for testing
Jan 29, 2025
89392c3
added self.data_dir to __init__, changed variable names to be the sam…
Jan 29, 2025
ee97af5
just quantum_annealing to test
Jan 29, 2025
1469ff9
Work in Progress -- going through carefully
Jan 29, 2025
d9a0f5b
chnaged variable names to match erik
Jan 30, 2025
f7d765f
WIP
Jan 30, 2025
5ef7498
fixed epsilon value
Jan 31, 2025
67423c9
comment and PEP changes
Jan 31, 2025
9c2c30f
PEP
Jan 31, 2025
54e37e2
reverted to the original code but with the new wrappings
Jan 31, 2025
b543d6a
PEP
Jan 31, 2025
5178d52
PEP and removed the remove vertices stuff (not required for SA)
Jan 31, 2025
3a2e575
removed references to old adjudicators
Jan 31, 2025
53051e2
changed a couple variable names and the function name of convert_my_g…
Feb 1, 2025
f5208b0
removed all references to old_adjudicator
Feb 1, 2025
50a6df4
WIP need to integrate bug fixes from tangled-cruft/visualize_and_enum…
Feb 1, 2025
9e15235
Integrated bug fixes from tangled-cruft/visualize_and_enumerate_k4
Feb 1, 2025
5463317
fragile and not great but enough to check adjudication results
Feb 1, 2025
3ad6120
moved over to new way of calling adjudicators and checked vs graph 2 …
Feb 1, 2025
6dfad14
moved Params and MinimalAdjudicationParameters into adjudicate
Feb 1, 2025
71bb9af
changed file name to indicate deprecation
Feb 1, 2025
9acd462
removed cruft
Feb 1, 2025
90187d6
PEP
Feb 1, 2025
537c80a
changed # graphs to 11
Feb 1, 2025
9336986
num_reads to 10000
Feb 2, 2025
b59f5a2
added graph 11
Feb 2, 2025
6cbf399
moved sampler initialization into __init__ to make it a class variable
Feb 3, 2025
1959093
changed default num_reads in SA to 10000
Feb 6, 2025
139e153
added moser spindle as graph 12
greatblueheron Feb 8, 2025
1af743a
added RSG and snark graphs
greatblueheron Mar 17, 2025
4fd8b3d
fixed output num_reads
greatblueheron Mar 24, 2025
d39a486
added conversion of input state from string in convert_my_game_state_…
greatblueheron Mar 24, 2025
5514b50
added graphs 17 and 18 (cube and 3-prism)
greatblueheron Mar 24, 2025
2a85a43
changed default num_reads to 10000
greatblueheron Jul 14, 2025
8efc970
changed default num_reads to 10000
greatblueheron Jul 14, 2025
7444bb3
updated to fix # samples bug and changed some default params
greatblueheron Jul 14, 2025
1c277ab
chaged deault anneal time to 40 ns
greatblueheron Jul 14, 2025
7b7f97f
anneal sched for advantage2.1.3
greatblueheron Jul 14, 2025
1d137c9
changed to use default anneal sched for advantage2.1.3
greatblueheron Jul 14, 2025
f1b3d47
added probabilities in z basis, plotting if required
greatblueheron Jul 14, 2025
1b2ce1c
removing old schedule
greatblueheron Jul 14, 2025
b6d0285
added vertex_ownership and graphs 19, 20
greatblueheron Jul 14, 2025
9b9e1dc
changed default grid_size to 12
greatblueheron Jul 14, 2025
8116042
generates unique terminal states for both fixed and free variants
greatblueheron Jul 14, 2025
1e9ae76
removed cruft
greatblueheron Jul 15, 2025
d5acefc
changed to call evaluate_winner imported from utilities.py
greatblueheron Jul 15, 2025
993756d
shows how to use solvers to adjudicate graphs in Phase 1 paper
greatblueheron Jul 15, 2025
90cb899
modified to read/write with solver parameters
greatblueheron Jul 15, 2025
9ff33f5
removed mock sampler; removed exception for unused params
greatblueheron Jul 15, 2025
47cc3f6
rewrote to focus on looking for mismatches (no plots)
greatblueheron Jul 15, 2025
45d684a
rewrote load_lookup_table, added evaluate_winner, commented out cruft…
greatblueheron Jul 15, 2025
3530914
removed unused plot imports
greatblueheron Jul 16, 2025
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
8 changes: 8 additions & 0 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[bumpversion]
current_version = 0.0.1
commit = False
tag = False

[bumpversion:file:pyproject.toml]
search = version = "{current_version}"
replace = version = "{new_version}"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ The full D-Wave setup instructions are [here](https://docs.ocean.dwavesys.com/en

## Tangled Game Graph Specification

A Tangled game graph is specified by a graph number, which label specific graphs included here. I've included ten graphs
numbered 1 through 10. Each graph requires specification of vertex count (how many vertices the graph has) and an
explicit edge list, which are included for these ten graphs. If you'd like to add a new graph, it's simple! Just add
A Tangled game graph is specified by a graph number, which label specific graphs included here. I've included eleven
graphs numbered 1 through 11. Each graph requires specification of vertex count (how many vertices the graph has) and
an explicit edge list, which are included for these 11 graphs. If you'd like to add a new graph, it's simple! Just add
it to the GraphProperties class, found in the /utils/game_graph_properties.py file.

## Tangled Game State Specification: Expected Input Format For Adjudicators
Expand Down
27 changes: 27 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[tool.poetry]
name = "tangled-adjudicate"
version = "0.0.1"
description = "Tangled adjudicators"
authors = ["Geordie Rose <geordie@snowdropquantum.com>"]
license = "MIT"
homepage = "https://www.snowdropquantum.com/"
packages = [
{ include = "tangled_adjudicate" },
{ include = "tests" },
]

[tool.poetry.dependencies]
python = "^3.8" # You may want to adjust this based on your needs
dwave-ocean-sdk = "*"
dwave-neal = ">=0.6.0"
matplotlib = "*"
gdown = "*"

[tool.poetry.group.dev.dependencies]
# Add development dependencies here if needed
# pytest = "^7.0.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

13 changes: 0 additions & 13 deletions setup.py

This file was deleted.

5 changes: 5 additions & 0 deletions tangled_adjudicate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .adjudicators.adjudicator import Adjudicator, GameState, AdjudicationResult
from .adjudicators.lookup_table import LookupTableAdjudicator
from .adjudicators.schrodinger import SchrodingerEquationAdjudicator
from .adjudicators.simulated_annealing import SimulatedAnnealingAdjudicator
from .adjudicators.quantum_annealing import QuantumAnnealingAdjudicator
386 changes: 0 additions & 386 deletions tangled_adjudicate/adjudicators/adjudicate.py

This file was deleted.

138 changes: 138 additions & 0 deletions tangled_adjudicate/adjudicators/adjudicator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from abc import ABC, abstractmethod
from typing import Any, TypedDict, List, Tuple, Optional, Dict, Union, Set
import numpy as np
import numpy.typing as npt

from tangled_adjudicate.utils.utilities import evaluate_winner


class GameState(TypedDict):
num_nodes: int
edges: List[Tuple[int, int, int]] # (node1, node2, edge_label=0,1,2,3)
player1_id: str
player2_id: str
turn_count: int
current_player_index: int
player1_node: Optional[int]
player2_node: Optional[int]


class AdjudicationResult(TypedDict):
game_state: GameState
adjudicator: str
winner: Optional[str] # 'red', 'blue', 'draw', or None
score: Optional[float]
influence_vector: Optional[npt.NDArray[np.float64]]
correlation_matrix: Optional[npt.NDArray[np.float64]]
parameters: Dict[str, Union[str, int, float, bool]]


class IsingModel(TypedDict):
h: Dict[int, float] # Local fields
j: Dict[Tuple[int, int], float] # Coupling strengths


class Adjudicator(ABC):
"""Base interface for game state adjudication implementations."""

def __init__(self) -> None:
"""Initialize base adjudicator."""
self._parameters: Dict[str, Any] = {}
self.j_map = {0: 0.0, # edge (i, j) uncolored , J_ij=0
1: 0.0, # edge (i, j) colored gray, J_ij=0
2: -1.0, # edge (i, j) colored green, FM coupling, J_ij=-1.0
3: 1.0} # edge (i, j) colored purple, AFM coupling, J_ij=+1.0

@abstractmethod
def setup(self, **kwargs) -> None:
"""Optional setup method for implementation-specific initialization."""
pass

@abstractmethod
def adjudicate(self, game_state: GameState) -> AdjudicationResult:
"""Adjudicate the given game state."""
pass

def _validate_game_state(self, game_state: GameState) -> None:
"""Validate the game state structure and contents."""
required_keys = {
'num_nodes', 'edges', 'player1_id', 'player2_id',
'turn_count', 'current_player_index', 'player1_node', 'player2_node'
}

if not all(key in game_state for key in required_keys):
missing_keys = required_keys - set(game_state.keys())
raise ValueError(f"Game state missing required keys: {missing_keys}")

if game_state['num_nodes'] < 1:
raise ValueError("Number of nodes must be positive")

for edge in game_state['edges']:
if len(edge) != 3:
raise ValueError(f"Invalid edge format: {edge}")
if not (0 <= edge[0] < game_state['num_nodes'] and 0 <= edge[1] < game_state['num_nodes']):
raise ValueError(f"Edge vertices out of range: {edge}")

def _game_state_to_ising(self, game_state: GameState) -> IsingModel:
"""Convert game state to Ising model parameters.

Args:
game_state: The current game state

Returns:
IsingModel containing h (local fields) and j (coupling strengths)
"""
h = {i: 0.0 for i in range(game_state['num_nodes'])}
j = {}

for edge in game_state['edges']:
v1, v2, edge_label = edge
if v1 > v2:
v1, v2 = v2, v1
j[(v1, v2)] = float(self.j_map[edge_label])

return IsingModel(h=h, j=j)

def _find_isolated_vertices(self, game_state: GameState) -> Set[int]:
"""Find vertices with no connections in the graph.

Args:
game_state: The current game state

Returns:
Set of isolated vertex indices
"""
connected_vertices = set()
for edge in game_state['edges']:
connected_vertices.add(edge[0])
connected_vertices.add(edge[1])

all_vertices = set(range(game_state['num_nodes']))
return all_vertices - connected_vertices

def _compute_winner_score_and_influence(
self,
game_state: GameState,
correlation_matrix: npt.NDArray[np.float64],
epsilon: float = 0.5
) -> Tuple[Optional[str], Optional[float], npt.NDArray[np.float64]]:
"""Compute winner, score and influence from correlation matrix."""
if not isinstance(correlation_matrix, np.ndarray):
raise ValueError("Correlation matrix must be a numpy array")

if correlation_matrix.shape[0] != correlation_matrix.shape[1]:
raise ValueError("Correlation matrix must be square")

if correlation_matrix.shape[0] != game_state['num_nodes']:
raise ValueError("Correlation matrix size must match number of nodes")

influence_vector = np.sum(correlation_matrix, axis=0)

if game_state['player1_node'] is None or game_state['player2_node'] is None:
return None, None, influence_vector

score = influence_vector[game_state['player1_node']] - influence_vector[game_state['player2_node']]

winner = evaluate_winner(score, epsilon)

return winner, score, influence_vector
119 changes: 119 additions & 0 deletions tangled_adjudicate/adjudicators/lookup_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
import pickle
from typing import Dict, Optional
import numpy as np

from ..utils.utilities import convert_erik_game_state_to_my_game_state, load_lookup_table
from .adjudicator import Adjudicator, GameState, AdjudicationResult


class LookupTableAdjudicator(Adjudicator):
"""Adjudicator implementation using pre-computed lookup tables"""

def __init__(self) -> None:
"""Initialize the lookup table adjudicator"""
super().__init__()
self.data_dir: Optional[str] = None
self.lookup_table: Optional[Dict[str, str]] = None
self.solver: Optional[str] = None
self.epsilon: Optional[float] = None
self.anneal_time: Optional[int] = None
self.num_reads: Optional[int] = None
self.graph_number: Optional[int] = None

def setup(self, **kwargs) -> None:
"""Configure lookup table parameters.

Args:
data_dir: Directory containing lookup table data files
lookup_args: Dictionary containing solver, epsilon, anneal_time and num_reads for lookup table
graph_number: int
Raises:
ValueError: If parameters are invalid or data directory doesn't exist
"""
if 'data_dir' in kwargs:
if not isinstance(kwargs['data_dir'], str):
raise ValueError("data_dir must be a string")
if not os.path.isdir(kwargs['data_dir']):
raise ValueError(f"Directory not found: {kwargs['data_dir']}")
self.data_dir = kwargs['data_dir']

if 'lookup_args' in kwargs:
if kwargs['lookup_args']['solver'] not in ['simulated_annealing', 'schrodinger_equation', 'quantum_annealing']:
raise ValueError("solver must be one of 'simulated_annealing', 'schrodinger_equation', 'quantum_annealing'")
self.solver = kwargs['lookup_args']['solver']
if not isinstance(kwargs['lookup_args']['epsilon'], float):
raise ValueError("epsilon must be a float")
self.epsilon = kwargs['lookup_args']['epsilon']
if not isinstance(kwargs['lookup_args']['anneal_time'], int):
raise ValueError("anneal_time must be an int")
self.anneal_time = kwargs['lookup_args']['anneal_time']
if not isinstance(kwargs['lookup_args']['num_reads'], int):
raise ValueError("num_reads must be an int")
self.num_reads = kwargs['lookup_args']['num_reads']

if 'graph_number' in kwargs:
if not isinstance(kwargs['graph_number'], int):
raise ValueError("graph_number must be an int")
self.graph_number = kwargs['graph_number']

self._parameters = {'data_dir': self.data_dir,
'solver': self.solver,
'epsilon': self.epsilon,
'anneal_time': self.anneal_time,
'num_reads': self.num_reads,
'graph_number': self.graph_number}

def _get_lookup_table(self) -> None:
"""Load the appropriate lookup table for the given graph_number and solver_used

Raises:
RuntimeError: If lookup table file cannot be loaded
"""
if not self.data_dir:
raise RuntimeError("Data directory not set. Call setup() first.")

self.lookup_table = load_lookup_table(data_dir=self.data_dir, graph_number=self.graph_number,
solver=self.solver, epsilon=self.epsilon,
anneal_time=self.anneal_time, num_reads=self.num_reads)

def adjudicate(self, game_state: GameState) -> AdjudicationResult:
"""Adjudicate the game state using the lookup table.

Args:
game_state: The current game state

Returns:
AdjudicationResult containing the adjudication details

Raises:
ValueError: If the game state is invalid or unsupported
RuntimeError: If lookup table is not loaded
"""
self._validate_game_state(game_state)

# Load lookup table if needed
if self.lookup_table is None:
self._get_lookup_table()

if not self.lookup_table:
raise RuntimeError("Failed to load lookup table")

# Convert game state to lookup format
lookup_state = convert_erik_game_state_to_my_game_state(game_state)

try:
winner = self.lookup_table[str(lookup_state)]
except KeyError: # key not in results_dict
raise RuntimeError("key not in lookup table...")

return AdjudicationResult(
game_state=game_state,
adjudicator='lookup_table',
winner=winner,
score=None, # Lookup table doesn't provide scores; note though all these are
influence_vector=None, # available if we need them from the raw data
correlation_matrix=None,
parameters=self._parameters
)

Loading