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 = False,
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', onehot: Literal['exact', 'quadratic', 'xor'] = 'exact', optimization: bool = False) -> '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 onehot,
62 optimization,
63 )
64
[docs]
65 def to_hamiltonian(self, onehot: Literal['exact', 'quadratic', 'xor'] = None) -> models.Hamiltonian:
66 if onehot is None:
67 onehot = self.onehot
68 cm = self.cm
69 aircrafts = self.aircrafts
70 size = len(cm)
71
72 onehot_hamiltonian = Equation(size)
73 for _, manouvers in aircrafts.groupby(by='aircraft'):
74 if onehot == 'exact':
75 h = ~hampy.one_in_n(manouvers.index.values.tolist(), size)
76 elif onehot == 'quadratic':
77 h = hampy.one_in_n(manouvers.index.values.tolist(), size, quadratic=True)
78 elif onehot == 'xor':
79 total = Equation(size)
80 for part in manouvers.index.values.tolist():
81 total ^= total[part]
82 h = (~total).hamiltonian
83
84 onehot_hamiltonian += h
85
86 triu = np.triu(cm, k=1)
87 conflict_hamiltonian = Equation(size)
88 for p1, p2 in zip(*np.where(triu == 1), strict=True):
89 eq = Equation(size)
90 conflict_hamiltonian += (eq[int(p1)] & eq[int(p2)]).hamiltonian
91
92 hamiltonian = onehot_hamiltonian + conflict_hamiltonian
93
94 if self.optimization:
95 goal_hamiltonian = Equation(size)
96 for i, (maneuver, ac) in self.aircrafts.iterrows():
97 if not isinstance(i, int):
98 raise TypeError
99 if maneuver != ac:
100 goal_hamiltonian += goal_hamiltonian.get_variable(i)
101 hamiltonian += goal_hamiltonian / cm.sum().sum()
102
103 return models.Hamiltonian(
104 hamiltonian,
105 mixer_hamiltonian=self.get_mixer_hamiltonian(),
106 initial_state=self.get_initial_state(),
107 )
108
[docs]
109 def get_mixer_hamiltonian(self) -> Equation:
110 mixer_hamiltonian = Equation(self.size)
111 for _, manouvers in self.aircrafts.groupby(by='aircraft'):
112 h = ring_ham(manouvers.index.values.tolist(), self.size)
113 mixer_hamiltonian += h
114 return mixer_hamiltonian
115
[docs]
116 def get_initial_state(self) -> QuantumCircuit:
117 qc = QuantumCircuit(self.size)
118 for _, manouvers in self.aircrafts.groupby(by='aircraft'):
119 qc.x(manouvers.index.values.tolist()[0])
120 return qc
121
[docs]
122 def analyze_result(self, result: dict) -> dict[str, np.ndarray]:
123 """
124 Analyzes the result in terms of collisions and violations of onehot constraint.
125
126 Parameters:
127 result (dict): A dictionary where keys are bitstrings and values are probabilities.
128
129 Returns:
130 dict: A dictionary containing collisions, onehot violations, and changes as ndarrays.
131 """
132 keys = list(result.keys())
133 vectorized_result = np.fromstring(' '.join(list(''.join(keys))), 'u1', sep=' ').reshape(len(result), -1)
134 cm = self.cm.copy().astype(int)
135 np.fill_diagonal(cm, 0)
136 collisions = np.einsum('ij,ij->i', vectorized_result @ cm, vectorized_result) / 2
137
138 df = pd.DataFrame(vectorized_result.transpose())
139 df['aircraft'] = self.aircrafts['aircraft']
140 onehot_violations = (df.groupby(by='aircraft').sum() != 1).sum(axis=0).to_numpy()
141
142 df['manouver'] = self.aircrafts['manouver']
143 no_changes = df[df['aircraft'] == df['manouver']]
144 changes = (len(no_changes) - no_changes.drop(['manouver', 'aircraft'], axis=1).sum()).to_numpy().astype(int)
145 changes[onehot_violations != 0] = -1
146
147 at_least_one = (df.loc[:, df.columns != 'manouver'].groupby('aircraft').sum() > 0).all().to_numpy().astype(int)
148
149 return {'collisions': collisions, 'onehot_violations': onehot_violations, 'changes': changes, 'at_least_one': at_least_one}