1
2extern crate mosek;
11extern crate itertools;
12use mosek::{Task,Boundkey,Objsense,Streamtype,Soltype,Transpose,Solsta};
13use itertools::{iproduct};
14use std::convert::TryInto;
15
16
17const INF : f64 = 0.0;
18
19#[allow(non_snake_case)]
20fn portfolio(w : f64,
21 mu : &[f64],
22 x0 : &[f64],
23 gammas : &[f64],
24 theta : &[f64],
25 GT : &Matrix) -> Result<Vec<(f64,f64)>,String> {
26
27 let mut task = match Task::new() {
28 Some(e) => e,
29 None => return Err("Failed to create task".to_string()),
30 }.with_callbacks();
31 task.put_stream_callback(Streamtype::LOG, |msg| print!("{}",msg))?;
32
33 let (kx,nx) = GT.size();
34 if mu.len() != nx { panic!("Mismatching data"); }
35
36 let k : i32 = kx.try_into().unwrap();
37 let n : i32 = nx.try_into().unwrap();
38 let total_budget : f64 = w + x0.iter().sum::<f64>();
39
40 let numvar = n;
42 let voff_x : i32 = 0;
43
44 let coff_bud : i32 = 0;
46
47 task.append_vars(numvar)?;
50
51 for j in 0..n {
53 task.put_var_name(voff_x+j,format!("x[{}]",j+1).as_str())?;
54 }
55 task.put_var_bound_slice_const(voff_x,voff_x+n, Boundkey::LO,0.0, INF)?;
56
57 task.append_cons(1)?;
59 task.put_con_name(coff_bud, "budget")?;
60
61 for j in 0..n {
63 task.put_aij(coff_bud,voff_x + j, 1.0)?;
64 }
65 task.put_con_bound(coff_bud, Boundkey::FX, total_budget, total_budget)?;
66
67 task.append_afes((k+n) as i64 + 1)?;
70 task.put_afe_f_row_list((1..1+k as i64).collect::<Vec<i64>>().as_slice(), vec![n; k as usize].as_slice(), (0..GT.len() as i64).step_by(n as usize).collect::<Vec<i64>>().as_slice(), iproduct!(0..k,0..n).map(|(_,b)| b).collect::<Vec<i32>>().as_slice(), GT.data_by_row().as_slice())?;
78 for (i,thetai) in (0..n).zip(theta.iter()) {
80 task.put_afe_f_entry(i as i64 + 1 + k as i64, voff_x + i, thetai.sqrt())?;
81 }
82
83 let qdom = task.append_quadratic_cone_domain(k as i64+ 1)?;
86 task.append_acc_seq(qdom, 0, vec![0.0; k as usize+1].as_slice())?;
88 task.put_acc_name(0, "risk")?;
89
90 for (j,&muj) in (0..n).zip(mu.iter()) {
92 task.put_c_j(voff_x + j, muj)?;
93 }
94 task.put_obj_sense(Objsense::MAXIMIZE)?;
95
96 Ok(gammas.iter().filter_map(|&gamma| {
97 task.put_afe_g(0, gamma).ok()?;
100 task.optimize().ok()?;
101 let _ = task.solution_summary(Streamtype::LOG);
103 let _ = task.write_data(format!("portfolio_6_factor-{}.ptf",gamma).as_str());
104
105 if task.get_sol_sta(Soltype::ITR).ok()? != Solsta::OPTIMAL {
107 eprintln!("Solution not optimal!");
109 std::process::exit(1);
110 }
111
112 let mut xx = vec![0.0; n as usize];
114 task.get_xx_slice(Soltype::ITR, voff_x, voff_x + n, xx.as_mut_slice()).ok()?;
115 Some((gamma,xx.iter().zip(mu.iter()).map(|(&xj,&muj)| xj*muj).sum::<f64>()))
116 }).collect::<Vec<(f64,f64)>>())
117}
118
119
120
121#[allow(non_snake_case)]
122fn main() -> Result<(),String> {
123 let n : i32 = 8;
126 let w = 1.0;
127 let mu = &[0.07197, 0.15518, 0.17535, 0.08981, 0.42896, 0.39292, 0.32171, 0.18379];
128 let x0 = &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
129 let B = Matrix::new_by_row(n as usize, 2,
131 [ 0.4256, 0.1869,
132 0.2413, 0.3877,
133 0.2235, 0.3697,
134 0.1503, 0.4612,
135 1.5325, -0.2633,
136 1.2741, -0.2613,
137 0.6939, 0.2372,
138 0.5425, 0.2116 ].to_vec()).unwrap();
139
140 let S_F = Matrix::new_by_row(2,2,
142 [ 0.0620, 0.0577,
143 0.0577, 0.0908 ].to_vec()).unwrap();
144
145 let theta : &[f64] = &[0.0720, 0.0508, 0.0377, 0.0394, 0.0663, 0.0224, 0.0417, 0.0459];
147 let S_sqrt_theta = Matrix::diag_matrix(theta.iter().map(|&v| v.sqrt()).collect());
148 let P = S_F.cholesky().unwrap();
149 let BP = B.mul(&P).unwrap();
150
151 let GT = BP.concat_h(&S_sqrt_theta).unwrap().transpose();
153 let gammas = &[0.24, 0.28, 0.32, 0.36, 0.4, 0.44, 0.48];
154
155 for (gamma,expret) in portfolio(w,
156 mu,
157 x0,
158 gammas,
159 theta,
160 >)? {
161 println!("Expected return {:.4e} for gamma {:.2}", expret, gamma);
162 }
163 Ok(())
164}
165
166#[derive(Copy,Clone)]
168pub enum MatrixOrder {
169 ByRow,
170 ByCol
171}
172
173pub struct Matrix {
174 fmt : MatrixOrder,
175 dimi : usize,
176 dimj : usize,
177 data : Vec<f64>
178}
179
180impl Matrix {
181 pub fn new(fmt : MatrixOrder, dimi : usize, dimj : usize, data : Vec<f64>) -> Option<Matrix> {
182 if dimi*dimj == data.len() {
183 Some(Matrix{fmt,dimi,dimj,data})
184 }
185 else {
186 None
187 }
188 }
189
190 pub fn new_by_col(dimi : usize, dimj : usize, data : Vec<f64>) -> Option<Matrix> {
191 Matrix::new(MatrixOrder::ByCol,dimi,dimj,data)
192 }
193 pub fn new_by_row(dimi : usize, dimj : usize, data : Vec<f64>) -> Option<Matrix> {
194 Matrix::new(MatrixOrder::ByRow,dimi,dimj,data)
195 }
196
197 pub fn size(&self) -> (usize,usize) { (self.dimi,self.dimj) }
198 pub fn len(&self) -> usize { self.data.len() }
199 pub fn diag_matrix(data : Vec<f64>) -> Matrix {
200 let mut rdata = vec![0.0; data.len()*data.len()];
201 for (r,&v) in rdata.iter_mut().step_by(data.len()+1).zip(data.iter()) { *r = v; }
202 Matrix{fmt:MatrixOrder::ByCol,dimi:data.len(),dimj:data.len(),data:rdata}
203 }
204 pub fn transpose(&self) -> Matrix {
205 Matrix {
206 fmt : match self.fmt { MatrixOrder::ByRow => MatrixOrder::ByCol, MatrixOrder::ByCol => MatrixOrder::ByRow },
207 dimi : self.dimj,
208 dimj : self.dimi,
209 data : self.data.as_slice().to_vec()
210 }
211 }
212
213 pub fn data_by_row(&self) -> Vec<f64> {
214 match self.fmt {
215 MatrixOrder::ByRow => self.data.clone(),
216 MatrixOrder::ByCol => iproduct!(0..self.dimi,0..self.dimj).map(|(i,j)| unsafe { *self.data.get_unchecked(self.dimi*j+i) }).collect()
217 }
218 }
219
220 pub fn data_by_col(&self) -> Vec<f64> {
221 match self.fmt {
222 MatrixOrder::ByCol => self.data.clone(),
223 MatrixOrder::ByRow => iproduct!(0..self.dimj,0..self.dimi).map(|(j,i)| unsafe { *self.data.get_unchecked(self.dimj*i+j) }).collect()
224 }
225 }
226
227 pub fn data_by_row_to(&self,r : &mut[f64]) {
228 match self.fmt {
229 MatrixOrder::ByRow => { r.clone_from_slice(self.data.as_slice()); },
230 MatrixOrder::ByCol => {
231 if r.len() != self.data.len() { panic!("Incorrect result array length"); }
232 for (r,(i,j)) in r.iter_mut().zip(iproduct!(0..self.dimi,0..self.dimj)) {
233 *r = unsafe { *self.data.get_unchecked(self.dimi*j+i) }
234 }
235 }
236 }
237 }
238
239 pub fn data_by_col_to(&self,r : &mut[f64]) {
240 match self.fmt {
241 MatrixOrder::ByCol => { r.clone_from_slice(self.data.as_slice()); },
242 MatrixOrder::ByRow => {
243 for (r,(j,i)) in r.iter_mut().zip(iproduct!(0..self.dimj,0..self.dimi)) {
244 *r = unsafe { *self.data.get_unchecked(self.dimj*i+j) };
245 }
246 }
247 }
248 }
249
250 pub fn cholesky(&self) -> Option<Matrix> {
251 if self.dimi != self.dimj { None }
252 else {
253 let mut resdata = self.data_by_col();
254 for ((j,i),d) in iproduct!(0..self.dimj,0..self.dimi).zip(resdata.iter_mut()) { if i < j {*d = 0.0}; }
256 if let Ok(_) = mosek::potrf(mosek::Uplo::LO,
257 self.dimi.try_into().unwrap(),
258 resdata.as_mut_slice()) {
259 Some(Matrix{fmt:MatrixOrder::ByCol,dimi:self.dimi,dimj:self.dimj,data:resdata})
260 }
261 else { None }
262 }
263 }
264
265 pub fn mul(&self,other : &Matrix) -> Option<Matrix> {
266 if self.dimj != other.dimi { None }
267 else {
268 let mut resdata = vec![0.0; self.dimi * other.dimj];
269 if let Ok(_) = mosek::gemm(match self.fmt { MatrixOrder::ByCol => Transpose::NO, MatrixOrder::ByRow => Transpose::YES },
270 match other.fmt { MatrixOrder::ByCol => Transpose::NO, MatrixOrder::ByRow => Transpose::YES },
271 self.dimi.try_into().unwrap(),
272 other.dimj.try_into().unwrap(),
273 self.dimj.try_into().unwrap(),
274 1.0,
275 self.data.as_slice(),
276 other.data.as_slice(),
277 1.0,
278 resdata.as_mut_slice()) {
279 Matrix::new_by_col(self.dimi,other.dimj,resdata)
280 }
281 else {
282 None
283 }
284 }
285 }
286
287 pub fn concat_h(&self, other : & Matrix) -> Option<Matrix> {
288 if self.dimi != other.dimi {
289 None
290 }
291 else {
292 let mut resdata = vec![0.0; self.data.len() + other.data.len()];
293
294 self.data_by_col_to(& mut resdata[..self.len()]);
295 other.data_by_col_to(& mut resdata[self.len()..]);
296
297 Matrix::new_by_col(self.dimi,self.dimj+other.dimj,resdata)
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 #[test]
305 fn test() {
306 super::main().unwrap();
307 }
308}