Skip to main content

mosekcomodel_optserver/
lib.rs

1//! This module implements a backend that uses a MOSEK OptServer instance for solving, for example
2//! [solve.mosek.com:30080](http://solve.mosek.com). 
3//!
4use mosekcomodel::domain::{QuadraticCone, VectorDomainType};
5use mosekcomodel::utils::Permutation;
6use mosekcomodel::*;
7use mosekcomodel::model::{IntSolutionManager, ModelWithIntSolutionCallback, ModelWithLogCallback};
8use mosekcomodel::utils::iter::{Chunkation, ChunksByIterExt, PermuteByEx, PermuteByMutEx};
9use itertools::{iproduct, izip, Permutations};
10use std::collections::HashMap;
11use std::fs::File;
12use std::io::{BufReader,BufRead,Read};
13use std::path::Path;
14
15//mod http;
16mod json;
17mod bio;
18mod msto;
19
20pub type Model = ModelAPI<Backend>;
21
22
23#[derive(Clone,Copy)]
24struct ConItem {
25    block_index : usize,
26    offset     : usize,
27}
28
29#[derive(Clone,Copy)]
30enum VarItem {
31    /// Variable element corresponds to underlying variable `index`
32    Linear,
33    /// Variable element corresponds to underlying variable `index`, and the dual corresponds to
34    /// the lower bound on the variable.
35    LinearLowerBound,
36    /// Variable element corresponds to underlying variable `index`, and the dual corresponds to
37    /// the upper bound on the variable.
38    LinearUpperBound,
39    /// Variable element corresponds to underlying variable `index` and 
40    Conic{conidx : usize}
41}
42
43#[derive(Clone)]
44enum VecCone {
45    Zero{dim : usize},
46    NonNegative{dim : usize},
47    NonPositive{dim : usize},
48    Unbounded{dim : usize},
49    Quadratic{dim : usize},
50    RotatedQuadratic{dim : usize},
51    PrimalPower{dim : usize, alpha : Vec<f64>},
52    DualPower{dim : usize, alpha : Vec<f64>},
53    PrimalExp,
54    DualExp,
55    PrimalGeometricMean{dim : usize},
56    DualGeometricMean{dim : usize},
57    ScaledVectorizedPSD{dim : usize},
58}
59impl VecCone {
60    pub fn dim(&self) -> usize {
61        use VecCone::*;
62        match self {
63            Zero{dim} => *dim,
64            NonNegative{dim} => *dim,
65            NonPositive{dim} => *dim,
66            Unbounded{dim} => *dim,
67            Quadratic{dim} => *dim,
68            RotatedQuadratic{dim} => *dim,
69            PrimalPower{dim, ..} => *dim,
70            DualPower{dim, ..} => *dim,
71            PrimalExp => 3,
72            DualExp => 3,
73            PrimalGeometricMean{dim} => *dim,
74            DualGeometricMean{dim} => *dim,
75            ScaledVectorizedPSD{dim} => *dim,
76        }
77    }
78}
79
80/// Simple model object that supports input of linear, conic and disjunctive constraints. It only
81/// stores data, it does not support solving or writing problems.
82#[derive(Default)]
83pub struct Backend {
84    name : Option<String>,
85    
86    /// Message callback
87    log_cb        : Option<Box<dyn Fn(&str)>>,
88    /// Integer solution callback
89    sol_cb        : Option<Box<dyn FnMut(&IntSolutionManager)>>,
90
91    /// Lower bounds of underlying variables
92    var_lb : Vec<f64>,
93    /// Upper bounds of underlying variables
94    var_ub : Vec<f64>,
95
96    /// Indicates per scalar variable which are integer constrained. 
97    var_int       : Vec<bool>,
98    /// Variable names.
99    var_names     : Vec<Option<String>>,
100    /// Corresponding native indexes
101    var_idx : Vec<usize>,
102    /// Variables. Each value is the index of the corresponding scalar constraint defining the
103    /// domain.
104    vars          : Vec<VarItem>,
105
106    /// Matrix storage
107    mx            : msto::MatrixStore,
108
109    /// Scalar constraint elements, each element corresponds to a scalar linear constraint or an
110    /// single element in a conic constraint.
111    cons          : Vec<ConItem>,
112    con_mx_row    : Vec<usize>,
113    con_rhs       : Vec<f64>,
114    con_block_ptr : Vec<usize>,
115    con_block_dom       : Vec<VecCone>,
116    con_names     : Vec<Option<String>>,
117
118    sense_max     : bool,
119    c_subj        : Vec<usize>,
120    c_cof         : Vec<f64>,
121
122    address       : Option<reqwest::Url>,
123    dpar          : HashMap<String,f64>,
124    ipar          : HashMap<String,i32>,
125}
126
127impl BaseModelTrait for Backend {
128    fn new(name : Option<&str>) -> Self {
129        Backend{
130            name : name.map(|v| v.to_string()),
131            vars : vec![VarItem::Linear],
132            var_idx : vec![usize::MAX],
133            ..Default::default()
134        }
135    }
136
137    fn free_variable<const N : usize>
138        (&mut self,
139         name  : Option<&str>,
140         shape : &[usize;N]) -> Result<<LinearDomain<N> as VarDomainTrait<Self>>::Result, String> where Self : Sized 
141    {
142        let n = shape.iter().product::<usize>();
143
144        let lb = vec![f64::NEG_INFINITY;n];
145        let ub = vec![f64::INFINITY;n];
146        let idxs = self.native_linear_variable(lb.as_slice(),ub.as_slice(),false);
147
148
149        let first = self.vars.len();
150        let last = first + n;
151
152        if let Some(name) = name {
153            self.linear_names(idxs.start,idxs.end, shape, None, name);
154        }
155
156        Ok(Variable::new((first..last).collect::<Vec<usize>>(), None, shape))
157    }
158
159    fn linear_variable<const N : usize,R>
160        (&mut self, 
161         name : Option<&str>,
162         dom  : LinearDomain<N>) -> Result<<LinearDomain<N> as VarDomainTrait<Self>>::Result,String>    
163        where 
164            Self : Sized
165    {
166        let (dt,b,sp,shape,is_integer) = dom.dissolve();
167        let n = sp.as_ref().map(|v| v.len()).unwrap_or(shape.iter().product::<usize>());
168
169        let (idxs,vit) =
170            match dt {
171                LinearDomainType::Free        => { (self.native_linear_variable(vec![f64::NEG_INFINITY;n].as_slice(), vec![f64::INFINITY; n].as_slice(), is_integer),VarItem::Linear) },
172                LinearDomainType::Zero        => { (self.native_linear_variable(b.as_slice(),b.as_slice(), is_integer), VarItem::Linear) },
173                LinearDomainType::NonNegative => { (self.native_linear_variable(b.as_slice(), vec![f64::INFINITY; n].as_slice(), is_integer),VarItem::LinearLowerBound) },
174                LinearDomainType::NonPositive => { (self.native_linear_variable(vec![f64::NEG_INFINITY;n].as_slice(), b.as_slice(), is_integer),VarItem::LinearUpperBound) },
175            };
176        let (first,last) = (self.var_idx.len(),self.vars.len() + n);
177
178        self.var_idx.reserve(n); for i in idxs.clone() { self.var_idx.push(i); }
179        self.vars.resize(last, vit);
180        
181        if let Some(name) = name {
182            self.linear_names(idxs.start, idxs.end, &shape, sp.as_deref(), name);
183        }
184
185        Ok(Variable::new((first..last).collect::<Vec<usize>>(), sp, &shape))
186    }
187    
188    fn ranged_variable<const N : usize,R>(&mut self, name : Option<&str>,dom : LinearRangeDomain<N>) -> Result<<LinearRangeDomain<N> as VarDomainTrait<Self>>::Result,String> 
189        where 
190            Self : Sized 
191    {
192        let (shape,bl,bu,sp,is_integer) = dom.dissolve();
193        let n = sp.as_ref().map(|v| v.len()).unwrap_or(shape.iter().product::<usize>());
194
195        let idxs = self.native_linear_variable(bl.as_slice(),bu.as_slice(), is_integer);
196        let first = self.var_idx.len();
197        self.var_idx.reserve(n*2);
198        for i in idxs.clone() { self.var_idx.push(i); }
199        for i in idxs.clone() { self.var_idx.push(i); }
200        self.vars.reserve(n*2);
201        self.vars.resize(first+n,   VarItem::LinearLowerBound);
202        self.vars.resize(first+n*2, VarItem::LinearUpperBound);
203
204        if let Some(name) = name {
205            self.linear_names(idxs.start, idxs.end, &shape, sp.as_deref(), name);
206        }
207
208        Ok((Variable::new((first..first+n).collect::<Vec<usize>>(), sp.clone(), &shape),
209            Variable::new((first+n..first+2*n).collect::<Vec<usize>>(), sp, &shape)))
210    }
211
212    fn linear_constraint<const N : usize>
213        (& mut self, 
214         name  : Option<&str>,
215         dom   : LinearDomain<N>,
216         _eshape : &[usize], 
217         ptr   : &[usize], 
218         subj  : &[usize], 
219         cof   : &[f64]) -> Result<<LinearDomain<N> as ConstraintDomain<N,Self>>::Result,String>  
220    {
221        let (dt,rhs,sp,shape,_is_integer) = dom.dissolve();
222
223        if sp.is_some() { return Err("Sparse domain on costraint not allowd".to_string()); }
224        assert_eq!(rhs.len(),ptr.len()-1); 
225        assert_eq!(ptr.len(),shape.iter().product::<usize>()+1);
226        let n = rhs.len();
227        
228        let first = self.con_rhs.len();
229
230        let ptrchunks = Chunkation::new(ptr).unwrap();
231        let rowidxs = izip!(ptrchunks.chunks(subj).unwrap(), 
232                            ptrchunks.chunks(cof).unwrap())
233            .map(|(subj,cof)| {
234                //println!("{}:{}: linear_constraint(), subj = {:?}",file!(),line!(),subj);
235                if let (Some(j),Some(c)) = (subj.first(),cof.first()) {
236                    if *j == 0 {
237                        self.mx.append_row(&subj[1..], &cof[..cof.len()-1], *c)
238                    }
239                    else {
240                        self.mx.append_row(subj, cof, 0.0)
241                    }
242                }
243                else {
244                    self.mx.append_row(subj, cof, 0.0)
245                }
246            });
247
248        let block_index = self.con_block_ptr.len();
249        self.con_block_ptr.push(first);
250
251        match dt {
252            LinearDomainType::Free        => self.con_block_dom.push(VecCone::Unbounded   { dim: n }),
253            LinearDomainType::Zero        => self.con_block_dom.push(VecCone::Zero        { dim: n }),
254            LinearDomainType::NonNegative => self.con_block_dom.push(VecCone::NonNegative { dim: n }),
255            LinearDomainType::NonPositive => self.con_block_dom.push(VecCone::NonPositive { dim: n }),
256        }
257        self.con_mx_row.extend(rowidxs);
258        self.con_rhs.extend_from_slice(rhs.as_slice());
259
260        self.cons.extend((0..n).map(|offset| ConItem{block_index, offset}));
261
262        if let Some(name) = name {
263            let mut name_index_buf = [1usize; N];
264            for _ in 0..n {
265                name_index_buf.iter_mut().zip(shape.iter()).rev().fold(1,|c,(i,&d)| { *i += c; if *i > d { *i = 1; 1 } else { 0 } });
266                self.con_names.push(Some(format!("{}{:?}", name, name_index_buf)));
267            }
268        }
269        else {
270            self.con_names.resize(self.con_names.len()+n,None);
271        }
272
273        Ok(Constraint::new((first..first+n).collect::<Vec<usize>>(), &shape))
274    }
275
276    fn ranged_constraint<const N : usize>
277        (& mut self, 
278         name : Option<&str>, 
279         dom  : LinearRangeDomain<N>,
280         _eshape : &[usize], 
281         ptr : &[usize], 
282         subj : &[usize], 
283         cof : &[f64]) -> Result<<LinearRangeDomain<N> as ConstraintDomain<N,Self>>::Result,String> 
284    {
285        let (shape,bl,bu,sp,_) = dom.dissolve();
286        if sp.is_some() { return Err("Sparse domain on costraint not allowd".to_string()); }
287        assert_eq!(bl.len(),ptr.len()-1); 
288        assert_eq!(bu.len(),ptr.len()-1); 
289        assert_eq!(ptr.len(),shape.iter().product::<usize>()+1);
290
291        let n = bl.len();
292        
293        let first0 = self.con_rhs.len();
294        let last0  = first0+n;
295        let first1 = last0;
296        let last1  = first1+n*2;
297
298        let ptrchunks = Chunkation::new(ptr).unwrap();
299        let rowidxs : Vec<usize> = izip!(ptrchunks.chunks(subj).unwrap(), ptrchunks.chunks(cof).unwrap())
300            .map(|(subj,cof)| {
301                if let (Some(j),Some(c)) = (subj.first(),cof.first()) {
302                    //println!("{}:{}: ranged_constraint(), subj = {:?}",file!(),line!(),subj);
303                    if *j == 0 {
304                        self.mx.append_row(&subj[1..], &cof[..cof.len()-1], *c)
305                    }
306                    else {
307                        self.mx.append_row(subj, cof, 0.0)
308                    }
309                }
310                else {
311                    self.mx.append_row(subj, cof, 0.0)
312                }
313            })
314            .collect();
315        let block_index = self.con_block_ptr.len();
316
317        self.con_block_ptr.push(self.con_rhs.len());
318        self.con_block_ptr.push(self.con_rhs.len()+n);
319
320        self.con_block_dom.push(VecCone::NonNegative { dim: n });
321        self.con_block_dom.push(VecCone::NonPositive { dim: n });
322
323        self.con_mx_row.extend_from_slice(rowidxs.as_slice());
324        self.con_mx_row.extend_from_slice(rowidxs.as_slice());
325        self.con_rhs.extend_from_slice(bl.as_slice());
326        self.con_rhs.extend_from_slice(bu.as_slice());
327
328        self.cons.extend((0..n).map(|offset| ConItem{block_index, offset} ));
329        self.cons.extend((0..n).map(|offset| ConItem{block_index : block_index+1, offset} ));
330
331        if let Some(name) = name {
332            let mut name_index_buf = [1usize; N];
333            for _ in 0..n {
334                name_index_buf.iter_mut().zip(shape.iter()).rev().fold(1,|c,(i,&d)| { *i += c; if *i > d { *i = 1; 1 } else { 0 } });
335                self.con_names.push(Some(format!("{}{:?}", name, name_index_buf)));
336            }
337        }
338        else {
339            for _ in 0..n {
340                self.con_names.push(None);
341            }
342        }
343        Ok((Constraint::new((first0..last0).collect::<Vec<usize>>(), &shape),
344            Constraint::new((first1..last1).collect::<Vec<usize>>(), &shape)))
345    }
346
347    fn update(& mut self, rowidxs : &[usize], shape : &[usize], ptr : &[usize], subj : &[usize], cof : &[f64]) -> Result<(),String>
348    {
349        if shape.iter().product::<usize>() != rowidxs.len() { return Err("Mismatching constraint and experssion sizes".to_string()); }
350
351        if let Some(&i) = rowidxs.iter().max() {
352            if i >= self.cons.len() {
353                return Err("Constraint index out of bounds".to_string());
354            }
355        }
356
357        let ptrchunker = Chunkation::new(ptr).unwrap();
358        for (i,subj,cof) in izip!(rowidxs.iter(),
359                                  ptrchunker.chunks(subj).unwrap(),
360                                  ptrchunker.chunks(cof).unwrap()) {
361            if let Some((j,c)) = subj.first().zip(cof.first()) {
362                if *j == 0 {
363                    self.mx.replace_row(*i, &subj[1..], &cof[1..], *c);
364                }
365                else {
366                    self.mx.replace_row(*i, subj, cof, 0.0);
367                }
368            }
369            else {
370                self.mx.replace_row(*i, subj, cof, 0.0);
371            }
372        }
373
374        Ok(())
375    }
376
377    fn write_problem<P>(&self, filename : P) -> Result<(),String> where P : AsRef<Path>
378    {
379        let p = filename.as_ref();
380        if let Some(ext) = p.extension().and_then(|ext| ext.to_str()) {
381            match ext {
382                "json"|"jtask" => {
383                    self.write_jtask(&mut File::create(p).map_err(|e| e.to_string())?).map_err(|e| e.to_string())
384                 },
385                _ => Err("Writing problem not supported".to_string())
386            }
387        }
388        else {
389            Err("Writing problem not supported".to_string())
390        }
391    }
392
393
394
395    fn solve(& mut self, sol_bas : & mut Solution, sol_itr : &mut Solution, sol_itg : &mut Solution) -> Result<(),String>
396    {
397        if let Some(url) = &self.address {
398            let mut url = url.clone(); 
399            url.set_path("/api/v1/submit+solve");
400
401            let (req_r,mut req_w) = std::io::pipe().map_err(|e| e.to_string())?;
402            let (mut resp_r,mut resp_w) = std::io::pipe().map_err(|e| e.to_string())?;
403
404            let t = std::thread::spawn(move || {
405                let client = reqwest::blocking::Client::new();
406                client.post(url)
407                    .header("Content-Type", "application/x-mosek-jtask")
408                    .header("Accept", "application/x-mosek-multiplex")
409                    .header("X-Mosek-Callback", "values")
410                    .header("X-Mosek-Stream", "log")
411                    .body(reqwest::blocking::Body::new(req_r))
412                    .send()
413                    .map_err(|e| e.to_string())
414            });
415
416            self.write_jtask(&mut req_w).map_err(|e| e.to_string())?;
417            drop(req_w);
418
419            let mut resp = t.join().unwrap()?;
420
421            if ! resp.status().is_success() {
422                return Err(format!("OptServer responded with code {}",resp.status()));
423            }
424
425            let mut content_type = None;
426            for (k,v) in resp.headers().iter() {
427                if k == "content-type" {
428                    content_type = Some(v);
429                }
430            }
431            if let Some(ct) = content_type {
432                if ct != "application/x-mosek-multiplex" {
433                    return Err(format!("Unexpected response format: {:?}",ct));
434                }
435            }
436            else {
437                return Err("Unexpected response format: Missing".to_string());
438            }
439
440            let t = std::thread::spawn(move || {
441                resp.copy_to(&mut resp_w).map_err(|e| e.to_string())
442            });
443
444            let res = self.parse_multistream(&mut resp_r,sol_bas,sol_itr,sol_itg);
445            t.join().unwrap().and_then(|_| res)?;
446        }
447        else {
448            return Err("No optserver address given".to_string());
449        }
450
451        Ok(())
452
453    } 
454
455    fn objective(&mut self, _name : Option<&str>, sense : Sense, subj : &[usize],cof : &[f64]) -> Result<(),String>
456    {
457        self.sense_max = match sense { Sense::Maximize => true, Sense::Minimize => false };
458        self.c_subj.resize(subj.len(),0); self.c_subj.copy_from_slice(subj);
459        self.c_cof.resize(cof.len(),0.0); self.c_cof.copy_from_slice(cof);
460        Ok(())
461    }
462}
463
464
465pub trait OptserverDomainTrait : VectorDomainTrait {
466
467}
468
469impl<D> VectorConeModelTrait<D> for Backend where D : VectorDomainTrait+'static {
470    fn conic_variable<const N : usize>(&mut self, name : Option<&str>,dom : VectorDomain<N,D>) -> Result<Variable<N>,String> {
471        let (dt,rhs,shape,conedim,is_int) = dom.dissolve();
472        self.conic_variable(name,shape,conedim,dt.to_conic_domain_type(),rhs.as_slice(),is_int)
473
474    }
475    fn conic_constraint<const N : usize>(& mut self, name : Option<&str>, dom  : VectorDomain<N,D>, _shape : &[usize], ptr : &[usize], subj : &[usize], cof : &[f64]) -> Result<Constraint<N>,String> {
476        let (dt,rhs,shape,conedim,_is_int) = dom.dissolve();
477
478        self.conic_constraint(name, ptr, subj, cof, shape, conedim, dt.to_conic_domain_type(), rhs.as_slice())        
479    }
480}
481
482
483impl ModelWithIntSolutionCallback for Backend {
484    fn set_solution_callback<F>(&mut self, func : F) where F : 'static+FnMut(&IntSolutionManager) {
485        self.sol_cb = Some(Box::new(func))
486    }
487}
488
489
490
491
492mod msgread {
493    use std::io::Read;
494
495    pub struct MessageReader<'a,R> where R : Read {
496        eof : bool,
497        frame_remains : usize,
498        final_frame : bool,
499        s : & 'a mut R
500
501    }
502    impl<'a,R> MessageReader<'a,R> where R : Read {
503        pub fn new(s : &'a mut R) -> MessageReader<'a,R> { MessageReader { eof: false, frame_remains: 0, s , final_frame : false}}
504        #[allow(unused)]
505        pub fn skip(&mut self) -> std::io::Result<()> {
506            let mut buf = [0;4096];
507            while 0 < self.read(&mut buf)? {}
508            Ok(())
509        }
510    }
511
512    impl<'a,R> Read for MessageReader<'a,R> where R : Read {
513        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
514            //println!("MessageReader::read(), eof = {}, frame remains : {}, final_frame = {}",self.eof,self.frame_remains,self.final_frame);
515            if self.eof {
516                Ok(0)
517            }
518            else {
519                if self.frame_remains == 0 {
520                    if self.final_frame {
521                        return Ok(0);
522                    }
523                    let mut buf = [0;2];
524                    //println!("MessageReader::read(), read frame header...");
525                    self.s.read_exact(&mut buf).map_err(|e| std::io::Error::other("Message stream error: Failed to read message header"))?;
526
527                    self.final_frame = buf[0] > 127;
528                    self.frame_remains = (((buf[0] & 0x7f) as usize) << 8) | (buf[1] as usize);
529                    //println!("MessageReader::read(), new frame : {}",self.frame_remains);
530                }
531
532                let n = buf.len().min(self.frame_remains);
533                //println!("MessageReader::read(), new frame : {}",self.frame_remains);
534                let nr = self.s.read(&mut buf[..n])?;
535                self.frame_remains -= nr;
536                Ok(nr)
537            }
538        }
539    }
540}
541
542use msgread::*;
543pub struct SolverAddress(pub String);
544
545impl SolverParameterValue<Backend> for SolverAddress {
546    type Key = ();
547    fn set(self,_parname : Self::Key, model : & mut Backend) -> Result<(),String> {
548        model.address = Some(reqwest::Url::parse(self.0.as_str())
549            .map_err(|_| "Invalid SolverAddress value".to_string())
550            .and_then(|mut url| 
551                if url.scheme() == "" {
552                    url.set_scheme("http").unwrap();
553                    Ok(url)
554                }
555                else if url.scheme().eq_ignore_ascii_case("http") || url.scheme().eq_ignore_ascii_case("https") { 
556                    Ok(url) 
557                } else {
558                    Err(format!("Invalid url scheme: {}",url.as_str()))
559                })?);
560        Ok(())
561    }
562}
563
564impl SolverParameterValue<Backend> for f64 {
565    type Key = &'static str;
566    fn set(self,parname : Self::Key, model : & mut Backend) -> Result<(),String> {
567        _ = model.dpar.insert(parname.to_string(), self);
568        Ok(())
569    }
570}
571impl SolverParameterValue<Backend> for i32 {
572    type Key = &'static str;
573    fn set(self,parname : Self::Key, model : & mut Backend) -> Result<(),String> {
574        _ = model.ipar.insert(parname.to_string(), self);
575        Ok(())
576    }
577}
578
579fn bnd_to_bk(lb : f64, ub : f64) -> &'static str {
580    match (lb.is_finite(),ub.is_finite()) {
581        (false,false) => "fr",
582        (false,true)  => "up",
583        (true,false)  => "lo",
584        (true,true)   => if lb < ub { "ra" } else { "fx" }
585    }
586}
587impl Backend {
588    fn parse_multistream<R>(&mut self, r : &mut R, sol_bas : & mut Solution, sol_itr : &mut Solution, sol_itg : &mut Solution) -> Result<(),String> where R : Read {
589        //println!("----- Backend::parse_multistream()");
590        // We should now receive an application/x-mosek-multiplex stream ending with either a
591        // fail or a result.
592        // parse incoming stream 
593        let mut buf = Vec::new();
594        let mut head = String::new();
595
596        'outer: loop { // loop over messages
597            // for each message loop over frames
598            head.clear();
599            {
600                let mut mr = BufReader::new(MessageReader::new(r));
601                mr.read_line(&mut head).map_err(|e| e.to_string())?;
602
603
604                loop {
605                    let n = mr.read_line(&mut head).map_err(|e| e.to_string())?;
606                    if n <= 1 { break; }
607                }
608                //println!("Backend::parse_multistream(), head = [[[{}]]]",head);
609
610                let head = head.trim_ascii_end();
611                let mut lines = head.as_bytes().chunk_by(|&a,_| a != b'\n');
612                let hd = lines.next().ok_or_else(|| "Invalid response format A".to_string())?.trim_ascii_end();
613                let headers = lines
614                    .map(|s| s.trim_ascii_end())
615                    .map(|s| { if let Some(p) = subseq_location(s, b":") { (&s[..p],&s[p+1..]) } else { (&s[..0],s) } });
616                
617                match hd {
618                    b"log" => {
619                        if let Some(f) = &self.log_cb {
620                            buf.clear();
621                            mr.read_to_end(&mut buf).map_err(|e| e.to_string())?;
622                            if let Ok(s) = std::str::from_utf8(buf.as_slice()) {
623                                f(s)
624                            }
625                        }
626                    },
627                    b"msg" => {},
628                    b"wrn" => {},
629                    b"err" => {},
630                    b"cbmap" => {},
631                    b"cbinfo" => {},
632                    b"cbcode" => {},
633                    b"cbwarn" => {},
634                    b"cb-intsol" => {
635                        if self.sol_cb.is_some() {
636                            let mut xxbytes = Vec::new();
637                            mr.read_to_end(&mut xxbytes).map_err(|e| e.to_string())?;
638                            let n = xxbytes.len()/size_of::<f64>();
639                            if n == self.var_lb.len() {
640                                let mut xx : Vec<f64> = Vec::new(); xx.resize(n,0.0);
641                                unsafe{xx.align_to_mut().1}.copy_from_slice(&xxbytes[..n*size_of::<f64>()]);
642
643                                let mut solxx = Vec::new(); solxx.resize(self.vars.len(),0.0);
644
645                                for (d,v) in solxx.iter_mut().zip( xx.permute_by(self.var_idx.as_slice())) { *d = *v; }
646                                    
647                                let obj : f64 = self.c_cof.iter().zip(solxx.permute_by(self.c_subj.as_slice())).map(|(c,x)| *c * *x).sum();
648
649                                if let Some(cb) = & mut self.sol_cb {
650                                    cb(&IntSolutionManager::new(obj,solxx));
651                                }
652                            }
653                        }
654                    },
655                    b"ok" => {
656                        let mut _trm = None;
657                        let mut content_type = None;
658                        for (k,v) in headers {
659                            match k {
660                                b"trm" => _trm = Some(v),
661                                b"content-type" => {
662                                    content_type = Some(v)
663                                }
664                                _ => {},
665                           }
666                        }
667
668                        match content_type {
669                            Some(b"application/x-mosek-b") => break 'outer self.read_bsolution(sol_bas,sol_itr,sol_itg,&mut mr).map_err(|e| e.to_string()),
670                            Some(b"application/x-mosek-b+zstd") => {
671                                let mut unzstd = zstd::Decoder::new(mr).map_err(|e| e.to_string())?;
672                                break 'outer self.read_bsolution(sol_bas,sol_itr,sol_itg,&mut unzstd).map_err(|e| e.to_string())
673                            },
674                            Some(content_type) => break 'outer Err(format!("Unexpected solution format: {}",std::str::from_utf8(content_type).unwrap_or("<?>"))),
675                            None => break 'outer Err(format!("Missing solution format"))
676                        }
677
678                    },
679                    b"fail" => { 
680                        let mut res = None;
681                        for (k,v) in headers {
682                            if k == b"res" { res = Some(v); }
683                        }
684
685                        buf.clear();
686                        mr.read_to_end(&mut buf).map_err(|e| e.to_string())?;
687                        let message = std::str::from_utf8(buf.as_slice()).unwrap_or("");
688                        break 'outer Err(format!("Solve failed ({}): {}",
689                                         std::str::from_utf8(res.unwrap_or(b"?")).unwrap_or("?"),
690                                         message));
691                    },
692                    _ => {},
693                }
694
695                let mut flushbuf = [0u8;4096];
696                while 0 < mr.read(&mut flushbuf).map_err(|e| e.to_string())? {}
697            }
698        }
699    }
700    
701    fn copy_solution(&self,
702                     psta : SolutionStatus,
703                     dsta : SolutionStatus,
704                     numvar : usize,
705                     //numcon : usize,
706                     pobj : f64,
707                     dobj : f64,
708                     //_varsta : Vec<u8>,
709                     xx : Option<Vec<f64>>,
710                     sx : Option<(Vec<f64>,Vec<f64>)>,
711                     //_consta : Vec<u8>,
712                     //xc : Option<Vec<f64>>,
713                     sc : Option<Vec<f64>>,
714                     sol : & mut Solution) -> std::io::Result<()>
715    {
716        let pdef = if let SolutionStatus::Undefined = psta { false } else { true };
717        let ddef = if let SolutionStatus::Undefined = dsta { false } else { true };
718
719        sol.primal.status = psta;
720        sol.dual.status = dsta;
721
722        if pdef {
723            sol.primal.obj = pobj;
724            sol.primal.con.clear();
725            if let Some(xx) = xx {
726                sol.primal.var.resize(self.var_idx.len(),0.0);
727                sol.primal.var[0] = 1.0;
728                xx.permute_by(&self.var_idx[1..])
729                    .zip(&mut sol.primal.var[1..])
730                    .for_each(|(&src,dst)| *dst = src);
731
732                self.mx.eval_into(sol.primal.var.as_slice(),&mut sol.primal.con).unwrap();
733            }
734            else if ! self.vars.is_empty() {
735                return Err(std::io::Error::other("Missing solution section sol/var/primal"));
736            }
737            else {
738                sol.primal.con.resize(self.con_rhs.len(),0.0);
739            }
740        }
741        
742        if ddef {
743            if let Some(sc) = sc.as_ref() {
744                if self.con_mx_row.len() != sc.len() {
745                    return Err(std::io::Error::other("Incorrect solution dimension in sol/acc/primal"));
746                }
747            }
748            sol.dual.obj = dobj;
749            if let Some(((sl,su),sc)) = sx.as_ref().zip(sc.as_ref()) {
750                if sl.len() != numvar || su.len() != numvar { return Err(std::io::Error::other("Incorrect solution dimension in sol/var/dual")); }
751                sol.dual.var.resize(self.var_idx.len(),0.0);
752                for (solx,index,e) in izip!(sol.dual.var[1..].iter_mut(),self.var_idx[1..].iter(),self.vars.iter()) {
753                    *solx = 
754                        match e {
755                           VarItem::Linear           =>  sl[*index]-su[*index],
756                           VarItem::LinearLowerBound => -su[*index],
757                           VarItem::LinearUpperBound =>  sl[*index],
758                           VarItem::Conic { conidx } =>  sc[*conidx],
759                        };
760                }
761            }
762            else if ! self.vars.is_empty() {
763                return Err(std::io::Error::other("Missing solution section sol/var/primal"));
764            }
765            
766            if let Some(sc) = sc.as_ref() {
767                sol.dual.con.copy_from_slice(sc.as_slice());
768            }
769            else if ! self.cons.is_empty() {
770                return Err(std::io::Error::other("Missing solution section sol/var/primal"));
771            }
772        }
773
774        Ok(())
775    }
776
777    /// The b-solution format.
778    ///
779    /// # Info section
780    /// The info section is identical to the btask info section. 
781    /// ```text
782    /// MSKSOLN [B
783    /// version III
784    /// numvar I
785    /// numbarvar I
786    /// numcon I
787    /// numcone I
788    /// numacc L
789    /// ```
790    /// 
791    /// # Solution 
792    /// Solution section is the same as in btask. 
793    ///
794    /// Note that both primal and dual solution values are present, even for values that are
795    /// logically unknown or undefined (for e.g. certificates or unknown status).
796    ///
797    /// ## Basic solution
798    /// The entire section is optional. 
799    /// ```text
800    /// sol/basic               [B[B   -- prosta, solsta
801    /// sol/basic/pobj          d      -- if primal solution is not undefined
802    /// sol/basic/dobj          d      -- if dual solution is not undefined
803    /// sol/basic/var/sta       [B 
804    /// sol/basic/var/primal    [d  
805    /// sol/basic/var/dual      [d[d  
806    /// sol/basic/con/sta       [B     -- if numcon > 0
807    /// sol/basic/con/primal    [d     -- if numcon > 0 and primal solution is defined
808    /// sol/basic/con/dual      [d[d[d -- if numcon > 0 and dual solution is defined
809    /// ```
810    ///
811    /// ## Interior solution
812    /// The entire section is optional. 
813    /// ```text
814    /// sol/interior               [B[B   -- prosta, solsta
815    /// sol/interior/pobj          d      -- if primal solution is not undefined
816    /// sol/interior/dobj          d      -- if dual solution is not undefined
817    /// sol/interior/var/sta       [B 
818    /// sol/interior/var/primal    [d  
819    /// sol/interior/var/dual      [d[d   -- if numcone = 0 
820    ///                            [d[d[d -- if numcone > 0
821    /// sol/interior/barvar/primal [d     -- if numbarvar > 0 and primal solution is defined
822    /// sol/interior/barvar/dual   [d     -- if numbarvar > 0 and dual solution is defined
823    /// sol/interior/con/sta       [B     -- if numcon > 0
824    /// sol/interior/con/primal    [d     -- if numcon > 0 and primal solution is defined
825    /// sol/interior/con/dual      [d[d[d -- if numcon > 0 and dual solution is defined
826    /// sol/interior/cone/sta      [B     -- if numcone > 0
827    /// sol/interior/acc/dual      [d     -- if numacc > 0 and dual solution is defined
828    /// ```
829    ///
830    /// ## Integer solution
831    /// The entire section is optional. 
832    /// ```text
833    /// sol/integer               [B[B   -- prosta, solsta
834    /// sol/integer/pobj          d      -- if primal solution is not undefined
835    /// sol/integer/var/sta       [B 
836    /// sol/integer/var/primal    [d  
837    /// sol/integer/barvar/primal [d     -- if numbarvar > 0 and primal solution is defined
838    /// sol/integer/con/sta       [B     -- if numcon > 0
839    /// sol/integer/con/primal    [d     -- if numcon > 0 and primal solution is defined
840    /// sol/integer/cone/sta      [B     -- if numcone > 0
841    /// ``` 
842    ///
843    /// ## Names
844    /// ```text
845    /// name/var       [I[B
846    /// name/barvar    [I[B 
847    /// name/con       [I[B 
848    /// name/cone      [I[B 
849    /// name/acc       [I[B
850    /// name/problem   [B
851    /// name/objective [B
852    /// ```
853    ///
854    /// # Information items
855    /// ```text
856    /// inf/f64    [B[B[d
857    /// inf/i32    [B[B[i 
858    /// inf/i64    [B[B[l 
859    /// ```
860    fn read_bsolution<R>(&self, sol_bas : & mut Solution, sol_itr : &mut Solution, sol_itg : &mut Solution,r : &mut R) -> std::io::Result<()> where R : Read 
861    {
862        let mut r = bio::Des::new(r)?;
863        
864        let mut note : Vec<u8> = Vec::new();
865        r.expect(b"MSKSOLN",b"[B")?
866            .read_into(&mut note)?;
867
868        let version = 
869            {
870                let mut entry = r.expect(b"version",b"III")?;
871                (entry.next_value::<u32>()?,
872                 entry.next_value::<u32>()?,
873                 entry.next_value::<u32>()?)
874            };
875
876        if version.0 != 10 || version.1 != 2 {
877            return Err(std::io::Error::other(format!("Unsupported solution format version: {}.{}.{}",version.0,version.1,version.2)));
878        }
879
880        sol_bas.primal.status = Undefined;
881        sol_bas.dual.status   = Undefined;  
882        sol_itr.primal.status = Undefined;
883        sol_itr.dual.status   = Undefined;
884        sol_itg.primal.status = Undefined;
885        sol_itg.dual.status   = Undefined;
886
887        let numvar    = r.expect(b"numvar",b"I")?.next_value::<u32>()? as usize;
888        let numbarvar = r.expect(b"numbarvar",b"I")?.next_value::<u32>()?;
889        let numcon    = r.expect(b"numcon",b"I")?.next_value::<u32>()? as usize;
890        let numcone   = r.expect(b"numcone",b"I")?.next_value::<u32>()?;
891        let numacc    = r.expect(b"numacc",b"L")?.next_value::<u64>()? as usize;
892
893        if self.var_lb.len() != numvar { return Err(std::io::Error::other("Invalid solution dimension")); }
894        if self.var_ub.len() != numvar { return Err(std::io::Error::other("Invalid solution dimension")); }
895        if 0 != numcone { return Err(std::io::Error::other("Invalid solution dimension")); }
896        if self.con_block_ptr.len() != numacc { return Err(std::io::Error::other("Invalid solution dimension")); }
897        if 0 != numbarvar { return Err(std::io::Error::other("Invalid solution dimension")); }
898    
899        let mut acc_pattern : Vec<u64> = Vec::new();
900        if let Some((b"acc/pattern",b"[L")) = r.peek()? {
901            r.next_entry()?.unwrap().read_into(&mut acc_pattern)?;
902        }
903        //let mut entry = r.next_entry()?.ok_or_else(|| std::io::Error::other("Missing solution entries"))?;
904 
905        use SolutionStatus::*;
906
907        if let Some((b"sol/interior",b"[B[B")) = r.peek()? {
908            sol_itr.primal.var.resize(self.vars.len(),0.0);
909            sol_itr.dual.var.resize(self.vars.len(),0.0);
910            sol_itr.primal.con.resize(self.cons.len(),0.0);
911            sol_itr.dual.con.resize(self.cons.len(),0.0);
912
913            let sta    = r.expect(b"sol/interior",b"[B[B").and_then(|mut entry| { entry.skip_field()?; Ok(str_to_pdsolsta(entry.read::<u8>()?.as_slice())?) })?;
914            let pdef   = !matches!(sta.0,Undefined);
915            let ddef   = !matches!(sta.1,Undefined);
916            let pobj   = if pdef { r.expect(b"sol/interior/pobj",b"d").and_then(|mut entry| entry.next_value::<f64>())? } else { 0.0 };
917            let dobj   = if ddef { r.expect(b"sol/interior/dobj",b"d").and_then(|mut entry| entry.next_value::<f64>())? } else { 0.0 };
918            let _varsta = r.expect(b"sol/interior/var/sta",b"[B").and_then(|mut entry| Ok(entry.read::<u8>()?))?;
919            let xx     = if pdef { Some(r.expect(b"sol/interior/var/primal",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
920            let sx     = if ddef { Some(r.expect(b"sol/interior/var/dual",b"[d[d").and_then(|mut entry| Ok((entry.read::<f64>()?,entry.read::<f64>()?)))?) } else { None };
921            //let consta = r.expect(b"sol/interior/con/sta",b"[B").and_then(|mut entry| Ok(entry.read::<u8>()?))?;
922            //let xc     = if numcon > 0 && pdef { Some(r.expect(b"sol/interior/con/primal",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
923            let sc     = if numcon > 0 && ddef { Some(r.expect(b"sol/interior/con/dual",b"[d[d[d").and_then(|mut entry| Ok((entry.read::<f64>()?,entry.read::<f64>()?,entry.read::<f64>()?)))?) } else { None };
924            let sn     = if numacc > 0 && ddef { Some(r.expect(b"sol/interior/acc/dual",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
925
926            self.copy_solution(sta.0,sta.1,
927                               numvar, 
928                               pobj,dobj,
929                               //varsta,
930                               xx,sx,
931                               //consta,
932                               //xc,
933                               sn,
934                               sol_itr)?;
935        }
936
937        if let Some((b"sol/basic",b"[B[B")) = r.peek()? {
938            let sta    = r.expect(b"sol/basic",b"[B[B").and_then(|mut entry| { entry.skip_field()?; Ok(str_to_pdsolsta(entry.read::<u8>()?.as_slice())?) })?;
939            let pdef   = !matches!(sta.0,Undefined);
940            let ddef   = !matches!(sta.1,Undefined);
941            let pobj   = if pdef { r.expect(b"sol/basic/pobj",b"d").and_then(|mut entry| entry.next_value::<f64>())? } else { 0.0 };
942            let dobj   = if ddef { r.expect(b"sol/basic/dobj",b"d").and_then(|mut entry| entry.next_value::<f64>())? } else { 0.0 };
943            let varsta = r.expect(b"sol/basic/var/sta",b"[B").and_then(|mut entry| Ok(entry.read::<u8>()?))?;
944            let xx     = if pdef { Some(r.expect(b"sol/basic/var/primal",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
945            let sx     = if ddef { Some(r.expect(b"sol/basic/var/dual",b"[d[d").and_then(|mut entry| Ok((entry.read::<f64>()?,entry.read::<f64>()?)))?) } else { None };
946            //let consta = r.expect(b"sol/basic/con/sta",b"[B").and_then(|mut entry| Ok(entry.read::<u8>()?))?;
947            let _xc     = if numcon > 0 && pdef { Some(r.expect(b"sol/basic/con/primal",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
948            let sc     = if numcon > 0 && ddef { Some(r.expect(b"sol/basic/con/dual",b"[d[d[d").and_then(|mut entry| Ok((entry.read::<f64>()?,entry.read::<f64>()?,entry.read::<f64>()?)))?) } else { None };
949            let sn     = if numacc > 0 && ddef { Some(r.expect(b"sol/basic/acc/dual",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
950
951            self.copy_solution(sta.0,sta.1,
952                               numvar,
953                               pobj,dobj,
954                               //varsta,
955                               xx,sx,
956                               //consta,
957                               //xc,
958                               sn,
959                               sol_bas)?;
960        }
961
962        if let Some((b"sol/integer",b"[B[B")) = r.peek()? {
963            let sta    = r.expect(b"sol/integer",b"[B[B").and_then(|mut entry| { entry.skip_field()?; Ok(str_to_pdsolsta(entry.read::<u8>()?.as_slice())?) })?;
964            let pdef   = !matches!(sta.0,Undefined);
965            let pobj   = if pdef { r.expect(b"sol/integer/pobj",b"d").and_then(|mut entry| entry.next_value::<f64>())? } else { 0.0 };
966            let varsta = r.expect(b"sol/integer/var/sta",b"[B").and_then(|mut entry| Ok(entry.read::<u8>()?))?;
967            let xx     = if pdef { Some(r.expect(b"sol/integer/var/primal",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
968            //let consta = r.expect(b"sol/integer/con/sta",b"[B").and_then(|mut entry| Ok(entry.read::<u8>()?))?;
969            let _xc     = if numcon > 0 && pdef { Some(r.expect(b"sol/integer/con/primal",b"[d").and_then(|mut entry| Ok(entry.read::<f64>()?))?) } else { None };
970            
971            self.copy_solution(sta.0,sta.1,
972                               numvar,
973                               pobj,0.0,
974                               //varsta,
975                               xx,None,
976                               //consta,
977                               //xc,
978                               None,
979                               sol_itg)?;
980        }
981
982        r.expect(b"name/var",b"[I[B")?.skip_all()?;
983        r.expect(b"name/barvar",    b"[I[B")?.skip_all()?;
984        r.expect(b"name/con",       b"[I[B")?.skip_all()?;
985        r.expect(b"name/cone",      b"[I[B")?.skip_all()?;
986        r.expect(b"name/acc",       b"[I[B")?.skip_all()?;
987        r.expect(b"name/problem",   b"[B")?.skip_all()?;
988        r.expect(b"name/objective", b"[B")?.skip_all()?;
989
990        r.expect(b"inf/f64", b"[B[B[d")?.skip_all()?;
991        r.expect(b"inf/i32", b"[B[B[i")?.skip_all()?;
992        r.expect(b"inf/i64", b"[B[B[l")?.skip_all()?;
993
994        Ok(())
995    }
996
997    /// JSON Task format writer.
998    ///
999    /// Relevant parts from [https://docs.mosek.com/latest/capi/json-format.html]:
1000    /// - `$schema`: JSON schema = "`http://mosek.com/json/schema#`".
1001    /// - `Task/name`: The name of the task (string).
1002    /// - `Task/INFO`: Information about problem data dimensions and similar. These are treated as hints when reading the file.
1003    ///     - `numvar`: number of variables (int32).
1004    ///     - `numcon`: number of constraints (int32).
1005    ///     - `numcone`: number of cones (int32, deprecated).
1006    ///     - `numbarvar`: number of symmetric matrix variables (int32).
1007    ///     - `numanz`: number of nonzeros in A (int64).
1008    ///     - `numsymmat`: number of matrices in the symmetric matrix storage E (int64).
1009    ///     - `numafe`: number of affine expressions in AFE storage (int64).
1010    ///     - `numfnz`: number of nonzeros in F (int64).
1011    ///     - `numacc`: number of affine conic constraints (ACCs) (int64).
1012    ///     - `numdjc`: number of disjunctive constraints (DJCs) (int64).
1013    ///     - `numdom`: number of domains (int64).
1014    ///     - `mosekver`: MOSEK version (list(int32)).
1015    /// - `Task/data`: Numerical and structural data of the problem.
1016    ///     - `var`: Information about variables. All fields present must have the same length as bk. All or none of bk, bl, and bu must appear.
1017    ///         - `name`: Variable names (list(string)).
1018    ///         - `bk`: Bound keys (list(string)).
1019    ///         - `bl`: Lower bounds (list(double)).
1020    ///         - `bu`: Upper bounds (list(double)).
1021    ///         - `type`: Variable types (list(string)).
1022    ///     - `con`: Information about linear constraints. All fields present must have the same length as bk. All or none of bk, bl, and bu must appear.
1023    ///         - `name`: Constraint names (list(string)).
1024    ///         - `bk`: Bound keys (list(string)).
1025    ///         - `bl`: Lower bounds (list(double)).
1026    ///         - `bu`: Upper bounds (list(double)).
1027    ///     - `objective`: Information about the objective.
1028    ///         - `name`: Objective name (string).
1029    ///         - `sense`: Objective sense (string).
1030    ///         - `c`: The linear part of the objective as a sparse vector. Both arrays must have the same length.
1031    ///     - `subj`: indices of nonzeros (list(int32)).
1032    ///     - `val`: values of nonzeros (list(double)).
1033    /// - `cfix`: Constant term in the objective (double).
1034    /// - `A`: The linear constraint matrix as a sparse matrix. All arrays must have the same length.
1035    ///     - `subi`: row indices of nonzeros (list(int32)).
1036    ///     - `subj`: column indices of nonzeros (list(int32)).
1037    ///     - `val`: values of nonzeros (list(double)).
1038    /// - `AFE`: The affine expression storage.
1039    ///     - `numafe`: number of rows in the storage (int64).
1040    ///     - `F`: The matrix as a sparse matrix. All arrays must have the same length.
1041    ///         - `subi`: row indices of nonzeros (list(int64)).
1042    ///         - `subj`: column indices of nonzeros (list(int32)).
1043    ///         - `val`: values of nonzeros (list(double)).
1044    ///     - `g`: The vector of constant terms as a sparse vector. Both arrays must have the same length.
1045    ///         - `subi`: indices of nonzeros (list(int64)).
1046    ///         - `val`: values of nonzeros (list(double)).
1047    /// - `domains`: Information about domains. All fields present must have the same length as type.
1048    ///     - `name`: Domain names (list(string)).
1049    ///     - `type`: Description of the type of each domain (list). Each element of the list is a list describing one domain using at least one field:
1050    ///         domain type (string).
1051    ///         (except pexp, dexp) dimension (int64).
1052    ///         (only ppow, dpow) weights (list(double)).
1053    /// - `ACC`: Information about affine conic constraints (ACC). All fields present must have the same length as domain.
1054    ///     - `name`: ACC names (list(string)).
1055    ///     - `domain`: Domains (list(int64)).
1056    ///     - `afeidx`: AFE indices, grouped by ACC (list(list(int64))).
1057    ///     - `b`: constant vectors 
1058    ///     , grouped by ACC (list(list(double))).
1059    /// - `DJC`: Information about disjunctive constraints (DJC). All fields present must have the same length as termsize.
1060    ///     - `name`: DJC names (list(string)).
1061    ///     - `termsize`: Term sizes, grouped by DJC (list(list(int64))).
1062    ///     - `domain`: Domains, grouped by DJC (list(list(int64))).
1063    ///     - `afeidx`: AFE indices, grouped by DJC (list(list(int64))).
1064    ///     - `b`: constant vectors 
1065    ///     , grouped by DJC (list(list(double))).
1066    /// - `Task/solutions`: Solutions. This section can contain up to three subsections called:
1067    ///     - `interior`
1068    ///     - `basic`
1069    ///     - `integer`
1070    ///     corresponding to the three solution types in MOSEK. Each of these sections has the same structure:
1071    ///     - `prosta`: problem status (string).
1072    ///     - `solsta`: solution status (string).
1073    ///     - `xx`, `xc`, `y`, `slc`, `suc`, `slx`, `sux`, `snx`: one for each component of the solution of the same name (list(double)).
1074    ///     - `skx`, `skc`, `skn`: status keys (list(string)).
1075    ///     - `doty`: the dual solution, grouped by ACC (list(list(double))).
1076    /// - `Task/parameters`: Parameters.
1077    ///     - `iparam`: Integer parameters (dictionary). A dictionary with entries of the form
1078    ///       name:value, where name is a shortened parameter name (without leading MSK_IPAR_) and
1079    ///       value is either an integer or string if the parameter takes values from an enum.
1080    ///     - `dparam`: Double parameters (dictionary). A dictionary with entries of the form
1081    ///       name:value, where name is a shortened parameter name (without leading MSK_DPAR_) and
1082    ///       value is a double.
1083    ///     - `sparam`: String parameters (dictionary). A dictionary with entries of the form
1084    ///       `name:value`, where name is a shortened parameter name (without leading MSK_SPAR_) and
1085    ///       value is a string. Note that this section is allowed but MOSEK ignores it both when
1086    ///       writing and reading JTASK files.
1087    fn write_jtask<S>(&self, strm : &mut S) -> std::io::Result<()> 
1088        where 
1089            S : std::io::Write 
1090    {
1091        use json::JSON;
1092        
1093        let mut doc = json::Dict::new();
1094        doc.append("$schema",JSON::String("http://mosek.com/json/schema#".to_string()));
1095
1096        if let Some(name) = &self.name {
1097            doc.append("Task/name", name.clone());
1098        }
1099
1100        doc.append(
1101            "Task/info",
1102            json::Dict::from(|taskinfo| {
1103                taskinfo.append("numvar",self.vars.len() as i64);
1104                taskinfo.append("numcon",0);
1105                taskinfo.append("numafe",self.mx.num_row() as i64);
1106                taskinfo.append("numacc",self.con_block_dom.len() as i64);
1107            }));
1108            
1109        doc.append(
1110            "Task/data",
1111            json::Dict::from(|taskdata| {
1112                {
1113                    let bk = self.var_lb.iter().zip(self.var_ub.iter()).map(|(lb,ub)| bnd_to_bk(*lb, *ub).to_string()).collect();
1114
1115                    taskdata.append("var",json::Dict::from(|d| {
1116                        d.append("bk",  JSON::StringArray(bk));
1117                        d.append("bl",  JSON::FloatArray(self.var_lb.clone()));
1118                        d.append("bu",  JSON::FloatArray(self.var_ub.clone()));
1119                        d.append("type",JSON::StringArray(self.var_int.iter().map(|&e| if e { "int".into() } else { "cont".into() }).collect()));
1120                    }));
1121                }
1122                {
1123                    taskdata.append("con",json::Dict::from(|d| {
1124                        d.append("bk",  JSON::StringArray(Vec::new()));
1125                        d.append("bl",  JSON::FloatArray(Vec::new()));
1126                        d.append("bu",  JSON::FloatArray(Vec::new()));
1127                    }));
1128                }
1129                taskdata.append(
1130                    "objective",
1131                    json::Dict::from(|d| {
1132                        d.append("sense", if self.sense_max { "max" } else { "min" });
1133                        d.append("cfix",0.0f64);
1134                        d.append("c", json::Dict::from(|d2| {
1135                            d2.append("subj",JSON::IntArray(self.var_idx.permute_by(self.c_subj.as_slice()).map(|&i| i as i64).collect()));
1136                            d2.append("val", self.c_cof.as_slice());
1137                    }));
1138                }));
1139                taskdata.append(
1140                    "A", 
1141                    json::Dict::from(|d| {
1142                        d.append("subi",JSON::IntArray(Vec::new()));
1143                        d.append("subj",JSON::IntArray(Vec::new()));
1144                        d.append("val", JSON::FloatArray(Vec::new()));
1145                }));
1146
1147                taskdata.append(
1148                    "AFE",
1149                    json::Dict::from(|d| {
1150                        let nnz = self.mx.num_nonzeros();
1151                        let mut subi = Vec::with_capacity(nnz);
1152                        let mut subj = Vec::with_capacity(nnz);
1153                        let mut val  = Vec::with_capacity(nnz);
1154                        let mut gs   = Vec::with_capacity(self.mx.num_row());
1155
1156                        self.mx.row_iter()
1157                            .enumerate()
1158                            .for_each(|(i,(jj,cc,g))| {
1159                                let base = subi.len();
1160                                let n = jj.len();
1161                                subi.resize(base+n, i as i64);
1162                                subj.extend(self.var_idx.permute_by(jj).map(|&i| i as i64));
1163                                val.extend_from_slice(cc);
1164                                gs.push(g);
1165                            });
1166                        d.append("numafe", JSON::Int( self.mx.num_row() as i64));
1167
1168                        d.append("F", json::Dict::from(|d| {
1169                            d.append("subi",JSON::IntArray(subi));
1170                            d.append("subj",JSON::IntArray(subj));
1171                            d.append("val", JSON::FloatArray(val));
1172                        }));
1173                        //d.append("g",JSON::FloatArray(self.mx.row_iter().map(|(_,_,g)| g).collect()));
1174                        d.append(
1175                            "g",
1176                            json::Dict::from(|d| {
1177                                d.append("subi",JSON::IntArray((0..self.mx.num_row() as i64).collect()));
1178                                d.append("val", JSON::FloatArray(self.mx.row_iter().map(|(_,_,g)| g).collect()));
1179                                }));
1180                    }));
1181
1182                taskdata.append(
1183                    "domains",
1184                    JSON::Dict(
1185                        json::Dict::from(|d| 
1186                            d.append(
1187                                "type",
1188                                JSON::List(self.con_block_dom.iter().map(|c| dom2json(c)).collect::<Vec<JSON>>())))));
1189                taskdata.append(
1190                    "ACC",
1191                    json::Dict::from(|d| {
1192                        d.append("domain",JSON::IntArray((0..self.con_block_dom.len()).map(|i| i as i64).collect()));
1193                        let mut acc_afe_idxs : Vec<Vec<i64>> = self.con_block_dom.iter().map(|d| vec![0i64; d.dim()]).collect();
1194                        acc_afe_idxs.iter_mut().flat_map(|r| r.iter_mut()).zip(self.con_mx_row.iter()).for_each(|(d,&s)| *d = s as i64);
1195                        let mut acc_b : Vec<Vec<f64>> = self.con_block_dom.iter().map(|dom| vec![0f64; dom.dim()]).collect();
1196                        acc_b.iter_mut().flat_map(|row| row.iter_mut()).zip(self.con_rhs.iter()).for_each(|(d,&s)| *d = s);
1197                        d.append("afeidx",JSON::List( acc_afe_idxs.into_iter().map(|row| JSON::IntArray(row)).collect() ));
1198                        d.append("b",     JSON::List( acc_b.into_iter().map(|row| JSON::FloatArray(row)).collect() ));
1199                    }));
1200        }));
1201        doc.append(
1202            "Task/parameters",
1203            json::Dict::from(|d| {
1204                if ! self.dpar.is_empty() {
1205                    d.append(
1206                        "dparam",
1207                        json::Dict::from(|d| for (k,v) in self.dpar.iter() { d.append(k.as_str(), JSON::Float(*v)); }))
1208                }
1209                if ! self.ipar.is_empty() {
1210                    d.append(
1211                        "iparam",
1212                        json::Dict::from(|d| for (k,v) in self.ipar.iter() { d.append(k.as_str(), JSON::Int(*v as i64)); }))
1213                }
1214            }));
1215        JSON::Dict(doc).write(strm)
1216    }
1217
1218
1219    fn linear_names<const N : usize>(&mut self, first : usize, last : usize, shape : &[usize;N], sp : Option<&[usize]>, name : &str) {
1220        let mut name_index_buf = [1usize; N];
1221        let mut strides = [0;N];
1222        strides.iter_mut().zip(shape.iter()).rev().fold(1,|s,(st,&d)| { *st = s; d * s });
1223        if let Some(sp) = &sp {
1224            for (&i,n) in sp.iter().zip(self.var_names.iter_mut()) {
1225                name_index_buf.iter_mut().zip(strides.iter()).fold(i,|i,(ni,&st)| { *ni = i/st; i%st });
1226                *n= Some(format!("{}{:?}", name, name_index_buf));
1227            }
1228        }
1229        else {
1230            for n in self.var_names.iter_mut() {
1231                name_index_buf.iter_mut().zip(shape.iter()).rev().fold(1,|c,(i,&d)| { *i += c; if *i > d { *i = 1; 1 } else { 0 } });
1232                *n = Some(format!("{}{:?}", name, name_index_buf));
1233            }
1234        }
1235    }
1236
1237
1238    /// Appends a range of native linear variables with upper and lower boundsm returns the range
1239    /// of the new variables.
1240    fn native_linear_variable(&mut self, lb : &[f64], ub : &[f64], is_int : bool) -> std::ops::Range<usize> {
1241        assert_eq!(lb.len(),ub.len());
1242        let n = lb.len();
1243        let first = self.var_lb.len();
1244        let last  = first + n;
1245        
1246        let first_nvar = self.var_lb.len();
1247        let last_nvar  = first_nvar+n;
1248
1249        self.var_lb.extend_from_slice(lb);
1250        self.var_ub.extend_from_slice(ub);
1251        self.var_int.resize(last_nvar, is_int);
1252        self.var_names.resize(last_nvar, None);
1253
1254        first..last
1255    }
1256
1257
1258
1259    fn conic_variable<const N : usize>(
1260        &mut self, 
1261        name  : Option<&str>,
1262        // domain
1263        shape   : [usize;N],
1264        conedim : usize,
1265        dt      : VectorDomainType,
1266        offset  : &[f64],
1267        is_int  : bool) -> Result<Variable<N>,String> 
1268    {
1269        use VecCone::*;
1270
1271        let n = shape.iter().product();
1272        let dim = shape[conedim];
1273        let numcone = n/dim;
1274
1275        assert_eq!(offset.len(),n);
1276
1277
1278        let ct = 
1279            match dt {
1280                VectorDomainType::QuadraticCone          => Quadratic{dim},
1281                VectorDomainType::RotatedQuadraticCone   => RotatedQuadratic{dim},
1282                VectorDomainType::SVecPSDCone            => ScaledVectorizedPSD{dim},
1283                VectorDomainType::GeometricMeanCone      => PrimalGeometricMean {dim},
1284                VectorDomainType::DualGeometricMeanCone  => DualGeometricMean{dim},
1285                VectorDomainType::ExponentialCone        => PrimalExp,
1286                VectorDomainType::DualExponentialCone    => DualExp,
1287                VectorDomainType::PrimalPowerCone(alpha) => PrimalPower{dim,alpha},
1288                VectorDomainType::DualPowerCone(alpha)   => DualPower{dim,alpha},
1289                // linear types                                                   
1290                VectorDomainType::NonNegative            => NonNegative{dim},
1291                VectorDomainType::NonPositive            => NonPositive{dim},
1292                VectorDomainType::Zero                   => Zero{dim},
1293                VectorDomainType::Free                   => Unbounded {dim},
1294            };
1295
1296        let base = self.var_lb.len();
1297        self.var_lb.resize(base+n,f64::NEG_INFINITY);
1298        self.var_ub.resize(base+n,f64::INFINITY);
1299        self.var_int.resize(base+n,is_int);
1300        self.var_names.resize(base+n,None);
1301
1302        let firstvar = self.var_idx.len();
1303        let lastvar = firstvar + n;
1304     
1305        let con_base = self.con_rhs.len();
1306        self.con_rhs.extend_from_slice(offset);
1307        self.con_names.resize(con_base+n, None);
1308
1309        self.con_mx_row.extend( (firstvar..lastvar).map(|i| self.mx.append_row(&[i], &[1.0], 0.0)) );
1310        self.con_block_dom.extend((0..numcone).map(|_| ct.clone()));
1311        self.con_block_ptr.extend((0..numcone).map(|i| base+i*dim ));
1312
1313        self.var_idx.extend(base..base+n);
1314        self.vars.extend((con_base..con_base+dim).map(|conidx| VarItem::Conic { conidx }));
1315
1316
1317        let shape3 = [ shape[..conedim].iter().product(),dim,shape[conedim+1..].iter().product()];
1318        let strides3 = [ shape3[1]*shape[2], 1, shape3[2]];
1319        
1320        let perm : Vec::<usize> = iproduct!(0..shape3[0],0..shape3[1],0..shape3[2]).map(|(i0,i1,i2)| strides3[0]*i0 + strides3[1]*i1 + strides3[2]*i2).collect();
1321       
1322        let varidxs = perm.iter().map(|i| firstvar + i).collect();
1323
1324        self.var_names.resize(base+n,None);
1325
1326        if let Some(name) = name {
1327            let mut idx = [1; N];
1328
1329            self.var_names[base..].permute_by_mut(perm.as_slice())
1330                .for_each(|n| {
1331                    *n = Some(format!("{}{:?}",name,idx));
1332                    _ = idx.iter_mut().zip(shape.iter()).rev().fold(1,|c,(i,&d)| { *i += c; if *i > d { *i = 1; 1 } else { 0 } });
1333                });
1334        }
1335
1336        Ok(Variable::new(varidxs, None, &shape))
1337    }
1338   
1339    fn conic_constraint<const N : usize>(
1340        &mut self, 
1341        name    : Option<&str>,
1342        // expr
1343        ptr     : &[usize], 
1344        subj    : &[usize], 
1345        cof     : &[f64],
1346        // domain
1347        shape   : [usize;N],
1348        conedim : usize,
1349        dt      : VectorDomainType,
1350        offset  : &[f64]) -> Result<Constraint<N>,String>
1351    {
1352        use VecCone::*;
1353        let n = shape.iter().product();
1354        let dim = shape[conedim];
1355        let numcone = n/dim;
1356
1357        assert_eq!(offset.len(),n);
1358
1359        let ct = 
1360            match dt {
1361                VectorDomainType::QuadraticCone          => Quadratic{dim},
1362                VectorDomainType::RotatedQuadraticCone   => RotatedQuadratic{dim},
1363                VectorDomainType::SVecPSDCone            => ScaledVectorizedPSD{dim},
1364                VectorDomainType::GeometricMeanCone      => PrimalGeometricMean {dim},
1365                VectorDomainType::DualGeometricMeanCone  => DualGeometricMean{dim},
1366                VectorDomainType::ExponentialCone        => PrimalExp,
1367                VectorDomainType::DualExponentialCone    => DualExp,
1368                VectorDomainType::PrimalPowerCone(alpha) => PrimalPower{dim,alpha},
1369                VectorDomainType::DualPowerCone(alpha)   => DualPower{dim,alpha},
1370                // linear types                                                   
1371                VectorDomainType::NonNegative            => NonNegative{dim},
1372                VectorDomainType::NonPositive            => NonPositive{dim},
1373                VectorDomainType::Zero                   => Zero{dim},
1374                VectorDomainType::Free                   => Unbounded {dim},
1375            };
1376
1377        let firstcon = self.con_rhs.len();
1378
1379        let ptr_perm = Chunkation::new(ptr).unwrap();
1380        self.con_mx_row.extend(
1381            izip!(ptr_perm.chunks(subj).unwrap(),
1382                  ptr_perm.chunks(cof).unwrap())
1383                .map(|(subj,cof)| {
1384                    //println!("{}:{}: conic_constraint(), subj = {:?}",file!(),line!(),subj);
1385                    if let (Some(j),Some(c)) = (subj.first(),cof.first()) {
1386                        if *j == 0 {
1387                            self.mx.append_row(&subj[1..], &cof[..cof.len()-1], *c)
1388                        }
1389                        else {
1390                            self.mx.append_row(subj, cof, 0.0)
1391                        }
1392                    }
1393                    else {
1394                        self.mx.append_row(subj, cof, 0.0)
1395                    }}));
1396        self.con_rhs.extend_from_slice(offset);
1397        self.con_names.resize(firstcon+n,None);
1398        let firstblock = self.con_block_dom.len();
1399        self.con_block_ptr.extend( (firstcon..firstcon+n).step_by(dim) );
1400        self.con_block_dom.extend( (0..numcone).map(|i| ct.clone() ));
1401
1402        let shape3   = [ shape[..conedim].iter().product(),dim,shape[conedim+1..].iter().product()];
1403        let strides3 = [ shape3[1]*shape3[2], 1, shape3[2]];
1404        
1405        let perm : Vec::<usize> = iproduct!(0..shape3[0],0..shape3[1],0..shape3[2]).map(|(i0,i1,i2)| strides3[0]*i0 + strides3[1]*i1 + strides3[2]*i2).collect();
1406        let rescon0 = self.cons.len();
1407
1408        self.cons.extend( iproduct!(firstblock..firstblock+numcone,0..dim).map(|(block_index,offset)| ConItem{ block_index, offset}));
1409
1410        if let Some(name) = name {
1411            let mut idx = [1; N];
1412
1413            self.con_names[firstcon..]
1414                .permute_by_mut(perm.as_slice())
1415                .for_each(|n| {
1416                    *n = Some(format!("{}{:?}",name,idx));
1417                    _ = idx.iter_mut().zip(shape.iter()).rev().fold(1,|c,(i,&d)| { *i += c; if *i > d { *i = 1; 1 } else { 0 } });
1418                });
1419        }
1420
1421        Ok(Constraint::new(perm.iter().map(|&i| rescon0+i).collect(), &shape))
1422    }
1423}
1424
1425
1426
1427
1428
1429impl ModelWithLogCallback for Backend {
1430    fn set_log_handler<F>(& mut self, func : F) where F : 'static+Fn(&str) {
1431        self.log_cb = Some(Box::new(func));
1432    }
1433}
1434
1435fn subseq_location_from<T>(pos : usize, src : &[T], seq : &[T]) -> Option<usize> where T : Eq {
1436    if pos+seq.len() > src.len() {
1437        return None;
1438    }
1439
1440    for i in pos..src.len()-seq.len()+1 {
1441        if unsafe{ *src.get_unchecked(i) == *seq.get_unchecked(0) } {
1442            let mut found = true;
1443            for (j0,j1) in (i..i+seq.len()).enumerate() {
1444                if unsafe{ *src.get_unchecked(j1) != *seq.get_unchecked(j0) } {
1445                    found = false;
1446                    break;
1447                }
1448            }
1449            if found {
1450                return Some(i);
1451            }
1452        }
1453    }
1454    None
1455}
1456fn subseq_location<T>(src : &[T], seq : &[T]) -> Option<usize> where T : Eq {
1457    subseq_location_from(0,src, seq)
1458}
1459
1460/// Convert a solution status string to primal/dual solution status.
1461fn str_to_pdsolsta(solsta : &[u8]) -> std::io::Result<(SolutionStatus,SolutionStatus)> {
1462    match solsta {
1463        b"UNKNOWN" =>            Ok((SolutionStatus::Unknown,SolutionStatus::Unknown)),
1464        b"OPTIMAL" =>            Ok((SolutionStatus::Optimal,SolutionStatus::Optimal)),
1465        b"PRIM_FEAS" =>          Ok((SolutionStatus::Feasible,SolutionStatus::Unknown)),
1466        b"DUAL_FEAS" =>          Ok((SolutionStatus::Unknown,SolutionStatus::Feasible)),
1467        b"PRIM_AND_DUAL_FEAS" => Ok((SolutionStatus::Feasible,SolutionStatus::Feasible)),
1468        b"PRIM_INFEAS_CER" =>    Ok((SolutionStatus::Undefined,SolutionStatus::CertInfeas)),
1469        b"DUAL_INFEAS_CER" =>    Ok((SolutionStatus::CertInfeas,SolutionStatus::Undefined)),
1470        b"PRIM_ILLPOSED_CER" =>  Ok((SolutionStatus::Undefined,SolutionStatus::CertIllposed)),
1471        b"DUAL_ILLPOSED_CER" =>  Ok((SolutionStatus::CertIllposed,SolutionStatus::Undefined)),
1472        b"INTEGER_OPTIMAL" =>    Ok((SolutionStatus::Optimal,SolutionStatus::Undefined)),
1473        _ => Err(std::io::Error::other(format!("Invalid solution format: {}",std::str::from_utf8(solsta).unwrap_or("<invalid utf-8>"))))
1474    }
1475}
1476
1477
1478fn dom2json(c : &VecCone) -> json::JSON {    
1479    use json::JSON;
1480    use VecCone::*;
1481    match c {
1482      Zero{dim}                  => JSON::List(vec![ JSON::String("rzero".to_string()), JSON::Int(*dim as i64)]),
1483      NonNegative{dim}           => JSON::List(vec![ JSON::String("rplus".to_string()), JSON::Int(*dim as i64)]),
1484      NonPositive{dim}           => JSON::List(vec![ JSON::String("rminus".to_string()), JSON::Int(*dim as i64)]),
1485      Unbounded{dim}             => JSON::List(vec![ JSON::String("r".to_string()), JSON::Int(*dim as i64)]),
1486      Quadratic{dim}             => JSON::List(vec![ JSON::String("quad".to_string()), JSON::Int(*dim as i64)]),
1487      RotatedQuadratic{dim}      => JSON::List(vec![ JSON::String("rquad".to_string()), JSON::Int(*dim as i64)]),
1488      PrimalGeometricMean{dim}   => JSON::List(vec![ JSON::String("pgmean".to_string()), JSON::Int(*dim as i64)]),
1489      DualGeometricMean{dim}     => JSON::List(vec![ JSON::String("dgmean".to_string()), JSON::Int(*dim as i64)]),
1490      PrimalExp                  => JSON::List(vec![ JSON::String("pexp".to_string())]),
1491      DualExp                    => JSON::List(vec![ JSON::String("dexp".to_string())]),
1492      PrimalPower { dim, alpha } => JSON::List(vec![ JSON::String("ppow".to_string()), JSON::Int(*dim as i64), JSON::FloatArray(alpha.clone())]), 
1493      DualPower { dim, alpha }   => JSON::List(vec![ JSON::String("dpow".to_string()), JSON::Int(*dim as i64), JSON::FloatArray(alpha.clone())]), 
1494      ScaledVectorizedPSD { dim } => JSON::List(vec![ JSON::String("svecpsd".to_string()), JSON::Int(*dim as i64)]), 
1495    }
1496}
1497
1498#[cfg(test)]
1499mod test {
1500    use super::*;
1501    #[test]
1502    fn test_optserver() {
1503        let addr = "http://solve.mosek.com:30080".to_string();
1504        let mut m = Model::new(Some("SuperModel"));
1505        m.set_parameter((), SolverAddress(addr));
1506
1507        let a0 : &[f64] = &[ 3.0, 1.0, 2.0, 0.0 ];
1508        let a1 : &[f64] = &[ 2.0, 1.0, 3.0, 1.0 ];
1509        let a2 : &[f64] = &[ 0.0, 2.0, 0.0, 3.0 ];
1510        let c  : &[f64] = &[ 3.0, 1.0, 5.0, 1.0 ];
1511
1512        // Create variable 'x' of length 4
1513        let x = m.variable(Some("x0"), nonnegative().with_shape(&[4]));
1514
1515        // Create constraints
1516        let _ = m.constraint(None, x.index(1), less_than(10.0));
1517        let _ = m.constraint(Some("c1"), x.dot(a0), equal_to(30.0));
1518        let _ = m.constraint(Some("c2"), x.dot(a1), greater_than(15.0));
1519        let _ = m.constraint(Some("c3"), x.dot(a2), less_than(25.0));
1520
1521        // Set the objective function to (c^t * x)
1522        m.objective(Some("obj"), Sense::Maximize, x.dot(c));
1523
1524        m.write_problem("lo1-nosol.jtask");
1525
1526        m.set_log_handler(|msg| print!("{}",msg));
1527        m.solve();
1528
1529        // Get the solution values
1530        let (psta,dsta) = m.solution_status(SolutionType::Default);
1531        println!("Status = {:?}/{:?}",psta,dsta);
1532        let xx = m.primal_solution(SolutionType::Default,&x);
1533        println!("x = {:?}", xx);
1534    }
1535}