Skip to main content

problemreductions/
export.rs

1//! JSON export schema for example payloads.
2
3use crate::rules::registry::ReductionOverhead;
4use crate::rules::ReductionGraph;
5use crate::traits::Problem;
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use std::fs;
9use std::path::Path;
10
11/// One side (source or target) of a reduction.
12#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
13pub struct ProblemSide {
14    /// Problem name matching `Problem::NAME` (e.g., `"MaximumIndependentSet"`).
15    pub problem: String,
16    /// Variant attributes (e.g., `{"graph": "SimpleGraph", "weight": "One"}`).
17    pub variant: BTreeMap<String, String>,
18    /// Problem-specific instance data (edges, matrix, clauses, etc.).
19    pub instance: serde_json::Value,
20}
21
22impl ProblemSide {
23    /// Build a serializable problem side from a typed problem.
24    pub fn from_problem<P>(problem: &P) -> Self
25    where
26        P: Problem + Serialize,
27    {
28        Self {
29            problem: P::NAME.to_string(),
30            variant: variant_to_map(P::variant()),
31            instance: serde_json::to_value(problem).expect("Failed to serialize problem instance"),
32        }
33    }
34
35    /// Extract the structural identity of this problem side.
36    pub fn problem_ref(&self) -> ProblemRef {
37        ProblemRef {
38            name: self.problem.clone(),
39            variant: self.variant.clone(),
40        }
41    }
42}
43
44/// Canonical structural identity for a problem node in the reduction graph.
45#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
46pub struct ProblemRef {
47    pub name: String,
48    pub variant: BTreeMap<String, String>,
49}
50
51/// One source↔target solution pair.
52#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
53pub struct SolutionPair {
54    pub source_config: Vec<usize>,
55    pub target_config: Vec<usize>,
56}
57
58/// A complete rule example: reduction + solutions in one file.
59#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
60pub struct RuleExample {
61    pub source: ProblemSide,
62    pub target: ProblemSide,
63    pub solutions: Vec<SolutionPair>,
64}
65
66/// A complete model example: instance + known optimal solution.
67#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
68pub struct ModelExample {
69    pub problem: String,
70    pub variant: BTreeMap<String, String>,
71    pub instance: serde_json::Value,
72    pub optimal_config: Vec<usize>,
73    pub optimal_value: serde_json::Value,
74}
75
76impl ModelExample {
77    pub fn new(
78        problem: &str,
79        variant: BTreeMap<String, String>,
80        instance: serde_json::Value,
81        optimal_config: Vec<usize>,
82        optimal_value: serde_json::Value,
83    ) -> Self {
84        Self {
85            problem: problem.to_string(),
86            variant,
87            instance,
88            optimal_config,
89            optimal_value,
90        }
91    }
92
93    pub fn problem_ref(&self) -> ProblemRef {
94        ProblemRef {
95            name: self.problem.clone(),
96            variant: self.variant.clone(),
97        }
98    }
99}
100
101/// Canonical exported database of rule examples.
102#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
103pub struct RuleDb {
104    pub rules: Vec<RuleExample>,
105}
106
107/// Canonical exported database of model examples.
108#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
109pub struct ModelDb {
110    pub models: Vec<ModelExample>,
111}
112
113/// Canonical exported database of model and rule examples.
114#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
115pub struct ExampleDb {
116    pub models: Vec<ModelExample>,
117    pub rules: Vec<RuleExample>,
118}
119
120/// Look up `ReductionOverhead` for a direct reduction using `ReductionGraph::find_best_entry`.
121pub fn lookup_overhead(
122    source_name: &str,
123    source_variant: &BTreeMap<String, String>,
124    target_name: &str,
125    target_variant: &BTreeMap<String, String>,
126) -> Option<ReductionOverhead> {
127    let graph = ReductionGraph::new();
128    let matched =
129        graph.find_best_entry(source_name, source_variant, target_name, target_variant)?;
130    Some(matched.overhead)
131}
132
133/// Convert `Problem::variant()` output to a stable `BTreeMap`.
134///
135/// Normalizes empty `"graph"` values to `"SimpleGraph"` for consistency
136/// with the reduction graph convention.
137pub fn variant_to_map(variant: Vec<(&str, &str)>) -> BTreeMap<String, String> {
138    variant
139        .into_iter()
140        .map(|(k, v)| {
141            let value = if k == "graph" && v.is_empty() {
142                "SimpleGraph".to_string()
143            } else {
144                v.to_string()
145            };
146            (k.to_string(), value)
147        })
148        .collect()
149}
150
151fn write_json_file<T: Serialize>(dir: &Path, name: &str, payload: &T) {
152    fs::create_dir_all(dir).expect("Failed to create examples directory");
153    let path = dir.join(format!("{name}.json"));
154    let json = serde_json::to_string_pretty(payload).expect("Failed to serialize example");
155    fs::write(&path, json).expect("Failed to write example JSON");
156    println!("Exported: {}", path.display());
157}
158
159fn render_compact_array<T: Serialize>(items: &[T]) -> String {
160    if items.is_empty() {
161        "[]".to_string()
162    } else {
163        let rows = items
164            .iter()
165            .map(|item| {
166                format!(
167                    "    {}",
168                    serde_json::to_string(item).expect("Failed to serialize example entry")
169                )
170            })
171            .collect::<Vec<_>>()
172            .join(",\n");
173        format!("[\n{rows}\n  ]")
174    }
175}
176
177fn write_example_db_file(dir: &Path, db: &ExampleDb) {
178    fs::create_dir_all(dir).expect("Failed to create examples directory");
179    let path = dir.join("examples.json");
180    let json = format!(
181        "{{\n  \"models\": {},\n  \"rules\": {}\n}}\n",
182        render_compact_array(&db.models),
183        render_compact_array(&db.rules)
184    );
185    fs::write(&path, json).expect("Failed to write example JSON");
186    println!("Exported: {}", path.display());
187}
188
189/// Write a merged rule example JSON file.
190pub fn write_rule_example_to(dir: &Path, name: &str, example: &RuleExample) {
191    write_json_file(dir, name, example);
192}
193
194/// Write a model example JSON file to a target directory.
195pub fn write_model_example_to(dir: &Path, name: &str, example: &ModelExample) {
196    write_json_file(dir, name, example);
197}
198
199/// Write the canonical rule database as a wrapped JSON object.
200pub fn write_rule_db_to(dir: &Path, db: &RuleDb) {
201    write_json_file(dir, "rules", db);
202}
203
204/// Write the canonical model database as a wrapped JSON object.
205pub fn write_model_db_to(dir: &Path, db: &ModelDb) {
206    write_json_file(dir, "models", db);
207}
208
209/// Write the canonical example database as a wrapped JSON object.
210pub fn write_example_db_to(dir: &Path, db: &ExampleDb) {
211    write_example_db_file(dir, db);
212}
213
214#[cfg(test)]
215#[path = "unit_tests/export.rs"]
216mod tests;