1use std::path::{Path, PathBuf};
9
10use serde::Serialize;
11use sprs::CsMat;
12
13use crate::Result;
14use crate::indexed::IndexedNetwork;
15use crate::io::mtx::{write_mtx, write_vector_mtx};
16use crate::matrix::incidence::{DcConvention, build_flow_map, build_incidence};
17use crate::matrix::laplacian::{build_weighted_laplacian, ground_at_each, reference_indicator};
18use crate::matrix::opf::{Units, build_opf_instance};
19use crate::network::Network;
20
21#[derive(Debug, Clone, Default)]
22pub struct DcOpfOptions {
23 pub convention: DcConvention,
24 pub units: Units,
25}
26
27#[derive(Debug, Clone)]
28pub struct DcOpfOutputs {
29 pub dir: PathBuf,
30 pub files: Vec<PathBuf>,
31}
32
33#[derive(Serialize)]
34struct DcOpfMeta {
35 case_name: String,
36 base_mva: f64,
37 n: usize,
38 m: usize,
39 n_gen: usize,
40 reference_buses: Vec<usize>,
45 convention: DcConvention,
46 units: Units,
47 files: Vec<String>,
48 powerio_version: String,
49}
50
51pub fn write_dcopf_bundle(
53 net: &Network,
54 out_dir: impl AsRef<Path>,
55 opts: &DcOpfOptions,
56) -> Result<DcOpfOutputs> {
57 let view = IndexedNetwork::new(net);
58
59 let dir = out_dir.as_ref().join(format!("{}_dcopf", view.name()));
60 std::fs::create_dir_all(&dir)?;
61
62 view.check_reference_coverage()?;
63 let refs = view.reference_bus_indices();
64 let inc = build_incidence(&view, opts.convention)?;
65 let l = build_weighted_laplacian(&inc.a, &inc.b);
66 let l_grounded = ground_at_each(&l, &refs);
67 let flow = build_flow_map(&inc.a, &inc.b);
68 let opf = build_opf_instance(&view, &inc, opts.units)?;
69 let e_r = reference_indicator(view.n(), &refs);
70
71 let mut files = Vec::new();
72
73 put_mat(&dir, "A.mtx", &inc.a, &mut files)?;
75 put_mat(&dir, "L.mtx", &l, &mut files)?;
76 put_mat(&dir, "L_grounded.mtx", &l_grounded, &mut files)?;
77 put_mat(&dir, "BAt.mtx", &flow, &mut files)?;
78 put_mat(&dir, "Cg.mtx", &opf.c_g, &mut files)?;
79
80 put_vec(&dir, "b.mtx", &inc.b, &mut files)?;
82 put_vec(&dir, "p_shift.mtx", &inc.p_shift, &mut files)?;
83 put_vec(&dir, "e_r.mtx", &e_r, &mut files)?;
86 put_vec(&dir, "q.mtx", &opf.bus.q, &mut files)?;
87 put_vec(&dir, "c.mtx", &opf.bus.c, &mut files)?;
88 put_vec(&dir, "pmax.mtx", &opf.bus.pmax, &mut files)?;
89 put_vec(&dir, "pmin.mtx", &opf.bus.pmin, &mut files)?;
90 put_vec(&dir, "fmax.mtx", &opf.f_max, &mut files)?;
91 put_vec(&dir, "pd.mtx", &opf.bus.p_d, &mut files)?;
92
93 put_vec(&dir, "q_gen.mtx", &opf.gen_costs.q, &mut files)?;
95 put_vec(&dir, "c_gen.mtx", &opf.gen_costs.c, &mut files)?;
96 put_vec(&dir, "pmax_gen.mtx", &opf.gen_costs.pmax, &mut files)?;
97 put_vec(&dir, "pmin_gen.mtx", &opf.gen_costs.pmin, &mut files)?;
98
99 let meta = DcOpfMeta {
100 case_name: view.name().to_string(),
101 base_mva: view.base_mva(),
102 n: view.n(),
103 m: inc.m(),
104 n_gen: opf.n_gen(),
105 reference_buses: refs.clone(),
106 convention: opts.convention,
107 units: opts.units,
108 files: files
109 .iter()
110 .filter_map(|p| p.file_name().and_then(|s| s.to_str()).map(str::to_string))
111 .collect(),
112 powerio_version: env!("CARGO_PKG_VERSION").to_string(),
113 };
114 let meta_path = dir.join("dcopf_meta.json");
115 let json = serde_json::to_string_pretty(&meta).map_err(|e| crate::Error::Mtx(e.to_string()))?;
116 std::fs::write(&meta_path, json)?;
117 files.push(meta_path);
118
119 Ok(DcOpfOutputs { dir, files })
120}
121
122fn put_mat(dir: &Path, name: &str, m: &CsMat<f64>, files: &mut Vec<PathBuf>) -> Result<()> {
123 let p = dir.join(name);
124 write_mtx(m, &p)?;
125 files.push(p);
126 Ok(())
127}
128
129fn put_vec(dir: &Path, name: &str, v: &[f64], files: &mut Vec<PathBuf>) -> Result<()> {
130 let p = dir.join(name);
131 write_vector_mtx(v, &p)?;
132 files.push(p);
133 Ok(())
134}