1use serde::{Deserialize, Serialize};
2
3use crate::distance::{DistanceResult, LogicalClass};
4use crate::distance_bound::DistanceBoundWitness;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum ExactCssDistanceMethod {
9 RstimIlpExact,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum ExactDistanceBoundType {
15 Exact,
16 Upper,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum ExactCssDistanceStatus {
22 Completed,
23 Timeout,
24 Incomplete,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
28#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
29#[serde(rename_all = "lowercase")]
30pub enum ExactCssDistanceBackend {
31 #[default]
32 Auto,
33 Highs,
34 Gurobi,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
38pub struct ExactCssDistanceSolverOptions {
39 #[serde(default)]
40 pub backend: ExactCssDistanceBackend,
41 pub time_limit_seconds: Option<f64>,
42 pub mip_gap: Option<f64>,
43 pub threads: Option<u32>,
44 #[serde(default, skip_serializing_if = "is_false")]
45 pub verbose_solver: bool,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub enum ExactCssDistanceSolverStatus {
51 Optimal,
52 TimeLimit,
53 SolutionLimit,
54 SubOptimal,
55}
56
57impl ExactCssDistanceSolverStatus {
58 pub fn is_exact(self) -> bool {
59 matches!(self, Self::Optimal)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub struct ExactCssDistanceSolverReport {
65 pub backend: ExactCssDistanceBackend,
66 pub status: ExactCssDistanceSolverStatus,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(tag = "input", rename_all = "snake_case")]
71pub enum ExactCssDistanceInput {
72 CodeId { code_id: String },
73 Files { hx: String, hz: String },
74 QuantumTannerSpec { quantum_tanner_spec: String },
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub struct ExactCssDistanceOptions {
79 #[serde(flatten)]
80 pub input: ExactCssDistanceInput,
81 #[serde(flatten)]
82 pub solver: ExactCssDistanceSolverOptions,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86pub struct ExactCssDistanceProvenance {
87 pub tool: String,
88 pub tool_version: String,
89 pub method_revision: u32,
90}
91
92impl ExactCssDistanceProvenance {
93 pub fn current() -> Self {
94 Self {
95 tool: "qec-code".to_owned(),
96 tool_version: env!("CARGO_PKG_VERSION").to_owned(),
97 method_revision: 1,
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct ExactCssDistanceResult {
104 pub status: ExactCssDistanceStatus,
105 pub distance: usize,
106 pub method: ExactCssDistanceMethod,
107 pub bound_type: ExactDistanceBoundType,
108 pub logical_class: LogicalClass,
109 pub witness: DistanceBoundWitness,
110 #[serde(default)]
111 pub requested_backend: ExactCssDistanceBackend,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub backend: Option<ExactCssDistanceBackend>,
114 #[serde(skip_serializing_if = "Option::is_none")]
115 pub solver_status: Option<ExactCssDistanceSolverStatus>,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub time_limit_seconds: Option<f64>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub mip_gap: Option<f64>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub threads: Option<u32>,
122 #[serde(default, skip_serializing_if = "is_false")]
123 pub verbose_solver: bool,
124 pub options: ExactCssDistanceOptions,
125 pub provenance: ExactCssDistanceProvenance,
126}
127
128impl ExactCssDistanceResult {
129 pub fn completed(distance: DistanceResult, options: ExactCssDistanceOptions) -> Self {
130 Self::completed_with_solver_report(distance, options, None)
131 }
132
133 pub fn completed_with_solver_report(
134 distance: DistanceResult,
135 options: ExactCssDistanceOptions,
136 solver_report: Option<ExactCssDistanceSolverReport>,
137 ) -> Self {
138 let solver_status = solver_report.map(|report| report.status);
139 let backend = solver_report.map(|report| report.backend);
140 let solver_certifies_exact = solver_status
141 .map(ExactCssDistanceSolverStatus::is_exact)
142 .unwrap_or(true);
143 let options_allow_exact = !has_positive_mip_gap(&options.solver);
144 let is_exact = solver_certifies_exact && options_allow_exact;
145 let status = match solver_status {
146 Some(ExactCssDistanceSolverStatus::TimeLimit) => ExactCssDistanceStatus::Timeout,
147 _ if !is_exact => ExactCssDistanceStatus::Incomplete,
148 _ => ExactCssDistanceStatus::Completed,
149 };
150 let bound_type = if is_exact {
151 ExactDistanceBoundType::Exact
152 } else {
153 ExactDistanceBoundType::Upper
154 };
155
156 Self {
157 status,
158 distance: distance.distance,
159 method: ExactCssDistanceMethod::RstimIlpExact,
160 bound_type,
161 logical_class: distance.logical_class,
162 witness: DistanceBoundWitness::from_pauli(&distance.witness),
163 requested_backend: options.solver.backend,
164 backend,
165 solver_status,
166 time_limit_seconds: options.solver.time_limit_seconds,
167 mip_gap: options.solver.mip_gap,
168 threads: options.solver.threads,
169 verbose_solver: options.solver.verbose_solver,
170 options,
171 provenance: ExactCssDistanceProvenance::current(),
172 }
173 }
174}
175
176fn is_false(value: &bool) -> bool {
177 !*value
178}
179
180fn has_positive_mip_gap(options: &ExactCssDistanceSolverOptions) -> bool {
181 options.mip_gap.map(|gap| gap > 0.0).unwrap_or(false)
182}