Skip to content

Commit

Permalink
Merge pull request #160 from oxfordcontrol/pg/streams
Browse files Browse the repository at this point in the history
Configurable print streams
  • Loading branch information
goulart-paul authored Feb 12, 2025
2 parents 8fd7afe + cf6a97e commit a25987b
Show file tree
Hide file tree
Showing 22 changed files with 577 additions and 221 deletions.
4 changes: 2 additions & 2 deletions examples/python/example_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
print(filename)

# Load problem data from JSON file
solver = clarabel.read_from_file(filename)
solver = clarabel.load_from_file(filename)
solution = solver.solve()

# export problem data to JSON file
# filename = os.path.join(thisdir, "../data/out.json")
# solver.write_to_file(filename)
# solver.save_to_file(filename)
4 changes: 2 additions & 2 deletions examples/rust/example_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ fn main() {

let filename = "./examples/data/hs35.json";
let mut file = File::open(filename).unwrap();
let mut solver = DefaultSolver::<f64>::read_from_file(&mut file, None).unwrap();
let mut solver = DefaultSolver::<f64>::load_from_file(&mut file, None).unwrap();
solver.solve();

// to write the back to a new file

// let outfile = "./examples/data/output.json";
// let mut file = File::create(outfile).unwrap();
// solver.write_to_file(&mut file).unwrap();
// solver.save_to_file(&mut file).unwrap();
}
4 changes: 2 additions & 2 deletions python/clarabel/tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ def test_json_solve():

# load and solve with settings in file
file = "../../examples/data/hs35.json"
solver = clarabel.read_from_file(file)
solver = clarabel.load_from_file(file)
solution = solver.solve()
assert solution.status == clarabel.SolverStatus.Solved

# load and solve with custom settings
settings = clarabel.DefaultSettings()
settings.max_iter = 1
solver = clarabel.read_from_file(file, settings)
solver = clarabel.load_from_file(file, settings)
solution = solver.solve()
assert solution.status == clarabel.SolverStatus.MaxIterations
54 changes: 54 additions & 0 deletions python/clarabel/tests/test_print_streams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import clarabel
import pytest
import numpy as np
from scipy import sparse


@pytest.fixture
def test_print_data():

P = sparse.csc_matrix([[4.]])
P = sparse.triu(P).tocsc()

A = sparse.csc_matrix(
[[1.]])

q = np.array([1.])
b = np.array([1.])

cones = [clarabel.NonnegativeConeT(1)]
settings = clarabel.DefaultSettings()
solver = clarabel.DefaultSolver(P, q, A, b, cones, settings)
return solver


def test_print_to_file(test_print_data):

import tempfile
import os

solver = test_print_data

with tempfile.NamedTemporaryFile(delete=False) as file:
filename = file.name

try:
solver.print_to_file(filename)
solver.solve()
with open(filename, "r") as f:
contents = f.read()

assert "Clarabel.rs" in contents, f"Bad file contents: {contents}"

finally:
os.remove(filename)


def test_print_to_buffer(test_print_data):

solver = test_print_data
buffer = solver.print_to_buffer()
solver.solve()
contents = solver.get_print_buffer()

assert "Clarabel.rs" in contents, f"Bad buffer contents: {buffer}"
1 change: 0 additions & 1 deletion src/algebra/dense/blas/cholesky.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ macro_rules! generate_test_cholesky {

eng.solve(&mut B);

println!("final check {:?}", B.data.norm_inf_diff(X.data()));
assert!(B.data.norm_inf_diff(X.data()) <= (1e-12 as $fxx).$tolfn());
}
};
Expand Down
195 changes: 195 additions & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//! Types for managing solver output to various targets.
//!
use std::fs::File;
use std::io::{Error, ErrorKind, Result, Sink, Write};

#[cfg(not(feature = "python"))]
#[allow(unused_imports)]
pub(crate) use std::io::{stderr, stdout, Stdout};

// configure python specific stdout and stdin streams
// when compiled with the python feature. This avoids
// problems when running within python notebooks etc.
#[cfg(feature = "python")]
#[allow(unused_imports)]
pub(crate) use crate::python::io::{stderr, stdout, Stdout};

/// Container for managing multiple print targets
pub(crate) enum PrintTarget {
Stdout(Stdout),
File(File),
Buffer(Vec<u8>),
Stream(Box<dyn Write + Send + Sync>), // Supports any stream that implements `Write`
Sink(Sink),
}

impl<'a> From<&'a mut PrintTarget> for &'a mut dyn Write {
fn from(target: &'a mut PrintTarget) -> Self {
match target {
PrintTarget::Stdout(ref mut stdout) => stdout,
PrintTarget::File(ref mut file) => file,
PrintTarget::Stream(ref mut stream) => stream.as_mut(),
PrintTarget::Buffer(ref mut buffer) => buffer,
PrintTarget::Sink(ref mut sink) => sink,
}
}
}

impl Clone for PrintTarget {
fn clone(&self) -> Self {
match self {
PrintTarget::Stdout(_) => PrintTarget::Stdout(self::stdout()),
PrintTarget::File(file) => PrintTarget::File(file.try_clone().unwrap()),
PrintTarget::Buffer(buffer) => PrintTarget::Buffer(buffer.clone()),
//arbitary stream cloning is not supported
PrintTarget::Stream(_) => PrintTarget::Sink(std::io::sink()),
PrintTarget::Sink(_) => PrintTarget::Sink(std::io::sink()),
}
}
}

