problemreductions/models/optimization/
spin_glass.rs1use crate::graph_types::SimpleGraph;
6use crate::traits::Problem;
7use crate::types::{EnergyMode, ProblemSize, SolutionSize};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct SpinGlass<W = f64> {
42 num_spins: usize,
44 interactions: Vec<((usize, usize), W)>,
46 fields: Vec<W>,
48}
49
50impl<W: Clone + Default> SpinGlass<W> {
51 pub fn new(num_spins: usize, interactions: Vec<((usize, usize), W)>, fields: Vec<W>) -> Self {
58 assert_eq!(fields.len(), num_spins);
59 Self {
60 num_spins,
61 interactions,
62 fields,
63 }
64 }
65
66 pub fn without_fields(num_spins: usize, interactions: Vec<((usize, usize), W)>) -> Self
68 where
69 W: num_traits::Zero,
70 {
71 let fields = vec![W::zero(); num_spins];
72 Self {
73 num_spins,
74 interactions,
75 fields,
76 }
77 }
78
79 pub fn num_spins(&self) -> usize {
81 self.num_spins
82 }
83
84 pub fn interactions(&self) -> &[((usize, usize), W)] {
86 &self.interactions
87 }
88
89 pub fn fields(&self) -> &[W] {
91 &self.fields
92 }
93
94 pub fn config_to_spins(config: &[usize]) -> Vec<i32> {
96 config.iter().map(|&x| 2 * x as i32 - 1).collect()
97 }
98}
99
100impl<W> Problem for SpinGlass<W>
101where
102 W: Clone
103 + Default
104 + PartialOrd
105 + num_traits::Num
106 + num_traits::Zero
107 + std::ops::AddAssign
108 + std::ops::Mul<Output = W>
109 + From<i32>
110 + 'static,
111{
112 const NAME: &'static str = "SpinGlass";
113 type GraphType = SimpleGraph;
114 type Weight = W;
115 type Size = W;
116
117 fn num_variables(&self) -> usize {
118 self.num_spins
119 }
120
121 fn num_flavors(&self) -> usize {
122 2 }
124
125 fn problem_size(&self) -> ProblemSize {
126 ProblemSize::new(vec![
127 ("num_spins", self.num_spins),
128 ("num_interactions", self.interactions.len()),
129 ])
130 }
131
132 fn energy_mode(&self) -> EnergyMode {
133 EnergyMode::SmallerSizeIsBetter }
135
136 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
137 let spins = Self::config_to_spins(config);
138 let energy = self.compute_energy(&spins);
139 SolutionSize::valid(energy)
140 }
141}
142
143impl<W> SpinGlass<W>
144where
145 W: Clone + num_traits::Zero + std::ops::AddAssign + std::ops::Mul<Output = W> + From<i32>,
146{
147 pub fn compute_energy(&self, spins: &[i32]) -> W {
149 let mut energy = W::zero();
150
151 for ((i, j), j_val) in &self.interactions {
153 let s_i = spins.get(*i).copied().unwrap_or(1);
154 let s_j = spins.get(*j).copied().unwrap_or(1);
155 let product: i32 = s_i * s_j;
156 energy += j_val.clone() * W::from(product);
157 }
158
159 for (i, h_val) in self.fields.iter().enumerate() {
161 let s_i = spins.get(i).copied().unwrap_or(1);
162 energy += h_val.clone() * W::from(s_i);
163 }
164
165 energy
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::solvers::{BruteForce, Solver};
173
174 #[test]
175 fn test_spin_glass_creation() {
176 let problem = SpinGlass::new(3, vec![((0, 1), 1.0), ((1, 2), -1.0)], vec![0.0, 0.0, 0.0]);
177 assert_eq!(problem.num_spins(), 3);
178 assert_eq!(problem.interactions().len(), 2);
179 assert_eq!(problem.fields().len(), 3);
180 }
181
182 #[test]
183 fn test_spin_glass_without_fields() {
184 let problem = SpinGlass::<f64>::without_fields(3, vec![((0, 1), 1.0)]);
185 assert_eq!(problem.fields(), &[0.0, 0.0, 0.0]);
186 }
187
188 #[test]
189 fn test_config_to_spins() {
190 assert_eq!(SpinGlass::<f64>::config_to_spins(&[0, 0]), vec![-1, -1]);
191 assert_eq!(SpinGlass::<f64>::config_to_spins(&[1, 1]), vec![1, 1]);
192 assert_eq!(SpinGlass::<f64>::config_to_spins(&[0, 1]), vec![-1, 1]);
193 assert_eq!(SpinGlass::<f64>::config_to_spins(&[1, 0]), vec![1, -1]);
194 }
195
196 #[test]
197 fn test_compute_energy() {
198 let problem = SpinGlass::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]);
200
201 assert_eq!(problem.compute_energy(&[1, 1]), 1.0);
203 assert_eq!(problem.compute_energy(&[-1, -1]), 1.0);
204
205 assert_eq!(problem.compute_energy(&[1, -1]), -1.0);
207 assert_eq!(problem.compute_energy(&[-1, 1]), -1.0);
208 }
209
210 #[test]
211 fn test_compute_energy_with_fields() {
212 let problem = SpinGlass::new(2, vec![], vec![1.0, -1.0]);
213
214 assert_eq!(problem.compute_energy(&[1, 1]), 0.0); assert_eq!(problem.compute_energy(&[-1, -1]), 0.0); assert_eq!(problem.compute_energy(&[1, -1]), 2.0); assert_eq!(problem.compute_energy(&[-1, 1]), -2.0); }
220
221 #[test]
222 fn test_solution_size() {
223 let problem = SpinGlass::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]);
224
225 let sol = problem.solution_size(&[0, 0]);
227 assert!(sol.is_valid);
228 assert_eq!(sol.size, 1.0);
229
230 let sol = problem.solution_size(&[0, 1]);
232 assert_eq!(sol.size, -1.0);
233 }
234
235 #[test]
236 fn test_brute_force_ferromagnetic() {
237 let problem = SpinGlass::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]);
241 let solver = BruteForce::new();
242
243 let solutions = solver.find_best(&problem);
244 for sol in &solutions {
246 assert_ne!(sol[0], sol[1]);
247 assert_eq!(problem.solution_size(sol).size, -1.0);
248 }
249 }
250
251 #[test]
252 fn test_brute_force_antiferromagnetic() {
253 let problem = SpinGlass::new(2, vec![((0, 1), -1.0)], vec![0.0, 0.0]);
256 let solver = BruteForce::new();
257
258 let solutions = solver.find_best(&problem);
259 for sol in &solutions {
261 assert_eq!(sol[0], sol[1]);
262 assert_eq!(problem.solution_size(sol).size, -1.0);
263 }
264 }
265
266 #[test]
267 fn test_energy_mode() {
268 let problem = SpinGlass::<f64>::without_fields(2, vec![]);
269 assert!(problem.energy_mode().is_minimization());
270 }
271
272 #[test]
273 fn test_num_variables_flavors() {
274 let problem = SpinGlass::<f64>::without_fields(5, vec![]);
275 assert_eq!(problem.num_variables(), 5);
276 assert_eq!(problem.num_flavors(), 2);
277 }
278
279 #[test]
280 fn test_problem_size() {
281 let problem = SpinGlass::new(3, vec![((0, 1), 1.0), ((1, 2), 1.0)], vec![0.0, 0.0, 0.0]);
282 let size = problem.problem_size();
283 assert_eq!(size.get("num_spins"), Some(3));
284 assert_eq!(size.get("num_interactions"), Some(2));
285 }
286
287 #[test]
288 fn test_triangle_frustration() {
289 let problem = SpinGlass::new(
291 3,
292 vec![((0, 1), 1.0), ((1, 2), 1.0), ((0, 2), 1.0)],
293 vec![0.0, 0.0, 0.0],
294 );
295 let solver = BruteForce::new();
296
297 let solutions = solver.find_best(&problem);
298 for sol in &solutions {
301 assert_eq!(problem.solution_size(sol).size, -1.0);
302 }
303 }
304}