problemreductions/models/graph/
spin_glass.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
6use crate::topology::{Graph, SimpleGraph};
7use crate::traits::{OptimizationProblem, Problem};
8use crate::types::{Direction, SolutionSize, WeightElement};
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "SpinGlass",
14 module_path: module_path!(),
15 description: "Minimize Ising Hamiltonian on a graph",
16 fields: &[
17 FieldInfo { name: "graph", type_name: "G", description: "The interaction graph" },
18 FieldInfo { name: "couplings", type_name: "Vec<W>", description: "Pairwise couplings J_ij" },
19 FieldInfo { name: "fields", type_name: "Vec<W>", description: "On-site fields h_i" },
20 ],
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct SpinGlass<G, W> {
62 graph: G,
64 couplings: Vec<W>,
66 fields: Vec<W>,
68}
69
70impl<W: Clone + Default> SpinGlass<SimpleGraph, W> {
71 pub fn new(num_spins: usize, interactions: Vec<((usize, usize), W)>, fields: Vec<W>) -> Self {
78 assert_eq!(fields.len(), num_spins);
79 let edges: Vec<_> = interactions.iter().map(|((i, j), _)| (*i, *j)).collect();
80 let couplings: Vec<_> = interactions.iter().map(|(_, w)| w.clone()).collect();
81 let graph = SimpleGraph::new(num_spins, edges);
82 Self {
83 graph,
84 couplings,
85 fields,
86 }
87 }
88
89 pub fn without_fields(num_spins: usize, interactions: Vec<((usize, usize), W)>) -> Self
91 where
92 W: num_traits::Zero,
93 {
94 let fields = vec![W::zero(); num_spins];
95 Self::new(num_spins, interactions, fields)
96 }
97}
98
99impl<G: Graph, W: Clone + Default> SpinGlass<G, W> {
100 pub fn from_graph(graph: G, couplings: Vec<W>, fields: Vec<W>) -> Self {
107 assert_eq!(
108 couplings.len(),
109 graph.num_edges(),
110 "couplings length must match num_edges"
111 );
112 assert_eq!(
113 fields.len(),
114 graph.num_vertices(),
115 "fields length must match num_vertices"
116 );
117 Self {
118 graph,
119 couplings,
120 fields,
121 }
122 }
123
124 pub fn from_graph_without_fields(graph: G, couplings: Vec<W>) -> Self
126 where
127 W: num_traits::Zero,
128 {
129 let fields = vec![W::zero(); graph.num_vertices()];
130 Self::from_graph(graph, couplings, fields)
131 }
132
133 pub fn graph(&self) -> &G {
135 &self.graph
136 }
137
138 pub fn num_spins(&self) -> usize {
140 self.graph.num_vertices()
141 }
142
143 pub fn num_interactions(&self) -> usize {
145 self.graph.num_edges()
146 }
147
148 pub fn interactions(&self) -> Vec<((usize, usize), W)> {
152 self.graph
153 .edges()
154 .into_iter()
155 .zip(self.couplings.iter())
156 .map(|((i, j), w)| ((i, j), w.clone()))
157 .collect()
158 }
159
160 pub fn couplings(&self) -> &[W] {
162 &self.couplings
163 }
164
165 pub fn fields(&self) -> &[W] {
167 &self.fields
168 }
169
170 pub fn config_to_spins(config: &[usize]) -> Vec<i32> {
172 config.iter().map(|&x| 2 * x as i32 - 1).collect()
173 }
174}
175
176impl<G, W> SpinGlass<G, W>
177where
178 G: Graph,
179 W: Clone + num_traits::Zero + std::ops::AddAssign + std::ops::Mul<Output = W> + From<i32>,
180{
181 pub fn compute_energy(&self, spins: &[i32]) -> W {
183 let mut energy = W::zero();
184
185 for ((i, j), j_val) in self.graph.edges().iter().zip(self.couplings.iter()) {
187 let s_i = spins.get(*i).copied().unwrap_or(1);
188 let s_j = spins.get(*j).copied().unwrap_or(1);
189 let product: i32 = s_i * s_j;
190 energy += j_val.clone() * W::from(product);
191 }
192
193 for (i, h_val) in self.fields.iter().enumerate() {
195 let s_i = spins.get(i).copied().unwrap_or(1);
196 energy += h_val.clone() * W::from(s_i);
197 }
198
199 energy
200 }
201}
202
203impl<G, W> Problem for SpinGlass<G, W>
204where
205 G: Graph + crate::variant::VariantParam,
206 W: WeightElement
207 + crate::variant::VariantParam
208 + PartialOrd
209 + num_traits::Num
210 + num_traits::Zero
211 + num_traits::Bounded
212 + std::ops::AddAssign
213 + std::ops::Mul<Output = W>
214 + From<i32>,
215{
216 const NAME: &'static str = "SpinGlass";
217 type Metric = SolutionSize<W::Sum>;
218
219 fn dims(&self) -> Vec<usize> {
220 vec![2; self.graph.num_vertices()]
221 }
222
223 fn evaluate(&self, config: &[usize]) -> SolutionSize<W::Sum> {
224 let spins = Self::config_to_spins(config);
225 SolutionSize::Valid(self.compute_energy(&spins).to_sum())
226 }
227
228 fn variant() -> Vec<(&'static str, &'static str)> {
229 crate::variant_params![G, W]
230 }
231}
232
233impl<G, W> OptimizationProblem for SpinGlass<G, W>
234where
235 G: Graph + crate::variant::VariantParam,
236 W: WeightElement
237 + crate::variant::VariantParam
238 + PartialOrd
239 + num_traits::Num
240 + num_traits::Zero
241 + num_traits::Bounded
242 + std::ops::AddAssign
243 + std::ops::Mul<Output = W>
244 + From<i32>,
245{
246 type Value = W::Sum;
247
248 fn direction(&self) -> Direction {
249 Direction::Minimize
250 }
251}
252
253crate::declare_variants! {
254 SpinGlass<SimpleGraph, i32> => "2^num_spins",
255 SpinGlass<SimpleGraph, f64> => "2^num_spins",
256}
257
258#[cfg(test)]
259#[path = "../../unit_tests/models/graph/spin_glass.rs"]
260mod tests;