1from pathlib import Path
2from typing import Literal
3
4import numpy as np
5import pandas as pd
6from qiskit import QuantumCircuit
7
8from qlauncher import hampy, models
9from qlauncher.base.base import Problem
10from qlauncher.hampy.object import Equation
11from qlauncher.problems.optimization.ec import ring_ham
12
13
[docs]
14class QATM(Problem):
15 def __init__(
16 self,
17 cm: np.ndarray,
18 aircrafts: pd.DataFrame,
19 onehot: Literal['exact', 'quadratic', 'xor'] = 'exact',
20 optimization: bool = True,
21 ) -> None:
22 self.cm = cm
23 self.aircrafts = aircrafts
24 self.onehot = onehot
25 self.optimization = optimization
26
27 self.size = len(self.cm)
28
[docs]
29 @staticmethod
30 def from_preset(instance_name: Literal['rcp-3'], **kwargs) -> 'QATM':
31 match instance_name:
32 case 'rcp-3':
33 cm = np.array(
34 [
35 [1, 0, 1, 0, 0, 0],
36 [0, 1, 0, 0, 0, 1],
37 [1, 0, 1, 0, 1, 0],
38 [0, 0, 0, 1, 0, 0],
39 [0, 0, 1, 0, 1, 0],
40 [0, 1, 0, 0, 0, 1],
41 ]
42 )
43 aircrafts = pd.DataFrame(
44 {
45 'manouver': ['A0', 'A1', 'A2', 'A0_a=10', 'A1_a=10', 'A2_a=10'],
46 'aircraft': ['A0', 'A1', 'A2', 'A0', 'A1', 'A2'],
47 }
48 )
49 case _:
50 raise KeyError
51 return QATM(cm, aircrafts)
52
[docs]
53 @classmethod
54 def from_file(cls, path: str, instance_name: str = 'QATM', optimization: bool = True) -> 'QATM':
55 cm_path = Path(path, 'CM_' + instance_name)
56 aircrafts_path = Path(path, 'aircrafts_' + instance_name)
57
58 return QATM(
59 np.loadtxt(cm_path),
60 pd.read_csv(aircrafts_path, delimiter=' ', names=['manouver', 'aircraft']),
61 'exact',
62 optimization,
63 )
64
[docs]
65 def to_hamiltonian(self, onehot: Literal['exact', 'quadratic', 'xor'] = 'exact') -> models.Hamiltonian:
66 cm = self.cm
67 aircrafts = self.aircrafts
68 size = len(cm)
69
70 onehot_hamiltonian = Equation(size)
71 for _, manouvers in aircrafts.groupby(by='aircraft'):
72 if onehot == 'exact':
73 h = ~hampy.one_in_n(manouvers.index.values.tolist(), size)
74 elif onehot == 'quadratic':
75 h = hampy.one_in_n(manouvers.index.values.tolist(), size, quadratic=True)
76 elif onehot == 'xor':
77 total = Equation(size)
78 for part in manouvers.index.values.tolist():
79 total ^= total[part]
80 h = (~total).hamiltonian
81
82 onehot_hamiltonian += h
83
84 triu = np.triu(cm, k=1)
85 conflict_hamiltonian = Equation(size)
86 for p1, p2 in zip(*np.where(triu == 1), strict=True):
87 eq = Equation(size)
88 conflict_hamiltonian += (eq[int(p1)] & eq[int(p2)]).hamiltonian
89
90 hamiltonian = onehot_hamiltonian + conflict_hamiltonian
91
92 if self.optimization:
93 goal_hamiltonian = Equation(size)
94 for i, (maneuver, ac) in self.aircrafts.iterrows():
95 if not isinstance(i, int):
96 raise TypeError
97 if maneuver != ac:
98 goal_hamiltonian += goal_hamiltonian.get_variable(i)
99 hamiltonian += goal_hamiltonian / cm.sum().sum()
100
101 return models.Hamiltonian(
102 hamiltonian,
103 mixer_hamiltonian=self.get_mixer_hamiltonian(),
104 initial_state=self.get_initial_state(),
105 )
106
[docs]
107 def get_mixer_hamiltonian(self) -> Equation:
108 mixer_hamiltonian = Equation(self.size)
109 for _, manouvers in self.aircrafts.groupby(by='aircraft'):
110 h = ring_ham(manouvers.index.values.tolist(), self.size)
111 mixer_hamiltonian += h
112 return mixer_hamiltonian
113
[docs]
114 def get_initial_state(self) -> QuantumCircuit:
115 qc = QuantumCircuit(self.size)
116 for _, manouvers in self.aircrafts.groupby(by='aircraft'):
117 qc.x(manouvers.index.values.tolist()[0])
118 return qc
119
[docs]
120 def analyze_result(self, result: dict) -> dict[str, np.ndarray]:
121 """
122 Analyzes the result in terms of collisions and violations of onehot constraint.
123
124 Parameters:
125 result (dict): A dictionary where keys are bitstrings and values are probabilities.
126
127 Returns:
128 dict: A dictionary containing collisions, onehot violations, and changes as ndarrays.
129 """
130 keys = list(result.keys())
131 vectorized_result = np.fromstring(' '.join(list(''.join(keys))), 'u1', sep=' ').reshape(len(result), -1)
132 cm = self.cm.copy().astype(int)
133 np.fill_diagonal(cm, 0)
134 collisions = np.einsum('ij,ij->i', vectorized_result @ cm, vectorized_result) / 2
135
136 df = pd.DataFrame(vectorized_result.transpose())
137 df['aircraft'] = self.aircrafts['aircraft']
138 onehot_violations = (df.groupby(by='aircraft').sum() != 1).sum(axis=0).to_numpy()
139
140 df['manouver'] = self.aircrafts['manouver']
141 no_changes = df[df['aircraft'] == df['manouver']]
142 changes = (len(no_changes) - no_changes.drop(['manouver', 'aircraft'], axis=1).sum()).to_numpy().astype(int)
143 changes[onehot_violations != 0] = -1
144
145 at_least_one = (df.loc[:, df.columns != 'manouver'].groupby('aircraft').sum() > 0).all().to_numpy().astype(int)
146
147 return {'collisions': collisions, 'onehot_violations': onehot_violations, 'changes': changes, 'at_least_one': at_least_one}