problemreductions/models/graph/
spin_glass.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
6use crate::topology::{Graph, SimpleGraph};
7use crate::traits::Problem;
8use crate::types::{Min, WeightElement};
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "SpinGlass",
14 display_name: "Spin Glass",
15 aliases: &[],
16 dimensions: &[
17 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
18 VariantDimension::new("weight", "i32", &["i32", "f64"]),
19 ],
20 module_path: module_path!(),
21 description: "Minimize Ising Hamiltonian on a graph",
22 fields: &[
23 FieldInfo { name: "graph", type_name: "G", description: "The interaction graph" },
24 FieldInfo { name: "couplings", type_name: "Vec<W>", description: "Pairwise couplings J_ij" },
25 FieldInfo { name: "fields", type_name: "Vec<W>", description: "On-site fields h_i" },
26 ],
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SpinGlass<G, W> {
68 graph: G,
70 couplings: Vec<W>,
72 fields: Vec<W>,
74}
75
76impl<W: Clone + Default> SpinGlass<SimpleGraph, W> {
77 pub fn new(num_spins: usize, interactions: Vec<((usize, usize), W)>, fields: Vec<W>) -> Self {
84 assert_eq!(fields.len(), num_spins);
85 let edges: Vec<_> = interactions.iter().map(|((i, j), _)| (*i, *j)).collect();
86 let couplings: Vec<_> = interactions.iter().map(|(_, w)| w.clone()).collect();
87 let graph = SimpleGraph::new(num_spins, edges);
88 Self {
89 graph,
90 couplings,
91 fields,
92 }
93 }
94
95 pub fn without_fields(num_spins: usize, interactions: Vec<((usize, usize), W)>) -> Self
97 where
98 W: num_traits::Zero,
99 {
100 let fields = vec![W::zero(); num_spins];
101 Self::new(num_spins, interactions, fields)
102 }
103}
104
105impl<G: Graph, W: Clone + Default> SpinGlass<G, W> {
106 pub fn from_graph(graph: G, couplings: Vec<W>, fields: Vec<W>) -> Self {
113 assert_eq!(
114 couplings.len(),
115 graph.num_edges(),
116 "couplings length must match num_edges"
117 );
118 assert_eq!(
119 fields.len(),
120 graph.num_vertices(),
121 "fields length must match num_vertices"
122 );
123 Self {
124 graph,
125 couplings,
126 fields,
127 }
128 }
129
130 pub fn from_graph_without_fields(graph: G, couplings: Vec<W>) -> Self
132 where
133 W: num_traits::Zero,
134 {
135 let fields = vec![W::zero(); graph.num_vertices()];
136 Self::from_graph(graph, couplings, fields)
137 }
138
139 pub fn graph(&self) -> &G {
141 &self.graph
142 }
143
144 pub fn num_spins(&self) -> usize {
146 self.graph.num_vertices()
147 }
148
149 pub fn num_interactions(&self) -> usize {
151 self.graph.num_edges()
152 }
153
154 pub fn interactions(&self) -> Vec<((usize, usize), W)> {
158 self.graph
159 .edges()
160 .into_iter()
161 .zip(self.couplings.iter())
162 .map(|((i, j), w)| ((i, j), w.clone()))
163 .collect()
164 }
165
166 pub fn couplings(&self) -> &[W] {
168 &self.couplings
169 }
170
171 pub fn fields(&self) -> &[W] {
173 &self.fields
174 }
175
176 pub fn config_to_spins(config: &[usize]) -> Vec<i32> {
178 config.iter().map(|&x| 2 * x as i32 - 1).collect()
179 }
180}
181
182impl<G, W> SpinGlass<G, W>
183where
184 G: Graph,
185 W: Clone + num_traits::Zero + std::ops::AddAssign + std::ops::Mul<Output = W> + From<i32>,
186{
187 pub fn compute_energy(&self, spins: &[i32]) -> W {
189 let mut energy = W::zero();
190
191 for ((i, j), j_val) in self.graph.edges().iter().zip(self.couplings.iter()) {
193 let s_i = spins.get(*i).copied().unwrap_or(1);
194 let s_j = spins.get(*j).copied().unwrap_or(1);
195 let product: i32 = s_i * s_j;
196 energy += j_val.clone() * W::from(product);
197 }
198
199 for (i, h_val) in self.fields.iter().enumerate() {
201 let s_i = spins.get(i).copied().unwrap_or(1);
202 energy += h_val.clone() * W::from(s_i);
203 }
204
205 energy
206 }
207}
208
209impl<G, W> Problem for SpinGlass<G, W>
210where
211 G: Graph + crate::variant::VariantParam,
212 W: WeightElement
213 + crate::variant::VariantParam
214 + PartialOrd
215 + num_traits::Num
216 + num_traits::Zero
217 + num_traits::Bounded
218 + std::ops::AddAssign
219 + std::ops::Mul<Output = W>
220 + From<i32>,
221{
222 const NAME: &'static str = "SpinGlass";
223 type Value = Min<W::Sum>;
224
225 fn dims(&self) -> Vec<usize> {
226 vec![2; self.graph.num_vertices()]
227 }
228
229 fn evaluate(&self, config: &[usize]) -> Min<W::Sum> {
230 let spins = Self::config_to_spins(config);
231 Min(Some(self.compute_energy(&spins).to_sum()))
232 }
233
234 fn variant() -> Vec<(&'static str, &'static str)> {
235 crate::variant_params![G, W]
236 }
237}
238
239crate::declare_variants! {
240 default SpinGlass<SimpleGraph, i32> => "2^num_spins",
241 SpinGlass<SimpleGraph, f64> => "2^num_spins",
242}
243
244#[cfg(feature = "example-db")]
245pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
246 vec![crate::example_db::specs::ModelExampleSpec {
247 id: "spin_glass_simplegraph_i32",
248 instance: Box::new(SpinGlass::<SimpleGraph, i32>::without_fields(
249 5,
250 vec![
251 ((0, 1), 1),
252 ((1, 2), 1),
253 ((3, 4), 1),
254 ((0, 3), 1),
255 ((1, 3), 1),
256 ((1, 4), 1),
257 ((2, 4), 1),
258 ],
259 )),
260 optimal_config: vec![1, 0, 1, 1, 0],
261 optimal_value: serde_json::json!(-3),
262 }]
263}
264
265#[cfg(test)]
266#[path = "../../unit_tests/models/graph/spin_glass.rs"]
267mod tests;