// explicit debug implementation because Python stdout does not implement Debug
impl std::fmt::Debug for PrintTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PrintTarget::Stdout(_) => write!(f, "PrintTarget::Stdout"),
PrintTarget::File(_) => write!(f, "PrintTarget::File"),
PrintTarget::Buffer(_) => write!(f, "PrintTarget::Buffer"),
PrintTarget::Stream(_) => write!(f, "PrintTarget::Stream"),
PrintTarget::Sink(_) => write!(f, "PrintTarget::Sink"),
}
}
}

impl Default for PrintTarget {
fn default() -> Self {
PrintTarget::Stdout(self::stdout())
}
}

impl Write for PrintTarget {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
match self {
PrintTarget::Stdout(stdout) => stdout.write(buf),
PrintTarget::File(file) => file.write(buf),
PrintTarget::Buffer(buffer) => {
buffer.extend_from_slice(buf);
Ok(buf.len())
}
PrintTarget::Stream(stream) => stream.write(buf),
PrintTarget::Sink(sink) => sink.write(buf),
}
}

fn flush(&mut self) -> Result<()> {
match self {
PrintTarget::Stdout(stdout) => stdout.flush(),
PrintTarget::File(file) => file.flush(),
PrintTarget::Buffer(_) => Ok(()),
PrintTarget::Stream(stream) => stream.flush(),
PrintTarget::Sink(sink) => sink.flush(),
}
}
}

/// Trait implemented by solvers that allow configurable print targets
pub trait ConfigurablePrintTarget {
/// redirect print output to stdout
fn print_to_stdout(&mut self);
/// redirect print output to a file
fn print_to_file(&mut self, file: File);
/// redirect print output to a stream
fn print_to_stream(&mut self, stream: Box<dyn Write + Send + Sync>);
/// redirect print output to sink (no output)
fn print_to_sink(&mut self);
/// redirect print output to an internal buffer
fn print_to_buffer(&mut self);
/// get the contents of the internal print buffer
fn get_print_buffer(&mut self) -> Result<String>;
}

impl ConfigurablePrintTarget for PrintTarget {
fn print_to_stdout(&mut self) {
*self = PrintTarget::Stdout(self::stdout());
}
fn print_to_file(&mut self, file: File) {
*self = PrintTarget::File(file);
}
fn print_to_stream(&mut self, stream: Box<dyn Write + Send + Sync>) {
*self = PrintTarget::Stream(stream);
}
fn print_to_sink(&mut self) {
*self = PrintTarget::Sink(std::io::sink());
}
fn print_to_buffer(&mut self) {
*self = PrintTarget::Buffer(Vec::new());
}
fn get_print_buffer(&mut self) -> std::io::Result<String> {
match self {
PrintTarget::Buffer(buffer) => Ok(String::from_utf8_lossy(buffer).to_string()),
_ => Err(Error::new(
ErrorKind::Other,
"Print buffering is not configured.",
)),
}
}
}

#[test]
fn test_print_target_from() {
use std::io::Cursor;

// stdout
let mut target = PrintTarget::Stdout(stdout());
let writer: &mut dyn Write = (&mut target).into();
assert!(writer.write(b"foo").is_ok());

// file
let file = tempfile::tempfile().unwrap();
let mut target = PrintTarget::File(file);
let writer: &mut dyn Write = (&mut target).into();
assert!(writer.write(b"foo").is_ok());

// buffer
let mut target = PrintTarget::Buffer(Vec::new());
let writer: &mut dyn Write = (&mut target).into();
assert!(writer.write(b"foo").is_ok());

// stream
let mut target = PrintTarget::Stream(Box::new(Cursor::new(Vec::new())));
let writer: &mut dyn Write = (&mut target).into();
assert!(writer.write(b"foo").is_ok());

// sink
let mut target = PrintTarget::Sink(std::io::sink());
let writer: &mut dyn Write = (&mut target).into();
assert!(writer.write(b"foo").is_ok());
}

#[test]
fn test_print_target_debug_and_clone() {
use std::io::Cursor;
//stdout
let target = PrintTarget::Stdout(stdout());
assert_eq!(format!("{:?}", target.clone()), "PrintTarget::Stdout");

//file
let file = tempfile::tempfile().unwrap();
let target = PrintTarget::File(file);
assert_eq!(format!("{:?}", target.clone()), "PrintTarget::File");

//buffer
let target = PrintTarget::Buffer(Vec::new());
assert_eq!(format!("{:?}", target.clone()), "PrintTarget::Buffer");

//stream
let target = PrintTarget::Stream(Box::new(Cursor::new(Vec::new())));
assert_eq!(format!("{:?}", target), "PrintTarget::Stream");
assert_eq!(format!("{:?}", target.clone()), "PrintTarget::Sink");

// sink
let target = PrintTarget::Sink(std::io::sink());
let cloned_target = target.clone();
assert_eq!(format!("{:?}", cloned_target), "PrintTarget::Sink");
}
Loading

0 comments on commit a25987b

Please sign in to comment.