flag_algebra/tools/
sdpa_problem.rs1use std::fs::File;
2use std::io;
3use std::io::{BufRead, BufReader, BufWriter, Write};
4use std::path::PathBuf;
5use std::process::{Command, Stdio};
6use std::result::Result;
7
8use log::*;
9
10use crate::sdpa::{Error, SdpaCoeff};
11
12const CS_COST: f64 = 100.;
13const INEQ_COST: f64 = 1.;
14
15pub const CERTIFICATE_FILE: &str = "certificate";
16pub const CERTIFICATE_MINIMIZE_FILE: &str = "certificate_minimize";
17
18#[derive(Debug, Clone)]
25pub struct SdpaProblem {
26 block_sizes: Vec<i32>,
27 b: Vec<f64>,
28 coeffs: Vec<SdpaCoeff>,
29}
30
31impl SdpaProblem {
32 pub fn write(&self, filename: &str) -> Result<(), io::Error> {
33 let mut w = BufWriter::new(File::create(filename)?);
34 writeln!(w, "{}", self.b.len())?;
35 writeln!(w, "{}", self.block_sizes.len())?;
36 for i in &self.block_sizes {
37 write!(w, "{i} ")?;
38 }
39 writeln!(w)?;
40 for x in &self.b {
41 write!(w, "{x} ")?;
42 }
43 writeln!(w)?;
44 for coeff in &self.coeffs {
45 writeln!(w, "{coeff} ")?;
46 }
47 Ok(())
48 }
49 pub fn load(filename: &str) -> Result<Self, Error> {
50 let mut filename = PathBuf::from(filename);
51 let _ = filename.set_extension("sdpa");
52 let buf = BufReader::new(File::open(filename)?);
53 let mut lines = buf.lines().map(|line| line.unwrap()).filter(|line| {
54 let l = line.trim_start();
55 !l.starts_with('*') && !l.is_empty()
56 });
57 let dim: usize = lines.next().unwrap().parse().unwrap();
58 let nblock: usize = lines.next().unwrap().parse()?;
59 let block_sizes: Vec<i32> = lines
60 .next()
61 .unwrap()
62 .split_whitespace()
63 .map(|x| x.parse().unwrap())
64 .collect();
65 assert_eq!(block_sizes.len(), nblock);
66 let b: Vec<f64> = lines
67 .next()
68 .unwrap()
69 .split_whitespace()
70 .map(|x| x.parse().unwrap())
71 .collect();
72 assert_eq!(b.len(), dim);
73 let mut coeffs: Vec<SdpaCoeff> = Vec::new();
74 for line in lines {
75 coeffs.push(line.parse().expect(&line))
76 }
77 Ok(SdpaProblem {
78 block_sizes,
79 b,
80 coeffs,
81 })
82 }
83 pub fn to_certificate_minimization(mut self, target_value: f64) -> Self {
85 self.b.push(target_value);
86 let new_mat = self.b.len();
87 for coeff in &mut self.coeffs {
89 if coeff.mat == 0 {
90 coeff.mat = new_mat
91 }
92 }
93 push_identities(&mut self.coeffs, 0, &self.block_sizes, -INEQ_COST, -CS_COST);
95 self
96 }
97}
98
99#[derive(Debug, Clone)]
104pub struct SdpaCertificate {
105 pub y: Vec<f64>,
106 pub coeffs: Vec<SdpaCoeff>,
107}
108
109impl SdpaCertificate {
110 pub fn write(&self, filename: &str) -> Result<(), io::Error> {
111 let filename = PathBuf::from(filename);
112 let mut w = BufWriter::new(File::create(filename)?);
114 for v in &self.y {
115 write!(w, "{v} ")?;
116 }
117 writeln!(w)?;
118 for coeff in &self.coeffs {
119 writeln!(w, "{coeff} ")?;
120 }
121 Ok(())
122 }
123 pub fn load(filename: &str) -> Result<Self, io::Error> {
124 let buf = BufReader::new(File::open(filename)?);
125 let mut lines = buf
126 .lines()
127 .map(|line| line.unwrap())
128 .filter(|line| !line.trim_start().is_empty());
129 let y: Vec<f64> = lines
130 .next()
131 .unwrap()
132 .split_whitespace()
133 .map(|x| x.parse().unwrap())
134 .collect();
135 let mut coeffs: Vec<SdpaCoeff> = Vec::new();
136 for line in lines {
137 coeffs.push(line.parse().expect(&line))
138 }
139 Ok(SdpaCertificate { y, coeffs })
140 }
141 pub fn to_certificate_minimization(mut self, problem: &SdpaProblem) -> Self {
142 self.y.push(-1.);
144 for coeff in &problem.coeffs {
152 if coeff.mat == 0 {
153 let new_coeff = SdpaCoeff {
154 mat: 1,
155 val: -coeff.val,
156 ..*coeff
157 };
158 self.coeffs.push(new_coeff)
159 }
160 }
161 sum_duplicates(&mut self.coeffs);
162 self
163 }
164 pub fn from_certificate_minimization(mut self) -> Self {
166 let _ = self.y.pop().unwrap();
168 self.y.push(0.);
169 for v in &mut self.y {
170 *v = 0.
171 }
172 self.coeffs.retain(|coeff| coeff.mat == 2);
174 self
175 }
176}
177
178pub fn csdp(filename: &str, initial_solution: Option<&str>) -> Result<f64, Error> {
180 let mut command = Command::new("csdp");
181 let _ = command.arg(filename).arg(CERTIFICATE_FILE);
182 if let Some(sol) = initial_solution {
183 let _ = command.arg(sol);
184 };
185 info!("Calling CSDP");
186 debug!("command: {:?}", command);
187 let mut child = command
188 .stdout(Stdio::piped())
189 .spawn()
190 .expect("Failed to call csdp");
191 let output = BufReader::new(child.stdout.take().unwrap());
192 use std::time::{Duration, Instant};
196 let time_start = Instant::now();
197 let mut lines = output.lines();
198 let mut stream = false;
199 let time_before_stream = Duration::from_secs(2);
200 while let Some(line_result) = lines.next() {
201 let line = line_result.unwrap();
202 if line.starts_with("CSDP") {
203 continue;
204 };
205 if line.starts_with("Iter") {
206 if !stream && time_start.elapsed() > time_before_stream {
207 stream = true;
208 info!(
209 "csdp is taking more than {}s, start streaming output",
210 time_before_stream.as_secs_f32()
211 )
212 }
213 if stream {
214 info!("{}", line)
215 } else {
216 debug!("{}", line)
217 }
218 } else {
219 let code = child
220 .wait()
221 .expect("csdp wasn't running")
222 .code()
223 .expect("No exit code");
224 if code == 0 {
225 let value: f64 = lines
226 .next()
227 .unwrap()
228 .unwrap()
229 .split_whitespace()
230 .nth(3)
231 .unwrap()
232 .parse()
233 .unwrap();
234 info!("{} with primal value {}", line, value);
235 return Ok(value);
236 } else {
237 info!("{}", line);
238 assert!(code <= 10, "{command:?} aborted with code {code}");
239 return Err(Error::SdpNotSolved(code));
240 }
241 }
242 }
243 panic!("CSDP output incorrectly parsed")
244}
245
246pub fn csdp_minimize_certificate(
247 filename: &str,
248 initial_solution: Option<&str>,
249) -> Result<f64, Error> {
250 let val = csdp(filename, initial_solution)?;
251 let problem = SdpaProblem::load(filename)?.to_certificate_minimization(val);
252 let cert = SdpaCertificate::load(CERTIFICATE_FILE)?.to_certificate_minimization(&problem);
253 let filename_minimize = format!("{filename}.minimize");
254 problem.write(&filename_minimize)?;
255 cert.write(CERTIFICATE_MINIMIZE_FILE)?;
256 info!("Try to minimize certificate");
257 match csdp(&filename_minimize, Some(CERTIFICATE_MINIMIZE_FILE)) {
258 Ok(_) => {
259 info!("Certificate minimized");
260 SdpaCertificate::load(CERTIFICATE_MINIMIZE_FILE)?
261 .from_certificate_minimization()
262 .write(CERTIFICATE_FILE)?;
263 }
264 Err(_) => {
265 warn!("Cannot minimize certificate");
266 }
267 }
268 Ok(val)
269}
270
271impl SdpaCoeff {
272 fn indices(&self) -> (usize, usize, usize, usize) {
273 (self.mat, self.block, self.i, self.j)
274 }
275}
276
277fn sum_duplicates(coeffs: &mut Vec<SdpaCoeff>) {
278 if coeffs.len() >= 2 {
279 coeffs.sort_by_key(SdpaCoeff::indices);
280 let mut write = 0;
281 for read in 1..coeffs.len() {
282 if coeffs[read].indices() == coeffs[write].indices() {
283 coeffs[write].val += coeffs[read].val
284 } else {
285 write += 1;
286 coeffs.swap(write, read);
287 }
288 }
289 coeffs.truncate(write + 1)
290 }
291}
292
293fn push_identities(
294 coeffs: &mut Vec<SdpaCoeff>,
295 matrix_number: usize,
296 block_sizes: &[i32],
297 scale_diag: f64,
298 scale_nondiag: f64,
299) {
300 for (block, &blocksize) in block_sizes.iter().enumerate() {
301 let val = if blocksize < 0 {
302 scale_diag
303 } else {
304 scale_nondiag
305 };
306 for i in 0..blocksize.unsigned_abs() as usize {
307 coeffs.push(SdpaCoeff {
308 mat: matrix_number,
309 block: block + 1,
310 i: i + 1,
311 j: i + 1,
312 val,
313 })
314 }
315 }
316}