From 5a572cca75c6cf381866a5a1e21d2a67ed45cfb5 Mon Sep 17 00:00:00 2001 From: "Michael C. Grant" Date: Wed, 16 Oct 2013 22:49:47 -0500 Subject: [PATCH] Initial commit --- .gitignore | 3 + @double/tfocs_dot.m | 80 +++ @double/tfocs_size.m | 9 + @double/vec.m | 11 + @single/tfocs_dot.m | 80 +++ @single/tfocs_size.m | 9 + @single/vec.m | 11 + @tfocs_tuple/abs.m | 12 + @tfocs_tuple/cell.m | 9 + @tfocs_tuple/disp.m | 37 ++ @tfocs_tuple/display.m | 14 + @tfocs_tuple/get.m | 12 + @tfocs_tuple/max.m | 25 + @tfocs_tuple/min.m | 25 + @tfocs_tuple/minus.m | 13 + @tfocs_tuple/mtimes.m | 12 + @tfocs_tuple/nnz.m | 6 + @tfocs_tuple/numel.m | 9 + @tfocs_tuple/plus.m | 13 + @tfocs_tuple/power.m | 15 + @tfocs_tuple/size.m | 9 + @tfocs_tuple/subsref.m | 6 + @tfocs_tuple/tfocs_dot.m | 13 + @tfocs_tuple/tfocs_normsq.m | 12 + @tfocs_tuple/tfocs_size.m | 7 + @tfocs_tuple/tfocs_tuple.m | 24 + @tfocs_tuple/tfocs_zeros.m | 12 + @tfocs_tuple/times.m | 9 + @tfocs_tuple/uminus.m | 12 + CHANGELOG | 229 +++++++ Contents.m | 113 ++++ LICENSE | 34 + README | 11 + continuation.m | 261 ++++++++ examples/demos/SIAM_demo.m | 195 ++++++ examples/demos/callandmap.m | 15 + examples/demos/demo_MatrixCompletion.m | 69 ++ examples/demos/demo_SVM.m | 147 +++++ examples/demos/demo_alternatingProjections.m | 402 ++++++++++++ examples/demos/project2DCone.m | 74 +++ examples/demos/recordPoints.m | 17 + examples/largescale/BugsBunny.mat | Bin 0 -> 97622 bytes examples/largescale/PsiTransposeWFF.m | 58 ++ examples/largescale/PsiWFF.m | 62 ++ examples/largescale/README.txt | 3 + .../largescale/example_quantumTomography.m | 223 +++++++ examples/largescale/explicitPauliTensor.m | 27 + examples/largescale/findWeights.m | 26 + .../largescale/image_denoising_withSPOT.m | 284 +++++++++ examples/largescale/plotNow.m | 40 ++ examples/largescale/test_SVM.m | 171 +++++ examples/largescale/test_sBPDN_W_largescale.m | 149 +++++ .../largescale/test_sTV_Analysis_largescale.m | 268 ++++++++ examples/largescale/test_sTV_largescale.m | 154 +++++ examples/smallscale/README.txt | 7 + examples/smallscale/project_WeightedNorm.m | 62 ++ .../smallscale/reference_solutions/LMI.mat | Bin 0 -> 1963 bytes .../reference_solutions/LMI_complex.mat | Bin 0 -> 1961 bytes .../smallscale/reference_solutions/LP.mat | Bin 0 -> 38477 bytes .../smallscale/reference_solutions/LP_box.mat | Bin 0 -> 14765 bytes .../smallscale/reference_solutions/SDP.mat | Bin 0 -> 47538 bytes .../reference_solutions/SDP_complex.mat | Bin 0 -> 4227 bytes ...asispursuit_WW_problem1_smoothed_noisy.mat | Bin 0 -> 12892 bytes ...basispursuit_W_problem1_smoothed_noisy.mat | Bin 0 -> 12756 bytes .../basispursuit_problem1_noisy.mat | Bin 0 -> 17702 bytes ...sispursuit_problem1_smoothed_noiseless.mat | Bin 0 -> 17610 bytes ...roblem1_smoothed_noiseless_nonnegative.mat | Bin 0 -> 27040 bytes .../basispursuit_problem1_smoothed_noisy.mat | Bin 0 -> 17698 bytes ...ursuit_problem1_smoothed_noisy_complex.mat | Bin 0 -> 24418 bytes ...suit_problem1_smoothed_noisy_complex_2.mat | Bin 0 -> 29950 bytes ...it_problem1_smoothed_noisy_nonnegative.mat | Bin 0 -> 27021 bytes .../blocknorm_smoothed_noisy.mat | Bin 0 -> 40754 bytes .../complicatedProblem1.mat | Bin 0 -> 6878 bytes .../dantzig_problem1_smoothed_noisy.mat | Bin 0 -> 25264 bytes .../lasso_problem1_noisy.mat | Bin 0 -> 37672 bytes .../nuclearNorm_problem1_noiseless.mat | Bin 0 -> 25656 bytes .../ordered_asso_problem1_noisy.mat | Bin 0 -> 45856 bytes .../traceLS_problem1_noisy.mat | Bin 0 -> 15333 bytes .../traceLS_problem2_noisy.mat | Bin 0 -> 20072 bytes .../tv_problem1_smoothed_noisy.mat | Bin 0 -> 15502 bytes examples/smallscale/test_LASSO.m | 95 +++ examples/smallscale/test_LMI.m | 134 ++++ examples/smallscale/test_LinearProgram.m | 153 +++++ examples/smallscale/test_OrderedLASSO.m | 103 +++ examples/smallscale/test_SDP.m | 143 +++++ examples/smallscale/test_TraceLS.m | 121 ++++ examples/smallscale/test_all.m | 30 + examples/smallscale/test_blockNorm.m | 211 ++++++ examples/smallscale/test_complicatedUsage.m | 274 ++++++++ examples/smallscale/test_nuclearNorm.m | 111 ++++ examples/smallscale/test_psdCompletion.m | 136 ++++ examples/smallscale/test_quadratic.m | 44 ++ .../smallscale/test_quadratic_constrained.m | 83 +++ examples/smallscale/test_sBP.m | 144 +++++ examples/smallscale/test_sBPDN.m | 102 +++ examples/smallscale/test_sBPDN_W.m | 149 +++++ examples/smallscale/test_sBPDN_WW.m | 130 ++++ examples/smallscale/test_sBPDN_complex.m | 120 ++++ examples/smallscale/test_sBPDN_complex_2.m | 126 ++++ examples/smallscale/test_sBPDN_nonnegative.m | 136 ++++ .../smallscale/test_sBPDN_withContinuation.m | 147 +++++ examples/smallscale/test_sBP_nonnegative.m | 149 +++++ examples/smallscale/test_sDantzig.m | 147 +++++ examples/smallscale/test_sDantzig_3methods.m | 146 +++++ examples/smallscale/test_sTV.m | 118 ++++ examples/smallscale/test_variousSolvers.m | 70 ++ linop_TV.m | 199 ++++++ linop_TV3D.m | 197 ++++++ linop_adjoint.m | 22 + linop_compose.m | 84 +++ linop_dot.m | 49 ++ linop_fft.m | 192 ++++++ linop_handles.m | 89 +++ linop_matrix.m | 93 +++ linop_normest.m | 79 +++ linop_reshape.m | 34 + linop_scale.m | 62 ++ linop_spot.m | 81 +++ linop_subsample.m | 161 +++++ linop_test.m | 206 ++++++ linop_vec.m | 40 ++ mexFiles/makeMex.m | 57 ++ mexFiles/proxAdaptiveL1Mex.c | 133 ++++ mexFiles/proxAdaptiveL1Mex.mex | Bin 0 -> 16581 bytes mexFiles/proxAdaptiveL1Mex.mexa64 | Bin 0 -> 9146 bytes private/linop_identity.m | 10 + private/linop_stack.m | 297 +++++++++ private/print_cell_size.m | 40 ++ private/prox_stack.m | 64 ++ private/size_ambig.m | 26 + private/size_compat.m | 57 ++ private/smooth_stack.m | 43 ++ private/solver_apply.m | 13 + private/tfocs_backtrack.m | 37 ++ private/tfocs_cleanup.m | 62 ++ private/tfocs_initialize.m | 598 ++++++++++++++++++ private/tfocs_iterate.m | 284 +++++++++ private/tfocs_prox.m | 113 ++++ private/tfocs_round.m | 10 + private/tfocs_smooth.m | 45 ++ private/tfocs_zeros.m | 54 ++ proj_0.m | 63 ++ proj_Rn.m | 14 + proj_Rplus.m | 62 ++ proj_affine.m | 85 +++ proj_box.m | 46 ++ proj_boxAffine.m | 111 ++++ proj_conic.m | 48 ++ proj_l1.m | 42 ++ proj_l2.m | 83 +++ proj_l2group.m | 79 +++ proj_linf.m | 36 ++ proj_linfl2.m | 64 ++ proj_max.m | 36 ++ proj_maxEig.m | 141 +++++ proj_nuclear.m | 56 ++ proj_psd.m | 138 ++++ proj_psdUTrace.m | 193 ++++++ proj_simplex.m | 61 ++ proj_singleAffine.m | 50 ++ proj_spectral.m | 226 +++++++ prox_0.m | 13 + prox_Ol1.m | 87 +++ prox_boxDual.m | 82 +++ prox_dualize.m | 94 +++ prox_hinge.m | 76 +++ prox_hingeDual.m | 97 +++ prox_l1.m | 54 ++ prox_l1l2.m | 66 ++ prox_l1linf.m | 107 ++++ prox_l1pos.m | 38 ++ prox_l2.m | 96 +++ prox_linf.m | 42 ++ prox_max.m | 53 ++ prox_maxEig.m | 62 ++ prox_nuclear.m | 140 ++++ prox_scale.m | 54 ++ prox_spectral.m | 105 +++ prox_trace.m | 139 ++++ smooth_constant.m | 29 + smooth_entropy.m | 21 + smooth_handles.m | 27 + smooth_huber.m | 47 ++ smooth_linear.m | 38 ++ smooth_logLLogistic.m | 41 ++ smooth_logLPoisson.m | 43 ++ smooth_logdet.m | 127 ++++ smooth_logsumexp.m | 21 + smooth_quad.m | 152 +++++ solver_L1RLS.m | 25 + solver_LASSO.m | 27 + solver_OrderedLASSO.m | 29 + solver_TraceLS.m | 48 ++ solver_psdComp.m | 66 ++ solver_psdCompConstrainedTrace.m | 84 +++ solver_sBP.m | 70 ++ solver_sBPDN.m | 83 +++ solver_sBPDN_W.m | 68 ++ solver_sBPDN_WW.m | 77 +++ solver_sDantzig.m | 84 +++ solver_sDantzig_W.m | 127 ++++ solver_sLMI.m | 101 +++ solver_sLP.m | 82 +++ solver_sLP_box.m | 83 +++ solver_sNuclearBP.m | 107 ++++ solver_sNuclearBPDN.m | 78 +++ solver_sSDP.m | 104 +++ test_nonsmooth.m | 219 +++++++ test_proxPair.m | 213 +++++++ test_smooth.m | 142 +++++ tfocs.m | 112 ++++ tfocs_AT.m | 92 +++ tfocs_GRA.m | 51 ++ tfocs_LLM.m | 77 +++ tfocs_N07.m | 82 +++ tfocs_N83.m | 78 +++ tfocs_SCD.m | 204 ++++++ tfocs_TS.m | 86 +++ tfocs_normsq.m | 22 + tfocs_version.m | 19 + tfocs_where.m | 18 + tfunc_scale.m | 95 +++ tfunc_sum.m | 46 ++ 223 files changed, 16947 insertions(+) create mode 100644 .gitignore create mode 100644 @double/tfocs_dot.m create mode 100644 @double/tfocs_size.m create mode 100644 @double/vec.m create mode 100644 @single/tfocs_dot.m create mode 100644 @single/tfocs_size.m create mode 100644 @single/vec.m create mode 100644 @tfocs_tuple/abs.m create mode 100644 @tfocs_tuple/cell.m create mode 100644 @tfocs_tuple/disp.m create mode 100644 @tfocs_tuple/display.m create mode 100644 @tfocs_tuple/get.m create mode 100644 @tfocs_tuple/max.m create mode 100644 @tfocs_tuple/min.m create mode 100644 @tfocs_tuple/minus.m create mode 100644 @tfocs_tuple/mtimes.m create mode 100644 @tfocs_tuple/nnz.m create mode 100644 @tfocs_tuple/numel.m create mode 100644 @tfocs_tuple/plus.m create mode 100644 @tfocs_tuple/power.m create mode 100644 @tfocs_tuple/size.m create mode 100644 @tfocs_tuple/subsref.m create mode 100644 @tfocs_tuple/tfocs_dot.m create mode 100644 @tfocs_tuple/tfocs_normsq.m create mode 100644 @tfocs_tuple/tfocs_size.m create mode 100644 @tfocs_tuple/tfocs_tuple.m create mode 100644 @tfocs_tuple/tfocs_zeros.m create mode 100644 @tfocs_tuple/times.m create mode 100644 @tfocs_tuple/uminus.m create mode 100644 CHANGELOG create mode 100644 Contents.m create mode 100644 LICENSE create mode 100644 README create mode 100644 continuation.m create mode 100644 examples/demos/SIAM_demo.m create mode 100644 examples/demos/callandmap.m create mode 100644 examples/demos/demo_MatrixCompletion.m create mode 100644 examples/demos/demo_SVM.m create mode 100644 examples/demos/demo_alternatingProjections.m create mode 100644 examples/demos/project2DCone.m create mode 100644 examples/demos/recordPoints.m create mode 100644 examples/largescale/BugsBunny.mat create mode 100644 examples/largescale/PsiTransposeWFF.m create mode 100644 examples/largescale/PsiWFF.m create mode 100644 examples/largescale/README.txt create mode 100644 examples/largescale/example_quantumTomography.m create mode 100644 examples/largescale/explicitPauliTensor.m create mode 100644 examples/largescale/findWeights.m create mode 100644 examples/largescale/image_denoising_withSPOT.m create mode 100644 examples/largescale/plotNow.m create mode 100644 examples/largescale/test_SVM.m create mode 100644 examples/largescale/test_sBPDN_W_largescale.m create mode 100644 examples/largescale/test_sTV_Analysis_largescale.m create mode 100644 examples/largescale/test_sTV_largescale.m create mode 100644 examples/smallscale/README.txt create mode 100644 examples/smallscale/project_WeightedNorm.m create mode 100644 examples/smallscale/reference_solutions/LMI.mat create mode 100644 examples/smallscale/reference_solutions/LMI_complex.mat create mode 100644 examples/smallscale/reference_solutions/LP.mat create mode 100644 examples/smallscale/reference_solutions/LP_box.mat create mode 100644 examples/smallscale/reference_solutions/SDP.mat create mode 100644 examples/smallscale/reference_solutions/SDP_complex.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_WW_problem1_smoothed_noisy.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_W_problem1_smoothed_noisy.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_noisy.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noiseless.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noiseless_nonnegative.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_complex.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_complex_2.mat create mode 100644 examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_nonnegative.mat create mode 100644 examples/smallscale/reference_solutions/blocknorm_smoothed_noisy.mat create mode 100644 examples/smallscale/reference_solutions/complicatedProblem1.mat create mode 100644 examples/smallscale/reference_solutions/dantzig_problem1_smoothed_noisy.mat create mode 100644 examples/smallscale/reference_solutions/lasso_problem1_noisy.mat create mode 100644 examples/smallscale/reference_solutions/nuclearNorm_problem1_noiseless.mat create mode 100644 examples/smallscale/reference_solutions/ordered_asso_problem1_noisy.mat create mode 100644 examples/smallscale/reference_solutions/traceLS_problem1_noisy.mat create mode 100644 examples/smallscale/reference_solutions/traceLS_problem2_noisy.mat create mode 100644 examples/smallscale/reference_solutions/tv_problem1_smoothed_noisy.mat create mode 100644 examples/smallscale/test_LASSO.m create mode 100644 examples/smallscale/test_LMI.m create mode 100644 examples/smallscale/test_LinearProgram.m create mode 100644 examples/smallscale/test_OrderedLASSO.m create mode 100644 examples/smallscale/test_SDP.m create mode 100644 examples/smallscale/test_TraceLS.m create mode 100644 examples/smallscale/test_all.m create mode 100644 examples/smallscale/test_blockNorm.m create mode 100644 examples/smallscale/test_complicatedUsage.m create mode 100644 examples/smallscale/test_nuclearNorm.m create mode 100644 examples/smallscale/test_psdCompletion.m create mode 100644 examples/smallscale/test_quadratic.m create mode 100644 examples/smallscale/test_quadratic_constrained.m create mode 100644 examples/smallscale/test_sBP.m create mode 100644 examples/smallscale/test_sBPDN.m create mode 100644 examples/smallscale/test_sBPDN_W.m create mode 100644 examples/smallscale/test_sBPDN_WW.m create mode 100644 examples/smallscale/test_sBPDN_complex.m create mode 100644 examples/smallscale/test_sBPDN_complex_2.m create mode 100644 examples/smallscale/test_sBPDN_nonnegative.m create mode 100644 examples/smallscale/test_sBPDN_withContinuation.m create mode 100644 examples/smallscale/test_sBP_nonnegative.m create mode 100644 examples/smallscale/test_sDantzig.m create mode 100644 examples/smallscale/test_sDantzig_3methods.m create mode 100644 examples/smallscale/test_sTV.m create mode 100644 examples/smallscale/test_variousSolvers.m create mode 100644 linop_TV.m create mode 100644 linop_TV3D.m create mode 100644 linop_adjoint.m create mode 100644 linop_compose.m create mode 100644 linop_dot.m create mode 100644 linop_fft.m create mode 100644 linop_handles.m create mode 100644 linop_matrix.m create mode 100644 linop_normest.m create mode 100644 linop_reshape.m create mode 100644 linop_scale.m create mode 100644 linop_spot.m create mode 100644 linop_subsample.m create mode 100644 linop_test.m create mode 100644 linop_vec.m create mode 100644 mexFiles/makeMex.m create mode 100644 mexFiles/proxAdaptiveL1Mex.c create mode 100755 mexFiles/proxAdaptiveL1Mex.mex create mode 100755 mexFiles/proxAdaptiveL1Mex.mexa64 create mode 100644 private/linop_identity.m create mode 100644 private/linop_stack.m create mode 100644 private/print_cell_size.m create mode 100644 private/prox_stack.m create mode 100644 private/size_ambig.m create mode 100644 private/size_compat.m create mode 100644 private/smooth_stack.m create mode 100644 private/solver_apply.m create mode 100644 private/tfocs_backtrack.m create mode 100644 private/tfocs_cleanup.m create mode 100644 private/tfocs_initialize.m create mode 100644 private/tfocs_iterate.m create mode 100644 private/tfocs_prox.m create mode 100644 private/tfocs_round.m create mode 100644 private/tfocs_smooth.m create mode 100644 private/tfocs_zeros.m create mode 100644 proj_0.m create mode 100644 proj_Rn.m create mode 100644 proj_Rplus.m create mode 100644 proj_affine.m create mode 100644 proj_box.m create mode 100644 proj_boxAffine.m create mode 100644 proj_conic.m create mode 100644 proj_l1.m create mode 100644 proj_l2.m create mode 100644 proj_l2group.m create mode 100644 proj_linf.m create mode 100644 proj_linfl2.m create mode 100644 proj_max.m create mode 100644 proj_maxEig.m create mode 100644 proj_nuclear.m create mode 100644 proj_psd.m create mode 100644 proj_psdUTrace.m create mode 100644 proj_simplex.m create mode 100644 proj_singleAffine.m create mode 100644 proj_spectral.m create mode 100644 prox_0.m create mode 100644 prox_Ol1.m create mode 100644 prox_boxDual.m create mode 100644 prox_dualize.m create mode 100644 prox_hinge.m create mode 100644 prox_hingeDual.m create mode 100644 prox_l1.m create mode 100644 prox_l1l2.m create mode 100644 prox_l1linf.m create mode 100644 prox_l1pos.m create mode 100644 prox_l2.m create mode 100644 prox_linf.m create mode 100644 prox_max.m create mode 100644 prox_maxEig.m create mode 100644 prox_nuclear.m create mode 100644 prox_scale.m create mode 100644 prox_spectral.m create mode 100644 prox_trace.m create mode 100644 smooth_constant.m create mode 100644 smooth_entropy.m create mode 100644 smooth_handles.m create mode 100644 smooth_huber.m create mode 100644 smooth_linear.m create mode 100644 smooth_logLLogistic.m create mode 100644 smooth_logLPoisson.m create mode 100644 smooth_logdet.m create mode 100644 smooth_logsumexp.m create mode 100644 smooth_quad.m create mode 100644 solver_L1RLS.m create mode 100644 solver_LASSO.m create mode 100644 solver_OrderedLASSO.m create mode 100644 solver_TraceLS.m create mode 100644 solver_psdComp.m create mode 100644 solver_psdCompConstrainedTrace.m create mode 100644 solver_sBP.m create mode 100644 solver_sBPDN.m create mode 100644 solver_sBPDN_W.m create mode 100644 solver_sBPDN_WW.m create mode 100644 solver_sDantzig.m create mode 100644 solver_sDantzig_W.m create mode 100644 solver_sLMI.m create mode 100644 solver_sLP.m create mode 100644 solver_sLP_box.m create mode 100644 solver_sNuclearBP.m create mode 100644 solver_sNuclearBPDN.m create mode 100644 solver_sSDP.m create mode 100644 test_nonsmooth.m create mode 100644 test_proxPair.m create mode 100644 test_smooth.m create mode 100644 tfocs.m create mode 100644 tfocs_AT.m create mode 100644 tfocs_GRA.m create mode 100644 tfocs_LLM.m create mode 100644 tfocs_N07.m create mode 100644 tfocs_N83.m create mode 100644 tfocs_SCD.m create mode 100644 tfocs_TS.m create mode 100644 tfocs_normsq.m create mode 100644 tfocs_version.m create mode 100644 tfocs_where.m create mode 100644 tfunc_scale.m create mode 100644 tfunc_sum.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4df70a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +hide +.dropbox +.DS_Store diff --git a/@double/tfocs_dot.m b/@double/tfocs_dot.m new file mode 100644 index 0000000..3503a7d --- /dev/null +++ b/@double/tfocs_dot.m @@ -0,0 +1,80 @@ +function v = tfocs_dot( x, y ) +% TFOCS_DOT Dot product . Returns real(x'*y) +% For matrices, this is the inner product that induces the Frobenius +% norm, i.e. = tr(x'*y), and not matrix multiplication. + +% Note: this is real(x'*y) and not real(x.'*y) + +if isempty( x ) || isempty( y ), + v = 0; + return; +end + +% Allow scalar times vector multiplies: +if isscalar(x) && ~isscalar(y) + if ~x, + v = 0; + return; + else +% x = repmat(x,size(y,1),size(y,2) ); % doesn't work if y is a cell + % The above code fails if y is multi-dimensional. Also, not + % memory efficient. Switching to this (10/9/2013) + % (Thanks to Graham Coleman for finding this bug, btw) + if issparse(y) + v = real( x * sum(nonzeros(y)) ); + else + v = real( x * sum(y(:)) ); + end + return; + end +elseif isscalar(y) && ~isscalar(x) + if ~y + v = 0; + return; + else + y = repmat(y,size(x,1),size(x,2) ); + if issparse(x) + v = real( y * sum(nonzeros(x)) ); + else + v = real( y * sum(x(:)) ); + end + return; + + end +end + +if isreal( x ) || isreal( y ), + if issparse( x ) || issparse( y ), + v = sum( nonzeros( real(x) .* real(y) ) ); + else + % Split this into two cases (first case could be handled by + % second case, but we're trying to make it very fast since + % this code is called very often) + if ndims(x)==2 && ndims(y)==2 && size(x,2) == 1 && size(y,2) == 1 && isreal(x) && isreal(y) + v = sum( x'*y ); % do we really need 'sum' ? + else + % Take real part first (since one of x and y is real anyhow) + % in order to save some computation: + v = real(x(:))' * real(y(:)); + end + end +else + if issparse( x ) || issparse( y ), + v = sum( nonzeros( real(x) .* real(y) ) ) + ... + sum( nonzeros( imag(x) .* imag(y) ) ); + else + % SRB: this is very slow: +% v = sum( real(x(:))' * real(y(:)) ) + ... +% sum( imag(x(:))' * imag(y(:)) ); + if ndims(x)==2 && ndims(y)==2 && size(x,2) == 1 && size(y,2) == 1 + v = sum(real( x'*y ) ); + else + % This is the most generic code. + v = real( x(:)'*y(:) ); + end + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/@double/tfocs_size.m b/@double/tfocs_size.m new file mode 100644 index 0000000..ac0b208 --- /dev/null +++ b/@double/tfocs_size.m @@ -0,0 +1,9 @@ +function v = tfocs_size( x ) + +% SIZE TFOCS-friendly size operator. + +v = { @zeros, m, n }; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@double/vec.m b/@double/vec.m new file mode 100644 index 0000000..1e37e52 --- /dev/null +++ b/@double/vec.m @@ -0,0 +1,11 @@ +function v = vec( x ) + +% VEC Vectorize. + +v = reshape( x, numel(x), 1 ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + + \ No newline at end of file diff --git a/@single/tfocs_dot.m b/@single/tfocs_dot.m new file mode 100644 index 0000000..3503a7d --- /dev/null +++ b/@single/tfocs_dot.m @@ -0,0 +1,80 @@ +function v = tfocs_dot( x, y ) +% TFOCS_DOT Dot product . Returns real(x'*y) +% For matrices, this is the inner product that induces the Frobenius +% norm, i.e. = tr(x'*y), and not matrix multiplication. + +% Note: this is real(x'*y) and not real(x.'*y) + +if isempty( x ) || isempty( y ), + v = 0; + return; +end + +% Allow scalar times vector multiplies: +if isscalar(x) && ~isscalar(y) + if ~x, + v = 0; + return; + else +% x = repmat(x,size(y,1),size(y,2) ); % doesn't work if y is a cell + % The above code fails if y is multi-dimensional. Also, not + % memory efficient. Switching to this (10/9/2013) + % (Thanks to Graham Coleman for finding this bug, btw) + if issparse(y) + v = real( x * sum(nonzeros(y)) ); + else + v = real( x * sum(y(:)) ); + end + return; + end +elseif isscalar(y) && ~isscalar(x) + if ~y + v = 0; + return; + else + y = repmat(y,size(x,1),size(x,2) ); + if issparse(x) + v = real( y * sum(nonzeros(x)) ); + else + v = real( y * sum(x(:)) ); + end + return; + + end +end + +if isreal( x ) || isreal( y ), + if issparse( x ) || issparse( y ), + v = sum( nonzeros( real(x) .* real(y) ) ); + else + % Split this into two cases (first case could be handled by + % second case, but we're trying to make it very fast since + % this code is called very often) + if ndims(x)==2 && ndims(y)==2 && size(x,2) == 1 && size(y,2) == 1 && isreal(x) && isreal(y) + v = sum( x'*y ); % do we really need 'sum' ? + else + % Take real part first (since one of x and y is real anyhow) + % in order to save some computation: + v = real(x(:))' * real(y(:)); + end + end +else + if issparse( x ) || issparse( y ), + v = sum( nonzeros( real(x) .* real(y) ) ) + ... + sum( nonzeros( imag(x) .* imag(y) ) ); + else + % SRB: this is very slow: +% v = sum( real(x(:))' * real(y(:)) ) + ... +% sum( imag(x(:))' * imag(y(:)) ); + if ndims(x)==2 && ndims(y)==2 && size(x,2) == 1 && size(y,2) == 1 + v = sum(real( x'*y ) ); + else + % This is the most generic code. + v = real( x(:)'*y(:) ); + end + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/@single/tfocs_size.m b/@single/tfocs_size.m new file mode 100644 index 0000000..ac0b208 --- /dev/null +++ b/@single/tfocs_size.m @@ -0,0 +1,9 @@ +function v = tfocs_size( x ) + +% SIZE TFOCS-friendly size operator. + +v = { @zeros, m, n }; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@single/vec.m b/@single/vec.m new file mode 100644 index 0000000..1e37e52 --- /dev/null +++ b/@single/vec.m @@ -0,0 +1,11 @@ +function v = vec( x ) + +% VEC Vectorize. + +v = reshape( x, numel(x), 1 ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + + \ No newline at end of file diff --git a/@tfocs_tuple/abs.m b/@tfocs_tuple/abs.m new file mode 100644 index 0000000..69568e9 --- /dev/null +++ b/@tfocs_tuple/abs.m @@ -0,0 +1,12 @@ +function x = abs( x ) + +% ABS Absolute value. + +n = numel( x.value_ ); +for k = 1 : n, + x.value_{k} = abs( x.value_{k} ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/cell.m b/@tfocs_tuple/cell.m new file mode 100644 index 0000000..ac7bb87 --- /dev/null +++ b/@tfocs_tuple/cell.m @@ -0,0 +1,9 @@ +function v = cell( x ) + +% CELL Conversion to a cell array. + +v = x.value_; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/disp.m b/@tfocs_tuple/disp.m new file mode 100644 index 0000000..d7066cc --- /dev/null +++ b/@tfocs_tuple/disp.m @@ -0,0 +1,37 @@ +function disp( x, prefix, inpname, nohead ) + +% DISP Manual display. DISP(x,prefix) adds the string prefix to each +% line of the display output. + +if nargin < 3, inpname = ''; end +if nargin < 2, prefix = ''; end +n = numel( x.value_ ); +if nargin < 4, + fprintf( '%stfocs tuple object:\n', prefix ); + prefix = [ prefix, ' ' ]; +end +for k = 1 : n, + ss = x.value_{k}; + inpname2 = sprintf( '%s{%d}', inpname, k ); + if isnumeric( ss ), + cls = class( ss ); + sz = size( ss ); + temp = sprintf( '%dx', sz ); + if all( sz == 1 ), + fprintf( '%s%s: [%g]\n', prefix, inpname2, ss ); + elseif isreal(ss), + fprintf( '%s%s: [%s %s]\n', prefix, inpname2, temp(1:end-1), cls ); + else + fprintf( '%s%s: [%s %s complex]\n', prefix, inpname2, temp(1:end-1), cls ); + end + elseif isa( ss, 'tfocs_tuple' ), + fprintf( '%s%s: tfocs tuple object\n', prefix, inpname2 ); + disp( ss, [ prefix, inpname2 ], '', 1 ); + else + fprintf( '%s%s: %s\n', prefix, inpname2, class(ss) ); + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/display.m b/@tfocs_tuple/display.m new file mode 100644 index 0000000..ff45db7 --- /dev/null +++ b/@tfocs_tuple/display.m @@ -0,0 +1,14 @@ +function display( x ) + +% DISPLAY Automatic display. + +long = ~isequal(get(0,'FormatSpacing'),'compact'); +if long, disp( ' ' ); end +disp([inputname(1) ' =']); +if long, disp( ' ' ); end +disp(x,' ',inputname(1)) +if long, disp( ' ' ); end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/get.m b/@tfocs_tuple/get.m new file mode 100644 index 0000000..05f95ca --- /dev/null +++ b/@tfocs_tuple/get.m @@ -0,0 +1,12 @@ +function y = get( x, ndxs ) +if nargin == 0, + y = x.value_; +elseif numel(ndxs) == 1, + y = x.value_{ndxs}; +else + y = x.value_(ndxs); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/max.m b/@tfocs_tuple/max.m new file mode 100644 index 0000000..66bdfc4 --- /dev/null +++ b/@tfocs_tuple/max.m @@ -0,0 +1,25 @@ +function z = max( x, y ) + +% MAX Maximum, z = max(x,y) + +if isa(x,'tfocs_tuple') + z = x; + if isa(y,'tfocs_tuple') + z.value_ = cellfun( @max, x.value_, y.value_, 'UniformOutput', false ); + elseif isscalar(y) + z.value_ = cellfun( @max, x.value_, {y}, 'UniformOutput', false ); + else + z.value_ = cellfun( @max, x.value_, {y}, 'UniformOutput', false ); + end +else + z = y; + if isscalar(x) + z.value_ = cellfun( @max, {x}, y.value_, 'UniformOutput', false ); + else + z.value_ = cellfun( @max, {x}, y.value_, 'UniformOutput', false ); + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/min.m b/@tfocs_tuple/min.m new file mode 100644 index 0000000..0c1f372 --- /dev/null +++ b/@tfocs_tuple/min.m @@ -0,0 +1,25 @@ +function z = min( x, y ) + +% MAX Minimum, z = min(x,y) + +if isa(x,'tfocs_tuple') + z = x; + if isa(y,'tfocs_tuple') + z.value_ = cellfun( @min, x.value_, y.value_, 'UniformOutput', false ); + elseif isscalar(y) + z.value_ = cellfun( @min, x.value_, {y}, 'UniformOutput', false ); + else + z.value_ = cellfun( @min, x.value_, {y}, 'UniformOutput', false ); + end +else + z = y; + if isscalar(x) + z.value_ = cellfun( @min, {x}, y.value_, 'UniformOutput', false ); + else + z.value_ = cellfun( @min, {x}, y.value_, 'UniformOutput', false ); + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/minus.m b/@tfocs_tuple/minus.m new file mode 100644 index 0000000..1c07f26 --- /dev/null +++ b/@tfocs_tuple/minus.m @@ -0,0 +1,13 @@ +function x = minus( x, y ) + +% MINUS Subtraction. + +if isnumeric( x ) && isscalar( x ) && x == 0, + x = -y; +elseif ~isnumeric( y ) || numel( y ) ~= 1 || y ~= 0 + x.value_ = cellfun( @minus, x.value_, y.value_, 'UniformOutput', false ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/mtimes.m b/@tfocs_tuple/mtimes.m new file mode 100644 index 0000000..b2a7ed5 --- /dev/null +++ b/@tfocs_tuple/mtimes.m @@ -0,0 +1,12 @@ +function y = mtimes( x, y ) + +% MTIMES Multiplication. TFOCS_TUPLE objects may only be left-multiplied +% by real scalars. + +for k = 1 : numel( y.value_ ), + y.value_{k} = x * y.value_{k}; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/nnz.m b/@tfocs_tuple/nnz.m new file mode 100644 index 0000000..f029176 --- /dev/null +++ b/@tfocs_tuple/nnz.m @@ -0,0 +1,6 @@ +function ans = nnz( x ) +ans = sum( cellfun( @nnz, x.value_ ) ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/numel.m b/@tfocs_tuple/numel.m new file mode 100644 index 0000000..3cdc284 --- /dev/null +++ b/@tfocs_tuple/numel.m @@ -0,0 +1,9 @@ +function v = numel( x, varargin ) + +% NUMEL Number of elements. + +v = numel( x.value_, varargin{:} ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/plus.m b/@tfocs_tuple/plus.m new file mode 100644 index 0000000..5769de5 --- /dev/null +++ b/@tfocs_tuple/plus.m @@ -0,0 +1,13 @@ +function x = plus( x, y ) + +% PLUS Addition. + +if isnumeric( x ) && isscalar( x ) && x == 0, + x = y; +elseif ~isnumeric( y ) || numel( y ) ~= 1 || y ~= 0 + x.value_ = cellfun( @plus, x.value_, y.value_, 'UniformOutput', false ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/power.m b/@tfocs_tuple/power.m new file mode 100644 index 0000000..192c068 --- /dev/null +++ b/@tfocs_tuple/power.m @@ -0,0 +1,15 @@ +function x = power( x, y ) + +% POWER Matrix power, z = x.^y + +if isa(y,'tfocs_tuple') + x.value_ = cellfun( @power, x.value_, y.value_, 'UniformOutput', false ); +elseif isscalar(y) + x.value_ = cellfun( @power, x.value_, {y}, 'UniformOutput', false ); +else + x.value_ = cellfun( @power, x.value_, {y}, 'UniformOutput', false ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/size.m b/@tfocs_tuple/size.m new file mode 100644 index 0000000..8813a60 --- /dev/null +++ b/@tfocs_tuple/size.m @@ -0,0 +1,9 @@ +function v = size( x ) + +% SIZE Size. + +v = cellfun( @size, x.value_, 'UniformOutput', false ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/subsref.m b/@tfocs_tuple/subsref.m new file mode 100644 index 0000000..b75ec16 --- /dev/null +++ b/@tfocs_tuple/subsref.m @@ -0,0 +1,6 @@ +function varargout = subsref( x, varargin ) +[ varargout{1:nargout} ] = subsref( x.value_, varargin{:} ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/tfocs_dot.m b/@tfocs_tuple/tfocs_dot.m new file mode 100644 index 0000000..8d07290 --- /dev/null +++ b/@tfocs_tuple/tfocs_dot.m @@ -0,0 +1,13 @@ +function v = tfocs_dot( x, y ) + +% TFOCS_DOT Dot products. + +if isempty( x ) || isempty( y ), + v = 0; +else + v = sum( cellfun( @tfocs_dot, x.value_, y.value_ ) ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/tfocs_normsq.m b/@tfocs_tuple/tfocs_normsq.m new file mode 100644 index 0000000..d49e89e --- /dev/null +++ b/@tfocs_tuple/tfocs_normsq.m @@ -0,0 +1,12 @@ +function v = tfocs_normsq( x ) + +% TFOCS_NORMSQ Squared norm. By default, TFOCS_NORMSQ(X) is equal +% to TFOCS_NORMSQ(X,X), and this numerical equivalence +% must be preserved. However, an object may overload +% TFOCS_NORMSQ to compute its value more efficiently. + +v = sum( cellfun( @tfocs_normsq, x.value_ ) ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/tfocs_size.m b/@tfocs_tuple/tfocs_size.m new file mode 100644 index 0000000..999b056 --- /dev/null +++ b/@tfocs_tuple/tfocs_size.m @@ -0,0 +1,7 @@ +function v = tfocs_size( x ) + +v = { @tfocs_tuple, cellfun( @size, x.value_, 'UniformOutput', false ) }; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/tfocs_tuple.m b/@tfocs_tuple/tfocs_tuple.m new file mode 100644 index 0000000..bc30eef --- /dev/null +++ b/@tfocs_tuple/tfocs_tuple.m @@ -0,0 +1,24 @@ +function v = tfocs_tuple( w ) + +% TFOCS_TUPLE The TFOCS tuple object. +% This object is used to create tuples, which are elements of +% vector spaces that are Cartesian products of other vector +% spaces. TFOCS assumes that any element in a tfocs_tuple can +% perform the following basic operations: +% --- addition (plus) +% --- subtraction (minus) +% --- multiplication by real scalars (times,mtimes) +% --- dot products (tfocs_dot) +% --- squared norm (tfocs_normsq, optional) +% --- size (size; single-argument calls only) + +if ~iscell(w) && ~isempty(w) + error('tfocs_tuple constructor: input must be a cell array'); +end +v.value_ = reshape( w, 1, numel(w) ); +v = class( v, 'tfocs_tuple' ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/@tfocs_tuple/tfocs_zeros.m b/@tfocs_tuple/tfocs_zeros.m new file mode 100644 index 0000000..01348be --- /dev/null +++ b/@tfocs_tuple/tfocs_zeros.m @@ -0,0 +1,12 @@ +function x = tfocs_zeros( x ) + +% ABS Absolute value. + +n = numel( x.value_ ); +for k = 1 : n, + x.value_{k} = zeros( size(x.value_{k}) ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/times.m b/@tfocs_tuple/times.m new file mode 100644 index 0000000..56d70b5 --- /dev/null +++ b/@tfocs_tuple/times.m @@ -0,0 +1,9 @@ +function v = times( x, y ) + +% MTIMES Multiplication. + +v = mtimes( x, y ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/@tfocs_tuple/uminus.m b/@tfocs_tuple/uminus.m new file mode 100644 index 0000000..4e31611 --- /dev/null +++ b/@tfocs_tuple/uminus.m @@ -0,0 +1,12 @@ +function x = uminus( x ) + +% UMINUS Unary minus. + +n = numel( x.value_ ); +for k = 1 : n, + x.value_{k} = -x.value_{k}; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..efa2252 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,229 @@ +Oct 10 2013, v1.3 + License changed! See LICENSE for details + New: + proj_linfl2 Projects rows onto l2 norm constraints + proj_affine.m Projects onto generic affine constraints + proj_singleAffine.m Projects onto single affine constraint + proj_boxAffine.m In response to + http://ask.cvxr.com/question/749/tfocs-projecting-on-box-affine-constraint/: + projects onto intersection of box constraint and affine constraints + proj_conic.m Projects onto the second-order (aka Lorentz) cone + Contributed by Joseph Salmon + proj_l2group.m Projections onto a partitioned l2 group constraint + Contributed by Joseph Salmon + linop_TV3D.m Total variation for 3D grids (linop_TV is for 2D); + contributed by Mahdi Hosseini (mahdi.hosseini@mail.utoronto.ca) + test_LASSO.m Tests the L1RLS (L1 regularized Least-Squares) + problem, aka LASSO + @single/* Allow TFOCS objects to have data type single + May not work well with sparse matrices (which are not allowed + to be sparse data type). Thanks to Graham Coleman. + mexFiles/* New director with mex files + Some non-core functions require mex files, and basic + installation routines and the mex files themselves are in this + directory. + solver_OrderedLASSO.m Solves the ordered LASSO problem + test_OrderedLASSO.m Test script for solver_OrderedLASSO + prox_OL1.m Proximity operator for the ordered LASSO + See http://www-stat.stanford.edu/~candes/OrderedL1/ + "Statistical Estimation and Testing via the Ordered l1 Norm" + by M. Bogdan, E. van den Berg, W. Su, and E. J. Candès, 2013 + mexFiles/proxAdaptiveL1Mex.c Mex file helper for the ordered LASSO + + Modified: + proj_maxEig.m Fixed bug with variable names + prox_l1linf.m Fixed bug with typo of repmat for some cases + prox_nuclear.m Removed dependence on ismatrix() for greater compatibility + @double/tfocs_dot.m Fixed bug for multi-dimensional arrays, and generally made + the code better. Thanks to Graham Coleman for finding the bug. @single/tfocs_dot.m + is similarly updated. + smooth_quad.m Added use_eig mode that makes an expensive + one-time calculation and all subsequent calculations are cheaper + tfocs_initialize.m Fixed bug in initialization code for rounding dimensions; added new + file private/round.m. This affected the demo for alternating completion. + Also allows data to be single data type instead of double (thanks to Graham Coleman) + proj_psdUTrace.m Allows optional constraints that matrix is real-valued. Nov 23 2012 + Can now use eigs for large-scale computation. Feb 15 2013 + Fixed bug in eigs, March 18 2013 + proj_psd.m Modified to allow eigs() version. Feb 15 2013. + prox_trace.m Allows optional constraints that specify the matrix is real-valued. Dec 9 2012 + Fixed bug with eigs parameters in largescale mode + linop_TV.m Implemented norms() function so it is now independent of CVX + Also, adjoint operation is much faster due to a data locality trick + proj_l2.m Fixed bad documentation in .m file. + Also, fixed a bug, so we now project onto the norm ball, not its boundary + Thanks to Graham Coleman, fixed a bug for multidimensional arrays + prox_l1l2.m Using bsxfun for speed improvement + tfocs_SCD.m lines 41-44 were commented out but shouldn't have been; + thanks to Mark Harfouche for noticing this on the ask.cvxr.com forums + + Modified for Octave compatibility: + private/tfocs_prox.m + prox_l1.m + prox_l1l2.m + prox_hingeDual.m + prox_hinge.m + prox_l1linf.m + prox_l1pos.m + prox_nuclear.m + prox_trace.m + proj_maxEig.m + proj_psd.m + + +Sept 7 2012, v1.2 + New: + proj_nuclear.m Projection onto nuclear norm ball. + proj_spectral.m Projection onto spectral norm ball. + prox_max.m Maximum. Dual of proj_simplex.m + proj_max.m Projection so max <= 1. Dual of prox_l1pos.m + prox_maxEig.m Maximum eigenvalue. Dual of proj_psdUTrace.m + proj_maxEig.m Projection so max eig <= 1. Dual of prox_trace.m + prox_dualize.m Computes the Legendre dual of a function. + examples/demos/ Directory with demos (featured on website) is + added + test_TraceLS.m Added. Shows how to run solver_TraceLS.m + test_psdCompletion.m Added. Shows how to run solver_psdComp.m and + solver_psdCompConstrainedTrace.m + Contents.m Added. Describes the relevant functions. + + Modified private/tfocs_prox.m, prox_l1.m, and tfocs_SCD.m to allow + mu to be a vector. Need to modify other prox functions. + Modified test_proxPair.m to test with symmetric, psd, and sparse matrices. + Modified prox_spectral.m to take advantage of symmetric matrices + Modified linop_subsample.m to work in more situations + Modified proj_psd.m to use eigs() if requested, though this is usually not + beneficial + + Fixed bug in prox_hinge.m (for y = [] case ). + Fixed bug in private/tfocs_iterate.m for stopCrit = Inf case + Thanks to Masoud Ahookhosh for finding this. + Fixed bugs in solver_psdComp.m and solver_psdCompConstrainedTrace.m + Made proj_l1.m more numerically stable; thanks to Chris Kauffman for finding + and fixing this. + Made size_compat.m allow multiple singleton dimensions in ND arrays; thanks + to Graham Coleman for finding and fixing this. + +Feb 29 2012, v1.1a + Minor bug fixes, thanks to Graham Coleman for finding them: + + private/print_cell_size.m incorrectly displayed the size of inputs (only + applied in "debug" mode) + private/size_compat.m has been fixed to work with 3D (or ND) arrays + when the final array dimension is a singleton (e.g. 20 x 30 x 1) + +Jan 25 2012, v1.1 + User guide updated. + Changes to code: + New: + linop_fft.m FFT and its transpose. Supports sub-sampling. + test_proxPair.m Tests whether f and fDual are really duals. Not yet + documented in user guide, but see the help text. + solver_sLP_box.m LP with equality and box constraints + test_sBPDN_nonnegative.m Tests BPDN using x >= 0 constraint. + image_denoising_withSPOT.m An example of image denoising + and using the SPOT toolbox. The helper file "plotNow.m" is + also new, and allows you to watch a movie in real-time of + the iterates. Also shows how to use reweighting. + prox_l1pos.m proximity operator for ||x||_1 restricted to x >= 0 + test_SVM.m demo with support vector machines and hinge-loss + test_complicatedUsage.m demo with several matrix variables and + other complicated terms + test_all.m runs all small scale examples + linop_reshape.m reshapes input (an extension of linop_vec). Thanks + to Graham Coleman for contributing. + tfocs.m "debug" option added to main tfocs routine + + Bug fixes: + solver_sSDP.m fixed bug, thanks to Brian Borchers. + prox_hingeDual.m fixed NaN bug + continuation.m fixed bug for case when there are multiple matrix + variables, and for 3D arrays (thanks to Graham Coleman). + prox_spectral.m fixed bugs + test_sTV_largescale.m fixed myAwgn() bug (thanks to Matthew Suttinger) + linop_stack.m fixed bug that occurs when domain is a set of + matrices rather than vectors; thanks to Graham Coleman for discovering. + all solvers: fixed a bug with variable "L" that arose whenever + there is a function called "L.m" in the path (for example, WaveLab has + such a function). + tfocs_LLM.m fixed bug that occurs when restart is used + tfos_initialize.m fixed bug for strong convexity case when Lexact not + specified + prox_boxDual.m fixed bug with bounds were not scalars + proj_l2.m fixed bug for q ~= 1 case + proj_0.m fixed bug for non-constant offsetsl thanks to Graham + Colemen + + Improvements: + tfocs.m allows new "debug" flag that gives more verbose + information, useful when debugging + tfocs_inizialize.m gives more useful error message when sizes are + incorrect + tfocs_iterate.m Improved performance for stopCrit = 3 + tfocs_iterate.m Now supports 'printStopCrit' to display progress of + whatever value is used to determine the stopping criteria + linop_test.m Supports conjugate-symmetric complex inputs/outputs + test_sBPDN_withContinuation.m Updated + solver_sBP.m Supports non-negativity constraints + solver_sBPDN.m Supports non-negativity constraints + tfocs_AT.m Now includes cntr_reset field, which explicitly + recalculates some quantities every so often to avoid accumulation + of roundoff error. + proj_boxDual.m --> prox_boxDual.m to have more consistent naming. + proj_l2.m handles diagonal scaling term; this feature is + experimental and uses a 1D optimization routine. It should + be efficient for N <= 2^18 at least + prox_l2.m same experimental modification as in proj_l2.m + linop_scale.m allows user to specify size explicitly, if desired + prox_hinge.m and prox_hingeDual.m allow more general form with "y" + variable + example_{LMI, LinearProgram, SDP} renamed to test_{LMI, LinearProgram, + SDP } + + +March 20 2011, v1.0c + An almost comprehensive lists of changes since December 2010: + New: + solver_TraceLS.m Solves trace-regularized least-squares problem + solver_sLP.m Linear Program solver + solver_sSDP.m Semi-Definite Program solver + solver_sLMI.m Linear Matrix Inequality solver + prox_hinge.m Proximity fcn for hinge-loss + prox_hingeDual.m Proximity fcn for dual of hinge-loss + proj_0.m Projection onto zero. Added for completeness + proj_boxDual.m Proximity fcn for dual of prox_box.m + smooth_huber.m Huber function + smooth_logLLogistic.m Log-likelihood of the logistic function + smooth_logLPoisson.m Log-likelihood of independent Poisson r.v. + + The continuation feature is now builtin to tfocs_SCD.m + + Updated and/or bug fixes: + linop_subsample.m -- can now handle matrix entry sampling + proj_l2.m -- bug fix + prox_l1.m -- allow scaling "q" to be a non-negative vector + proj_box.m -- bug fix + private/tfocs_initialize.m -- bug fixes + private/tfocs_iterate.m -- bug fixes + + smallscale/examples: the .mat files have been moved to a separate + directory + smooth_quad.m -- allows nonsmooth usage too now + solver_L1RS.m -- bug fix (typo); thanks to Ewout van den Berg + linop_test.m -- now compatible with multidemensional arrays + + New demos: + smallscale/example_LinearProgram.m linear programming + smallscale/example_SDP.m semi-definite programming + smallscale/example_LMI.m linear matrix inequality + largescale/image_denosing_withSPOT.m + + User guide: + Updated to mention the new routines + Describes scaling issues + Describes continuation + Added acknowledgements section + + Misc: + Thanks to Graham Coleman for bug-fixes related to multidimensional + arrays diff --git a/Contents.m b/Contents.m new file mode 100644 index 0000000..8b0a581 --- /dev/null +++ b/Contents.m @@ -0,0 +1,113 @@ +% TFOCS: Templates for First-Order Conic Solvers +% TFOCS v1.3 +%10-Oct-2013 +% +% Main TFOCS program +% tfocs - Minimize a convex problem using a first-order algorithm. +% tfocs_SCD - Smoothed conic dual form of TFOCS, for problems with non-trivial linear operators. +% continuation - Meta-wrapper to run TFOCS_SCD in continuation mode. +% Miscellaneous functions +% tfocs_version - Version information. +% tfocs_where - Returns the location of the TFOCS system. +% Operator calculus +% linop_adjoint - Computes the adjoint operator of a TFOCS linear operator +% linop_compose - Composes two TFOCS linear operators +% linop_scale - Scaling linear operator. +% prox_dualize - Define a proximity function by its dual +% prox_scale - Scaling a proximity/projection function. +% tfunc_scale - Scaling a function. +% tfunc_sum - Sum of functions. +% tfocs_normsq - Squared norm. +% linop_normest - Estimates the operator norm. +% Linear operators +% linop_matrix - Linear operator, assembled from a matrix. +% linop_dot - Linear operator formed from a dot product. +% linop_fft - Fast Fourier transform linear operator. +% linop_TV - 2D Total-Variation (TV) linear operator. +% linop_TV3D - 3D Total-Variation (TV) linear operator. +% linop_handles - Linear operator from user-supplied function handles. +% linop_spot - Linear operator, assembled from a SPOT operator. +% linop_reshape - Linear operator to perform reshaping of matrices. +% linop_subsample - Subsampling linear operator. +% linop_vec - Matrix to vector reshape operator +% Projection operators (proximity operators for indicator functions) +% proj_0 - Projection onto the set {0} +% proj_box - Projection onto box constraints. +% proj_l1 - Projection onto the scaled 1-norm ball. +% proj_l2 - Projection onto the scaled 2-norm ball. +% proj_linf - Projection onto the scaled infinity norm ball. +% proj_linfl2 - Projection of each row of a matrix onto the scaled 2-norm ball. +% proj_max - Projection onto the scaled set of vectors with max entry less than 1 +% proj_conic - Projection onto the second order (aka Lorentz) cone +% proj_l2group - Projection of each group of coordinates onto the 2-norm ball. +% proj_singleAffine - Projection onto a single affine equality or in-equality constraint. +% proj_boxAffine - Projection onto a single affine equality along with box constraints. +% proj_affine - Projection onto a general affine equation, e.g., solutions of linear equations. +% proj_nuclear - Projection onto the set of matrices with nuclear norm less than or equal to q. +% proj_psd - Projection onto the positive semidefinite cone. +% proj_psdUTrace - Projection onto the positive semidefinite cone with fixed trace. +% proj_Rn - "Projection" onto the entire space. +% proj_Rplus - Projection onto the nonnegative orthant. +% proj_simplex - Projection onto the simplex. +% proj_spectral - Projection onto the set of matrices with spectral norm less than or equal to q +% proj_maxEig - Projection onto the set of symmetric matrices with maximum eigenvalue less than 1 +% Proximity operators of general convex functions +% prox_0 - The zero proximity function: +% prox_boxDual - Dual function of box indicator function { l <= x <= u } +% prox_hinge - Hinge-loss function. +% prox_hingeDual - Dual function of the Hinge-loss function. +% prox_l1 - L1 norm. +% prox_Ol1 - Ordered L1 norm. +% prox_l1l2 - L1-L2 block norm: sum of L2 norms of rows. +% prox_l1linf - L1-LInf block norm: sum of L2 norms of rows. +% prox_l1pos - L1 norm, restricted to x >= 0 +% prox_l2 - L2 norm. +% prox_linf - L-infinity norm. +% prox_max - Maximum function. +% prox_nuclear - Nuclear norm. +% prox_spectral - Spectral norm, i.e. max singular value. +% prox_maxEig - Maximum eigenvalue of a symmetri matrix. +% prox_trace - Nuclear norm, for positive semidefinite matrices. Equivalent to trace. +% Smooth functions +% smooth_constant - Constant function generation. +% smooth_entropy - The entropy function -sum( x_i log(x_i) ) +% smooth_handles - Smooth function from separate f/g handles. +% smooth_huber - Huber function generation. +% smooth_linear - Linear function generation. +% smooth_logdet - The -log( det( X ) ) function. +% smooth_logLLogistic - Log-likelihood function of a logistic: sum_i( y_i mu_i - log( 1+exp(mu_i) ) ) +% smooth_logLPoisson - Log-likelihood of a Poisson: sum_i (-lambda_i + x_i * log( lambda_i) ) +% smooth_logsumexp - The function log(sum(exp(x))) +% smooth_quad - Quadratic function generation. +% Testing functions +% test_nonsmooth - Runs diagnostic tests to ensure a non-smooth function conforms to TFOCS conventions +% test_proxPair - Runs diagnostics on a pair of functions to check if they are Legendre conjugates. +% test_smooth - Runs diagnostic checks on a TFOCS smooth function object. +% linop_test - Performs an adjoint test on a linear operator. +% Premade solvers for specific problems (vector variables) +% solver_L1RLS - l1-regularized least squares problem, sometimes called the LASSO. +% solver_LASSO - Minimize residual subject to l1-norm constraints. +% solver_OrderedLASSO - LASSO using ordered l1-norm. +% solver_sBP - Basis pursuit (l1-norm with equality constraints). Uses smoothing. +% solver_sBPDN - Basis pursuit de-noising. BP with relaxed constraints. Uses smoothing. +% solver_sBPDN_W - Weighted BPDN problem. Uses smoothing. +% solver_sBPDN_WW - BPDN with two separate (weighted) l1-norm terms. Uses smoothing. +% solver_sDantzig - Dantzig selector problem. Uses smoothing. +% solver_sDantzig_W - Weighted Dantzig selector problem. Uses smoothing. +% solver_sLP - Generic linear programming in standard form. Uses smoothing. +% solver_sLP_box - Generic linear programming with box constraints. Uses smoothing. +% Premade solvers for specific problems (matrix variables) +% solver_psdComp - Matrix completion for PSD matrices. +% solver_psdCompConstrainedTrace - Matrix completion with constrained trace, for PSD matrices. +% solver_TraceLS - Unconstrained form of trace-regularized least-squares problem. +% solver_sNuclearBP - Nuclear norm basis pursuit problem (i.e. matrix completion). Uses smoothing. +% solver_sNuclearBPDN - Nuclear norm basis pursuit problem with relaxed constraints. Uses smoothing. +% solver_sSDP - Generic semi-definite programs (SDP). Uses smoothing. +% solver_sLMI - Generic linear matrix inequality problems (LMI is the dual of a SDP). Uses smoothing. +% Algorithm variants +% tfocs_AT - Auslender and Teboulle's accelerated method. +% tfocs_GRA - Gradient descent. +% tfocs_LLM - Lan, Lu and Monteiro's accelerated method. +% tfocs_N07 - Nesterov's 2007 accelerated method. +% tfocs_N83 - Nesterov's 1983 accelerated method; also by Beck and Teboulle 2005 (FISTA). +% tfocs_TS - Tseng's modification of Nesterov's 2007 method. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c708006 --- /dev/null +++ b/LICENSE @@ -0,0 +1,34 @@ +Copyright (c) 2013, California Institute of Technology and CVX Research, Inc. +All rights reserved. + +Contributors: Stephen Becker, Emmanuel Candes, and Michael Grant. +Based partially upon research performed under the DARPA/MTO Analog-to- +Information Receiver Development Program, AFRL contract #FA8650-08-C-7853. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the names of California Institute of Technology + (Caltech), CVX Research, Inc., nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..8183d0f --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +Templates for First-Order Conic Solvers (TFOCS) +Version 1.3, October 10, 2013 +by Stephen Becker, Emmanuel Candes, and Michael Grant. +Copyright 2011-2013, California Institute of Technology and CVX Research. + +Please see the file tfocs_userguide.pdf for instructions on how to use TFOCS +and how to submit a support request. + +As of October 2, 2013, TFOCS is now subject to a 3-Clause BSD license. Please +see the file LICENSE for the full text of the license. + diff --git a/continuation.m b/continuation.m new file mode 100644 index 0000000..8e32292 --- /dev/null +++ b/continuation.m @@ -0,0 +1,261 @@ +function [ x, odata, optsOut ] = continuation( fcn, mu, x0, z0, opts, contOpts ) +% CONTINUATION Meta-wrapper to run TFOCS_SCD in continuation mode. +% [...] = CONTINUATION( FCN, MU, X0, Z0, OPTS, CONT_OPTS ) +% is a wrapper to perform continuation on FCN, where +% FCN is a function that calls tfocs_SCD or a tfocs solver. +% FCN must accept input arguments MU, X0, Z0, and OPTS, +% e.g. FCN = @(mu,x0,z0,opts) solver_sBP( A, b, mu, x0, z0, opts ) +% +% CONT_OPTS are options that affect how continuation is performed. +% To see the default options, call this script with no inputs. +% +% Options for CONT_OPTS: +% maxIts - max # of continuation iterations +% accel - use accelerated continuation or not +% betaTol - every continuation iteration, the tolerance +% is decreased by 'betaTol' +% innerTol - every continuation iteration, except the last one, +% is solved to this tolerance. +% This option overrides 'betaTol' +% tol - the outer loop will stop when either +% 'maxIts' is reached, or when the change +% from one step to the next is less than 'tol'. +% If innerTol is not specified, then "tol" and "betaTol" +% are used to determine the tolerance of intermediate solves. +% initialTol - For use with "tol". This overrides betaTol, but in turn is +% overridden by innerTol. If specified, then the first +% problem is solved to tolerance "initialTol" and the final +% problem is solved to tolerance "tol", and all problems +% in between are solved to logarithmically interpolated values. +% E.g. if maxits = 3 and initialTol = .1 and tol = .001, +% then the tolerances are .1, .01, .001. +% finalTol - For use with "tol". If "finalTol" is set, then "tol" +% and "betaTol" are used to determine the tolerance of +% intermediate solves, but the last solve is solved +% to the "finalTol" tolerance. +% innerMaxIts - maximum number of inner iterations during +% every continuation iteration except the last one. +% muDecrement - after every iteration, mu <-- mu*muDecrement +% (default: muDecrement = 1, so mu is constant) +% verbose - true (default) prints out information, false will +% not print out any information +% +% As of release 1.1, you do not need to call this file directly. Instead, +% you can specify the continuation option in TFOCS_SCD.m directly. +% See also TFOCS_SCD.m + +if nargin < 6, contOpts = []; end +kMax = setOpts('maxIts',3); +ACCEL = setOpts('accel',true); +betaTol = setOpts('betaTol',2); +innerTol= setOpts('innerTol',[] ); +stopTol = setOpts('tol',1e-3); +finalTol= setOpts('finalTol',[]); +verbose = setOpts('verbose',true); +innerMaxIts = setOpts('innerMaxIts',[] ); +initialTol = setOpts('initialTol',[]); +muDecrement = setOpts('muDecrement',1); +finalRestart = setOpts('finalRestart',[]); % not yet documented. usefulness? + +if nargin == 0 + if nargout == 0 + disp('Default options for CONTINUATION are:'); + end + x = contOpts; + return; +end + +error(nargchk(2,6,nargin)); +if nargin < 3, x0 = []; end +if nargin < 4, z0 = []; end +if nargin < 5, opts = []; end + +% what is the user specified stopping tolerance? +if ~isfield(opts,'tol') + % get the default value: + defaultOpts = tfocs_SCD(); + opts.tol = defaultOpts.tol; +end +if isfield(opts,'fid'), fid = opts.fid; else fid = 1; end + + +finalTol2 = opts.tol; +if isempty( innerTol ) && ~isempty(initialTol) + % calculate the value of betaTol that will + betaTol = exp( log(initialTol/finalTol2)/(kMax-1) ); +end +% tol = finalTol2*betaTol^(kMax+1); % bug fixed Feb 2011 +tol = finalTol2*betaTol^(kMax); + +xOld = x0; +odataCumulative = []; +odataCumulative.niter= 0; +extraVerbose = verbose && (isfield(opts,'printEvery') && (opts.printEvery <= 0 ... + || opts.printEvery == Inf ) ); +for k = 1:kMax + if kMax > 1 && verbose +% fprintf(fid,'--- Continuation step %d of %d ', k, kMax ); + fprintf(fid,'--- Continuation step %d of %d, mu: %.1e', k, kMax, mu ); + if extraVerbose + fprintf(fid,' ...'); + else + fprintf('\n'); + end + end + + optsTemp = opts; + if ~isempty( innerMaxIts ) && k < kMax + optsTemp.maxIts = innerMaxIts; + end + tol = tol/betaTol; + if ~isempty( innerTol ) && k < kMax + optsTemp.tol = innerTol; + else + if ~isempty(finalTol) && k == kMax + optsTemp.tol = finalTol; + else + optsTemp.tol = tol; + end + end + if k == kMax && ~isempty( finalRestart ) + optsTemp.restart = finalRestart; + end + + % call the solver + [x, odata, optsOut ] = fcn( mu, x0, z0, optsTemp ); + + if iscell(x) + % Thanks to Graham Coleman for catching this case + if isempty(x0) + mu2normXX = mu/2*sum(cellfun( @(x) norm(x(:))^2, x )); + elseif iscell(x0) + mu2normXX = mu/2*sum(cellfun( @(x) norm(x(:))^2, ... + cellfun( @minus, x, x0,'uniformoutput',false ) )); + else + mu2normXX = mu/2*sum(cellfun( @(x) norm(x(:))^2, ... + cellfun( @(x) x-x0, x, 'uniformoutput',false ) )); + end + else + if isempty(x0) + mu2normXX = mu/2*norm(x(:))^2; + else + mu2normXX = mu/2*norm(x(:)-x0(:))^2; + end + end + + % (possibly) decrease mu for next solve + mu = mu*muDecrement; + + % update output data + fields = { 'f', 'normGrad', 'stepsize','theta','counts','err' }; + for ff = fields + f = ff{1}; + if isfield(odata,f) + if isfield(odataCumulative,f) + odata.(f) = [odataCumulative.(f); odata.(f)]; + end + odataCumulative.(f) = odata.(f); + end + + end + if isfield(odata,'niter') + % include the old iterations in the count + odataCumulative.niter = odata.niter + odataCumulative.niter; + odata.niter = odataCumulative.niter; + + % record at which iterations continuation happened + if isfield(odataCumulative,'contLocations') + odataCumulative.contLocations = [odataCumulative.contLocations; odata.niter]; + odata.contLocations = odataCumulative.contLocations; + else + odata.contLocations = odata.niter; + odataCumulative.contLocations = odata.niter; + end + end + + if kMax > 1 && extraVerbose + fprintf(fid,'\b\b\b\b'); % remove the " ..." from earlier + end + % if the tfocs solver is set to be quiet, but the continuation + % script still has the "verbose" flag, then print out + % some information + if extraVerbose && isfield( odata, 'err' ) && ~isempty( odata.err ) + fprintf(fid,', errors: '); + fprintf(fid,' %8.2e', odata.err(end,:) ); + end + if verbose + if extraVerbose + fprintf(fid,', mu/2||x-x_0||^2: %.1e', mu2normXX ); + else + fprintf(fid,'Continuation statistics: mu/2||x-x_0||^2: %.1e', mu2normXX ); + end + + if kMax > 1 && extraVerbose + fprintf(fid,' ---'); + end + fprintf(fid,'\n'); + end + + + if k == kMax, break; end + + % Update the prox center + + if ACCEL + if iscell(x) + if isempty(xOld) || ~iscell(xOld) + xOld = cell( size(x) ); + end + x0 = cell( size(x) ); + for nCell = 1:length(x) + if isempty(xOld{nCell}), xOld{nCell} = zeros( size(x{nCell}) ); end + x0{nCell} = x{nCell} + (k-1)/(k+2)*( x{nCell} - xOld{nCell} ); + end + + else + if isempty(xOld), xOld = zeros(size(x)); end + x0 = x + (k-1)/(k+2)*( x - xOld ); + end + else + x0 = x; + end + + if isa( odata.dual, 'tfocs_tuple') + z0 = cell( odata.dual ); + else + z0 = odata.dual; + end + + + if iscell(x) + n1=sqrt(sum(cellfun( @(x) norm(x(:))^2, cellfun( @minus, x, xOld,'uniformoutput',false ) ))); + n2=sqrt(sum(cellfun( @(x) norm(x(:))^2, xOld ))); + else + n1 = norm(x(:)-xOld(:)); + n2 = norm(xOld(:)); + end + + if n1/n2 <= stopTol && verbose + fprintf(fid,'Continuation ending, due to convergence\n'); + break; + end + xOld = x; + if k == kMax && verbose + fprintf(fid,'Continuation ending, due to reaching maximum number of outer iterations\n'); + end +end + + +function out = setOpts(fieldName,default) + if ~isfield(contOpts,fieldName) + contOpts.(fieldName) = default; + end + out = contOpts.(fieldName); +end + + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/demos/SIAM_demo.m b/examples/demos/SIAM_demo.m new file mode 100644 index 0000000..aa2efe7 --- /dev/null +++ b/examples/demos/SIAM_demo.m @@ -0,0 +1,195 @@ +%% RPCA demo using TFOCS +% This is a demo of Robust PCA (RPCA) using . Since 2009, +% there has been much interest in this specific RPCA formulation +% (RPCA can refer to many different formulations; we will state +% our formulation precisely below), but most solvers +% assume equality constraints. One great feature of TFOCS +% is how easy it is to prototype new formulations, so we show +% here a formulation using inequality constraints. +% +% The basic (equality constraint) RPCA formulation is: +% ( see +% for references ) +% +% $$ \min_{S,L} \|L\|_* + \lambda \|S\|_{\ell_1} \quad\textrm{subject to}\;L+S = X $$ +% +% The idea is that some object $X$ can be broken into two components $X=L+S$, +% where $L$ is low-rank and $S$ is sparse. To tease out this separation, +% RPCA penalizes $\|L\|_*$ (the nuclear norm of $L$), which is a proxy for +% the rank of $L$. We also penalizes $\|S\|_{\ell_1}$ which is just the sum +% of the absolute value of all the entries of the matrix $S$; this is a proxy +% for the number of non-zero entries of $S$. +% +% The parameter $\lambda$ is important, as it controls how much weight +% to put on $S$ relative to $L$. This parameter is not overly sensitive, +% and we picked the value used in this demo by a few quick rounds of trial-and-error. +% +% _Code by Stephen Becker, May 2011_ + +%% Inequality constraints +% In this demo, $X$ is a video. In particular, each column of $X$ is the set +% of $m n$ pixels from a $m \times n$ pixel frame of the video, reshaped +% so that it is now a vector instead of a matrix. +% +% The particular video we use is not compressed using a video codec, so +% each frame of the video is stored. This leads to huge files, so to save +% a bit of space, the value of each pixel is stored as an 8 bit number, +% which means we can think of it as a value from $0,1,2,\ldots,255$. Now, +% we may imagine that there is some "true" video where each pixel +% is a _real_ number between $[0,255]$, and that we see a quantized version of it. +% It would be nice to apply RPCA to this true version instead of applying +% it to the quantized version. In particular, it is unlikely that the quantized +% version can be split nicely into a low-rank and sparse component. +% To account for this quantization, we will ask that $S+L$ is not exactly equal +% to $X$, but rather that $S+L$ agrees with $X$ up to the precision +% of the quantization. The quantization can induce at most an error of .5 in +% the pixel value (e.g. true value is 5.6, which is rounded to 6.0, so an error of .4; +% or a true value of 6.4, which is also rounded to 6.0, so also an error of 0.4). +% A nice way to capture this is via the $\ell_\infty$ norm. +% +% So, the inequality constrained version of RPCA uses the same objective +% function, but instead of the constraints $L+S=X$, the constraints are +% +% $$ \|L+S-X\|_{\ell_\infty} \le 0.5. $$ + +%% Numerical demo +% We demonstrate this on a video clip taken from a surveillance +% camera in a subway station. Not only is there a background +% and a foreground (i.e. people), but there is an escalator +% with periodic motion. Conventional background subtraction +% algorithms would have difficulty with the escalator, but +% this RPCA formulation easily captures it as part of the low-rank +% structure. +% The clip is taken from the data at this website (after +% a bit of processing to convert it to grayscale): +% http://perception.i2r.a-star.edu.sg/bk_model/bk_index.html + +% Load the data: +disp('Please download the escalator_data.m (3.7 MB) file from:') +disp(' http://tfocs.stanford.edu/demos/rpca/escalator_data.mat'); +load escalator_data % contains X (data), m and n (height and width) +X = double(X); + +% addpath ~/Dropbox/TFOCS/ % add TFOCS to your path, so change this to suit your computer + + +%% +nFrames = size(X,2); + +lambda = 1e-2; + +opts = []; +opts.stopCrit = 4; +opts.printEvery = 1; +opts.tol = 1e-4; + +opts.maxIts = 25; + +opts.errFcn{1} = @(f,d,p) norm(p{1}+p{2}-X,'fro')/norm(X,'fro'); + +largescale = false; + +for inequality_constraints = 0:1 + + + if inequality_constraints + % if we already have equality constraint solution, + % it would make sense to "warm-start": +% x0 = { LL_0, SS_0 }; + % but it's more fair to start all over: + x0 = { X, zeros(size(X)) }; + z0 = []; + else + x0 = { X, zeros(size(X)) }; + z0 = []; + end + + + obj = { prox_nuclear(1,largescale), prox_l1(lambda) }; + affine = { 1, 1, -X }; + + mu = 1e-4; + if inequality_constraints + epsilon = 0.5; + dualProx = prox_l1(epsilon); + else + dualProx = proj_Rn; + end + + tic + % call the TFOCS solver: + [x,out,optsOut] = tfocs_SCD( obj, affine, dualProx, mu, x0, z0, opts); + toc + + % save the variables + LL =x{1}; + SS =x{2}; + if ~inequality_constraints + z0 = out.dual; + LL_0 = LL; + SS_0 = SS; + end + +end % end loop over "inequality_constriants" variable + +%% show all together in movie format +% If you run this in your own computer, you can see the movie. On the webpage, +% we have a youtube version of the video. +% The top row is using equality constraints, and the bottom row +% is using inequality constraints. +% The first column of both rows is the same (i.e. it is the original image). +mat = @(x) reshape( x, m, n ); +figure(); +colormap( 'Gray' ); +k = 1; +for k = 1:nFrames + + imagesc( [mat(X(:,k)), mat(LL_0(:,k)), mat(SS_0(:,k)); ... + mat(X(:,k)), mat(LL(:,k)), mat(SS(:,k)) ] ); + + axis off + axis image + + drawnow; + pause(.05); + + if k == round(nFrames/2) + snapnow; % Take a single still snapshot for publishing the m file to html format + end +end + +%% Is there a difference between the two versions? +% Compare the equality constrained version and the inequality +% constrained version. To do this, we can check whether +% the "L" components are really low rank +% and whether the "S" components are really sparse. +% Even if the two versions appear visually similar, the variables +% may behave quite differently with respect to low-rankness +% and sparsity. + +fprintf('"S" from equality constrained version has \t%.1f%% nonzero entries\n',... + 100*nnz(SS_0)/numel(SS_0) ); +fprintf('"S" from inequality constrained version has \t%.1f%% nonzero entries\n',... + 100*nnz(SS)/numel(SS) ); + +s = svd(LL); % inequality constraints +s0 = svd(LL_0); % equality constraints + +fprintf('"L" from equality constrained version has numerical rank\t %d (of %d possible)\n',... + sum( s0>1e-6), min( m*n, nFrames) ); +fprintf('"L" from inequality constrained version has numerical rank\t %d (of %d possible)\n',... + sum( s > 1e-6), min( m*n, nFrames) ); + +figure(); +semilogy( s0 ,'o-') +hold all; +semilogy( s ,'o-') +legend('Equality constraints','Inequality constraints'); +xlabel('sorted singular value location'); +ylabel('value of singular value'); +title('Comparison of RPCA using equality and inequality constraints'); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/examples/demos/callandmap.m b/examples/demos/callandmap.m new file mode 100644 index 0000000..92b5f3a --- /dev/null +++ b/examples/demos/callandmap.m @@ -0,0 +1,15 @@ +function varargout = callandmap(fcn, ix, varargin) +%CALLANDMAP Call a function and rearrange its output arguments +% varargout = callandmap( fcn, ix, varargin ) +% +% Suggested here: +% http://stackoverflow.com/questions/3673392/define-anonymous-function-as-2-of-4-outputs-of-m-file-function + +tmp = cell(1,max(ix)); % Capture up to the last argout used +[tmp{:}] = fcn(varargin{:}); % Call the original function +varargout = tmp(ix); % Remap the outputs + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/examples/demos/demo_MatrixCompletion.m b/examples/demos/demo_MatrixCompletion.m new file mode 100644 index 0000000..6539035 --- /dev/null +++ b/examples/demos/demo_MatrixCompletion.m @@ -0,0 +1,69 @@ +%% Matrix completion demo +% This short demo shows how to use TFOCS to perform nuclear norm minimization. +% Nuclear norm minimization is used for recovering all the entries of +% a partially observed low-rank matrix. + +%% Setup a problem +rng(234923); % for reproducible results +N = 16; % the matrix is N x N +r = 2; % the rank of the matrix +df = 2*N*r - r^2; % degrees of freedom of a N x N rank r matrix +nSamples = 3*df; % number of observed entries + +% For this demo, we will use a matrix with integer entries +% because it will make displaying the matrix easier. +iMax = 5; +X = randi(iMax,N,r)*randi(iMax,r,N); % Our target matrix + +%% +% Now suppose we only see a few entries of X. Let "Omega" be the set +% of observed entries +rPerm = randperm(N^2); % use "randsample" if you have the stats toolbox +omega = sort( rPerm(1:nSamples) ); + +%% +% Print out the observed matrix in a nice format. +% The "NaN" entries represent unobserved values. The goal +% of this demo is to find out what those values are! + +Y = nan(N); +Y(omega) = X(omega); +disp('The "NaN" entries represent unobserved values'); +disp(Y) + +%% Matrix completion via TFOCS +% We use nuclear norm relaxation. There are strong theorems that +% show this relaxation will usually give you the *exact* original low-rank +% matrix provided that certain conditions hold. However, these +% conditions are generally not possible to check 'a priori', +% so I cannot guarantee that this will work. But by choosing +% enough measurements, it becomes increasingly likely to work. + +% Add TFOCS to your path (modify this line appropriately): +addpath ~/Dropbox/TFOCS/ + +observations = X(omega); % the observed entries +mu = .001; % smoothing parameter + +% The solver runs in seconds +tic +Xk = solver_sNuclearBP( {N,N,omega}, observations, mu ); +toc + +%% +% To display the recovered matrix, let's round it to the nearest +% .0001 so that it displays nicely: +disp('Recovered matrix (rounding to nearest .0001):') +disp( round(Xk*10000)/10000 ) + +% and for reference, here is the original matrix: +disp('Original matrix:') +disp( X ) + +% The relative error (without the rounding) is quite low: +fprintf('Relative error, no rounding: %.8f%%\n', norm(X-Xk,'fro')/norm(X,'fro')*100 ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/examples/demos/demo_SVM.m b/examples/demos/demo_SVM.m new file mode 100644 index 0000000..65bfda9 --- /dev/null +++ b/examples/demos/demo_SVM.m @@ -0,0 +1,147 @@ +%% Support Vector Machine (SVM) example +% +% We have binary data, and the two classes are labeled +1 and -1. +% The data is d-dimensional, and we have n samples +% +% +% This example show show to solve the standard SVM using the hinge-loss +% and l2 penalty. Then it shows how it is easy with TFOCS to generalize +% the SVM problem and use the l1 penalty instead of the l2 penalty, +% which has the effect of encouraging sparsity. +% +% For a more detailed example, see the /examples/largescale/demo_SVM.m +% code in the TFOCS release. + +% Before running this, please add the TFOCS base directory to your path +addpath ~/Dropbox/TFOCS/ + +%% Generate a new problem + +randn('state',23432); +rand('state',3454); + +n = 30; +d = 2; % 2D data is nice because it's easy to visualize + +n1 = round(.5*n); % number of -1 (this is data from one "class") +n2 = n - n1; % number of +1 (this is data from the other "class") + +% Generate data +mean1 = [-.5,1]; +% mean2 = [.6,.2]; % can be separated +mean2 = [.1,.5]; % cannot be separated, so a bit more interesting +s = .25; % standard deviation +x1 = repmat(mean1,n1,1) + s*randn(n1,2); +x2 = repmat(mean2,n2,1) + s*randn(n2,2); + +X = [x1; x2]; +labels = [ ones(n1,1); -ones(n2,1) ]; + +% Plot the data: +figure(1); clf; +hh = {}; +hh{1}=plot(x1(:,1), x1(:,2), 'o' ); +hold all +hh{2}=plot(x2(:,1), x2(:,2), '*' ); +legend('Class 1','Class 2'); +xl = get(gca,'xlim'); +yl = get(gca,'ylim'); +legend([hh{1:2}],'Class 1','Class2'); +%% Introduce the SVM problem formulations +% The hinge-loss can be defined in Matlab as: +hinge = @(x) sum(max(0,1-x)); +%% +% To see what it looks like: +grid = linspace(-2,2,30); +figure(2); +plot( grid, max(0,1-grid) ) +ylim( [-.5,3] ); set(gcf,'Position',[100,100,350,250] ); +%% +% Then the standard SVM in primal form is the following: +% +% minimize_{m,b} hinge( labels.*( X*m - b ) ) + lambda_A*norm(m,2) +% +% where X is the data matrix (dimensions n x d ), m is the slope (dimension d x 1 ), +% and b is an offset (dimension 1 for our example). +% +% To put this in a more amenable format, introduce the variable "a" +% and let a = [m;b], so "a" has dimensions (d+1) x 1. +% Then we can express "X*m-b" as "[X,-ones(n,1)]*a". +% For this reason, we introduce the following linear operator: +linearF = diag(labels)*[ X, -ones(n,1) ]; + +%% +% So now, the problem is: +% +% minimize_{a} hinge( linearF*a ) + lambda_A*norm( [ones(d,1); 0] * a ) +% +% (in the "norm" term, we want a 0 term in front of a(d+1) since we +% do not wish to penalize the norm of the constant offset). +% +% A reasonable value of lambda_A is: +lambda_A = 10; + +%% Introduce the sparse SVM formulation +% The sparse SVM is designed to induce sparsity in the slope variable +% m by replacing the lambda_A*norm(m,2) term with a lambda_B*norm(m,1) +% term, since the l1 term drives small coefficients to zero. +% Here's a reasomable value for lambda_B: +lambda_B = 4; + +%% Solve SVM +mu = 1e-2; % smoothing parameter +%% +% Dualize the hinge loss and the l2 terms: +prox = { prox_hingeDual(1,1,-1), proj_l2(lambda_A) }; +%% +% Make the affine operator: +offset1 = []; +linearF2 = diag( [ones(d,1);0] ); +offset2 = []; +affineF = { linearF, offset1; linearF2, offset2 }; +ak = tfocs_SCD([],affineF, prox, mu); + +%% +% Plot +m = ak(1:2); % slope +b = ak(3); % intercept +grid = linspace(-.5,1,100); +figure(1); +hh{3} = plot( grid, (b-m(1)*grid)/m(2) ); +xlim(xl); ylim(yl); +legend([hh{1:3}],'Class 1','Class2','SVM'); + +%% Solve Sparse SVM +PROBLEM = 2; +mu = 1e-2; % smoothing parameter +opts = []; +opts.tol = 1e-3; +%% +% Dualize the hinge loss to get 'prox_hingeDual' +% For the hingeDual, scale by -1 since we want polar cone, not dual cone +% Dualize the l1 term to get the linf term +prox = { prox_hingeDual(1,1,-1), proj_linf(lambda_B) }; +linearF2 = diag( [ones(d,1);0] ); +ak = tfocs_SCD([],{linearF,[];linearF2,[]}, prox, mu,[],[],opts); + +%% +% Plot +m = ak(1:2); % slope +b = ak(3); % intercept +grid = linspace(-.5,1,100); +hh{4} = plot( grid, (b-m(1)*grid)/m(2) ); +xlim(xl); ylim(yl); +legend([hh{:}],'Class 1','Class2','SVM','sparse SVM'); + +%% +% You can see that the sparse SVM hyperplane is vertical! This is because +% only the x-component is zero, and only the y-component is non-zero. +% So it is "sparse" (though in this 2D example, "sparsity" doesn't mean +% much). For larger dimensional data, the idea is that identifying +% a hyperplane that only has a few non-zeros will allow you to identify +% which dimensions are actually important. + +%% +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/demos/demo_alternatingProjections.m b/examples/demos/demo_alternatingProjections.m new file mode 100644 index 0000000..5af800d --- /dev/null +++ b/examples/demos/demo_alternatingProjections.m @@ -0,0 +1,402 @@ +%% Projection onto the intersection of two sets +% Our goal is to find a point in the intersection of two convex sets C1 and C2. +% A similar goal is to find the projection of an arbitrary point onto the +% intersection (or if the intersection is empty, find the point(s) in C1 +% that are as close as possible to C2). In this demo, the intersection +% of C1 and C2 is a singleton, so the concepts are the same. +% +% We assume that we know how to project on the sets C1 and C2 individually, +% but not onto C1 intersect C2. +% +% The basic algorithm to solve this problem is the alternating projection method, +% first studied by John von Neumann for the case where C1 and C2 are affine +% spaces. This algorithm can be extended to arbitrary convex sets, although +% you may not converge to the *projection* of the original point. +% +% This algorithm is very simple: starting at some point y, and with x <-- y, +% we update +% x <-- Proj_{C1}( Proj_{C2}( x ) ) +% where Proj_{C1} is the projector onto the set C1. +% +% Better methods (which actually give you the projection of y onto the intersection) +% are based on Dykstra's algorithm (not to be confused with the Dijkstra algorithm +% described here: http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm ). +% +% Dykstra's algorithm also uses only Proj_{C1} and Proj_{C2}, but with a few +% intermediate steps. For an overview, see the 2011 book by Raydan: +% http://www.amazon.com/Alternating-Projection-Methods-Fundamentals-Algorithms/dp/1611971934 +% +% Another overview of both alternating projection and Dykstra are in the book chapter +% "Proximal splitting methods in signal processing" by P. L. Combettes and J.-C. Pesquet, +% in the book 'Fixed-Point Algorithms for Inverse Problems in Science and Engineering', +% (ed.: H. H. Bauschke, R. S. Burachik, P. L. Combettes, V. Elser, D. R. Luke, and +% H. Wolkowicz, Editors), pp. 185-212. Springer, New York, 2011. +% The chapter can be downloaded at http://www.ann.jussieu.fr/~plc/prox.pdf +% +% The purpose of this demo is to show the above two methods, and also +% how to formulate this with TFOCS. The advantage of solving this with TFOCS +% is that we can use an accelerated solver (Nesterov-style) and use large +% step-sizes and line search (whereas Dykstra has no step-size parameter). +% In the first example, it is especially apparent that TFOCS avoids +% the slow convergence that both the alternating projection and Dykstra +% algorithms suffer from. In the second example, the performance +% of alternating projections and TFOCS are quite similar. + +%% Mathematical formulation +% For a given point y, and two convex sets C1 and C2, we wish to find +% the closest point x to y, such that x is in both C1 and C2. We write this +% as: +% +% $$ \textrm{minimize}_{x\in C_1 \cap C_2} \,\, \mu/2||x\textrm{--}y||_2^2 $$ +% +% The parameter $\mu > 0$ is arbitrary and does not affect the answer. +% +% This fits naturally into the TFOCS formulation since the primal +% problem is already strongly convex. + + + +%% Demo with two polygon sets in 2D that intersect at (0,0) +% Set the dimension to 2 +N = 2; % 2D is best for visualization + +%% +% Define some 2D polygons + +mx = 1; +x1a = [1;.1]; +x1b = [1;.2]; +xx1 = [0,x1a(1),2*mx,x1b(1)]; +yy1 = [0,x1a(2),2*mx,x1b(2)]; + + +x2a = [1;.3]; +x2b = [1;.35]; +xx2 = [0,x2a(1),2*mx,x2b(1)]; +yy2 = [0,x2a(2),2*mx,x2b(2)]; + +%% +% The solution is at x = (0,0), so our error function is simple + +err = @(x) norm(x); +%% +% plot the regions + +figure +red = [255,153,153]/255; +blue = [204,204,255]/255; +fill( xx1, yy1,red); +hold on +fill( xx2, yy2,blue); + +xlim( [0,mx] ); ylim( [0,.5*mx] ); +axis equal +text(.5,.07,'set C1'); +text(.5,.22,'set C2'); + +%% +% Make some operators that will be used with TFOCS + +addpath ~/Dropbox/TFOCS/ % modify this to wherever it is installed on your computer +op1 = project2DCone(x1a, x1b ); +op2 = project2DCone(x2a, x2b ); + +offset1 = 0; +offset2 = 0; +op1_d = prox_dualize(op1); +op2_d = prox_dualize(op2); + +% and some simpler operators that we will use with alternating projections +% and with Dykstra +proj1 = @(x) callandmap( op1, 2, x, 1); % gamma is irrelevant +proj2 = @(x) callandmap( op2, 2, x, 1 ); % gamma is irrelevant + +% for all methods, we need to specify a starting point + +x0 = [1; 1/4]; + + +%% solve in TFOCS +% We can pick any mu and should get the same result, although due to +% some scaling issues in stopping criteria, it does have a small effect. +mu = 1e-6; + +opts = struct('debug',false,'printEvery',20,'maxIts',200); +opts.errFcn{1} = @(f,d,x) err(x); +opts.errFcn{2} = @(f,d,x) recordPoints(x); +recordPoints(); % zero-out counter. We use this to plot the points later + +opts.tol = 1e-15; + +affineOperator = {eye(N),offset1;eye(N),offset2}; +dualProx = {op1_d,op2_d}; +% Solve in TFOCS: +[x,out,optsOut] = tfocs_SCD( [], affineOperator, dualProx , mu, x0, [], opts ); + +% Record the path: +path = recordPoints(); +path = [x0,path]; + +figure +subplot(1,2,1); +fill( xx1, yy1,red); +hold on +fill( xx2, yy2,blue); +xlim( [0,mx] ); ylim( [0,.5*mx] ); +text(.5,.07,'set C1'); +text(.5,.22,'set C2'); +plot( path(1,:), path(2,:),'ko-','linewidth',2 ) +title('Path for TFOCS solving the intersection of 2 polygons'); + +subplot(1,2,2); +semilogy( out.err(:,1) ,'o-'); xlabel('iterations'); ylabel('error'); +title('Error for TFOCS method'); +set(gcf, 'Position', [400 200 800 400]); +%% solve via alternating projection method +maxIts = 500; +x = x0; +path = [x0]; +errHist = []; +for k = 1:maxIts + % basic alternating projection method: + x = proj1(x); + path= [path, x]; + x = proj2(x); + path= [path, x]; + errHist = [ errHist; err(x) ]; + if ~mod(k,50) + fprintf('Iter %4d, error is %.2e\n', k, errHist(end) ); + end +end +figure +subplot(1,2,1); +fill( xx1, yy1,red); +hold on +fill( xx2, yy2,blue); +xlim( [0,mx] ); ylim( [0,.5*mx] ); +text(.5,.07,'set C1'); +text(.5,.22,'set C2'); +plot( path(1,:), path(2,:),'ko-','linewidth',1 ) +title('Path for alternating projection method solving the intersection of 2 polygons'); + +subplot(1,2,2); +semilogy( errHist, 'o-' ); xlabel('iterations'); ylabel('error'); +errHist_1 = errHist; +title('Error for alternating projection method'); +set(gcf, 'Position', [400 200 800 400]); +%% solve via Dykstra's algorithm + +maxIts = 500; +x = x0; +[p,q] = deal( 0*x0 ); +p = -.25*[1;1]; +path = [x0]; +errHist = []; +for k = 1:maxIts + % If x + p is feasible, the y = (x+p), so p=x+p-y = 0. + y = proj1( x + p ); + p = x + p - y; + x = proj2( y + q ); + q = y + q - x; + path = [path,y,x]; + errHist = [ errHist; err(x) ]; + if ~mod(k,50) + fprintf('Iter %4d, error is %.2e\n', k, errHist(end) ); + end +end +figure +subplot(1,2,1); +fill( xx1, yy1,red); +hold on +fill( xx2, yy2,blue); +xlim( [0,mx] ); ylim( [0,.5*mx] ); +text(.5,.07,'set C1'); +text(.5,.22,'set C2'); +plot( path(1,:), path(2,:),'ko-' ) +title('Path for Dykstra''s algo solving the intersection of 2 polygons'); + +subplot(1,2,2); +semilogy( errHist, 'o-' ); xlabel('iterations'); ylabel('error'); +title('Error for Dykstra''s method'); +set(gcf, 'Position', [400 200 800 400]); + +%% +% With Dykstra's algo (and alternating projections), we get +% very slow convergence, because of the very low angle between the +% two sets. Here is a zoom in on the graph of the iterates: +figure +fill( xx1, yy1,red); +hold on +fill( xx2, yy2,blue); +xlim( [0,mx] ); ylim( [0,.5*mx] ); +text(.5,.07,'set C1'); +text(.5,.22,'set C2'); +plot( path(1,:), path(2,:),'ko-' ) +title('Path for Dykstra''s algo solving the intersection of 2 polygons (zoom)'); +xlim( [.25,.4] ); +ylim( [.058,.1]) +%% Plot all the errors together +figure +semilogy( out.err(:,1),'-' ,'linewidth',3); +hold all +semilogy( errHist_1,'-','linewidth',3); +semilogy( errHist,'--','linewidth',3); +legend('TFOCS','Alternating projection','Dykstra'); +xlabel('iteration'); ylabel('error'); +%% Demo with two circles that intersect at (0,0) +center1 = [0;.5]; +radius1 = .5; +center2 = -center1; +radius2 = radius1; + +figure +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +axis equal +hold on +text(0,.5,'set C1'); +text(0,-.5,'set C2'); + + +offset1 = center1; +offset2 = center2; + +op1_d = prox_l2(radius1); +op2_d = prox_l2(radius2); +x0 = [1; .2]; + +% and some simpler operators that we will use with alternating projections +% and with Dykstra +proj1 = @(x) radius1*(x-center1)/norm(x-center1) + center1; +proj2 = @(x) radius2*(x-center2)/norm(x-center2) + center2; +%% solve in TFOCS +opts.maxIts = 300; +affineOperator = {eye(N),offset1;eye(N),offset2}; +dualProx = {op1_d,op2_d}; +% Solve in TFOCS: +[x,out,optsOut] = tfocs_SCD( [], affineOperator, dualProx , mu, x0, [], opts ); + +% Record the path: +path = recordPoints(); +path = [x0,path]; +% Plot: +figure +subplot(1,3,1); +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +hold on +text(0,.5,'set C1'); +text(0,-.5,'set C2'); +plot( path(1,:), path(2,:),'ko-','linewidth',2 ) +title('Path for TFOCS solving the intersection of 2 circles'); + +subplot(1,3,2); +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +hold on +plot( path(1,:), path(2,:),'ko-','linewidth',2 ) +xlim([0,.15]); +ylim([-.1,.1]); +title('(zoom)'); + +subplot(1,3,3); +semilogy( out.err,'o-') +title('Error of iterates for TFOCS method'); +set(gcf, 'Position', [400 200 800 400]); +%% solve via alternating projection method +maxIts = 300; +x = x0; +path = [x0]; +errHist = []; +for k = 1:maxIts + % basic alternating projection method: + x = proj1(x); + path= [path, x]; + x = proj2(x); + path= [path, x]; + errHist = [ errHist; err(x) ]; + if ~mod(k,50) + fprintf('Iter %4d, error is %.2e\n', k, errHist(end) ); + end +end +figure +subplot(1,3,1); +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +hold on +text(0,.5,'set C1'); +text(0,-.5,'set C2'); +plot( path(1,:), path(2,:),'ko-' ) +title('Path for alternating projection method solving the intersection of 2 circles'); + +subplot(1,3,2); +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +hold on +plot( path(1,:), path(2,:),'ko-','linewidth',2 ) +xlim([0,.15]); +ylim([-.1,.1]); +title('(zoom)'); + +subplot(1,3,3); +semilogy( errHist,'o-') +errHist_1 = errHist; +title('Error of iterates for alternating projection method'); +set(gcf, 'Position', [400 200 800 400]); +%% solve via Dykstra's algorithm + +maxIts = 300; +x = x0; +[p,q] = deal( 0*x0 ); +path = [x0]; +errHist = []; +for k = 1:maxIts + % If x + p is feasible, the y = (x+p), so p=x+p-y = 0. + y = proj1( x + p ); + p = x + p - y; + x = proj2( y + q ); + q = y + q - x; + path = [path,y]; + path = [path,x]; + errHist = [ errHist; err(x) ]; + if ~mod(k,50) + fprintf('Iter %4d, error is %.2e\n', k, errHist(end) ); + end +end +figure +subplot(1,3,1); +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +hold on +text(0,.5,'set C1'); +text(0,-.5,'set C2'); +plot( path(1,:), path(2,:),'ko-' ) +title('Path for Dykstra''s algo solving the intersection of 2 circles'); + +subplot(1,3,2); +rectangle('Position',[-radius1,0,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',red) +rectangle('Position',[-radius1,-2*radius2,2*radius1,2*radius1],'Curvature',[1,1],'FaceColor',blue) +hold on +plot( path(1,:), path(2,:),'ko-','linewidth',2 ) +xlim([0,.15]); +ylim([-.1,.1]); +title('(zoom)'); + +subplot(1,3,3); +semilogy( errHist,'o-') +title('Error of iterates for Dykstra''s algo'); +set(gcf, 'Position', [400 200 800 400]); + +%% Plot all the errors together +figure +semilogy( out.err(:,1),'-' ,'linewidth',3); +hold all +semilogy( errHist_1,'-','linewidth',3); +semilogy( errHist,'--','linewidth',3); +legend('TFOCS','Alternating projection','Dykstra'); +xlabel('iteration'); ylabel('error'); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/examples/demos/project2DCone.m b/examples/demos/project2DCone.m new file mode 100644 index 0000000..6e74b4e --- /dev/null +++ b/examples/demos/project2DCone.m @@ -0,0 +1,74 @@ +function op = project2DCone(x1, x2 ) +% OP = project2DCone( y, x1, x2 ) +% represents the 2D point cone defined +% by the vectors x1 and x2. x1 should be "left" of x2, +% and the cone is defined as the region to the right +% of x1 and to the left of x2. + +error(nargchk(1,2,nargin)); +if nargin < 2, u = []; end +%op = @proj_Rplus_impl; +% bugfix, March 2011: +op = @(varargin)proj_cone2(x1,x2,varargin{:}); + +function [v,x] = proj_cone2(x1,x2,y,t) +% "t" doesn't matter, since the only values are 0 and Inf +v = 0; +if size(y,2) > 1, y = y.'; end + +% Assume that "x1" is active +n1 = [x1(2); -x1(1)]; % the normal direction +t1 = -y'*n1/(n1'*n1); + + +% Now assume "x2" is active: +n2 = [x2(2); -x2(1)]; +t2 = -y'*n2/(n2'*n2); + +switch nargin, + case 3, + if nargout == 2, + error( 'This function is not differentiable.' ); + end + if ( t1 > 0 ) && ( t2 < 0 ) + v = 0; + else + v = Inf; + end + case 4, + if y'*x1 <= 0 && y'*x2 <= 0 + x = 0*y; v = 0; + return; + end + + p1 = y + t1*n1; + p2 = y + t2*n2; + + % now determine which one is active: + if p1'*x1 < 0 + % p1 cannot be active + x = p2; + elseif p2'*x2 < 0 + % p2 cannot be active + x = p1; + elseif ( t1 > 0 ) && ( t2 < 0 ) + % we are feasible! + x = y; + else + % both could be active, so pick the best: + if norm( p1 - y ) < norm( p2 - y ) + x = p1; + else + x = p2; + end + end + + + otherwise, + error( 'Wrong number of arguments.' ); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/demos/recordPoints.m b/examples/demos/recordPoints.m new file mode 100644 index 0000000..8bea9a4 --- /dev/null +++ b/examples/demos/recordPoints.m @@ -0,0 +1,17 @@ +function er = recordPoints( x ) + +persistent history + +if nargin == 0 + er = history; + history = []; + return; +end + +history = [history, x ]; +er = 0; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/examples/largescale/BugsBunny.mat b/examples/largescale/BugsBunny.mat new file mode 100644 index 0000000000000000000000000000000000000000..c56305bc2aa4f5c221e65a3e2206aa31f263242a GIT binary patch literal 97622 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 where <,> is the +trace inner product. A_i is a Hermitian matrix, and is a tensor product +of Pauli matrices. The observations are noisy. + +To recover X, we can consider solving several related variants: + +notation: +x = vec(X), X = mat(x) + + + minimize 1/2||A*x - b ||^2 + subject to + mat(x) >= 0 + trace(mat(x)) <= 1 + +(this variant does not require smoothing) + +or + + minimize trace(mat(x)) + subject to + mat(x) >= 0 + ||A*x - b || <= eps +(this variant requires smoothing and/or continuation ) + +%} +%% Setup a matrix +randn('state',2009); +rand('state',2009); + +% -- Generate the state matrix "M" (aka rho) +n1 = 128; % fast +% n1 = 256; +% n1 = 512; % works, but slow +n2 = n1; r = 2; +M = ( randn(n1,r) + 1i*randn(n1,r) )*... + ( randn(r,n2) + 1i*randn(r,n2) )/2; +M=M*M'; % M is Pos. Semidefinite +M = M / trace(M); % and has trace 1 + +df = r*(n1+n2-r); % The degrees of freedom +oversampling = 5; % Information-theoretic limit corresponds + % to oversampling = 1 +m = min(5*df,round(.99*n1*n2) ); + +fprintf('%d x %d matrix, %d measurements (%.1f%%, or %.1f oversampling)\n',... + n1,n2,m,100*m/(n1*n2),m/df ); +fprintf('True rank is %d\n', r); +if ~isreal(M), fprintf('Matrix is complex\n'); end + +vec = @(x) x(:); +mat = @(x) reshape(x,n1,n2); + +%% make some pauli matrices, to use for observations +%{ +Using the convention: + PX = [0,1;1,0]; PY = [0,-1i;1i,0]; PZ =[1,0;0,-1]; PI = eye(2); + X will be labelled "1", Y labeled "2", Z "3" and I "4" +%} +myRandint = @(M,N,d) ceil(d*rand(M,N)); + +PAULI_T = sparse([],[],[],n1*n2,m,m*n1 ); +fprintf('Taking measurements... '); +for i=1:m + fprintf('\b\b\b\b\b\b%5.1f%%', 100*i/m ); + % pick from the X, Y, Z and I uniformly at random + list = myRandint( log2(n1),1,4 ); + E_i = explicitPauliTensor( list ); + % NOTE: with this definition, E_i is Hermitian, + % but not positive semidefinite, and not scaled properly. + + PAULI_T(:,i) = E_i(:); % access via column is MUCH faster +end +fprintf('\n'); +PAULI = PAULI_T.'; % transpose it, since we were implicitly +clear PAULI_T % dealing with transpose earlier + % (since updating the columns of a sparse + % matrix is much more efficient than updating + % the rows ) + +fprintf('Computing the scaling factor...'); +% scale = 1/normest(PAULI*PAULI',1e-2); +% scale = 1/my_normest( @(x) PAULI*(PAULI'*x), size(PAULI,1)*[1,1] ); +fprintf(' done.\n'); + +data = PAULI*vec(M); +% even if measurements are complex, data should still be real +fprintf('due to roundoff, measurements have %.2e imaginary part\n',... + norm(imag(data))/norm(data) ); +data = real(data); + +%% Add some noise +%{ + Physics of noise: + in addition to noise from measurement error, we have a dominant + source of 'noise' which is because the outcome of our + measurements is really the result of measuring a quantum wave + function. Experimentalists will repeat the same + measurement many times, so they can estimate the true state, + but there is inherent quantum mechanical uncertainty to this. + Basically, we have a state that is determined by a Bernoulli + parameter 0 < p < 1, but the measurement returns either 0 or 1, + so we have to take a lot of measurements to estimate p. + + There's also depolarizing noise, which we don't discuss here. + + For simplicity in this example, we don't worry about that model, + and just add in some pseudo-random white noise +%} + +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); +snr = 40; +b = myAwgn( data, snr ); +rms = @(x) norm(x)/sqrt(length(x)); +snrF = @(sig,noise) 20*log10( rms(sig)/rms(noise) ); +sigma = std( b - data ); +fprintf('SNR of measurements is %.1f dB, and std of noise is %.2f\n',... + snrF(data,data-b), sigma ); +%% Solve in TFOCS +%{ + +Formulation: + minimize 1/2||PAULI*x - b ||^2 + subject to + mat(x) >= 0 + trace(mat(x)) <= 1 + +No smoothing is required, but... it's hard to exploit the low-rank +structure of the problem + +We do not expect the "error" to go to zero, since the data are noisy +and we don't have the true answer, so we're not really looking +at "error" of the software, but rather modelling error... + +%} +opts = []; +opts.maxIts = 300; +opts.printEvery = 25; +if n1 > 256 + opts.maxIts = 50; + opts.printEvery = 5; +end +opts.errFcn = {@(f,x) norm( vec(x) - vec(M) )/norm(M,'fro')}; +% this may be slow: +% opts.errFcn{2} = @(f,x) rank(mat(x)); + +tau = 1; % constraint on trace +x0 = []; + +A = linop_matrix( PAULI, 'C2R' ); +[x,out,optsOut] = tfocs(smooth_quad,{A,-b}, proj_psdUTrace(tau) , x0, opts ); +X = mat(x); + +figure(1); +semilogy( out.err(:,1) ); +hold all +%% Solve in TFOCS +%{ + +Formulation: + minimize trace(mat(x)) + mu/2*||x-x0||^2 + subject to + mat(x) >= 0 + ||PAULI*x - b || <= eps + +We solve this via a dual method, hence the mu term. +We can reduce its effect by using continuation (i.e. updating x0) + +%} +opts = []; +opts.maxIts = 50; +opts.printEvery = 25; +opts.errFcn = {@(f,dual,x) norm( vec(x) - vec(M) )/norm(M,'fro')}; +% this may be slow: +% opts.errFcn{2} = @(f,dual,x) rank(mat(x)); +opts.stopCrit = 4; +opts.tol = 1e-8; +contOpts = []; +contOpts.betaTol = 10; +contOpts.maxIts = 5; + +epsilon = norm(b-data); % cheating a bit... +x0 = []; +z0 = []; +mu = 5; + +largescale = ( n1*n2 >= 256^2 ); +% obj = prox_nuclear(1,largescale); % works, but unnecessarily general +obj = prox_trace(1,largescale); + +prox = prox_l2(epsilon); +A = linop_matrix( PAULI, 'C2R' ); +I = []; +It = linop_handles({ [n1,n2],[n1*n2,1] }, vec, mat, 'C2C' ); +A = linop_compose( A, It ); + +[x,out,opts] = tfocs_SCD( obj,{ A, -b; I, 0 }, {prox, proj_psd}, mu, x0, z0, opts, contOpts ); +X = mat(x); + +fprintf('Error is %.2e, error after renormalizing is %.2e\n',... + opts.errFcn{1}(1,1,X), opts.errFcn{1}(1,1,X/trace(X) ) ); +fprintf('Rank of X is %d\n', rank(X) ); + +figure(1); +semilogy( out.err(:,1) ); +hold all +semilogy(out.contLocations, out.err(out.contLocations,1), 'o' ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/explicitPauliTensor.m b/examples/largescale/explicitPauliTensor.m new file mode 100644 index 0000000..8e145ad --- /dev/null +++ b/examples/largescale/explicitPauliTensor.m @@ -0,0 +1,27 @@ +function u = explicitPauliTensor( list ) +% u = explicitPauliTensor( list ) +% makes the explicit matrix corresponding to the tensor +% product of Pauli matrices. +% "list" should be an ordered vector, where each entry is +% either 1, 2, 3 or 4, correspodning to the X, Y, Z and I +% Pauli matrices, respectively. + +if ~isvector(list) || max(list) > 4 || min(list) < 1 + error('Error making Pauli matrices'); +end + +PX = [0,1;1,0]; PY = [0,-1i;1i,0]; PZ =[1,0;0,-1]; PI = eye(2); + +PAULI{1} = PX; +PAULI{2} = PY; +PAULI{3} = PZ; +PAULI{4} = PI; + +u=1; +for i = 1:length(list) + u = kron( u, sparse( PAULI{list(i)} )); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/findWeights.m b/examples/largescale/findWeights.m new file mode 100644 index 0000000..9a8d995 --- /dev/null +++ b/examples/largescale/findWeights.m @@ -0,0 +1,26 @@ +function w = findWeights( coeff, p ) +% weights = findWeights( coefficients, p ) +% creates a nice vector of reweighting coefficients +% for use with re-weighted l1. +% The regularization parameter is chosen to be the coefficient +% that accounts for p-percent (0 1 + error('p must be a scalar between [0,1]' ); +end + +coeff = abs(coeff); +coeffSort = sort(coeff,'descend'); +c = cumsum( coeffSort.^2 ); +indx = find( c/c(end) > p, 1, 'first' ); +if ~isempty(indx) + delta = coeffSort( indx ); +else + delta = 1e-15; +end + +w = delta./( coeff + delta ); +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/image_denoising_withSPOT.m b/examples/largescale/image_denoising_withSPOT.m new file mode 100644 index 0000000..a8ce478 --- /dev/null +++ b/examples/largescale/image_denoising_withSPOT.m @@ -0,0 +1,284 @@ +%{ + Tests total-variation problem and l1 analysis + on a large-scale example + + min_x alpha*||x||_TV + beta*||Wx||_1 +s.t. + || A(x) - b || <= eps + +where W is a wavelet operator. + + +This requires: + + (1) SPOT www.cs.ubc.ca/labs/scl/spot/ + (2) Wavelab www-stat.stanford.edu/~wavelab/ +and optionally, + (3) CurveLab www.curvelet.org/ + + Also demonstrates how to use SPOT with TFOCS. + To install SPOT, please visit: + http://www.cs.ubc.ca/labs/scl/spot/ + + We use SPOT to call WaveLab and CurveLab, + which may need to be installed separately. + Please contact the TFOCS and/or SPOT authors if you need help. + + Also, SPOT v1.0 has a typo in its curvelet code: + after adding SPOT to your path, edit this file + edit spotbox-1.0p/opCurvelet + and change all instances of "fdct_c2v" and "fdct_v2c" + to "spot.utils.fdct_c2v" and "spot.utils.fdct_v2c", resp. + +%} + +% Setting up the various packages (change this for your computer): +addpath ~/Dropbox/TFOCS/ +addpath ~/Documents/MATLAB/spotbox-1.0p/ +addpath ~/Documents/MATLAB/CurveLab-2.1.2/fdct_wrapping_cpp/mex/ + +myAwgn = @(x,snr) x +10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*... + randn(size(x)); +%% Load an image and take noisy measurements + + +n1 = 256; +n2 = 256; +N = n1*n2; +x = phantom(n1); + +% Signal-to-noise ratio, in dB +snr = 5; + + +x_original = x; +mat = @(x) reshape(x,n1,n2); +% vec = @(x) x(:); + +% Add noise: +randn('state',245); rand('state',245); +x_noisy = myAwgn( x_original, snr); + +maxI = max(vec(x_original)); % max pixel value +PSNR = @(x) 20*log10(maxI*sqrt(N)/norm(vec(x)-vec(x_original) ) ); + +% Take measurements +b = vec(x_noisy); +b_original = vec(x_original); +EPS = .8*norm(b-b_original); + +figure(2); imshow( [x_original, x_noisy] ); drawnow; +M=N; +fprintf('Denoising problem, %d x %d, signal has SNR %d dB\n',M, N, round(snr) ); + +REWEIGHT = false; % whether to do one iteration of reweighting or not +%% Call the TFOCS solver + +mu = 5; +er = @(x) norm(x(:)-x_original(:))/norm(x_original(:)); +opts = []; +opts.errFcn = @(f,dual,primal) er(primal); +opts.maxIts = 100; +opts.printEvery = 20; +opts.tol = 1e-4; +opts.stopcrit = 4; + +x0 = x_noisy; +z0 = []; % we don't have a good guess for the dual + +% build operators: +A = linop_handles([N,N], @(x)x, @(x) x ); +normA2 = 1; +W_wavelet = linop_spot( opWavelet(n1,n2,'Daubechies') ); +if exist('fdct_wrapping') + DO_CURVELETS = true; + W_curvelet = linop_spot( opCurvelet(n1,n2) ); +else + % You either don't have curvelab installed + % or you need to add it to the matlab path. + DO_CURVELETS = false; +end +W_tv = linop_TV( [n1,n2] ); +normWavelet = linop_normest( W_wavelet ); +if DO_CURVELETS, normCurvelet = linop_normest( W_curvelet ); end +normTV = linop_TV( [n1,n2], [], 'norm' ); + +contOpts = []; +contOpts.maxIts = 4; + +%% -- First, solve just via wavelet -- +clc; disp('WAVELETS'); +[x_wavelets,out_wave] = solver_sBPDN_W( A, W_wavelet, b, EPS, mu, ... + x0(:), z0, opts, contOpts); + +if REWEIGHT + % do some re-weighting: + coeff = W_wavelet( x_wavelets, 1 ); + weights = findWeights( coeff, .85 ); + W_weights = linop_handles( [length(weights),length(weights)], @(x)weights.*x,... + @(y) weights.*y, 'R2R' ); + [x_wavelets,out_wave] = solver_sBPDN_W( A, ... + linop_compose(W_weights,W_wavelet), ... + b, EPS, mu, x_wavelets, out_wave.dual, opts, contOpts); + % and of course, you could keep iterating... +end +%% -- Second, solve just via curvelets -- +if DO_CURVELETS + opts_copy = opts; + opts_copy.maxIts = 10; + opts_copy.normA2 = 1; + opts_copy.normW2 = normCurvelet^2; + clc; disp('CURVELETS'); + [x_curvelets,out_curve] = solver_sBPDN_W( A, W_curvelet, b, EPS, mu, x0(:), z0, opts_copy); + + if REWEIGHT + % do some re-weighting: + coeff = W_curvelet( x_curvelets, 1 ); + weights = findWeights( coeff, .85 ); + W_weights = linop_handles( [length(weights),length(weights)], @(x)weights.*x,... + @(y) weights.*y, 'R2R' ); + % really, we should update the norm of W, since it is changing slightly + % by adding in the weights, but that is a slow calculation, and the norm + % doesn't change too much, so we skip that. + [x_curvelets,out_curve] = solver_sBPDN_W( A, ... + linop_compose(W_weights,W_curvelet), ... + b, EPS, mu, x_curvelets, out_curve.dual, opts_copy); + end +end + +%% -- Third, solve just via TV -- +clc; disp('TV'); +opts_copy = opts; +opts_copy.normA2 = 1; +opts_copy.normW2 = normTV^2; +opts_copy.continuation = false; +[x_tv,out_tv] = solver_sBPDN_W( A, W_tv, b, EPS, mu, x0(:), z0, opts_copy, contOpts); + +if REWEIGHT + % do some re-weighting: + coeff = W_tv( x_tv, 1 ); + weights = findWeights( coeff, .95 ); + W_weights = linop_handles( [length(weights),length(weights)], @(x)weights.*x,... + @(y) weights.*y, 'C2C' ); + % really, we should update the norm of W, since it is changing slightly + % by adding in the weights, but that is a slow calculation, and the norm + % doesn't change too much, so we skip that. + [x_tv,out_tv] = solver_sBPDN_W( A, ... + linop_compose(W_weights,W_tv), ... + b, EPS, mu, x_tv, out_tv.dual, opts_copy); +end +%% -- Fourth, combine wavelets (or curvelets) and tv -- +alpha = 1; % weight of TV term +beta = .05; % weight of wavelet term +normW12 = normTV^2; +if DO_CURVELETS, normW22 = normCurvelet^2; beta = .5; +else, normW22 = normWavelet^2; end + +% x =solver_sBPDN_WW( A, alpha, W_tv, beta, ... +% W_wavelet, b, EPS, mu,vec(x0), z0, opts, contOpts); + +% this is what solver_sBPDN_WW is doing: +W1 = W_tv; +if DO_CURVELETS + W2 = W_curvelet; +else + W2 = W_wavelet; +end +prox = { prox_l2( EPS ), ... + proj_linf( alpha ),... + proj_linf( beta ) }; +affine = { A, -b; W1, 0; W2, 0 }; + +% x = tfocs_SCD( [], affine, prox, mu, x0(:), z0, opts, contOpts ); +% X_wavelets_tv = mat(x); +%% try with box constraints +% Constrain the image pixels to be in the range 0 <= x <= 1 +prox = { prox_l2( EPS ), ... + proj_linf( alpha ),... + proj_linf( beta ), ... + proj_Rplus , ... + proj_Rplus}; + +affine = { A, -b; W1, 0; W2, 0 ; 1, 0; -speye(n1*n2), maxI*ones(n1*n2,1) }; + +x_wavelets_tv = tfocs_SCD( [], affine, prox, mu, x0(:), z0, opts, contOpts ); +fprintf('Min and max entry of recovered image are %.1f and %.1f\n', min(min(x_wavelets_tv)),... + max(max(x_wavelets_tv)) ); +%% another way to use box constraints +% Instead of thinking of it as two scaled X >= 0 constraints, +% we call the special purpose atom + +prox = { prox_l2( EPS ), ... + proj_linf( alpha ),... + proj_linf( beta ), ... + prox_boxDual(0,maxI,-1) }; + +affine = { A, -b; W1, 0; W2, 0 ; 1, 0 }; +opts_copy = opts; +opts_copy.continuation = false; +opts_copy.maxIts = 30; +[x_wavelets_tv,out] = tfocs_SCD( [], affine, prox, mu, x0(:), z0, opts_copy, contOpts ); + +if REWEIGHT + % do some re-weighting: + weights = findWeights( W1(x_wavelets_tv,1), .95 ); + W_weights1 = linop_handles( [length(weights),length(weights)], @(x)weights.*x,... + @(y) weights.*y, 'C2C' ); + weights = findWeights( W2(x_wavelets_tv,1), .95 ); + W_weights2 = linop_handles( [length(weights),length(weights)], @(x)weights.*x,... + @(y) weights.*y, 'C2C' ); + + affine = { A, -b; linop_compose(W_weights1,W1), 0; ... + linop_compose(W_weights2,W2) , 0 ; -1, 0 }; % Note: we need the "-1", not +1 + [x_wavelets_tv,out] = tfocs_SCD( [], affine, prox, mu, x0(:), out.dual, opts_copy, contOpts ); + +end +fprintf('Min and max entry of recovered image are %.1f and %.1f\n', min(min(x_wavelets_tv)),... + max(max(x_wavelets_tv)) ); +%% Plot everything +figure(1); clf; +splot = @(n) subplot(2,3,n); + +splot(1); +imshow(x_original); +title(sprintf('Noiseless image,\nPSNR %.1f dB', Inf )); + +splot(2); +imshow(x_noisy); +title(sprintf('Noisy image,\nPSNR %.1f dB', PSNR(x_noisy) )); + +if DO_CURVELETS + splot(3); + imshow(mat(x_curvelets)); + title(sprintf('Curvelet regularization,\nPSNR %.1f dB', PSNR(x_curvelets) )); +end + +splot(4); +imshow(mat(x_wavelets)); +title(sprintf('Wavelet regularization,\nPSNR %.1f dB', PSNR(x_wavelets) )); + +splot(5); +imshow(mat(x_tv)); +title(sprintf('TV regularization,\nPSNR %.1f dB', PSNR(x_tv) )); + +splot(6); +imshow(mat(x_wavelets_tv)); +if DO_CURVELETS + title(sprintf('Curvelets + TV, and box constraints,\nPSNR %.1f dB', PSNR(x_wavelets_tv) )); +else + title(sprintf('Wavelets + TV, and box constraints,\nPSNR %.1f dB', PSNR(x_wavelets_tv) )); +end + +%% Bonus: movie. +% This uses the helper file "plotNow.m" to show you the image +% at every iteration. It slows down the algorithm on purpose +% so that you have time to watch it. +figure(2); clf; plotNow(); +time_delay = 0.1; +opts.errFcn = {@(f,d,p) er(p), @(f,d,p) plotNow( mat(p), time_delay) }; +x_tv = solver_sBPDN_W( A, W_tv, b, EPS, .1*mu, vec(x0), z0, opts); +title('THAT''S ALL FOLKS'); +%% +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/plotNow.m b/examples/largescale/plotNow.m new file mode 100644 index 0000000..0ab7071 --- /dev/null +++ b/examples/largescale/plotNow.m @@ -0,0 +1,40 @@ +function out = plotNow( x, pause_t , plotFcn ) +% out = plotNow( x, pause_t ) +% helper function for plotting a movie during +% the TFOCS iterations. +% Call this function with no inputs in order +% to reset it. +% Call this with a matrix and it will plot it using "imshow". +% +% The input "pause_t" controls how long to pause +% (this is useful when solving a small problem that would +% otherwise progress too fast between iterates). +% +% out = plotNow( x, pause_t, plotFcn ) +% will plot "x" using "plotFcn" instead of the default "imshow" + +persistent counter +if isempty(counter) + counter = 0; +end +if nargin == 0 + counter = 0; + return; +end +counter = counter + 1; + +if nargin < 3 || isempty(plotFcn), plotFcn = @(x) imshow(x); end + +plotFcn(x); +title(sprintf('Iteration %2d',counter)); +drawnow + +if nargin > 1 && ~isempty(pause_t) + pause(pause_t); +end + +out = 0; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/test_SVM.m b/examples/largescale/test_SVM.m new file mode 100644 index 0000000..b03424c --- /dev/null +++ b/examples/largescale/test_SVM.m @@ -0,0 +1,171 @@ +%{ + Support Vector Machine (SVM) example + + This is not a largescale test but it's neat, so it's in this directory + + We have binary data, and the two classes are labeled +1 and -1 + The data is d-dimensional, and we have n samples + + + This example show show to solve the standard SVM using the hinge-loss + and l2 penalty. Then it shows how it is easy with TFOCS to generalize + the SVM problem and use the l1 penalty instead of the l2 penalty, + which has the effect of encouraging sparsity. + +%} + +% Before running this, please add the TFOCS base directory to your path + + +% Generate a new problem + +randn('state',23432); +rand('state',3454); + +n = 30; +d = 2; % 2D data is nice because it's easy to visualize + +n1 = round(.5*n); % number of -1 (this is data from one "class") +n2 = n - n1; % number of +1 (this is data from the other "class") + +% Generate data +mean1 = [-.5,1]; +% mean2 = [.6,.2]; % can be separated +mean2 = [.1,.5]; % cannot be separated, so a bit more interesting +s = .25; % standard deviation +x1 = repmat(mean1,n1,1) + s*randn(n1,2); +x2 = repmat(mean2,n2,1) + s*randn(n2,2); + +X = [x1; x2]; +labels = [ ones(n1,1); -ones(n2,1) ]; + +figure(1); clf; +hh = {}; +hh{1}=plot(x1(:,1), x1(:,2), 'o' ); +hold all +hh{2}=plot(x2(:,1), x2(:,2), '*' ); +legend('Class 1','Class 2'); +xl = get(gca,'xlim'); +yl = get(gca,'ylim'); + +%% compute a separating hyperplane with SVM +% First, we use CVX to get a reference solution + +hinge = @(x) sum(max(0,1-x)); +lambda_1 = 10; +lambda_2 = 4; + +if exist( 'cvx_begin','file') + +% -- Problem 1: standard SVM in primal form +PROBLEM = 1; +cvx_begin + cvx_quiet true + cvx_precision best + variables a(d) b(1) + minimize hinge( labels.*( X*a - b ) ) + lambda_1*norm(a,2) +cvx_end +SLOPE{PROBLEM} = a; +INTERCEPT{PROBLEM} = b; + +% -- Problem 2: "sparse" SVM (use l1 norm as penalty) +PROBLEM = 2; +cvx_begin + cvx_quiet true + cvx_precision best + variables a(d) b(1) + minimize hinge( labels.*( X*a - b ) ) + lambda_2*norm(a,1) +cvx_end +SLOPE{PROBLEM} = a; +INTERCEPT{PROBLEM} = b; + +% An equivalent way to solve via method 2: +% X_aug = [diag(labels)*X, -labels ]; % include the "b" variable +% cvx_begin +% variables a(d+1) +% minimize hinge( X_aug*a ) + lambda_2*norm(a(1:2),1) +% cvx_end + +else + % load the pre-computed reference solutions + lambda_1 = 10; + lambda_2 = 4; + SLOPE = { [ -1.084604434515675; 0.783281331181009]; ... + [ -2.391008972722243; 0] }; + INTERCEPT = { [0.75708813868169] ; [0.260615633092470] }; +end + +%% plot the separating hyper-planes +for PROBLEM = 1:length(SLOPE) + a = SLOPE{PROBLEM}; b = INTERCEPT{PROBLEM}; + + if abs(a(2)/a(1)) < 1e-10 + % vertical line: + hh{PROBLEM+2}=line( [1,1]*b/a(1), [-.3,2] ); + else + grid = linspace(-.5,1,100); + hh{PROBLEM+2}=plot( grid, (b-a(1)*grid)/a(2) ); + end +end +legend([hh{:}],'Class 1','Class2','SVM','sparse SVM'); +xlim(xl); ylim(yl); + +%% Solve Problem 2 in TFOCS +PROBLEM = 2; +linearF = diag(labels)*[ X, -ones(n,1) ]; +objF = prox_l1(lambda_2*[1,1,0]'); +% objF = prox_l1(lambda_2); +prox = prox_hingeDual(1,1,-1); % but use -x since we want polar cone, not dual cone + +mu = 1e-2; +opts = []; +opts.maxIts = 1500; +opts.continuation = false; +opts.stopCrit = 4; +opts.tol = 1e-12; +opts.errFcn = @(f,d,x) norm( x - [SLOPE{PROBLEM};INTERCEPT{PROBLEM}] ); +x0 = zeros(d+1,1); % i.e. 3 for 2D SVM +y0 = zeros(n,1); +[ak,out,optsOut] = tfocs_SCD( objF,linearF, prox, mu,x0,y0,opts); +% an alternative way to call it: +% [ak,out,optsOut] = tfocs_SCD( objF,{linearF,[]}, prox, mu,x0,y0,opts); + +%% Solve Problem 2 in TFOCS a different way +% We dualize the l1 term now +PROBLEM = 2; +prox2 = { prox, proj_linf(lambda_2) }; +linearF2 = diag( [ones(d,1);0] ); +opts.errFcn = @(f,d,x) norm( x - [SLOPE{PROBLEM};INTERCEPT{PROBLEM}] ); +[ak,out,optsOut] = tfocs_SCD([],{linearF,[];linearF2,[]}, prox2, mu,[],[],opts); + + +%% Solve Problem 1 in TFOCS +% Keep l1 term in primal; this is an "experimental" feature +% since the l1 proximity operator is not exactly in closed form +% (it relies on a 1D linesearch; this should be very fast, but +% it could have numerical issues for very high accuract solutions) +PROBLEM = 1; +objF = prox_l2(lambda_1*[1,1,0]'); +opts.errFcn = @(f,d,x) norm( x - [SLOPE{PROBLEM};INTERCEPT{PROBLEM}] ); +% opts.tol = 1e-16; +opts.continuation = true; +mu2 = 10*mu; +[ak,out,optsOut] = tfocs_SCD( objF,linearF, prox, mu2,[],[],opts); + +%% Solve Problem 1 in TFOCS a different way +% Dualize the l2 term +PROBLEM = 1; +prox2 = { prox, proj_l2(lambda_1) }; +linearF2 = diag( [ones(d,1);0] ); +opts.errFcn = @(f,d,x) norm( x - [SLOPE{PROBLEM};INTERCEPT{PROBLEM}] ); +opts.tol = 1e-15; +opts.continuation = false; +[ak,out,optsOut] = tfocs_SCD([],{linearF,[];linearF2,[]}, prox2, mu,[],[],opts); + + + + +%% +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/test_sBPDN_W_largescale.m b/examples/largescale/test_sBPDN_W_largescale.m new file mode 100644 index 0000000..5cfdb1e --- /dev/null +++ b/examples/largescale/test_sBPDN_W_largescale.m @@ -0,0 +1,149 @@ +%{ + Tests the "analysis" form of basis pursuit de-noising + + min_x ||Wx||_1 +s.t. + || A(x) - b || <= eps + +This version uses a realistic problem + +see also test_sBP.m and test_sBPDN.m and test_sBPDN_W.m + +This example requires PsiTransposeWFF.m and PsiWFF.m (should be included in this directory) + +%} + +% Before running this, please add the TFOCS base directory to your path + + +HANDEL = load('handel'); % This comes with Matlab +x_original = HANDEL.y; % It's a clip from Handel's "Hallelujah" +FS = HANDEL.Fs; % The sampling frequency in Hz + +PLAY = input('Play the original music clip? (y/n) ','s'); +if strcmpi(PLAY,'y') || strcmpi(PLAY,'yes') + sound( x_original ); +end +N = length(x_original); +% Simplest to use signals of size 2^k for any integer k > 0 +% So, padd the signal with zeros at the end +k = nextpow2(N); +k = k-1; +x_original = [x_original(1:min(N,2^k)); zeros(2^k-N,1) ]; +N_short = N; +N = 2^k; + +% We'll perform denoising. Measurement is the identity. +M = N; +Af = @(x) x; +At = Af; +normA = 1; +A = linop_handles( [M,N], Af, At ); +% linop_test(A) + +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +snr = 10; % in dB +b_exact = Af(x_original); +x_noisy = myAwgn(x_original,snr); +b = Af(x_noisy); + +PLAY = input('Play the noisy music clip? (y/n) ','s'); +if strcmpi(PLAY,'y') || strcmpi(PLAY,'yes') + sound( x_noisy ); +end + +EPS = norm( b - b_exact ); +fprintf('||b-Ax||/||b|| is %.2f\n', EPS/norm(b) ); + +%% SETUP AN ANALYSIS OPERATOR +% The PsiWFF and PsiTransposeWFF code is a Gabor frame +% (i.e. a short-time Fourier transform) +% written by Peter Stobbe +% +% PsiWFF is the synthesis operator, and acts on coefficients +% PsiTransposeWFF is the analysis operator, and acts on signals +gMax = 0; +% gMax = -8; % 2.6e-1 er with -8 +gLevels = 1 - gMax; +tRedundancy = 1; +fRedundancy = 0; + +gWindow = 'isine'; +logN = log2(N); +psi = @(y) PsiWFF(y,gWindow,logN,logN-gLevels,logN+gMax,tRedundancy,fRedundancy); +psiT = @(x) PsiTransposeWFF(x,gWindow,logN,logN-gLevels,logN+gMax,tRedundancy,fRedundancy); + +x = x_original; +y = psiT(x); +x2 = psi(y); +d = length(y); +% The frame is tight, so the psuedo-inverse is just the transpose: +fprintf('Error in PSI(PSI^* x ) - x is %e; N = %d, d = %d, d/N = %.1f\n', ... + norm(x-x2),N,d,d/N); + +normW = 1; +W = linop_handles([d,N], psiT, psi ); +linop_test(W) +%% Call the TFOCS solver + +x0 = x_noisy; +mu = .5*norm(psiT(x0),Inf); +norm_x_orig = norm(x_original); +er_signal = @(x) norm(x-x_original)/norm_x_orig; + +opts = []; +opts.errFcn = @(f,dual,primal) er_signal(primal); +opts.maxIts = 80; +opts.tol = 1e-6; +opts.normA2 = normA^2; +opts.normW2 = normW^2; % both of these are 1 +opts.printEvery = 20; +z0 = []; % we don't have a good guess for the dual +tic; + +W_weighted = W; +% do some reweighting +for rw = 1:2 + + % This is the old method of doing continuation. The current version + % supports a simpler way to make the solver use continuation; + % for an example, see "example_LinearProgram.m" in the examples/smallscale directory. + solver = @(mu,x0,z0,opts) solver_sBPDN_W( A, W_weighted, b, EPS, mu, x0, z0, opts ); + contOpts = []; + contOpts.maxIts = 1; + [ x, out, optsOut ] = continuation( solver, mu, x0, z0, opts,contOpts ); + + % update weights + coeff = psiT(x); + cSort = sort(abs(coeff),'descend'); + cutoff = cSort( find( cumsum(cSort.^2)/sum(cSort.^2) >= .9, 1 ) ); + weights = 1./( abs(coeff) + cutoff ); + weightsMatrix = spdiags(weights,0,d,d); + W_weighted = linop_compose( weightsMatrix , W ); + opts.normW2 = []; % let the solver estimate it for us + + x0 = x; + z0 = cell( out.dual ); +end + + +time_TFOCS = toc; + +fprintf('Denoised signal has error %.2e, noisy signal has error %.2e\n',... + er_signal(x), er_signal(x_noisy) ); +%% +PLAY = input('Play the recovered signal music clip? (y/n) ','s'); +if strcmpi(PLAY,'y') || strcmpi(PLAY,'yes') + sound( x ); +end +PLAY = input('Play the noisy music clip? (y/n) ','s'); +if strcmpi(PLAY,'y') || strcmpi(PLAY,'yes') + sound( x_noisy ); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/test_sTV_Analysis_largescale.m b/examples/largescale/test_sTV_Analysis_largescale.m new file mode 100644 index 0000000..995365b --- /dev/null +++ b/examples/largescale/test_sTV_Analysis_largescale.m @@ -0,0 +1,268 @@ +%{ + Tests total-variation problem and l1 analysis + on a large-scale example + + min_x alpha*||x||_TV + beta*||Wx||_1 +s.t. + || A(x) - b || <= eps + +where W is a wavelet operator. +The solvers solve a regularized version + +see also test_sBP.m and test_sBPDN_W.m and test_sTV.m and test_sTV_largescale.m + +This version has no reference solution. + +This code uses continuation + +Requires wavelet toolbox and image processing toolbox + +%} + +% Before running this, please add the TFOCS base directory to your path + + +% Generate a new problem + +randn('state',245); +rand('state',245); + +% image = 'phantom'; +image = 'bugsbunny'; + +switch image + case 'phantom' + n = 256; + n1 = n; + n2 = n; + % n2 = n-1; % testing the code with non-square signals + N = n1*n2; + n = max(n1,n2); + x = phantom(n); + x = x(1:n1,1:n2); + +% snr = -5; % SNR in dB + snr = 5; + case 'bugsbunny' + % Image is from wikimedia commons: + % http://commons.wikimedia.org/wiki/File:Falling_hare_bugs.jpg + load BugsBunny + x = bugs; + [n1,n2] = size(x); + N = n1*n2; + + snr = 15; % SNR in dB +end +x_original = x; + +mat = @(x) reshape(x,n1,n2); +vec = @(x) x(:); + +% -- Choose what kind of measurements you want: +measurements = 'identity'; % for denoising + +switch measurements + case 'identity' + M = N; + Af = @(x) x; + At = @(x) x; +end + +% treat X as n1*n2 x 1 vector? +VECTOR = true; +% or treat X as n1 x n2 matrix? +% VECTOR = false; + +if VECTOR + A = linop_handles([M,N], Af, At ); + VEC = vec; +else + Af = @(x) vec(Af(x)); + At = @(x) mat(At(x)); + A = linop_handles({[n1,n2],[M,1]}, Af, At ); + VEC = @(x) x; +end + + +b_original = Af(vec(x_original)); + +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Either add white noise: +x_noisy = myAwgn( x_original, snr ); +% Or add a random mask: +% x_temp = myAwgn( x_original, 0); +% index = randperm(N); +% index = index( 1 : round(.2*N) ); +% x_noisy = x_original; +% x_noisy(index) = x_temp(index); + +b = Af( vec(x_noisy) ); +EPS = .9*norm(b-b_original); +figure(2); imshow( [x_original, x_noisy] ); + +%% Setup a sparsifying dictionary to use for denoising: +% Choose an extension mode; see "dwtmode". This makes things square +dwtmode('per'); +X = x_original; +Xbp = x_noisy; +maxI = 1; % max pixel value +PSNR = @(x) 20*log10(maxI*sqrt(N)/norm(vec(x)-vec(x_original) ) ); + +% symmetric 9/7 biothogonal wavelets are what JPEG-2000 uses +waveletType = 'bior4.4'; % 9/7 wavelet +nLevels = min( 1, wmaxlev(size(Xbp),waveletType) ); +[c,s] = wavedec2(Xbp,nLevels,waveletType); % see 'wfilters' for options. Needs wavelet toolbox +PLOT = true; +if PLOT + D = detcoef2( 'h', c, s, 1 ); + D = [D,detcoef2( 'd', c, s, 1 )]; + a = appcoef2( c, s, 'db3', 1); + D = [D;detcoef2( 'v', c, s, 1 ), a]; + cSort = sort(abs(c),'descend'); + + % Try wavelet hard thresholding to denoise + erBest = -Inf; + for gamma = .81:.01:.999 + ind = [find( cumsum(cSort.^2)/sum(cSort.^2) > gamma ),N]; + cutoff = cSort(ind(1)); + c2 = c; + c2 = c.*( abs(c)>cutoff); + X_hat = waverec2(c2,s,waveletType); + er = PSNR(X_hat); +% fprintf('Gamma is %.3f, PSNR is %.2f\n',gamma,er ); + if er > erBest, erBest = er; gammaBest = gamma; end + end +% gammaBest = .9; + ind = [find( cumsum(cSort.^2)/sum(cSort.^2) > gammaBest ),N]; + cutoff = cSort(ind(1)); + c2 = c.*( abs(c)>cutoff); + X_hat = waverec2(c2,s,waveletType); + + figure(2); + imshow( [X,Xbp,X_hat] ); + title(sprintf('Original\t\tNoisy (PSNR %.1f dB)\tOracle Wavelet Thresholding (PNSR %.1f dB)',... + PSNR(Xbp), PSNR(X_hat) ) ); +end + +% Forward wavelet operator +Wf = @(X) wavedec2(mat(X),nLevels,'bior4.4')'; +W_invF = @(c) vec(waverec2(vec(c),s,'bior4.4') ); +pinvW = W_invF; + +Wt = @(c) vec(waverec2(vec(c),s,'rbio4.4') ); +pinvWt = @(X) wavedec2(mat(X),nLevels,'rbio4.4')'; +d = length(c); +if d ~= N, disp('warning: wavelet transform not square'); end + +W_wavelet = linop_handles([N,N], Wf, Wt ); +normWavelet = linop_test(W_wavelet); +%% +mu = 1e-3*norm( linop_TV(x_original) ,Inf); +x_ref = x_original; + +% imshow( [x_original, x_noisy] ); + +sz = A([],0); +M = sz{2}(1); +N = sz{1}(1); +[n1,n2] = size(x_original); +norm_x_ref = norm(x_ref,'fro'); +er_ref = @(x) norm(vec(x)-vec(x_ref))/norm_x_ref; + +fprintf('\tA is %s, %d x %d, signal has SNR %d dB\n', measurements, M, N, round(snr) ); + +%% Call the TFOCS solver +er = er_ref; +opts = []; +opts.restart = 1000; +opts.errFcn = @(f,dual,primal) er(primal); +opts.maxIts = 100; +opts.printEvery = 20; +opts.tol = 1e-4; +if strcmpi(measurements,'identity') + x0 = x_noisy; +else + x0 = At(b); +end +z0 = []; % we don't have a good guess for the dual +if VECTOR + W_tv = linop_TV( [n1,n2] ); +else + W_tv = linop_TV( {n1,n2} ); +% z0 = {zeros(M,1), zeros(n1*n2,1) }; +end +normTV = linop_TV( [n1,n2], [], 'norm' ); +opts.normW12 = normTV^2; +opts.normW22 = normWavelet^2; +opts.normA2 = 1; % "A" is the identity + + +% Make sure we didn't do anything bad +% linop_test( A ); +% linop_test( W ); + +contOpts = []; +contOpts.maxIts = 4; +contOpts.betaTol = 2; + + +% -- First, solve just via wavelet -- +disp('============ WAVELETS ONLY ==============='); +opts.normW2 = normWavelet^2; +solver = @(mu,x0,z0,opts) solver_sBPDN_W( A, W_wavelet, b, EPS, mu, x0, z0, opts); +[ x, out, optsOut ] = continuation(solver,5*mu,VEC(x0),z0,opts, contOpts); +X_wavelets = mat(x); + +% -- Second, solve just via TV -- +disp('============ TV ONLY ====================='); +opts.normW2 = normTV^2; +solver = @(mu,x0,z0,opts) solver_sBPDN_W( A, W_tv, b, EPS, mu, x0, z0, opts); +[ x, out, optsOut ] = continuation(solver,5*mu,VEC(x0),z0,opts, contOpts); +X_tv = mat(x); + +% -- Third, combine wavelets and tv -- +% -- Here is what we are solving -- +% minimize alpha*||x||_TV + beta*||W_wavelet(x)||_1 +% subject to ||x-x_noisy|| <= EPS +alpha = 1; +beta = .1; + +disp('============ WAVELETS AND TV ============='); +solver = @(mu,x0,z0,opts) solver_sBPDN_WW( A, alpha, W_tv, beta, W_wavelet, b, EPS, mu,x0, z0, opts); +[ x, out, optsOut ] = continuation(solver,5*mu,VEC(x0),z0, opts,contOpts); +X_wavelets_tv = mat(x); + +%% +figure(1); clf; +splot = @(n) subplot(2,3,n); + +splot(1); +imshow(x_original); +title(sprintf('Noiseless image, PSNR %.1f dB', Inf )); + +splot(2); +imshow(x_noisy); +title(sprintf('Noisy image, PSNR %.1f dB', PSNR(x_noisy) )); + +splot(3); +imshow(X_hat); +title(sprintf('Oracle wavelet thresholding, PSNR %.1f dB', PSNR(X_hat) )); + +splot(4); +imshow(X_wavelets); +title(sprintf('Wavelet regularization, PSNR %.1f dB', PSNR(X_wavelets) )); + +splot(5); +imshow(X_tv); +title(sprintf('TV regularization, PSNR %.1f dB', PSNR(X_tv) )); + +splot(6); +imshow(X_wavelets_tv); +title(sprintf('Wavelet + TV regularization, PSNR %.1f dB', PSNR(X_wavelets_tv) )); + +%% +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/largescale/test_sTV_largescale.m b/examples/largescale/test_sTV_largescale.m new file mode 100644 index 0000000..4e42ecd --- /dev/null +++ b/examples/largescale/test_sTV_largescale.m @@ -0,0 +1,154 @@ +%{ + Tests total-variation problem on a large-scale example + + min_x ||x||_TV +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version + +see also test_sBP.m and test_sBPDN_W.m and test_sTV.m + +This version has no reference solution. +Requires image processing toolbox + +%} + +% Before running this, please add the TFOCS base directory to your path + + +% Generate a new problem + +randn('state',245); +rand('state',245); + +n = 256; +n1 = n; +n2 = n; +% n2 = n-1; % testing the code with non-square signals +N = n1*n2; +n = max(n1,n2); +x = phantom(n); +x = x(1:n1,1:n2); +x_original = x; + +mat = @(x) reshape(x,n1,n2); +vec = @(x) x(:); + +% -- Choose what kind of measurements you want: +measurements = 'identity'; % for denoising +% measurements = 'partialFourier'; % partial 2D DCT + +switch measurements + case 'identity' + M = N; + Af = @(x) x; + At = @(x) x; + case 'partialFourier' + + M = round( N / 4 ); + % omega = randsample(N,M); + omega = randn(N,1); + [temp,omega] = sort(omega); + omega = sort( omega(1:M) ); + downsample = @(x) x(omega); + SS.type = '()'; SS.subs{1} = omega; SS.subs{2} = ':'; + upsample = @(x) subsasgn( zeros(N,size(x,2)),SS,x); + + + % take partial 2D DCT measurements. We have Af(At) = identity (not + % vice-versa of course). Randomly permuted. + rp = randperm(N); + [~,rp_inv] = sort(rp); + rpF = @(x) x(rp); + rp_invF = @(x) x(rp_inv); + + my_dct2 = @(x) dct(dct(x).').'; + my_idct2= @(x) idct(idct(x).').'; + Af = @(x) downsample(vec( my_dct2( mat(rpF(mat(x) )) ) ) ); + At = @(y) vec( rp_invF( my_idct2( mat( upsample( y ) ) ) ) ); +end + +% treat X as n1*n2 x 1 vector? +VECTOR = true; +% or treat X as n1 x n2 matrix? +% VECTOR = false; + +if VECTOR + A = linop_handles([M,N], Af, At ); + VEC = vec; +else + Af = @(x) vec(Af(x)); + At = @(x) mat(At(x)); + A = linop_handles({[n1,n2],[M,1]}, Af, At ); + VEC = @(x) x; +end + +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +b_original = Af(vec(x_original)); +snr = 10; % SNR in dB +x_noisy = myAwgn( x_original, snr ); +b = Af( vec(x_noisy) ); +EPS = norm(b-b_original); + +mu = 1e-5*norm( linop_TV(x_original) ,Inf); +x_ref = x_original; + +imshow( [x_original, x_noisy] ); + +sz = A([],0); +M = sz{2}(1); +N = sz{1}(1); +[n1,n2] = size(x_original); +norm_x_ref = norm(x_ref,'fro'); +er_ref = @(x) norm(vec(x)-vec(x_ref))/norm_x_ref; + +fprintf('\tA is %s, %d x %d, signal has SNR %d dB\n', measurements, M, N, round(snr) ); + +%% Call the TFOCS solver +er = er_ref; +opts = []; +opts.restart = 1000; +opts.errFcn = @(f,dual,primal) er(primal); +opts.maxIts = 250; +if strcmpi(measurements,'identity') + x0 = x_ref; +else + x0 = At(b); +end +z0 = []; % we don't have a good guess for the dual +if VECTOR + W = linop_TV( [n1,n2] ); +else + W = linop_TV( {n1,n2} ); +end +normW = linop_TV( [n1,n2], [], 'norm' ); +opts.normW2 = normW^2; + +% Make sure we didn't do anything bad +% linop_test( A ); +% linop_test( W ); + + +tic; +% [ x, out, optsOut ] = solver_sBPDN_W( A, W, b, EPS, mu, VEC(x0), z0, opts +% ); +solver = @(mu,x0,z0,opts) solver_sBPDN_W( A, W, b, EPS, mu, x0, z0, opts); +[ x, out, optsOut ] = continuation(solver,5*mu,x0(:),z0,opts); + +time_TFOCS = toc; +fprintf('Solution has %d nonzeros. Error vs. original solution is %.2e\n',... + nnz(x), er(x) ); + +x = mat(x); +imshow( [x_original, x_noisy, x] ); +maxI = 1; % max pixel value +PSNR = @(x) 20*log10(maxI*sqrt(N)/norm(vec(x)-vec(x_original) ) ); +title(sprintf('No denoising, PSNR is %.1f dB; TV denoising, PSNR is %.1f dB',... + PSNR( x_noisy ), PSNR( x )) ); +%% +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/README.txt b/examples/smallscale/README.txt new file mode 100644 index 0000000..4525db0 --- /dev/null +++ b/examples/smallscale/README.txt @@ -0,0 +1,7 @@ +This directory contains basic tests that have little *scientific* +value, but demonstrate how to use different solvers. + +Most of the tests use a stored reference solution (generated +from an interior-point solver) to measure error. + +These are generally small-scale tests, designed as a proof-of-concept. diff --git a/examples/smallscale/project_WeightedNorm.m b/examples/smallscale/project_WeightedNorm.m new file mode 100644 index 0000000..2a2c589 --- /dev/null +++ b/examples/smallscale/project_WeightedNorm.m @@ -0,0 +1,62 @@ +%{ + +It is very quick and basically closed-form to project onto ||x||_1 + +e.g. Proj_x0 = argmin_{ ||x||_1 <= 1 } 1/2|| x - x0 ||^2 + +but it is hard to project onto the set ||Wx||_1 <= 1, for general W +(i.e. non-invertible) + +Here we show how to solve the weighted projection with an iterative method + +%} + +N = 100; +x0 = randn(N,1); +W = randn(2*N,N); +%% project x0 onto l1 ball, no weighting (easy: one-step) +tau = .8*norm(x0,1); % make sure x0 isn't already feasibly +projection = proj_l1(tau); +[value,projX] = projection(x0,1); +fprintf('tau - ||x||_1 is %.2e\n', tau - norm(projX,1) ); + + +%% project x0 onto l1 ball, with weighting (harder: iterate) +tau = .92*norm(W*x0,1); + +mu = 1; % any value works +e = abs(eig(W'*W)/mu); +L = max(e); +strngCvxty = min(e); +opts = []; +opts.alg = 'AT'; +% opts.alg = 'N83'; +% opts.alg = 'GRA'; +opts.tol = 1e-12; +opts.maxits = 500; + +opts.L0 = L; +opts.Lexact = L; +opts.mu = strngCvxty; + +% opts.beta = 1; % prevents backtracking +opts.printEvery = 5; + +% opts.restart = 5; + +p = prox_linf(tau); % project onto the ||z||_1 <= tau ball +[projWX,outData,optsOut] = tfocs_SCD( [],W,p, mu, x0, [], opts ); + +fprintf('tau - ||Wx||_1 is %.2e, and x0 and projected version differ by %.2e\n', ... + tau - norm(W*projWX,1), norm(x0-projWX)/norm(x0) ); + +% Check that we are within allowable bounds +if abs(tau - norm(W*projWX,1)) < 1e-10 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/examples/smallscale/reference_solutions/LMI.mat b/examples/smallscale/reference_solutions/LMI.mat new file mode 100644 index 0000000000000000000000000000000000000000..1a80d09fe435bc9a5519f8c457c7d6def61def92 GIT binary patch literal 1963 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�ycY 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 P%}?P2oeuCn08esi)o^q| literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/LP_box.mat b/examples/smallscale/reference_solutions/LP_box.mat new file mode 100644 index 0000000000000000000000000000000000000000..d34b583d2c16bd7681cee5d919a35469412f88ac GIT binary patch literal 14765 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 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/SDP.mat b/examples/smallscale/reference_solutions/SDP.mat new file mode 100644 index 0000000000000000000000000000000000000000..f742b2b8cda1a421127a0dcb7d50c0579a18b8ca GIT binary patch literal 47538 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 z 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 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/SDP_complex.mat b/examples/smallscale/reference_solutions/SDP_complex.mat new file mode 100644 index 0000000000000000000000000000000000000000..f061a808e59c6c06a79ac085a723f014b23fdf72 GIT binary patch literal 4227 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 ogQYa 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 QS{NIF$N~XjasaLUA35g25&!@I literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/basispursuit_problem1_noisy.mat b/examples/smallscale/reference_solutions/basispursuit_problem1_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..6feadd2071e8376bdfd8ccae0c5aaa9d3d4cf432 GIT binary patch literal 17702 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 Ha9ao1Pz(v8 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noiseless.mat b/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noiseless.mat new file mode 100644 index 0000000000000000000000000000000000000000..f8f4aa5b770d79ee7eb0398a2ceabf3fe1636efc GIT binary patch literal 17610 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 Rv_J9SPflH{5xv+h{T|b~rJDc% literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy.mat b/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..ae4120c80b1886226184257c4068ba0c0cd88622 GIT binary patch literal 17698 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 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_complex.mat b/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_complex.mat new file mode 100644 index 0000000000000000000000000000000000000000..a41cb1d54c01e2d416402fd05507082f9f01f087 GIT binary patch literal 24418 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 aS>`{?UfjtL6%sw35_TUFjMN`|^}hg9WO?=g literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_complex_2.mat b/examples/smallscale/reference_solutions/basispursuit_problem1_smoothed_noisy_complex_2.mat new file mode 100644 index 0000000000000000000000000000000000000000..a48f6dfd58b8007fbbefbd6c38b1244493b0d777 GIT binary patch literal 29950 zcmb4~W91VoVTlok+>jscYrq&sKGp<{?4 zhMC{}{Rf`4p0n0j=hf%!-g{m9I(o{+y2`IuC51#-^^}bT-@1Fc2(Z5PbO?OwE$YHe?opH8Z;g-e&1b$is*$vip4I# z$jad@K5)9=stYh#QV~=2HuZ~Eo^E645MO{WKaQ4lqD|5vH{K$jSlU4%(VxR0R(Zkd zEaDLOg{R}qm^yi2L(kOAx_vCCg6%+;nFPk zMa!D}##DnUtlR*d-F(L%NwT+aklv28@Y?D|j!XuQivoWAeVC+qO1_8y%R#*G-TN>) znS$;-oaMa}(a-WE=U<1L4ZYlOJ*!`z3QOQlT8_%LeRN3(TH@rIek7G2-(;Mt*0Dht zdl~OtjA#A#G~=B{V@Xt(rFo$sS)B0&K~t@bZ!@kJ%)f#8B!$47hm_(MfOJfeA0=m> zP+o8G0YG0d5MHXvPS4djxVm}y()uapjm^52%IcBdDyA>!iPR4tX>!ZC_x;KTBf+}S zgH{&?gT}taQJPOvbpxXeV1kdYrn6r+A6aUQl6@M}8G$?3F_@01KG$hmgX5hlW(+z^2UWM(m0{(BZ7KKheqH({aYyY~AT)%>$w5o|RS+XO)E9+<6Iw(TeK&*LWD{r+L z2!zA&49s{EBboX=TH}P< zb*HD9XvJ(KPQ+nG#pnX+MCp+a85b9h^cp5;?(ovi{C)g^zYOY|?jABevIx*xAW=4; zTn|-u&C%)02`fn<88iDvs+`HpQh$?v65%H-bllE30{1Iflk0!|s&(Mq7UBsa%zW{# z{&(<_4)7*E;KHasoiDw<)s%ylxRtl)r?ZQkQ=~gdK#p!)oQirAsT7kh-_~2Nk{aB| zGkYx>x`sqrxPH@b=yHwaOMU%I6)rzI3udBlr2rS4 z9?p!8vxg^>;Y2MR)rOxHvbx5tK1#EXSh8jYVi?aKyIxHIPYL9c8yKv?Z1_8`jz>X7 zZP6kS^@qvL7lap8hs(>r*vl*%gNNfZGyCg!Ref?60@->X-;D4V*0*be=^I}`eUvsP zWy9NWx+!nnRtQbU@pB)p7@%rPSFQ1|6~SZ=B}2sCt;}*;r>RoUgJ2V@Q?3Ue?w7-h zfNCM)pJ(dANnR4Tnb^O%#zGLB0v!dwT-EdKXFn2=B2LfTI+i|F-gEU8eyE@JX^vmtY|pCD}U4YivM0V76~gEZeC^o zMwx2JU;Rv(>*d(Lg+R*o!NnI11A33T%G_tyt`pQheaZD-92!Pzw>uHXi^ddLTH z=-oPAGgW3Rf5>C}CS>7ZVqsLg$c8}FIzhc#BqzM)C+pqZ(uW`M+Ou?)%mVkmj8mqw zg7aU3qKOU+S)td@T>>o`TqBrX0Sk~r@;r|A8NouoYTGC}B$=j#3N2N&3=XUUOt@d0 z7OX}nRFctBD)g2Gq!+uQ8d|-SRQ7Z_!jF5YeI@~2A>vB^Yft?D-O&6kCwu!JIF3@R zyYHu2`-peMe~F4md%P6E<2FXew6EFJnvIa-$XntQ{x8z@_L$ZEx2z9AR9$?Y@#Lvs<+?Dm3#Dppfd^XDwFL$@kr^hV#K zfk6&jN_v<6#~glrF9~rg$4-v`>m3NpZpb*Q0M!DQsh|9~bt&BTT9^w9xlDe4!yRVv z(y=0odFmdx!rUzHeQz7={na+q=}Vq(YZ#=F4=0(+`rVZ}iAcQvNqktSMk$S_;m9l&V-S%=pWiyK-?@#El(sYY>-mq86n5Z z3-piPe5oHYy;&^1{!ka}FG_)DN6Q>~#6p~z)WzrXIlOO)qxWwbrGmOgjbI_m<*#h? zb_>h~VMuv>f|wa|v_62lM;L7o;KoleLpg~Y`onHv9OZ>K$@ZJv$1(Ap9q_$ta+3Fx zcjLx!s(na$N(<}1-&M0Ejiket=;cnx2IKiZhKgS;nkIb(x{dgp*d_Fa4l8kO88*s$ zGJR>c^*jY-P0S7#f;u(%AD2mkcB-fD}owyuP%SpaTCAr^UgY4W7>`EInMp_8j$ z+9m8<@O)Z?hwta#aEFe)AM2S>edr0LSe-ZLDKe~Awb?$d zPs5!LRVa~kEh*yn){5#IQ7kc^cUGD!8geQ-zN!soVQX*ETwh#(QopZkyDUNKeIj4j5)C6sGmynt6B?n_!tx#s;)Vx07!!Dyj${a6WY$W^6_i1jgR&uhX1cBJB*$X=aPOU#YMp ze|p25X_=t&&=>xB=uMBcR6!+XFb$x?ydTG~8zO!`MtM-1dsiN*SQPr(I^&xU*SY~_ zm!T+JGnjTP>RP3w;&$5lqYbIz;jn@5Sj=^)=i(%N6VSnJK_zl9ThS#J9W)Il4bRqz zaCao!+HQzDctiR8PA#099kK;^&9p3xKlfGde$>WE;o2_2;J%V|`=odLD&mIXE=jcN zf8{Hh_kZO}HHXOjpU3?WiJ&GEn=?^)({=1?KMiM+r6x{x=R}(F*D`U$Y<~sDB#y%^ zD+Y{EKdWB z`(SWua9o7oD=;=THo>5#)NVI=f)OWzd2Q{oK3Etk2y+U#kM1`iehyYRzCQPF3I(*4CfacZHGyOVq^_lGn%CCXId|D^-F?PqA+xHzw6_k; z#tAE38QY;E!A$Lo0Pc>D?jvl#8e4NAJG9t zb}R>3%50b_#8LbW2|+)b!0n`Eo*|_U_1K#k^Jb)8l<~D8w4f6+e?tuU{x8niFD0!Lxd$ltV)7J!Ty!8Dk;{miHdlb;0&c zMd6!Go2Nr=zTPF0tRipf@|;5D8WzNaC9E_0l@UUjcA{h3xXsrVON`EqYw5NcapuDo zCqDk2{qlL?q(whGk4Bl+&*EHe?|vv;=dL=_h96KZfEEKgZji(tU#>iAd7PDidM!hJ z!>>->wt0q3t#mEl3Y*%I3Vo|q2Fi~KJKQl{ z^dFXlDYclR5kp1zS$i?yp?A;&1+hp-*fv9&^|BoFs0r>bmGm?8;B3iEf8cFPjmCoO z*ZBFrC#Tlp`@brsq{v2!7jvYwJrkzg=d`1z5o^=cz7v{SGiwr-YgVn3ys{alZvS+Y zIQ7|FA4Ukq4gb=d?SH)ZgMNV&Hma)v<356uKi zu4##KY-x!PSO$F;`C_b;!TUYzk6{=lb*4yvVEQxA##6Pk@#o^XZ5PCvbS+cx$B8?w z%9amPJZ}#`&-+T0=*fNQ{$5|sI|Z3s40u=S-D(m~4NKY5)S`3(8avM0%*d#g>uq_* zU#MX;phaetw5LfdbkObwztfa!ZdX|hi`J}>!&(dPmVdTRhD}Y3q)g%XG)rVXi1Bsr z18@H^F^pkm$9m0x44g5s#E&H@XR+{>jV%h**TW;6$X8p0e22Z2C5L#tlD#?rq=cq6 z#g#_;p>Epr&jKQ7cdu3zr=O)xz?Y9BC-hf8R{7a7Ixz`qX)j*P%u@&7GU(6E+htAg z5mnNMbXD{!G3a9q77mnbtI=RYb}9MbMxaYa_2It;#}N-+arqz{-F4~Hq1BXxebpU# zETYhH8mnk#>&4A=ID_YTO-?^kkD|KpHxZSjiOC<1U}vZO?Kil4%Wl-(z%QEhX&AF} zzndOCg0S84(3jyay0>_|p3PfIdm%`jub#jI?*tc_80>6~1zpubiT;|~0`1nTz`6|9 za^>2ZD5m*!aEPI1e}W}yT81qs_cqSLwkpNw*Q*v9jgQXZe?m}r6m0Isg3S*b?BB?P zk#WDXNVF6k4h}g3GVCBn&km(cG$%+SddRRU8i?Q|kfCAEw=`4For;E?nm?Jl&Z!Vm75Ozmd){V5&)dAP^ zmRZF6uc5OmQzNL`PP12MS&FYLtGM%)IA3daJ!!~UR_1%LFI6yMx%4UlHQe5;4|<4P z9S<%wdqr$1@AJn@kFw{JG;~$$mFm`tzFAQ-y&-+qpKVqEWlJC`KS;q)b!|5^g2Z%v zsK%~P7;sHLgSxB2$6JWsAKn<3Wp_hvEAN8OmZ;&R2SeQqMaSjMYZ{9MJ9GGWqw{Hbx0%W`@07pR zNJ;Cv`L?i~NQu!zvUjx?Z!X9U*K4X*yB{7;J$|TmsBv{Obebu}#b$plK^)>ABJR99MHk(bXWDjRnnTDSp|lpW3#4kHAC20;%ig5EC* zxEC}-TG;HyXF6vw9Jv)dr+J7kNDIuPciBGTAygz)Z6;6y?~!}+=N6r-J|9xl#}so{ zeyC_F+Q^weJ{#JG#0YxK5$~_P9Nj)vRNXcQ_4`EmD;`R(R7)OHf3+Vxh~}SGHQrpi z-kgWAhe?8m4MJ>09}q|9!sH_n;t~UMeCQN^K+wH$7oVA~IG`G%59X-2ej5|?QCX%( zF>Rr@Pc;7zV-fRrLbA%$_OVe7#n&sF4q5e9w-sB=R zg|Oys`lPu{>Vx{kcUz#khWh$%4Fe@v3-x~C`|gNNef2MEo!5oWcsQ6*bLR+g7YXAK z4cJMti$p~J(1GfhW+zYqdwfFlb#{iUY#?0aMa*5{Y%d*^mLzvqdON*FYI4O$`tKrk z-r`fjA|WorZ{Ch4-w-rvfP)GiZI!Y;t6OSle}>_UXO+T*-zoLD$LpGz9jNsfoIcD- zQmDPTO%Bg!pqY|TgO@E#v&9Ar%S#QrnUZn}0xh#b#9}2L24(q9242S@04H)}N#!K~ zC&qw~fm9XWv$@t^=F9ia32v{7Wc=@rzJ@tyFmcvB2R#^rn3ys4ynCKB2KaSlb6NO< zGni3ol9~0vnMMYSTYgi{$>~~?HnjyR&60qK@bh$4xl0j9xt}+?EQ|0zDKP`~spbLT zXJPKC1x_c=^47oU`x_Bz#|~odd(W18)(c)Kxw;NP4>GhNN`Z-EaARaz;r_~$c+v|N zr!k=%o8oCFm(3ag{hnK`mhDJp;Bw=nOk)IEK;1C!;Q4>q?cB2HEkZ9yR3m*qar;vZC0b2&)jPTv2?LN}<@J zNvqsn>H3`4Ekvz%f1G>3IZptv9ONWRhLzOpbn>U2o9zIel>HNSU4D2}dj&|{D*u?b zp=GfDX;kUAV7TS*3~%U5ucU;lt00oy43|UR`9BSTf*FyUx^0v0U39(h$a}FB^1$$s z#OCG4diBZX^EbEeBb>?FVwv1ufA1LEqF;Dia37hb!6eYq-CptG3bmuf^#DrJ8&bFq z6EFE}60FT1ISw5`Vx|&Rni4=ZGyGlnqY0sHhJ(eI8!?`uy`c9KtoVn)l8ZeKR=A7@ z6!&+nNMi0)(L>kW(42)}tFF_RC>`|PwzlL1^um1RGThiZNJ#hrj^ur?C11%3R|<9? z-8BljT`!Mr0*$Tq#0%B#pyl43pc(xvJpXE}8ArMzUqZ(jat_6pJQrqw#5LlJVWu4o z{YRwUnqvHOJO-}!ClHe%0bK;G);X-)Oz211^3XK*n7e1BbMt$2GEi@oxqt11}OZojno-(-u%V z+ct?(A%Rp}zBgq;T)ENIi?cTQ<%Kj8k1E3y$r3}aPdQ148xpo2#1YRVyuLWmHjapr zw8+qrk)bE-{2CQj)V_!N+Y0zdi{{AOIFTk}Yb(n$b#L2`dcU6kde*wQe4E@+BZ}@C zcv$bwck}MST!38L+~&o#k9zxMa$WT%S_&@(5?bDUTWK9q^|P!gQJER!Iq3=PUNB7K z@fLkBaXObu1$<{(M)Gl|Mi?nXliz9}KkS#$HteeTmydp?cHqJS*JHpzEe83d1zp^a*lW7y9wZGJIzeykRn5UJ%@5U`FPhm zc$&yH$-KXG9?#98rRStHQlT^?$fM)|bRX@u0qf)JBZWz|oH# zowko$ls;k|tkN!D{h6<1rr!u`I@qpndG)9%^xkON#7BQ>%WZH|cq`=Sqf$m5)ydwv zJguvC{EF^VMeCPH0q1)bBERyOSCJyZA@hx)Yy>wSyJ61-oMWyS#+bunzBif9z3{C= zp1(ykW+8Y0oFPLP7GdXydxCCyGD>8<$W*+97u$Usli1`ful<6q%ctZ00@QE4gG0H`fdN^R_2QAxJHC_$8Gic*O8M=I zA$;)Z?9K-ljmO=31+DF*;*gx1SIz%}1I6OEgIz~A5 zI6gQkIz>43I6XK4?IHG1d#pXs0pb94z&ZdOA&yW-tRv6~;skZVZYlKLzQ1^cAQ-34 z7nbqy#lRJQ-G00vM$nIo=ljT<`eHT|{@i}Lph9quzsYZq@$tiKDh%F!zIcw19uLSr zl{xjpII-9(+1uJj+85gQ+OOMV>{%R?9BdsT9SR+K9o8K%4lIsJj<$}Gj)jiBj_ZyX zM;0d~CtIgTr$VP*r*$U`2L&sDRhSjXYQSp8>ca|QO=c}(ZD576PO!pQPg$|76l?%C zVKyL}0h=A04;zFnnXQPefep$w!3JYHWy7*jumjkI*@5f^>~`!v>=5>3_9FHMb}0J< zJB!xiB`xH4P?t_IhDYr%EkdT;}{A^Z*81a1bmfLpPqt;EiCY(2Y>2@QrY&$c;#-=#6No*o|1I_>Fj| z#EnF#KnQ{Ed95!i_?x;*Dac(v4Cm@CFFRLF0gN z(YWA8=tp2YG#>ad`Y{+EjSqf;egY;y6MzZPgkT~x5ttZF3?@O7fJxD$U@|lrm>f+G zra)7GpQ4|FDbbW*Dl`?C8chwRLDPU~(X?PXG#!{8O%HyCeg`K=Jq3q=JLApE|8k_J$C zB-^_6^Aje*7dy#I;vo^B0`7rxVFRe-1a;lmZ?7-62<=z*#0c)?4*yB}4i7cUFP|xn zii%EhIpoUjP8GcKIF=JKDVzqdkWlkKraqGEI52l|1M+|8A7-MTu9b1#i?pn@e4xg?8*pdz&iaq_@)xuhGeYDc zHF_Ei?4zrt+&Ryz7bOGjOr(O#{**#qD%r-m>@gQvf0`8q1n-aY_D9{pB6X{I)r`W- zFQ>AyM3f6I1)yhg0;_S?rnd@xpUCF3ypzI`n_`HP4(%XcuQ}CC=joC;t5ztBFp1te zjSCRG%Iby$js7dsw{1e&Z;ptE1j;V_9qPxDj!j%St42iTLOtS{YCD%2{REts(Wtj+ zZ)vr=x6)&Y8i@ZKbjMW>#eq9|y@ms~EI?G^!43m;L$Xc3gqu5mPkEjvp%p;Np_vLc zVu9Wcn|@kW)fZ=~?iK&$)5j;@74YKfC5GKsi@&&NRvTO`A99}Vz3jW5-%pi3A8C;{jx+71^e7m=^MZow7)ov}XkJ>bA*lRw>G#u4HSmDC17E+WP^^tE{}+>|7P z3`@-)-pTDEzdrW6I$*8~r4}Xu41HuU_>$Fnm3Z19!^FM*##;})`jlUfdUZUaH8JDG zL))>LDj+jWe4Z}iLO{KuQjf>r88t_Jg_U;Cwfd;tRv6mGoQ1P9*Yc3ttL%l&P}}+S znWl2~+NXkM4rlQ1x{rUMv58LiiPR`v;xv>y31qK=nQt6_|NOe7ziDSM%btli-L}%k zCIWaj+UOJK`l1lVfnhetx))v+kTN^iEGx)#YCeS~X3oX3PGR4i^^NGt{OqjFgTUjB zB`G|`$B#b#YxIh2wf_Tfd=M=)Kp0eSI;?qx-Bdf@3d%n~EkMur%h&1;`;7fnRwRgs#b9>6xs0ifv>ULP_+}JE2?a389Tsa_)-#qWbBH zru8?kpYn4Lwna9>#7x)K9vs~>1Iz>c<%EV=Am*!JQTO|TXN?`-Jfs%I)t{gj`8ZZ& z)Pp_S@tI3?5OIi^E5m0fz5KZga?p6{ua%;XLvoXkm=2 zn~*-QG?5C-X8l=Dop!b&HqmjtJsx<|2A0xFz6d0vFYoC`K92L{4F)cV??N_^fgi4# zIc5p$uHyrFqzs2tHjSD{`|;F?wR_(`7H zGG;p0K!V4K1bKfu&U^`yyxEm*jD9rm+kYkJPaZ+Jmi*NP-f47`{XdmN$PX>o~VHw%55tMj)%VOLlh7d`uh&1b!K#&Y|hpexnPF%91j$I z%GbM0kp3&)9%fl~Y8y7|zt1w1M)H4p+C3Kd-LLv!(V=ctjQ9_qV-^_)sQnW#@;!hu zh+7D%sJ6tnVCt;me$o6z&~o=yFFVnjHkJ^Bo`R?;4`$(UeFdVKa1DXN7uM=Y<=2^7 ziL_GU8tReMPOo_nf8ERfSxlNPp> ziZpHFX&pO!TbTDkj)MF<1ykya>eJ7Wt-`az!Wp?w6K|jImld4&+m?LQ5pPZ<3z*%k zIWe#Bd!uuZo%?r9qo&+Xv#v7jm0)Y4_v*jS8mh{g{n59THO_AVaaVtp_zX#JuF7(n zwU-Gzt=s)lwR7Lj7A|0IIWOeUED#L?&U>=?q+s{5)F~mxe<9sQ35db=5kzvjSoMz< zOs|zTsHNRi9hRvWs2wu9QWc!T=MrvWq?ED=cGnWqJTPwnK-&1!kgMw^*NVT1)fLVvu-l#7l?2o-0+9p4#*i;}s&9 z@ywch4pg&(R4UR1b~hJk_ulRV*q6JE-dp-t!1N5`{oO;)OBnb>*I>6$X&vL#*2u|U zI_2L~jiRn~W~k?gHk?jYr@7{*~%eH#c237qwLJmV#k&3hWyK5fo$fUJYKu;~mh*>}yw`_9^;~N% z7Zg+J*Qil+w1L6=a4q?0LYS=kALz`#3}yw1rWbA&yG<(3{lr31|G9k}c)?Pq`by=L zGc1i>qz!K0K0$!26-l2Y6j9SNWs{9{H9@t+Q$&g~ku3E!uVv*JOS_eQqJOEuA4?v&ye!C)nC^^cxNj zv%Ku}_*u;gt%;&$t8wl-{psn2`A>EWX_@kAvrTO%=VlkKcMLFZP{mwi#%H-B&=F|# zAxO4YdnrGaCnym;f1JDAl(mKpPjB4&;jEu~$9EF??eooevq`#sRW|nK%?uhJW2CU$ zGQI>#)6E9396I#So^}Jd%WpoGUi!fkgn-q4`=@nn6Z;rkFO^!Pd;#$fr2imvpl4!Z zJT^IqqTaT@!3MSX+{KuqU-wYU9_eB2#uwQblv6)8+)GCPD;%1-HczfN6NLF1`lvnjjj&dZt165>KJU-XUDjPTLKz-F9_(9yRVWFEOEgDStoH!I`a^c znW^1Tvn~n8iOInODMKu4ch`7X#16$d41Y^HI;hsqF=C!-E<9Z>#)H6Qw85Z3@3_5ntj@5MqrOK{gh!k30n{hEMLB)g~cf2eGu@U)_PqA&ei2mz1atbL>ac;w#L&PRzfkvR{bD#xTgelp__73TymgM}0hjkHb+lK%Q(4Dcu+}l)naA|ht`Si1$X)T|o+EsC)|(>g^*_qfKn0&m+YgvpyJN26On{zIsMQwE z$}Z1Xmmz#RnifVs`R|v3A??bbwu$QW4MzB}H$e3E){#6;gA~HR!^1^D?R+Kb#I8^%w#_68W0j2m`skqfSHp|~4SgW;&QO*l zVH^-5WR9Z#Ocr@y%-~9r_iU}a!7*;r1B+Pr1);}upJ7QLMQ11yY~k4{iCyy96_H)T z*%b*a`RsuR)^PSf(hzrcN7#^cc1P4ud3HzK&~|o5avFDrAw11G!w{WTo?(bj+s-f~ z6VYd2!imH)G~qK&Vg6i;vlAwBgu_PDoMF4~sA4GuQF>0xQ^aKAG?J+nPgxLmt z-H&$ZMJC?gKKUnakHNionw{L&{dkvK#31UH_@9+M<}PM6`e?1g?b`7THV77YUY!`cII?r|6C`!G1yL_h4_N8lX+>;bstY`Xi`4;K{w{Vpczf^xFN0U33KF z5zHPya4);K(2pU%2K|6VM*xogqp~=+IKw0_BXN+AQ-t4l<8D*1Cga>b9_D`;iHjsn z@p<2kw+&#Wz`Z3L*6Bs2IU;G;>?o0RY!FK1Gqxg1n)$OBn7Q#bn(3 z$j6tQ#X#H$;ytsMut#_FlSA(#pIk~7LvbU>_d;I6@a_PU1W}QMmukhpM-kIz(91b# zqK6qXz}1{VR4?(RS@Fc92fDqkmstEek;&w!UeZg~VgtN~=X)zJu}|(~CMTkL$uC2S zC-5Fv_U>O|3GQA_QhexrdKp)2@c4m!k5L&*c&9mO@S&ILGOKvv@dMYMyfT*P?)7By zhhCb?%3=fj2fjTkWi0WX`Q*fhUb@S+;tBi*p}lZrEXkeyBt>-Zv&*4kgC`GSdwI%O z(!0He$h+DGx;u}O3A#J4l2f`npAriCJHHZP`n!MpjUm8Lp*&a+s}C?xIJNlyDW6lTc=O`@C!gno9DBaC+&vD*5%0(A`Cr6H z>3{a7KuCzp!YK+$sn*)0ot~NgibnRox!ZV{kZk((Fw2sa`n9ca_(c=qTtX~W&y-h9 zQ;m1PYG(y;_i%D3hw3;9s6Rvpv7olj?aN9^42{2)IH0rI&?JLc8!Y*6EM&&Bw4|i` z7IF^F-O@Xh55c-rV6Q&9yImffSWoD9d^ma22DM5+lrc85uG|i2>gvoZL5zW9N(fTE zBqqS%08~Z~lrG@p3xM{47 zM@d9Rv|Kcw&eq1)n+PSRs&{Mz@;)%zJUU`^hobd&<0BGopbBTmag8sPe!DuOgyLV(IC>+n4TUaQV$g7YL}b&%0rrwgbd?(FE>PCy~FM zt%PJ=}`0O3_R;zWT}irlO=!1GifeH(R*y??e-b-CXm%!xM_z$hHRI1cR2x z^PgRdXFm3~jqh=XJ}_iy59GLAVtDD7-@g2{TGm(6a-frKSn%m6!+-IlQ$Mc1C63PKl8wRbw(m4GUYGc?K7DGR;JWNR@0d%u3*T+E`0hhnhHBNs ziQ*ba09Smum3KIqUiqr0oA^w-U(vd@YognL3*SG$I*_)o|Lc-RSIFnLDV}6=yxsmT zJfdDkIa>5&gClG(jXXuRjKYmd#Dc=lrd*fRCFTBM3-uvtr9u%N&T7tZ+*B6f5k;YY}20-&P6G; zCf7tU_&u%Z=reOnC-}ArbD|9!Ygf8Z!ra!e3aousxDsVXSu`|cW zp5Oco*>X=7I1{4K*U~@XGISO508BzU1;3VCB@e=;n;FjGfT@673f9*{VR(3E{+ zoJ+bcs36?7lA$7FRx&JTL$GZh`cIV4?n{#yK&g8tlQd*Hy{;kd_4~AoNTxikC+mN& zE(+3oUm{jx@1t2Vt9;*W^^w`}6QZ@uE_iWD%>71=p9075f2+U$=gTGKQ0e+4-`y}_jv+B{dbbM-EylbiC+qw+r)ZnKo zDu>=~m5dM9#=|{9b&w-bibaD5?Ud)*gC#nydTFb=KSVF@rHXTv`*=_HOFnqcPW1e! z7o?|HCLr}$_ne6yTNdYCz#p|=Qg_AF(9nubj5c2JvMkMyx(6 zXXcpU7UT(a0NLp7>X9xh+GS5xk!i@`zW&sp{hqyJUf8NN=9P|Q zVy!uEGJ;DkzZI@0{PjcR*s~$_f%dycO0pvAiNw#}8|LN=RDTr9UOmj4k)|f$4*g#A z*~*Z$^f>O;--m}%&R*)*KdJ;$Yc|qV@)yMIUIj${nf=*~xXH@*BJc`2x3TJlr`Yp@ zQ`ks5^Gd;Vj`lO&$p)`0>sk>i@78>yhmy%ysB|0QWP_@su+DT(*p>W^N$+P+mf_>Y zeP*WbhhOk`6Y*z=N(_yTr+!{-z9NuAb$zK;eD_$2F3-jf>GuPH6fKz@4k^}!y!`RL z@yR@p?!odzi+~duD#T%<^n6wS^QcwWql&JJYpeYfue*?7~h zesNR#WeeBLoqBMV&{~SPcPA6`DXWiyNBYmQs~bf8j;Spox>74k^B+ZDryo6iV{lVH z*Y)*<+#~((yfy#>a-74(l1|k)qkoZ965gJe{Om735<+CNh{r&I(AZezk%T^zR4$_0giEzh!@QY)5NLpd6HGQ zDaPt%zjdECc&RtBc2bGmuu=+IsPF+Be-F(qEM~T*IF1W^;UYJ3UjBHia$NrJH@}&z zc;a`wq@F@VRtv4Kwu;V&?!7@Oms9>b?CYuBl(K6R{tV<3COhOYq!hp?yCC@q%mQN?C(NOrJ3K8E1~wR8j}O%WI$Cg#4YXV@ zt853#LgWiZMt<9iX3C#~Rq$kqoUrBzRKoKD)$ZpnQgG)na5K{~{Y z$BF{hjN}L*#E^p6*avz^olMSjw;OT2o*&Zfq?OLmGGD~Ge~Hum-RCVX{U`^qi;Cn_ z|Jn1+4~U9Q*N2*4yK<54G`QC5YX?W=PG4uL(EAks<7(*S$gK9gqWiaU{6#HiFY5vJ z<oq$2SF&KE`JkLJ>4Bk@j1e?(m0y&U^av6*Vt z)8+o?Pj?MXl{LXnNxW7VS zw$!Er)Ks2EYGlgH-0%rkcCsUwpohZr_>>>-;ady{i$AtsZ_aKjn z8nZtZwiRL6!rdxT{IcjK!_n++@iVTH+6ZVKUigVH`m{|^*aSBGA=Ui(81IsGIwS16 z=J)WD>{$fPNPOU=`yH!aw>TBc;!bwh)Kx0*&OM##`pu69Hs8}}lqQ@H!r@QK|Mj8P z0+u*l-N*n-KM!TCi6u8lj;Faedk4{gU2f_6wecW#e>eH9~pZlb?P^~RLR35CM4+dQM$35Pl z)TXD!qP)_JwstjljE9L0PTN)2J6plhw2A@ZpCSc*&UO>sjdU5U-)-xpPq5$_dOc>D zngY$7JD;N{e0MKzMo+!L7Z@X@Zg$q1cq3f$t4${e0qg)EXOu3%!{zx0+}Sx2RQk)& zCh1ZGv9cJg9T{G}^T$M6O)+}Rf_7`EyqZ{zg{+Ty&(t8ftHE$n3>yttWcfzk0l*72LP3Wny8m;`JuECZun}tv^ z=n>-!E*P{Sfh)6f+q`)loQ)iOG7nZ@{@+Z6bb_t>v{X0jc0)R<<_Sl+agw4(S-#rT zG$k5XIUk3Pc}NtZy>d|pMFH)bv8)eynXc`R+HEUR8y zbc35Qd(&>LN`cVRV0~MCoGGD4aB+1$2&(I(8e(awVyN(bmKxU14Gfbl0zoa+gYfyO z)LKc>koA8DF1gX9cRry_Ax zb$+X;*8VAVJUv|e+Oj>SJ#XK!m83xsdqITy?3L?fj3gTva4eDQhgd67qEbefh0c!+ zfLTN1y(Fn)cRdC!5@Yz@nOAi1QlbIt2bTo`aa0VI&+70u<-tZD0B2kW$5#;JXOv)69iLl$XQkp1%%vNNMYZtmiRMgwKK2pxu^}upgcM2f&v8)+&tcaOpS(L@P+yU)5^H5Y#i4k)K; zEnKdcD@Vf*SHhJKvsZ+DJ<-oaiDl7M(MKE;VjN3RqryUftJ+|nt?Nt&%kqzh1{2O2 zm!4;iryg`Eq|gvFvxVf6XVpJ%43>}C*$hSGc@^$orDp@8^)aM9kLtW6!}_46?xv{# z!b!H$J4pha8`G=H)g022ujA>lIQ~%I>(#Pwys#sdSpM2!UVje3tKs6=vAqu4{--sv zdM%xw9IwKOg1`L`9zKWDaNr0xS5}so!2+$>#!&MA1mKeo3t@`o3Q&X=@wGdF&Ropb zJ=gq#zW9utu6?;Z%o+mVk6~c@&;9c-=O+VaC3FqwML!Hr z5~Q5+I(~$4F<9^oijDK(rY^7y{zZ+3Kzd)&x-VksUQVwWcloJM=Hj~Ra{OfDmM{$4FFjD;`pLO-np7;84D-p-a< z!hv9E{PnhvBgSO|sGvyB!-AZm6-b(~hpC(_V&fxIib$b;TQ)9F33%%jd&p~p`)HOw z*yv7@UI$-9z5K)iZ)Lv#@!{UQ6$~`Jg!f{j9=_M`eV`G&6|Ut{4Klgh6?rY&RRN_2 zzVzmSyWcxUK}|JI17)z&dV2q&r*@3Pv&DA*WvF@|OAS<$Hz+KfbK=a7=^;nbu z5WQYuF*YWM0VrxMGT4lAKytF=2A5wiB87mAa@c$Ch&)U}kSHdsh>d>;juHiDV~Rv4UM`L@2Nugjr=jgy-=84$<{hgG%vi zs`-Qk3mAb~le#Z<)oAw<&l@<4a7=gRy$OX zK1wWlp<>&C_%|T0qH?CdXNm~bDvwSLJ%wdK}1BW5XfT z`YrV_VW=ZirAyzu9 zX!I)I2GE|uPVh?9&yEDgE2o_PP7DB=RmPsVp@=?Ps3%pCfa^A`x~6+TE+~txAe1?E z?DjIzdR?)ayW%4v@q}@%k)1lp)*vgRjl$)_TaiAUx3Sb_tTSZ)yh^KN?IB!dFIoJ6zgV&x#pkX;b3bW6-Yf-&b&+}6`h)Q@q)`7rV~i@_0H$W+9VzEN=}OGwI=u4E6lfq z*wRTWF&Dm%N;TeHtxCvMNaB9UM`1NeIgVPJl)$90Vz-Xm$^=?R2PCmeY1UDmQdasX z;i|P(|7ke03w6o)c6AV9yeVP-+~s1Bvcx-N^Y&)L>l}O~5ffGpJg6%i+eW}qM0K9o zIh_27mToXF5@g7+IpC+}ml!=0{7$QuD@(Aj6@;8j9ctI+6nC#FHFEjVRDZ28Ou{2{+G3+->6ZbgpNT&Kvjg)m@Du306@g^9Z+7_VLyFsU zP24seJtCRbVW~)y4UEnw=_%`75)9kBcn;{pql4@!=WC@Jveu3O$1bz-=yP{yDdvU} z^5vt9YvdW-s1Jf~#hz`5Hk-H~5F^2Pq@$CMwhu-qe`z55nH{Y=wuOrSySjgT`f%j$ z#T8jRs5Xn%8R$_e`0&qroCI;HcRH(qNnlm8+(u?EUVK*CG4yr1>0YrZEv;vRUDAhuazr z^c}XpgcsU0Y^~DsdQ&2(T*=_&cP9bIx8hl(f{zDSE55EqU)*|#pPb>(9ME`tv$t3A zOd^fBXURc)@`YD5 z@R__NSG8f&|BKWhN*(=;qbX%< zlDrX!&N9i%0FT{U_I08$H*VP1gaxN%juIN5PZcK1x_t=eo#pT$Cw>@y!VsaXhk9VK z8nPLCh0TL2>Ct*+B_>dWzOb}ZlVsJPNvN+NXu2j$ZSd*)_LZQ-0Xy-C@7Qt|G{5Fe z>~u6taF<(6_iIaaSwoEzmd2vsQ}MoQeWZ0vhL^#8VLk`jz7uAs}l)ii?cX5N#v9WpSUM2!d$?FjzURMMC zH8vuvO$kO}ZLnuEs2JUY0P(q761J~N)fIl zJ&gsa$q>U+dY%f>O`n#J-JvD#t65CDlFKe$X2d>aykd`Lzj*arn&wNjgCl}6~ z^2ZPE6*`C=RK-P|7Te8LooM=O5=;lkoREG(+# zWH4H>HMN45GvO{hdWii4z9%aO-&A??O+O%`6{m<{eY7>4Sptq4L+Ipm#D~4cl9R!y z63Goq7*iW{L5!tJnKPXZZrR6PLTT3#$krS&5hTmpu6ezejjLphV66RlFpeAEitQ7$ z%e;9bysRt94du7Xh3kM8P7SH@mbiqpMRe;iJYvO_2>yofwrBm$etik9JHR!<&(orb z$3+{eC4-rF zB5<9?LmWo;P0aVS646YyDqS9k^e}4x$Y?bl$nqY6sLK zCtl^Kq%Ktu&HJcrLf}k}JS*L9+peiH=(pbKURc0RGT`~We0bsXNkqc0H<2Qw#l>5t zd8h71ZO4G~B81-VEQghqSuuJu^7P7V;zAFn<~4jdAkf=0nwl7~hP|q+%q{zf$XNc$ zFY`$VEkK{-yLqy`J?(azH_0R#j5Jhdg<-}1$c))#8e(Rg{hZB>h!889{(e41uChqr zIchM<$Bgz08CE63vc&fE2FvqfGEqRb(s3k!Aidpd28{}f`yCxrqgMzlJ6^s+;xM@1x{F(h<9ZPBt+-ei4w&M0*no$hk-67^IXi@V8GOu|(s3c5L0;D-+xu zvjW$T|CF2KdQ&~>900sqA?VR`6xAgTvpl`9X)e8O4vB6E``h^OXqIC~a-sWC1axG~?;InE=n#v9Z4|VGObL^0Zr{vr*QF_OH`_ zZQ{V$`JKFdB}>r&P~$5$@}S0d*89HOB?xiIhsZ3B+^M5**x!eD`9$+l5VVhI;0Jxt z9A7^^wHH*`E^YwTl6RFlCV_KvP<{E^rO$0skbb@Wge-QrKpViHygAQU6UQewxhE?O z6&)~8**|K+_KGP{2czNp>q=$i$HJr5fw*B^uK`}=YqEV1ID(YFL3Zmi+aUWYFI zG-&s&>?nRufkE9?K%JNeTILk}Jwx>?;L`wXWt9r=6)Tsh!i2A@JKW{L#r)`7c@z2F zv+w+c$<{48jX%mhE%CS^O3;vNLH{>a#`wAO)7m3<`=mbYp$04gM{bw>&y+oGf zF@DeV`R(R@*Pb(Ow7}-RJoDC^GGe?9j`DUY2ofcqs~MAu4S^ACer~+*=d0@9{u%Tg zr4?;Ot4?teVJ!jwSn#!Ew}t96QuJtTAV;}8{4T8wTT>3Jpr{liI0?_>g zM(w5QF65HE2md(xjV7En3j;pfs6mL$`dwR6#&e8cKT*uW1M8@tx)K+T8SuGyKftN7W*~%D>1@k6fu~~5lSD$}q71mUvPE#;9 zv+bWZp)d-#LBWPT{)v5rSlWV>ck+;Nb0?ZI{?Su2nEMOnannNjybKlsZW5OQP&|_G z+V2e{NcJdXzrafJAEU}Crr1kzm$yDeUrwA*{M60W(_fN2)lqS9jcP3>py)_wJ-M8wxMcahkfW2ss$6- z*u4VNqE9rB({B)e9VNs1&rM;%SKunY3QSaiyB|kzUq!4%?q{Dq-7PB>*e*&BMM~V% z;+ikCJZWIoW~fWPGwDFCVQN3B)*pPXi2g9m$^vUt2dXJYsGmq44uDiT1ipHGw~U-e z@D1r@fWwd2_CEhCJ-ZYkUG-jDJfH^#nLJO*;>SDk)BdwSPM_`!QQ>m2^`~{5pWtT5IVlUwL zWN(zHK*FJ){2{6!AdlWGgcPyw9>Ird5|6we?Qu)r>n-`M_M~80VP3`xUK`-^F^R?x zf$VmnAjMag(HfD>5)es?&OWyHEU@2`6lPPz_S5a|!S;FptmU;@lBdo9Joq!*Y30NC zrl&}*U8=D08Fe$x|cy*H+an6aD&jD-SvtpmUoq^vO%Oe}C}iBvJHE z>cW~;SCEir*<>}6{hSP*p%lM$(;hw?o@MfvRh~$`H|M%4UppISc}oAa=c2E z^{-aR?h>{YI+65>-{O%g6eim5lG>pJ-!?u4x2khp;PJfsTHQp0z(SMZY{xB=&I2tc z!SqX(3;d(#8G1> zd%bi2lre^PiW`gDDEQ${@g!HA=V9Jrnds%ppXC}s>)a5*BAocs`R?5z0Q_^WOgA&b z^}BO!TfDMJ_~wCl{JW$NHBu(E5I{&ydwN7u5na&e^iHj|z(7V~8q0QDJ1{Q$TDy4F zH!{ekP1Y2^fWx0y?Ptk>5k`zi zKZFqvnxczi3M(2t4+oNZwa~!QkwhG+n`ql$vwTn+D<1jpX5vf42nNjl{FZ3k)q4Q(f-90CP}@KSq~od)og?^W*)JgP(= zxs+vpt^~B+9m_{cMZAu(rCu)2c6UOhHIYmPQYD=v37tZG2m(p{dxy=!*Wz z`eGXb(@VI(f0od54;8+uG`w|6Me{)GtL@3!t6T?u8nxHr6J#48t{?r7qL;iuqe zYiSZSHV^22!^x1lt*!c*=1zmZpP1I|9VUo<3eJSV)`4H6g%X*)IlzU}wvvZzrgxrS zld(IZsQ;NP__otfgCDzz-fLetik>jXL%FZX-eSz#s%XytiZuR{pPDc`u>Bm8roC@A zInB%^H6PC=B%V)Wt!#(VH(5tVmk$lNlY>f4N2gYtY@mDc6k((C=B(#W^Ma1F6@l|{nH`mI(;ziY^{;-TA&$46B=Z8FibyObmy?8X2xlf+!-E4b|%UMjK z$Lp~RR%%4J(jo`@!vz_peM3nM4UyT`Ydjrvk}*4HRP9(%PH$FC#~GBpQ1dtHJM&GC zXEidJ9LM50ZPyA%hX~K)vjlV(HY}04>*T{!@ZWn|kkNh$jQ^PCWcvuoFvs)CaOzg@ zeLZy3<5q9bzqWQ1aQ3VHpyA4p=-$60#@NK0|0VQzFq*r3;FksD-;}RBqie1=c)nNN zZWx5b%^RQO#_iKKA>x&!hb$FeEk@_wTE_)Sd zuUo}?11R1%&PDbqrQ+L~WeLpqXTFt0j{YFgxlN-O`X=b_@%H^N;JjV#$x&``AJ{H7 z6uI^y%Gud-lC_&aLk~iH)zgq3%wzsuBOy8C+_!4>6JN&od#qB5pHqyy?l4wEDSs1K z`;|;ealz`@PYo=_in!SM<9DC6VdFG?qo?{zW4SBiHDh?D475$Nx8pts51i+iTYfsA zVK|svJ6dXtYJbKYf`e^K6uM`AZegQ3&aO=<&$-5|yAspeGj;>{bQ9gs_-7AIRrDxF z9=e`qTbp8VV*{KDHvIuud zulQ#!KMUj^96*!m!RS+P@q4|U4`s$<3w|hV*g@2#*P2QVbW5G_rfVD@+ASuQGzB-B z;Oa`Xg-awUyEZ3eWG;K3;Dzqo+{%ujQ41$cqIx((Hz6L}gJgkrGOSuHI>;|?bM|Zj z&0w&#Q~Wbz%iuox9jG-$&_N>(^{7m)yH0o~5s?2cU%wvAn#441$kOR^3VdqANGvoH~|lV9^lB1G?CA?aM+yn1bD}i095?&2zoMD?WF;w%<2$?y4G-w$5f=Ns9r}XbGk3(MjrnjQxcpGb z12FP^mDZJ5rWBHh#*OwCFIq4grt_YkdOPASU=!MdA3R|9%-fG1^id$70R=rk=E>)m z;8xQSi4WDE!ru*(@j%JzaWhpah+9VLDouv(pT3r_6d)Y`QTxF>>cp5=N`vXGc1xydKViNBW+In&>>23v`2eDX>kwguksM z&SN?KoTT6y)Lmz~)TW6GQuivbqy`keLx(AsB106}plM%*t5Zl%E{PEa$qPZ{ex{C#c`p1LJix^?(uY6)ro3 zR6$UakTVw{)a3Z(YJT~D8tVw`Sa+N#Mdae6#LQZfg17S>!Y`f~-Co^%|iT7t1nN{8bW)-r}A$&uI zp&me=G4exJ{=S;lPh62wr564+i=b+Os_VTj6N$b_d&f~OEGx9i3b*U0VReym=gl11 z*8($hD$h*}z5K05@lYC2$Ig7Yt2{x>Sh%SFW}+sU}wWcLOx zoxRLUTU%r94bd-))^FDu76g;M9=&r&*I;A>wiW(4SA#cADRTnjAAYs7xV43BfzA7A zKH+B{oC8#EH?>_nqWIh0-bV@8-4%D^3XXK~D96c45qWGw+qHi6t}|{%lz5Z7@sp$5 zCuXgzHEx9+@uT)BMSYVEFH@msD?XPko_D$Sfu2nx7KwH@wb&^zbMOT zT&2DcG~M^?kbGKneR(>Tr0H?ZK36pJz)(iVO+zM#q-(p1zuI?1<8v>N{X>jjTiR_d zH)bri*B*Y+)m7Nb);r?L@-G2_J435hL-^LerZz1P=7(#IVAxm5jULNi5i7epHw~_4 zc1>~5Y4>~%#rBG@vYqO`gLqP1dGw}L1S|Fn4yT&f4Q zQmBe+O*roGKPOx{>p->I-zyUY)e~*4s+Nz1sdn2nk-Ur0V zwO~hp@Ru}=W8iRh+3&MLuMTpjV>Q(RB%VZk-vxHxC=04T2mFe^RnFiggV6Nc)-<_455;B;v=Z?2{0c zGwx*TCcS|>fSSecjE@igjkh+vH#8op*wl>D2L+=^_?J9 zNCvVh?V8N?!M3I~KYn3T%e%6uj(h`!hTlz)v+<9|Y45aG^pO>R!6 z`NoT(%w-VIf4(1C%;Pd#>%ZT$-3i!ngDuQ4v495y?UJ^QvINX9#a_;{rdr?&FqJfX zD>PzrUx`JvK(8PQ9t(CA=ZcpkNTlO2iJat6}y!Y3p6rasQenn3@;GL7w z;x+%zMDMHq2Hi6sSm|H83ri)k#<%d>dOb(_hO2hZ%NA}-EHMkLwouSR?>*3s=J!&> zhd7UFhtMEz_8n@Bc6DHDuCwsVPP7&#vsyTHd(ewY;E5eas<%=wUh`+MgfdQVgYF`- zm*UKHKW*HN%2vQovJmv;cHdj{PIoSkalr|;NMSGPP~xk=n*}S+o6#UP^(hSBS!Ie> zp7_8nmoq=@0t*i=fHlW`DA(=H)c+YB=J0hvT=ccEIx1jhD zR;gvjAFJUwd``u)_Uh80BYB8@4B+oL=l`v zsHg3cCMQhX3@fXpQm9Z{HjI9?Ew~-w;%ubC=MhZT1%e_x+nZThdyNDy0#2<_kI`IP zfk80WUY?h73Zt0{+IkTbS#+eYq(AZfZO7uBd8&*+QN5^$F$UQMJ;G-h_^_=nphKTs zo8T6ea<)1VW>_TixxF;<-T-7p@!p_)6E4pU)iBvr4#c?mbyx+PGgGUmKzFwcSI0SA-_!O%FB_t7^;_ zS~U5wa`T+n!-w>?8pWrP&ZFL!AW0@s5`E6T8R9C@mv8WPaD8E8^ls&XtB0`0f6SK5 z$c(!jY^9rthwx&50v|unOY4mMzm(P?(*yXX5c`N_ri#r$)`oIAhPh{zFsr>^1cJrM zJ_01B_%Ld-MH%_dveW|PXna0DCFPQGLU#IpF|1agOpIE7F?g4Odq1C?b((0!d!G=N z?8Cu@yXjLE?)FR$n!C=Y|FsNl@iZ!4^h4}#jq@7Tq{ZK>c?r2U&b4hGTXEp#ZirX# zwGaG&3Fsk2}6Ijd8 zx*z10@mJ`8cE5W1X#z77Q1tJdN&c(;==(nQXVQj;I9$1Pm=q{P`c}AEz%*;Lw7iUl3J(SaB%|$tR-?kwzl`lh5#7Xg zl5~y!Ij-v$NEL(3?Ok}Nqawn2E>olD^*IimVuqfi{&w=lZCbU2_JP{jv2?-pNe5as zr^nH$J|ot-+ty1CW4{isGJ#V_UGwB-l|_EHmpHoS z+hJ-jpy8q_){8)!Vr~Q>7g{(J+nIR(@Os)<&b^;)9$jy(dbh@jCzR$2>3po=&yWM} z;jB20oVMfq?#HB=Cgi52hNK|E(p+n4tknxzch5(X1fy>r>4EXoIy%sstDJ8$24 zFZ5_CHbK5~sL9Lz+blj=tne*Qfs^W~yh)0v5(rLKVT1V9+#kr)F51B|H1Lt!Rt6yw z1X!l~VAOv8>U`k4@Vba?6dWc%zhWJmqq7=RR0tpaz15G&q#Pj!^VTNbG3l_UaSo{C z`}5tW)JP%9DKMGw1n}&l*}v^9>V0BCz-J@5>z9cX$;{XHdcAB-M4+8=Ra(|7rpjW< zs$MVBY#BbVTC+%4#p?H3;KDU$J!@_IrWEkH@Fa9^rLbV4JN!2-ConPREj9H@pNz-K ziN$RfN}Sk`;d5=tH>lj(e-S>x^mYC3b-O?g2rmEQIBTo3BWc`jiBn6w?d3Cm-m>$M zpMU4Bl>XLXnsFv*+$}^SH4|wU1tYf1I0$cs)371->=Ye$&CBNgo495jF#;H1`Lx#$ zJ+AF0M6><-=r<~cCF^T?M^h2rUI-+H zgV}mM$mN!2YZjHEej&%NmIqvUzXhkb* zA9C3fA?ceFvaN)vt=>s3K>OL{#~+WfAT#WL-mA##Rcc3tHVbXY*_|sk7;J>mV#KX{ zb{2bG$mcxU0ynQh#ICWP3TGY%(?4%fIUcVI<>OL`TY+1F!@Y$T$6W3#(iR_TFmJqd zqBbElR7U9^?WG2;b@`%13DvsaIHYc?l_Objl0#hLPmB@fn$FK;oh2mv)-JA+jrXS8 z*54c10rT{%v!RU*L$J&Eg>m&maqT>v6PHmgusZQ4tALCb(fwvcCm8{(3>!=4clEHq za9$5^^jBM=`;?KhEFqNYUQwRc{p0}LvJ0n+8)4~N_F_4E^55Y}{IYH&V@C5$?cNIR z0f9rQwoKkBah@R@^WjNm@HM!y}kJ0yxHu~!%^un%%o{}m|vP_Bqo^|T868`p$4B1zEvkuqDmXc%P+$wG~85gY;P zHD|H>KQ@%MnnpD09Z$rMv&#zicj9HeTV-W-D)OdP^c&aPvEu27GHrQx)`ja?E@zt} zXpZfTn~@KSHw}-KzAGp1CKlMlHwqhXI;F4 zUw?Ia$QivTOB#WCl!s_~Vy5|lcZO&gcO1LXG+f%`+7W)Lu6$fW5Rpzc+ohLfCa3VJ zn7P7u!uhu_b9!-O*e=oK$X#VC0V&n8DfDV859hZv=A%Rt8Q8jwlTN(Yx!Wr>9Vptc zhA)-xO?^*~p7*dSE^jo*3)#tBpL_VpupRs=^Ooekyex8Q7;(eI&Vuj?sUcc~CRqQA z&p%Vb2Sci(Iu6;IsHxeGz@pDx=rhlz9*)e-mpTH}yPaBDlk7H{T%5NUr z6-x9>k?Tql3XGhhL*&(jEtJv(8PTSA4t=_W_7V4%X_Yca^?Y3B3S`0aratOp5%PbV z9zN_gjPv4V0vA!VX^8|Y)c9xgsDQmaFrDblzf%^wbe1@gm$2{VfIjsMv^ zf*&On9S+-*3QpE8W?orGS`^W`RLahx!9%XfC1tlUPzWR?(w80>wUtRz&=H^AK~YAR zcvTo)#x1_1N6F{%cR|pXAMDXd4*xh8mgv^1#vSO1j0;BF9*9j;bngX*-a|yXI>)TW zqY*c{C12)Ur13;_Ec|;(3>T`Hg6Benbd+Tl6w^nL$Lp!mcB*zw3Y`%j4Ck24FC%c&;J!Wi2t=XmB3D zwAnI;Mh{}Nlyf(PB?nar^U$`6MKYwjZX6vxVnkF-JV<+`ge6iAvF5h87TjNBN9T7! zwY^o_#WcGJEO^cIMTSQT7`td}aB|12ckD8!mUuv{k}RsZ!Mz~T^gyw|;S&jL)$gCH z17zkSi;T2UB!`fw)5U|2p;$bxXeKHD6Z-kx1Rs$SzGZBT8zr>kq2rBqPMwnscXA`! zmpyR#u{r3pbnxy+Qe&0Q zl7GGV+<}vx6ArN1p~&2Qdi1|jI)NnqwEtc@q!IN$r}Q5lbJYoWmG?S_RF0u#7xiL4 zM3!9p=<&7)oH3lJrtuD=w!>N(kMV2zEu#foR`bq`BqT#U#P>Gv!FRt*4DbTzX8!$; zvz+0-^}n0Y9RBYnG$KPcf?4^=D0(Sb$iTbH#!nyjoPJ=uW{~LrgE0R;5N2Z&V`K0|0t6I12<-xY zsC)u)a2PfgbsT0enLq2)IFLog$3MYBtbgGB4>$ea=bgi#3Cq3`{oqyC2oKr>{$pH? zzLMMMRSQ+!@TH|K78g&GC4;>~ahOZ`RY5_av^q?9Pg}fLDJ}lXFO6UQEAaI5G$)hC zZFV|`$EtOXmWYT5<@De{Z}lht_xbn2fz9gfiSBHTPlu5IE%_?sgDB8_P90C(t`4Nzv3bjvd(?RGz5*7 z)YS7KP0i|})JC6__Y;Qes8J&3P#aRTCCI(2@Hy!_B{0u3>!{-=+4m7c1*aD|24+0B zu;*gtZwnqX&3<-n*1vwWE*Hw}h)*UbRU8%3(`PuBIUG!*{tSj*?Ua!_lnv}@h^FFk z9`3a*v93g`lp$>EQ>BU9#2ureNnN+Fyt#Q$Lo({Ctn|0c zhZQ(nyUr6j&QCiYSB?#$wG?vZCIo3Cl~%NfGKf@i+fK2*L`z}8G>lc!&^&}nv8{#A zNn!0l8suF0l|-L2w9KZw$_yIti9OU{qicQGv9L_kApomc->Cphr#mGzoZ<^oJAGW; z@`@N^ANW1NJF9FEzo`BDMC#yl@fS)6Cd**-Ja58Pt#DJ!mZP_ynFq<5P!rGR)UCak zU7Xxk2R&xfu(8#6GO}8ltp8NU-xxe#mNjRTPYTk=A}0@agNJ-1s^A?4 zOCf`ZRYsdcF4e|N%l=T;HM0UK6%Y!~X0p%Rn8~;3Yi5&RuPISIgNP`sn*uo9zGCr? ze~bB@tfm^{XC=7`r$RnIc$ihmst?d|`3Aenp`POUEDa5Xm%GrZO5Af|+RL(YV78r$ zt$k!m4PzV1*T!piPO5;B&AzaqzDRzg%e9sKkB#BeT~?$;!zDCiphC zM>_cET;su%k6T|#zIf1U3&7(RUfG#XU04vGS8Ac@vYKca<`T})hM zfm?O9+PxG_%rbC+R4eJ=d2OT5Zo_t;9p^?JrwEKHy?9WzF1m2%u)J&piOu=QPxv3a zCUjwtr9oF#t77P?W)Dg(49(jKv^%!?Krm0F zwI%3gfEmH{w2)fI=3F*gG{d0O>+Cl8PW17j7|!-JsbrUD-7g6snnf=re+Ui}8kxr+ z1}e99JZ8oA&S8ce0sgtgk`vgzHZQCAU#l0!pktO`DkY4K>g=2DknmZ|!du9kQ*22( z)aMJ?%cWW!zC9UTMu+yH|6uhG{yv%P|6DK6SEdn|)$44tW*G9$D6OoZh%KjKma*ZeCjzHv`Oi8~x1A60+2&y(D< z2f$lFIXw+5$#|d<6aT>93da%I{<1IMajwF#RaqKk)sig?X&))?OlPjH(S0&0Rl!|* z2@}jC(I&xVre?-2jLLBmz&BiWb2eF^wq!BDeVEgapU^9SbJ^2Lr=_z{d48;lGb1z; z|2EmgyXGKRc6%k|YzGNw?VtAGUcgayYPHs#T9unfx2}h{FfhFE(q3FS>kvR1e;5tDR$0SCNx%Gx z5ryboo7VVp-%gdF(j@EMZU0#(vS$%RWERhhkcF+FiE`pz`N>k>XD@M9X}0yV>%1DUf)lBjqkio{*Ft!$g^ku z_GB$6jYo=;9YffOE^R;Ju;ltlWP239H*3`Hu5sKqb%2OtJ|ss6oTQ)%Y+vs(VkD3L z5kVLcF^-GSJONa1wyZGI0KldpSN#0GUIKhBDD9uAlx?tqJPLH2rD-~H;?4X?p>oGMEgRQ0B?2TZ9k%g(i$_YU#a1Xy`b2@c+fXmG?n) zX8?9cXz={mWeEPv6_BuFeIHI&$8Da0X^-?UAHe+V!a?zBSC z)_G}W4B&%b57d-fL#8=+>M46uN`*H$8t7Ifwc=n_x_+wQBK*aRJiWbM9|-}CHpiIT zR~#qm(^0_n_9$W|wVz8xwM+suiJs$u=B+QV^KR3>*>zgva!avF1Vlsf7%&%k#dPQOZd~FmD-n{CZO_$w#D-N@SIkx? zO!F^Ea)5i~u}UHEWHzY$I5$q+o)R5I-5UYiJ+el~aJ1K+kfD)>q{@uz`aXG^ zKJtiy-ydpr`qDqy7%7vY+iV8adUBcID=ocA1uLd$mEe~E>tBZU=wP>#vn-udKZz$w72yIPh@^E`^QEU;7bAF1xJEcu2KAC)5v~#m^R% zx+Z`M^~%d*z#Ro5`Z5{UdKg#o{FY+i?06Zj=&I9Q;($`o6g+Uvt<`apRaX5xme}Ou z`)n${rZwj$eZTI~_AUo2gkiTW>QD#hEe56nzIZ&|OH6c0g7CD>LiOUv*7_BZ%lso` z0S;(VMr#7Jd!4i}Te;&(UY{HN=$vkw^0N5)d-LW(eC0(p&!|$t4>^A2>k6b7$b!?I z;`bG}Dx)(;+1nYEQTt03KMwdt`ezyB?k}s^A0^0t)~!?&G{`S`)TKefz~sM!b@0m!zYDSnmtWn zA;~cROBZzgk7g;5D=+?EGA)~%uF|-ht6`uB1<$eo^IeXRE?j{oJ2 z&7t(n#6^^g4SPl6L`~YN)Vc$)?g=Flh=Tw0r$T1Ah!d{?VF}XJ}|Sw?|oj=`+`%#}$6lUb{U;`WXk9o<*e7sTJHF48`h|6mGiPWjmQoye@m&lYGD*zRO-xDp-a2OG@5Re6-o*)$2?PY(Qb?2j# zxWZ9jzofOmVS|RUJ^yxE9Z$`iDzE8!a~mX@akt{DWg(v(TXLzvEYdLRNA817xHFQA z=&f{l5Z4A2Bj#2)XmKDYQ~M2N@UxMmQ3cYd4n*DF&>!5XkZ&zkc938+c(Af@kwFr$ znqr097Q4qeoy%3azbyscM5{y7&D_tLT!RsY-TNL{La5$-TT10S9e^3${%EB7mXDa} zS(}G6Wqs5m(ssB{Z2vLk0P2!{SP||yn&>(yg77U9U!7NzbIAE$x>QM-*jS#WAj_e*JQ)@tpW^5-7% z*`6&SDJ2M+o%MRxS*-JQE_C|x$gSeD+oDAFMZ&nRM6=`vFT89(E;+^zMc)TnqH)rz zKWlpwlkWTarRgrh#f;T2gt@-78o0WHv_cNv6i~H}myw=!a@;5d+qRqXgS&N)%J&fP z?AV9)7JG}gmFh!NNs&ILcHmSK=Oq-IM0e{h{fP)wTJS~!XoFyoomqk2Em(qS_BJ$$ zt6|AqOQ|0a*EXJrS`C$0UY3TT#l@3#cDJAdNPc~ZvU)H53$>}?E3&WYz{ zEX>}h{30+Q+6eX52t7Zp#WK^dMudAqG(q}O!_mnjqX0oI@lk^S zuQ2^~0Rm;K+WfJ|+2{RUxO2-;CKNPgfs;wJ<#7h>db+$sLzPEEBFLanJ{WJf>LUgZ z23s;Yshoc!%ZF_5$?p4n1Mcv0DIdh6hrwL;TDz`cA|O6GQZc&)m0w|oUg#MERRvDa z+CC~PdfM|u6Q132o4%}iTdH@V{Aj{|UE08zB}I$)!szbmb?&%*sV6re$Qm}@xjqrV z>^h+5A09rxf=PNgH*9>=YNwk??|vmWNzhCgBoOev<(!4%xEy0M^&uh3aM<{-Ih}=b zwKdv~rgH!8s0h|(B0PaYl5hQ_1QL(mjWvYp3}sE#t%F7e8akVA3N0NN_3!u2mY$sg zbH7$>DyL=C=X)hb)z$bdU^CN=`hT9j=l}NfKJ3e}Afopqi_vvN%Td-*PO(sWH?=wx z(kOMOZU)^H>QvRV!gP9?NNSy?)Wt&jfZCGAW@$7u(bYd#&HGz^fqj0n0&^XpZJ`^V z+5gC;M4AFEU7if~TSR5?`v5@t_{|ZSCw@r&neJZ<_{v`g^awrVDj8Lub{h|^|Kr{{ zc67=3i*X~EClwJF5_WT%ETi1&V?uk4xi@42atE6gJ>(1m{#CT+JA<`TiaHfrqasGT3t zsxyUqM9?5LZ9h1&3l7_L`@2Pq2P6DEbTP=0dq{F^owPy5dn%Y$ckCT|9L}c4IW2C?5u^PmgdFJn0epi5#x;J-VU7Nm*oA~^o zd#a*@bK;|Ez+n?pm*F1UwJj*XNXDb7v5RVLY2`D$NjmT(CH1L^j1~PQR^`EUUSObW z11XfpGCHCL-&uiI?yuc>v!8(w*k)-!KgAy65gR;fB4xZ2)JrWotOR|^>@I2Yas5@7tW^zd7R5v|$&xoL?o=?ENFerj` zEA-K86*v%O3ev&^*nrw%3aKFuZhqB*v@Je!m2ZEIF#N zKL1+#&WJl5iqgPOiF0BT=}Q&7@JwgFy{1ZpH$7En=4L(EuFz3Knfs9XdWHZevQdz( zJhL`E-tPJ%))o5$(0cUM5SYtCZc!X)83cCHIuX3#DS9&Uxcm#C(7xwf>@Iu?PYPfz ze>j^^F8a7`7{enecrU!a!5s67P#`GN9h&Z^{3pp3t#ImanZms)l)8v-L z_KjG>!t2{**$9Ey_OGWtCmc-7)gSjg?^BtePl7wd^fB;n2n2v=8~H)Nu0V8uIDM2j zqYc~AN;sNmnweJszNx;>NyqOoR_nJ1DiA?fEK+Yaq|7Wvx8;+IxZ)*xq66nRTFU2S zQMPKnp&Zr;S<^Illn#pTb)FPeYWW$CxnH_k9W-T7+X(r(Zbo(~E00@R90uWghIB9L zq48JL$`V>r_cbb}%WIG?^Zk8SlRp_EbTuy3uuYougP!-*dPBN6N7NWhR5YY=SZlYOd|1rb3e07WA}9yx3m zlfc_*M!ZYn{ef8rQPfUQ&5#?FWe;1zp~Qh)Z2bFSYfK_F$~X_yMByJr4BDpk6||rWb$Crv%&u8xneTFnQ7wh57%P-A zUsEeE+@X@wb0qCUv=$u`rFUN zKF%G)=-B6qqS)a$&QYxOBJLg3S9AGitHf5K^SV7G9uNFZ?N7%#=lG(|=4MWrTfsjPASsa}jx8RxbbLBk=*SZN7{MiU@D%^!AbR#i$)?7=M*SwkVpoZeFgx~~2SkUKUcf;}1pUz^R< zJ;so@H~4~p3CC4v9zA3oE*d=L&lyuWH0z8^yK%JVw1|FXx33DKu zH|lkj1Mp}=Zk{qnDKd~888%ECSbvLi*mBoY&;OhC-DMlcGqe14XLBStc6T9QFHf~V zd*JTWx)wq10d`XT`>UoA1{G5IZK{G|iwcNe5{qND=d{w4RW|^W z6-oKmxWCExoCCspwwc3(1ZRETum8ro(H@^wlZZH7=dH3Ib7G5VGA6!#6NKoY@j#u; zR{n@0j$^xOw2WECLZ@l3vQKnCb#b9K4`DQ0xA0d0!r5VfP*(pWk)S#TpJ3xRE%tpC zI$JqctVy$r(s{-#$^weH4lZXo0taw&jBSrCpWW$pLVxh5$;jXH*Wyy}E_{x+I>H7~ z;1F$Kn1()m!)>gRQhJa_A12@$9UUS{+F^V`ZL^(ye!IYf5M1lozz_jbJ#?`e(U8<7 ziks~L`+5SO_XY>xsfL_xA{+79iN}c8LO3IDxsn!?hWwqZ%!PjZ@e!~nBTozo+Q{l5 z!lj_quPf^l5)$)&sY%K)E{`e5f)+ndb3IED#++(c785%d>uvmp;oknudZCj^EpOCg zfKy-#;gn~|?r66t`WC-_bJUm|+A&Jg%WcIg{|gdmoo+hx&Mj83Ed%#q-C?9(NQO8x ztxTN$Pq)3B!ic0%5n~Zw3v(M@?ME!flM~PYO*H&FnX?Pg)mrmnz^a-u>LG7v^9F{k z-`!ZXf?FFYYh=-|`hjDpY{ZuB9WB;HW_Q+V_WgCi#pWyFA{Kd=vT~S9NRYQVm5CqUe%ixXBx~fVnSc_0T>&<(O_FCz)stPedPn3aJVD{m^+=P0w z3em8^=o>MM1e_{q$%ot$7wL_7mD4tHlt^;oCWXSn8&5z(t5+8%hu`85e@_8jgr~_V zENOcFD|PrkbFmg_M78bb{Y&@E|1LL27?j61l$GqY(S*R#PW$^o1?qm}0_kcleQ3Zw zm45Y?hI^^Ito~j_l+GIV>`miR#Aq$}*VIg?N$5$hl~@`5N1#}(Qu6hwL4B&hA~quw zRSnWa{oMB}dCXcH$35(THnrEQjt~Qde_@qxngc95rpC(M za)o7}{D%7BHwLG|h52hJys$`N-S#3~EQFePgS!|$ei8;z|0E180JIFq=7<=vc)cWpT^H<5-{t;NnJAa=TfB}M2t{cWao-#?My)eKUP$S$OM zAG!U0BBbojn@OINSwF}|6;8C8IeuFP1efYfhdb}-dqoQWNZC@ZTpu|~G<-|3(w9`}O^~#Gzke{tO1WH4-V_4l1=EMG;!Sy9E zEedm~#g=FAlV|o*DLIs>psn{zwdh$PBJb?VNc>5_xUN>JQ_~i^-}Fbe$V&*ll#({@ zM1W){9KEZ~;-8M#MY)P>$oo$gZ?^d@%2ZhB_S`VPYYi7u2dc7d)2syP81uiB69w;L zb^=sxE@!V6^2l9NU^Z#5R-*oNeK?^>#8^dR&F-We;uH~ebsTE;m%c>TL2WZaxIAnyJ&GC_7s zk%HW#9Ih4My5D*ztI9Lu=DiI)73F@m@}S2Fwjg|1zRZuE#ebD2O^NzPv@jQa*=cG+ zql-E*B=uBM_Y>8k^EvCl$s1w{z~~tdO&1}lH7sQ*qxqkYL37|O`;GJYj8X@8SvILt z%KRAf=d2ozwl-5$mzy|OX!I^5uk@NJCb((l&^8z^`VBIio%J|CpeIoB@87C}Wc*SE z7AWFlW-jy7ebbma)J9(y@2Ik)M&~9H86N-h0Y>YnkVIOI5dzH1vVSvrzLv%;?0Y6m z^c=z3Q_CIg`BQAeTtfu^q@`~rDgRbpd$TKik^J=kz_bx8Uq1;vxkx1Lp4?7o8?mvq z{+BrA-7*2wB|#=wqL}@AeV+ ze*LlY#fAs`@Wjp@njXf0hW&ZxC<%!V0d2_wZUxFTCd|=BVm~U2xu6vfBv6| z|Nra&u!YzOGym5C*p7X88v_ET3BUwkAUFO8(T4rsEx%hEo;yVkn4Ol&@DFvU)@{xWoqdXbU=A2A5M9pev+=|?_AL4{z z*xZEPw6~p@iIm;cC5+P#FiM7{Unk1&$!lDp=~TKSiK3NtH-JSIq<^`trSY6g}eKKT72l-8>-*$ATUGa3;Su@$&~F~738{^PcGC_i3zSAp2I z2$=^!7UJcmxW`keYqr1Cju1mv{@eCD$#U)oACNcW!#46N-U#(yn~lRH%*V$JI{U;8 z>-cHvg0u`Lz>wDh@Y;-LML&7+^WZ8PDmVU(<^!S(WU^Q~;EtnBoLU+vT zPmTD0)k4&OaPoH^9oB(=L5TkT>n-BflQE3WTmIJDu0I<0FrhsCFDfj8vbzewIe7+v z2Q;m&chg78xw-)yeNDUFxKBOsweebh(QO~M{!O)$XJ}q4bju!G4=pu6=@Nhe+`Av$ zVkFNmx>jS(?8+fy=R4IoSUqCSUw1Vx8=~E}Z;+hr&VD7U3A3Q7Iw3d;nW=OFKBDsF z&3(nzutUjjBnc+ZrhON0R!yo_ln;>WZ_PzZ2H90RVF7E7{U^tZ*DdDr=2H$mZl{zf zIHT1kl-F)x$~d;9%mY|Jpy#VFt1p2PDi=)8v0gB31TiU4b2fBrWMUm9<7Ho*m|v;V zv7=~$sRp_<9MJmy#3>)shNnCNS6l${FIB_$)c^QpAK0V59&L%|uDCHcV1jzF?lZ`l z<_j+*G4#{h69GVieLW2QeepwDC8MX{KgdW&ZIfaO$(u8Ff{8_ zFB%vYdj^>dXN;9zf@KrcyN5MWH2O|`2FmB0wmyZjg_NeT!}EDQ7#l(M6AkwrcP2da zWr&N%Mq>gHEN&EP1Chc)Yr-1wAyWlufg#k^eqYBtw0^G1(Lz%-UvbO z^`&GqG|Sk5=X0GiYtg{(3cW;a3|-VpD)8q6|5L#j7ZS@uO24Ac7$aWHr8MAN5~6a5 z<1th}VwVqI>ldhAjAI#mb6NlJUiIZ@ag%}uXvy{`t&!`EPxR67A|7%>0esWCtk{PJ z{s8Pu7a#L4KCM(d>$Lw)&n&$hCT5?%m+yOd9WChE16XA?B=H326qUGFmD7JXUhcYq z9IL@I*P6KUjvs%A)XF}KYa-tM(L|NeI)k1_eW>QHL1AhGC~b4PVr@80eUtH6ut3Cf z4Fv7-Fsb+U5OdadMrKk+8MXyNMoRI9t_>0G!TUbzKPQUL&TXCPZrKWS2K^(iv*xpE z|LeNzsJ|`cgVg!XTD)T&`^VRCg6W+CeI5dzi)?Pd!OB_KeCif_1-fKkMpScUfW4gn zZB;g55%}Ey&XCNKF&F1ieg^$6WAi3U+%VSHQglM|={JAbLv=SQ5i!&N((@^_Fpst7 z-jj82)gjW5s~d7 zFTyp-D9eQOU&>maZg|FtdD5=A)oAZW1tK+JU#_7!4qnnP6zoPTktbdjR~!a5#Cl+% z?$ja{llPY5u746E9v2&AVm#2+OR`*ukIe=q;M;B#Cu*@=zmu2y>%Z94BT@)157-fl z0=*9Z;Ym%K@&*+ilvtf~6|~EyCq43Uy#OxKk5qwV($3#RY-*d!9|VSg7se8YnETMH zn#Pufr1mF7Yvt=HZ+gMcfKOe}c;_l6yLivFOKm0E>%8Dn3pSBSPx!sW!baj-v>$%Z zA2)v`M`qUVfGcIsUq`ld-gSedIf9W_Outg+XsY{!9-gN!^(;f1mBu$XMl?Sc<93Cx zF-ouGf$`X+yXwF3Rq-E#-q+fKiACq%yC7%5eO?KXMizRE9cQIgM!6q}3INUc#a2VF z+Rk4dIFM)vTSf{3yE{`ds+*~7pU=Z^C0yo`{Yqy6t-BeUH!m!Yz4zwY1TK9? z7K>1}j)$jmtohu0)3M9Fn24uhygH%MBNt`+({kM=YoN6TRL+|?glSZ6NIQYn{lneyYcaD7vY02SfcBUq8^>|feW1Azb3 z)AzTTFnDPVVpin{@%6%pOHlQBXC&Ij-1Uz=9k$&6=_{uH=Krc?I{&|GnPes?eH3h5 z9J_4&>FASrlU~<-|G)vzU`{sB`Tv8(68{fIpYFxJ2K@dude&@AVr6Y?`vZD-C6Q3Y zL&GGcD=MH~Oh~;VQ=hD!wkKU%%1sndGi>d?%0%rGJbmw)?U?=4kH6Fh=%3zs=Zsy? zDv~8Ghe=bs0Vbv2gg*BQ-@mQU7$U_b-ugcKGP*_x^|}C)k9}PLV7F|ywbI1uVCp*4 zz$AZndw|a=7>ozmx63vt#EEp(97~{7OIohIjYeFes!eS3cj1v?6KVeqy__>V_E79V z0Hok1f?BWH@fcz&<~R#cGz=aP^J>XZR1yGnGZ-n=1y zV`fm4|4GvlCzkfN^5}<&Rffn6A2F0UxFG3L(!}oC%_R<`;EE^iYynKA*94ar7HWJ- zc$>Bj8@+-gY(#;ycRH;nJ0vLl$4UgX#)z;n7pUnjS@KT6X%Tf2CDn|6SmljKlJn8)dDt?Dnym7 z^A172t)SP4lB|H^91#wE)lzH@Juq~E9p}ur*gyV@y;>Z9VF3)>0^2o%;X*DX24avN7ZP%H|p5o9|2chL}pQP!C z9x^rD?T@sP4EU+yG*%R26#*eQ^1|j;zPO)>0{6e)r>W|Vvdc zqh_0weLtjw8ngG8o&G2?xO%Ktf+P0sv!&NApsw1Pgv#F0udz2Z1#2lXW^v^|LKE@rirjhp#&=PQLI!C)%|;~}4(jH1zwHESq6#Gtf2q6o z;B0~D&zIB7MXJB(BYiYVklnbg9{x7Da(zu6x|L2vik6$MmMsRZBQ?z}3!J0T<%=ba zL@uO5_03h`?YE#qaMq4Nc*!voCADPLpK(A9z8lQPFQ41aTQkG3_%Md&pk{qzDtzZZwsXNU22qlxk0P^etMSkvBxLaUj!_j z3dG^9DRORfM((Rv%vjYDO@z(iIGT86{q-in9KRiUz~^0SV^r5%fdjr9zOz3n6!O)^ z0U>rY>kCFM8~LMK6B2azoI~Z6sHIOR;AuTXPCo4hNbdpjo>S-GHK!wYx_L$yenu;z zw$CF>M5q7l%?sDuh(V} z#j5sA-!XI1l&q4oh`e1^AO!8dr@r2@rZ9XC6qUYu)yRM89*AS*O~}q{CI*y@*bdc| zV(`f}bWOg-TASs@xcNP+VFfmP@r0OtDy@3TaFY+Vrd^MSf+YS+O28fdq1>oAV)fd= z9F=Hm{qad0n7MZpgOAK)#%AFuJ7EvYfLxVgKmry0YMT$(?+Q}~(y2q7FQMgA)Vj@i z;5-_BRHnNTjqvJOZEHesChF%pQeW6fL2+JT%x0LZYbQ$V-LKZ00)I}r;9HV8jkP{r zOyZ@1b_8}*tPw%3O^pnXtZFyXog#SMcwgf;yPJrw$KzFt4< z#)0lQ-$Fg|t+ucHOT7fwZ3(k=?>leAF>)2PEK-~?cJ7Jf<%`-8%~nCQJWZE#JbRUt zByR71neBJ2)BtpN5mL{%6#y{;fxaz?UN7-`)`?6vF48!#x5@s)6b4p}Rw&d6YlH)t zf)kz47ME30I!6 zUVqv+e8%tBfODxEPWhseXqnlkMRen*OO8vkNDB(zyA?ls9i-E~gi$WboF7%TO_TAw zmnz3ZZ0F!*JT8X245z&>KGNC%z-0*sQ*N5aKg9F^{%w*yM|`1%(B-RI{|xG^;HvrN zC)FcCa|nlsC_TEPEIq7N^>cFNu{rxyASaaL*fDV~T#mSI`pvBuJypNT?prr*yt}+= zVkFySKJj$h+zm5dZ16XJF8qaH>g{%rfy|>kgKLc{$%gl|Xq17xaoacyUO=%PqbJ{o5GHj<6FA=ohqPnRhp^!q3cbXlCbG z5u8q|b<(m5&GIAr!`Vg^269F8D~jQ8-CEHxBS_p(oCvDaYtEf z$kRMQ>mCrb!>%1AvJ6)iX@Trp{K+Pi>kAbm?x;&GU< z8}Bt8yVnfAIE=5z*vV>~{k{mMoEH=d$5+1ZE?8603GU%MHvQ5>yn}X;62Sk;YL9F1 z5pT%x1|?7{>-5^q9{Yof_h2UFue^vo`SZe|Q6=foI|(w2CT!-S?LY-CAfc!xHe?YJ zip-3@Hiq|!ZDzULZ?=t)EYgDvv68SbASo1@uEpQT#7Oj7h-uGE&y4JgUCCO5S4&hk zFEC&+$mEJWuX68m3mFCIajG?aADhA7H(&pSDfm%p+x}N`jdIif_^;>70>P*hiwi8z zKwL!V=ek0qESN+m7uQ8L>%k8W9Hf7MVO68y4^X=+s>ZYgeKTVXxrj(*I*7{0g~l#~ zwiM~db^`fnjlwnt26`Uk8TfrZRfln}sw;R!U*E~VIVVJF{X|-W9ePtm*9$IO8lYyXN1hsE${yB?W}$hB+9(Ot(4 zlf3&0-zI0;*%`BqWv6B^_6C zNDsS4NkVy_cbpOoI%Mb1+u`mBcg3XLu0)f{-?fiKMkj;<(#$S)(y>VsNpp4F%oLsp zomyN9^@K^TlUvUzDo{X&+Rnv0nV^pKYDH4fn+`_vzYf9@wJ$OG6{wj#4h#CqtrZ_S zm}k3Yh0JvQcFHAvImD1S9WM91&`$K|37qE6AMNh|; zw71d%u^1y&tfzW@s~B>uMF5FEe%~imc9RSIU6d5R{RkCv+XwAfPF;O9pk+Sj&YoVT zY_ij(`4-*WUiu@um3j&aOMm`QzeXVTzU(J4pJW?p@@n7|s8fhE!FFWG>v!hM4hu@E z$?k6=A`UL(7z?u9o^!NWx||Rg!Fd5IAwIdka?c|Me1KX)=QPubt9EcCU9;X95I*N|X9#Ilq^K*> z;hZfbk3tuwH%;HXtU}!cqIO|qAeU7K<47s7pvW7L;&&b!cz@LfrQS}s*6-pXE^C*N z9VWKjpkq;*O0qahyw7uYj~pg%n>`;Ua)zZh38|t#GMf&ht3tqjgj`2je0z&kxp+La*DCVr?ZW+RD!Kw2x^w5gRzVoVj|jOZ{YJ;&c=K`_b8$nuZVb(?D5z3JxAe ze&$UpTR@qV3VwxXR25+(<$MB*!Y)!ok*y3&_==C=7`k*5i0pLtBTtpVB9i%^5aq0ZY$Nci!@g(^radE>|m9-O0 zIZAS~s3UC@fp4EggOrwG7PSE(N<*j+A?Gk~bybE_YFFWR`}l*J77xy*%qshrB?RqQ zl)oe{c_8SmLi@PvkqdTP798TiYcjM(#A_bS8A?AjAKx4p;kK!fA8$>sLJhjrpMCEr zf^7waSQtH_1TuQ3t^-rV+jaVQ-{~{^-y-OAAO0hI@W0`s7+YHj@;2AK&qV}RhIc~k z=psRS`jK4e5T2+G7kpoBY-0;@9pz0EQS9D}tuc9#yR4HGei_}>LLqJm_i3{NXQC8< zJjl;|Sn1f#69A<>nj}d2HOp#7_%gj{Y-5gN;PeK_fS!nMH!1GFl*xVM7or=r@1g)?r0F7+w`elluT{oe?W zZzK7C`yEuoP+j?K8ZQd)J~&xqff3(3xIdnQOl~q{SvUFBwLqIq$QV;eT5XE%K}H4& zvA{usc>>_{Qnz&fV6xm{1kr1-rrR1acT=1ZZ%p8BC4H>Y+Dd?vJuN4v-TP88$ce3V+AW$aFd^gP7r95(PkcrH3?5H;oIW$J)-Mt>?5o_aTn5ie=oK8Hg31|A+PIiQV1^>WoXpV%`=6%Kq;?s}k@`Q^4G248*&Ikg zrD(M`V-X~Bq`^X0Xa5)>t9s5s}CCPxkl7 zj#?$$FBJ!cm&XxYW%T*%iyA{4{=EEGY{q~^>x=q)xneDNV3pcpDNvCYqWit|%s|fK zr6i?rw?d)$Ik-VFR*88jG>6iO!WMG)H#ko3dD(1?0BWN%KIV7qB>YtUBgMQ%EZbo_ z0rJu=)%G>s`Jd^rhS35d`u3dLltp7re)e2`@fKT_pIB&CS&)F}qsDja>Hx0JgUemL z9s?OY>WTtp$z~_0o#$IRR}O|yJ*&==Kw!=9nf$a=yL21priY0f1@^7=#cF|1K1rVK zbIPK)eF9vAS73~0jUBmUFBX>$&75d+q97%~DekB%!Kq^7;@p7f9k}CM-uGM2?J4cl zI|qelyOPV?mlMvr>`O(o0eh&22Np44!U+aVwo`MsJSy*>^T%kR0g_Xo2T%LwwzyW9 zRFX!ndB~=N#OhWsVfj8IU^4I_PuUX02{~Cj@?98E7L#*RxNZ`n?jJlMl&6dHsl+0nl=O6N9n_s!LAdGpX8){b`;OP4j z%fcfMoM+mMA1QNU7S1VUukXs5$giUfOW0B!<0!Wm65%yCUY|~+2*1(-QlDb0BU@Gi zUf1i|cS0in`TYrEvKmy+E_sjSJoW*fNC>Br7M1)jyPe&!TVlgG62D|wW2hWPOk67Q z7}1Z3OK;sPrz@xxpqq(eQ+@R=_VZ#TgrXt+L~O5&RA}=P1Rt?ZzqQ>n6n+xmXVLNB zywtgsTfv&@c03VV(TTFEKdbqON8Y-azRO}i>fSp&mdQRzc9SjXDwBj_dS@C^s{we> zeszS{3_`r3rxNX@RgB<)pN0j!`KOG*3SS0*P7S^jj)5t)xt^$*TUzQ^!}?(+LH`XB zebAg2H7589CKb!H?cL|CG)eH!O@8v*03 z)wuG-K+v6KJ&LJi;6|Sc(J?{a{L*bbVTvY|nNjiSP*H?_v(Gm->HKHalFg-uyOF<$ zQDHZh|6T2X8xlSaR_eb6w-@xv!&-qrL2tBOea5V+jq|b1Ev2vt=#~Bf@{mfR&!!Ce zil^?e4to@;&yNOgpC6wc^_ZC`sZa`YLt17_-(-G6S|zcG3dZM4O~ z$V>6zSo@-z4i<_!yYzfl=Xu&%$G3>F`eWCY+~}K;A!i!`VPsBTW?nE z?PQ@ly>n{g^fKVn?4n7W1sIhII}jeNQGFj4dsk$nIf0BrxIqsB+WYOat$g+asxFPJy_m$uUETKe6!B3%2*` zdeMDV<_P~EwE5P-(8{&)4K9uxf#QEi#&COnm;yhGIvU2xYfdB7yI;~|DmKw4P z=U8gT6arULo&4xSt~fH{(oTw#x0wds?#I>2o>LI3Tqa`r))O;) z(rY7M-UL4>;%R^Nx={F@ht*ZtRD|PZ^ou>5=ioi`U+p{{jrT5>nAruAv1j_skVvK@ zoWk~BU8tD?{XP7fL5iaoZ}+{dCvLm(J4f>Rr@~jUgeUcSn$(-fg=&V=c!q}9ay}qZ zrP&A~UOzaZ<%S_FVQ+=BY&Lcqo#>SqpT|mfHhTXC3;{*^@C`oG4J=%lBlt%=1y%CX z91Uw>Cx-J&Yo8`8U=KoDZI+qGA*gFg)?Q``0=CO$a%g%nZI#xFzGuw1f3BSlIcXHt ziOTlhd$f&)-&}p%t4d9X(aU&mcG2U=fK}kG=~+1SwD@X9(FoShcq{OM?jrDVTV?uc zZDBdf=FnqT05wLI?>)6Dfc9`UXXBp^G(na~A;pg(-587{!t)i|**v(erM`rMH+&0I zs|L{%t>Xu$R2Se&&>0M04!x6bj64&v!3mucA^{J*CQj{4WvH)KsQQPyCWw zrZ>#$gX0yyKZyzsVFQg6Kh<3g=t!I1Of%hneCbw(gTne%7_27>EFMmw^C3cd%`#Mk zE2N49X^+6}bcNFRo8=gx5SHAcI0amMn=XNUv5>vbleUbC0spUXz+t9$H$F$U*O%d> zCRWhKPP0QR@a7*?ESIJuu6@3i`>8+(_hmJ{ln8?`|FZC;HZ?VI^XW)A#i|G`o)_$% z|Hso0GEM#6C#i{7hY!AaR!4*Ho%@s;YCeo5^p}5co@qk5d02)a-*4==znarQwKk*| z^qIRLnvsb0es*$ui~`MRpR*2?k%$6&1$~o*A#^?Npc&QG87$I{ytl}kmLLwM>-g;J zgao#}(iGE5v?;WP2cR6kV_4F#Udl4v#+IEX2;uN(^!ZT<;)lBj04|S+tf4 znJE3%t;b<7Y|0?OI`9|yl|Svktm~n)ho&^OXb5tC_RVoRyo3uM#ja(?6+oH6jb|+} zs(8ALNBm0qB)a-&P{Y@M2yTcOus#zS1AS8w71wNLqFqWeulCy*rrLA;uK&?$;LxxL z_AVaAXzI&lN<6XTcM@W2G`Z)mjmC}5c%2_mIR-t4%FoK=g z*ZJ=I_8hoI2DN{2j)l8p-Vq&phoRuk&1>h>=i%sc>w+KwD*W}BUKO7W6`{U&&F`o+ z4Pg{_@)zYeBax*+OOELz;do{1p#}{q;%i)Yl(mL99?Sl9ylbA3II{f6ebSMMxL`I= z>K%9%o{KX!MUhM3i%*c0ihLc$#{KE@dG8Ix^I2DHTb3GMr?(QU37>%8*8Rw%g$C!U z-n$Zdu?6&fhn{9x2;%RYFk3Ge6eP`Dlz$EU_f$WAeIdo0hPQFeOKx$sGJGDK^_baWUFUtZlwI2qEH}wrqiT*hlRKlP*Z27MMIQD{B+nnJC8Z1d0Enzmw{4` zb7HqZ5t7Ku^o!W_8Y*@lBhn}o?2}1~!ZBkOg6h?qan*V{;!9V?vDlwmXvog|+>-rw6%5c&avj0U)0bXGz3f1{N0fI&@Q(w0ctc$5>F`Bc{yXHZdHA+2)yR@E1aCj+MjFi~U<)DnX(K~(puf_vKfB}@ z{&)ML(R7tC-gqH7|A^T*C~J_nYyVK7uHTPC&8rE$4yJnf`|m~k_)6$R{iR;y+Tf$? zF+zjCbE`h2HP;G`IxWFE9Y%Qny0lWs6D~Yf*w*0rzj-D8H~J8jUL6WOZ)s5LL_@^* zJIdxBW5FMr_&oAun#bA>#8)IKOrRM}-s=OdyNEZfR9zikTCnhf+SK>7G(_V4G`W%4 zRm|%44xg>~7wnMJ;DOA)?MNW3v+EQ8cdX&RV7ruCB%;mrf(V#QpuNJSXk~|y2r~Mf zHQm++!nd>9Y-%@9bI(xgksoYCn2xGUpd<}3C!b1Pf1HlcH<5Ae%dZ3aO5H0*)o2Lw zAF>pOM^otc6X)5&m3~aEW!8f;IRO+7iWi+_>OdFZ{ai}iKW@$_|;`^*@&(rDDorZEM^rYc{U zeo+wx0UVc=ALc+_zLJB{I}*O2#U-LKGXS|1t&dl7euMJ4Z+~a0Byc&^73Zw~7SRg= zq{T$ZSh~_}Ki}AS?5|R0^a_avKOfKAC4BHZr0|P!-u^eIs6=yQH*M#UkPscu{YqNg zl*VC8;SwX^lD%|@zB>mF@wTWsH?*J+DJopGow;y!U^RN!X$JQ0oiWfpN1!<=onr47 zo#4Fgc}YulH~v%A!!J-{6o7{6*VEDw#O^3*X4}7k9VodsZGB=3nNLuE#X2`on@{G( z#&QPCa*sX@8==CTT^)tj40IoGU!z*bQ#+z?daQs!Lx&2f(I69>&FT2;E_F1k;PHY_FV;p!r*QkkH!&LdUB{n>l zx|w4}CKq_4@k@)vU68C&E887Pga1DO009600YCu1|Fd3sDo-T=zPjesQaSYRKc{f2 zwyGcey(xSBU(zq-Jra_dT4Fs%KH&YKo%r_oy$8dh>AFVbJ^8~2E_*)aKUTR6I1c~- z05AXm0C=3^V_;x#0Ae;E=77>5zzXFvB4KtQ&QHpUFG@{g;DpNm-@~)x*o6cK0C9{6 z7Y_gc0BRoq0C=1%cXu?`@B6=>7%ZgAD87aR$=li?PxzFQV=RVgz*SQ{#>v5eWARxHKPC!8R56KDs5C0dW|34=n zAs`?m_`mnR{DHZ zr8hId*G|=KgDQ7_13>HXchn0uqVPopGANC{gFse($Ue`qd5q@|#7cl0Gu6>yuBDNZU zQ1N#R9v*(+KBb_pCp-q99oVS+62ilY`kNekXc87h(<V6xT<@JZKHP&_!+nO$Bs)(s`uYz!G@H zKeC^`_aBsh9d*vS(jF<>Um`zn0?6e_;fmmj8KQegmrkl*0ncmxWzUfvfX1XdRpUem z%lt1st@oV`km0~Gd=3@C4>7S1`{GQfrT)Zv+@L457lkI&;>p+nkWPW{_<_gEZcuU~y)a8w0rANq#1rA_MH4N?tGWn2*Qpf$Jt{*5W1pRS!@hEp5f6`R34(pe$ww?{K1~Rmj z%EOr^K#+|k*iqvLlq?v$X6&MaOpO_u8C?88NuwF{d(kvxRmKs6P_D4y=^8lM^Dwb(ti^J)^hArV z=R<2vZ&HJp1mu)?sEJoH0}Vn+gpFzukh%FWiSX|nD3T)hx87?W>In~hxb)Kk#KdWY z=NdTx-cHNPXSxyS%yx-m(Lo!gO1e#{Rwj;QZ@CFh9qE9#8Xbwbe#`JrWYJ~brS%Uh zD*BJ48mxgV17VyE&j;+s8S}p~a$x`|b~K;)Fb#w3!sNH58&@FOwTcz6_kOwGex66IT-i_-2ur?C~_w06gp@s#BN>&)x`11rkSR}8{7h8fa zm~F_9Ph7zq^ttfn$uc}_+Z7nUW{gf3GV}FZAxB1$Cy7z#nxW@gGxCf(-Ixy7{c!c5 z7Z~>)Do)w-2V0Cy^t=>4!FEGlFPHa<;C4It zR}#Tyto@I3&Ng}p&&x)bgcdjefwP|NG()neeK~ZU=X@&En0&XqDc=G6J!y!3;Ia`9 z1^<69Xw^{2t8;n@4cw@p((cyZe>!u)w_v>1IT3KxNP3a_T7ztZzdThBTH)Wn5dz!$ zcR=TTDp3dN9&E@!?Cy=)E6BcHhR4Cs?aW3>;x`*uwPB+zaJUE+SvTmrCHgSkNx#1IxCJ<1 zCD@S0XNlnJQrvg8VaSr1VOA~d02IS4US#y%LbY*;^Nr0>;My3cs$TDRsOA35_jj8O zC|}4N`B3c$W|(4Z`DqoAbsqls+l#MoWGlR!-Mu(goi*%8Eo~(~h;<60&@4^%i{dB>Q*e@AN z)qBGsnc@nVOZFeK>*68Lt4Qyr)n54OM$l!-iBIse#M)jst0_pJT4dEJ7=ruZUJmz0 zoshn+!T04oBg7Z6y8NUd20RdR&VmKiSf7yIEjk-9RJT@W&T*$522e55(v>wq5<(a8 z#F(eZE|yY4S+WF8e9b1Jb+$p3GKLnFf!^R#-7Ht;179>Z(vtjhMF~~H0kbeaSMa6| zTfU#ekA&bEwK$(~SmBOn26?Nnci-t`Q`#P2(k4o}Towx8RgPR+(DVwd=c(p4Jm>YV zh}%7m_W+H>aPHl-F$1@`(+DtmUo^3kLD~1H4$8!PInm0_!7vr$m5HVy=%BYY_{BFJ z4yco4NNw%|P6O_?n{)kG>|4!rg14Gzce~q!Q6>T83=bEw-d91y4Bjjg1vf#f${VZ? z?+Msh|NMP%FbM1K>BfUXYZP+1Wn^)x9ZTQLH$Q9f9Q2h$om?m!gAV>1ujmq&L5k~r zfo~g*XyCcSCGiif=w&_A6Anf@Fq&P6UWmj2gRZ8dB~%3q8-l6&PT3(2r)SsC^2#C| zi3F(`Zb{_$i@M#4ryu)af1{J$>=`)UbJ{J6Mh}(ZTUi_S9FVZ>s73;j5;9mFTf?3Q zgLrUPv?k31u`WtU>9Vu~<@OB3ctr+rJ=hYFUyTQinSq__Dwe?6&S>g_oB;ycLAer^ zMu;L}Vo5#J9(Cb+UlOW?0J&JcapnJPK(E>j`fui6;2j5pcw_ zz5H(xdbSJ6#yDvJLzOq}S%x1mugA|xlI9voN*YFe0=HxGjaiqq8l_VvJcUyGldaxxC=<+DU@m*?1f)`93xf2 zK`4FH=M{cbhB4-yB2vi@fr0-uQaH0Yf}LCuddF&0)KH%Av48MBviX7{GcL`-@*TQ! zO*M0HM$Gh-wV*XJXHVP;&`PGV&e|^hNk*Dv7umg`nw|(qW9|E6i zj9HXI3c%j6F|lyJ4a@#2Bhp}QjhH4q@C7!OWbXoK{->;6qt_52Cx#>Carx&KXd&$^S zcw^@o4kBybFT=P>nfW9_1MpYtQjf0LAoNn$6XZ5&g-i#3vijs3F-hi!`;uktu;_bQ ze`<#)$PqFP@t?bkJdDejb4&}d6IJ!Mmy;coDv&NFeiw!Gg9Y^_-1Jev z!u97}alKIIJjV{S>_zdr1xJC47KkX|i>&Z!9me_Fy6v6GH^^|$FrNJA8yvZBf7w`S z9lDfJI-P$OfCP>zGLn9FVK+F#pn82g7U&mP)cX1XX8p9G-H|XC46sPtadOMXkZvg5 zE%|gb3co_ZNGeo`_;?Lj~4>_W- zo57V)YVTIj1erJx@7TS&aa66hU+!HteUT*E(~zh+CE|BK)vV&ku)KJ8-)w3GWw zY-a;NiM&UCrK$zEJNhIshsOSFWspaD!U3zAH9D4d^ax{O27R1YIL;|~BO2Lnj#kv2 z@bDp{!yP$YB(2HQjr(N|ZX5k}V0TJG*}5e1!nQcDsqMGJ-Q5LylCM4Q5f=x8q=aHQ zjq0dyU`b8Vs}gpB)dKH2LnI*y-whLZ0*-{p6F4SZPHB`US|$MzR()1I3mf!`%Dmspbii+{c`+N{hxkQ9g3F5eLGy1fc7`IOlCeA$5fals4_`O%p2yp>|e2$qUh4U_oCe_aBPED8C4bMZX!d`Uz_q`oyJfw{BIBq9jfWSR9 z08xcOQO1nhzlzE6bHKGf)3E$d^9$trA6OqkoB(2alW9!1Cn+bTUWcBLQon@%F z6J0E8QxE!_XNK%i9lZb4kwbJ(2i^U0Y|HWqfOdivvvYEHKz!Lsso{($VBl*>9DK-% z6pPgRuaOj@-Q}TkudXSfa{&+abl!qD~QtDz4^eG{)vhL{$^ExRJxJ zS5k=y_}4j?fkn7|cPMkp(idFW&P*ix9F66)|HXDw$06m|^`wnh0Q%Z-_vcAH!BMM< zK<(>cNO|sxW9l>xIpgqIT!znJjrcCEBi0ICd!u|xpAtcgJA}XGM;w|~VZSHtEDi3S zqwV|BZwSa2ic9k5-GLSVnL9izv#@V3?$xcu1-S8Bp*?@z5gk=OpFUu?iOg-`*+ct5 zxVOFkD%aKz+?))XH<1!WzF)LTlp3?4__^n4MjqqX)_Kz6ok(tQl4tRg{o)ny2uUY& zTr35oe4<(f44Gi&9{uFthXn`&DfzdX8ZmAvbO=&MXh)HVucZ7;z*LS$Oufi%J&7poK_AOb`QQor=+ch?Uz09(Pqu663!P`vGaZ79rgz1YAn9pi{1Dpi1c6htl8RQzl3?RSS%FNl9m*#nv9;%HfyW|D99DM!_=P%P0fK&SfIKP1mG>?2R6M}ass<4w1`L5 z{#^o?nTTVFT(U!@YiEpnZ8jnOwxqI-O*!Q`06ju?9($L3+a^l>P# zW@h85dF+Q=_)DUn~Zt2Re65a1t2 z4YO(sqaG{!)tO(8;QsaEthC`g*gl=|j4js+`3>$hanFPxa+bQR4TI0<#MOkQFGn0u zJmUZTLRSxT#H{!{8s$ep@49f6{%bImq4Dc;1$R)NKlwqPUj;xGW%;#ZClE#rVjpjH%Gx_Vd0> zpZ~DVPmdEb4A!82?_b)X(qPad^Jssgt^vw5hpCp<38Lr#sl2?FN~k_T6nvDS4d&#c zMY1_|r8Akj2w$?kh0V1hF0{|I(M~sJp_pm~RFVc;qrBH){Vv*W~-3&Q(Ch{U_Gdx8hUwjK)V@`x;LNF_qu3h6nV`9Wp+Z__KwW^%KM0G81& zLf)?TkeYtei&BMMcsDaBiOQNAoVOH0CERwU>|0 z3Q$cw&zpoCZLV+i4DSI_HfPbCVgvO0!8oN=yAI$h3G`euwgQ5~jjbJWH^JOQ{@9*k z3+8Roo_Udc3HI-%$v3I<0k`Ih7k6JS!A~m{^V4TOLn&9*Hz#@OAVup_?a_AxnPn;* z1fEx6ip#9uOPwO2n8)fn`BODmvsnA7=uCSc#zZ9PRELLIHw}-tbehnFL5eIXR~R}o z)WUMFzY80yHIVqQV~E21bf`FleL-`eoxlxFKQNo|vGwxST2F;=!KvS=xU_6 zZ;!y&g(2GRUyQ)>{{6r?AxqSoZC%gZsE=mq+j)~;YoU*}N1~NF1K5HNN&3hwdC=e( z$C;eL50;l)pWuA%qs1p#`}V<};9knq@d|zrYWzOt>v#MPGnM78FbX7rjpo8!uPzl} zOf71ER{9xw^L~0r`UD5MV_TGCU+Dq!Epba@24(Q4sn>g-CI+3v=xmbXEYKa2?Nr`{ z64(um8u)esU@z-;_I?I0G|7JVj1yiLP` zMwkEASoZ}A8ZK-%UH3hU>*z;_F3=~+qv6sE*&_bk-`N$71UC36P> z7BciFK7AH;RjR4JB`pP+)uheMS-!~N^{*0F11;24_>}z9dwpR2_6y0Ue|Od0U%`4g znp4n+OHBE5!T_ww4|-$wc^z_@Wdt7;4q^1~O{6rsQ?aL6@m7{^TmWBXtNl)L3nU1P zw`1>-Lk4@3k+ajx@GjuCs;IpdUNKI8(K=F>N(06^K1(y#vL&Vj_iOnnWY6m@azbi$!Dx7w%x*?8bW ze4hM;?ht%Hz>HCfSb`3!(Lfe5Pf)-dDEzTy8Mb#?(79PFBAoJHxuFzq46JVD&R5)Ur?oIY!h_6e|}!Z-`iOOdvgXBUMOH_t?FDO({6 zBQ~9`8{|L>SRK&()g^tZ4hY8^;aEndgN3+(($!`miEzx&-J zlt}_po;}Sh@54a!m%5lv*U5qg~Oz;{o6% z*WV(xtKc$$V@{tYM6{vTCwjar0Nv@bgXf7q;fwyBq+)Lo@NE4=C)ULQ3Dt!XW;bM` zeO$b_7l|6s;#DxaS!{u5XRZfuJnICDx2QzqIyYgHxoH}y zCQXjuw(bst&O-z6l3=^_0iHkZ8^puo$N1A+OkG&oTEU*`1uKMdX%zeqJD?Qa zkhQ8@1-Nc%JwA1I7#bdUYzDb$10M2iYAQS)1~fE#*HfCISYz7j_Lgl>V_Eu&8BYk< ztoI1Jclk5C$`ClDWUUYW9{>OV|Nj9%0KfmF3z;;ol0v<6l&RzsQ0_mutHQVhYf-)6 uJn`M^95ub-2OqMW!oxoY`R+#8(^EoZaB)YV;$))z{)^qMt*zRs?W(TsoAJHgMbzX(eiL&rG84;-s4-Yr+nLi7E7=*lS^%8wd5ItG^n2G;?4iHdjd1Md}kQOu$5YHS2O)qYAz2*GI z7#!0~95&Ov^tCKc>B?0feks*#0hB$a&JPPdldhESoEClDX18==TTL@ zJzOq6WgfZv5_zoGcUjoRQgVd%lJpmw)L^;aq)1uhGGcRiW!T%u<%vgc5{hxi%(&GK zn;5NyVCU>_?XdkxV2762uEf4798;8sKRnW4KHS!wY7~b+Mqz~94a(rW5wv?7RnerJ z$KGDv5J*2Qy+fR<_^~3YZt#1OX@ya`9cl&CQ0cDCfqA^_@QraSe|VP zFoo6++wHZMS|NB|%{DP@zab0oJKIe!tHBtcIYyvj*hw+UF7y96M*_C0T93EkiS=>i z%=Y%&Aj5RaEwZkX>_1Q`+T%fQc>34vJwGZ!1Zc$ky~CV1eBq#62!s@StMOpgmkf`H zcGs2S?Db`Hr7WRve@@$_b2D`o~P`SahX~j?e;^4iiX?;WzKrl; z;eI=m?X?*~72cvqAsE3%i;<4$+MBMYAG|6G;=k4 zC-2m6$SX=;8x#GTjmz%Mj1!vP3W|iz^7tUKJs92lgGA|zLQEyLDn9hm4A&Z#6lh`N zzAmka8^`O)%_01cQ>Mpckf~O6@D2}7Gt;27-~M>XfP`)nMPm=}lRFIF>`qv?#3!XR zXd>3_9fhDTS^d#kH9CJ1xvp@hF9S@kNw<>bfo}2d_-Gf)2hqK!kib?Z5R~5}d7u?T zxVj-wzcgF8&&oxKWKp!%%4M=m2s+e)Cn8-o_2bBKRt=n*fr^F) zFb3UE)Zepo`R7@hV|E_IlIL18{6Ebbj@-K)d-20bJ4lz3RC z$^T;hNQ3gw?ZwCUF;Q=smkC-k%*tJo{s)cQi2=_?yL3=}aSNW@Ec>d-$7}>p2r+0m zeO!4`{#B`mqKqpY@a4^bM?Q^}kfA<1+TzA`u9O<-JOpWfo_f*kh$BwZiuuimw~(4s~@UbmG&J^$!vf2lLNu3)}@Pfttn;8 z0Z)PA#j}39G*rI#QxHL#%$O^i{9(Pl-F2aW-e{EhK=09{|KrgL$({?jgvf7fLwTtN z-tTk}E=_NkG0lX62)Dex`ax1!rQJJK*APgT%_r2>Xe#CQmsS}(PI$(qvK{=js6+GO zdSJu=sa5Cp(_p_3Dg)okRx^VEMxQkQ0fyOFKNj1W1o5xH>FUW#vV8G_3!*vX@vos_ zs?*|=Dwl6C&g+YGhvZRBpBPW5V@Stg#J<#{nE`N7y z9y$)yPO~2uVtkY)$c{0gv+jhQL-`gtUVjv3;O^id0LD01p-Yr2Weu6G?71weMJ5FM!QyRMGtIQ_1@hbQ$ z!oDIA6M9n@$;Yigf5Mh|`U@aABGN)zw~-F(>e^bLZdZr>?G^X4N!3EIg5x}NC+-GH z_H_|HaPOMK(K4{#&jEEUhsmTTqB=4qXx}cyh&hDU!Y6AVV*S>2rCO1vb_^|9s6f=@HF?nw44*a&=7!DeXgLV3$bk>G-qI5? zvouYu&F%s7zo&bW$K1z$+ii6U=(10O?dMR8RtKFxxlcMa{vS*6x`U ze%meR&bem@V^~H*6@o>5G`}GxTdK_>^{?7Ar6T13iiRObw zn?NQ?$7gcOGR>S7o*FXOy-ucU)th}GkQ@4ORBp-gdn|H6stDJf)6FTXwiU4E7O?h7@k~u65 z7o5-Beyw_2JWzL6d&GCU3d|O5UDH#4GvQ6(W#7q=G^X=}PK-}{^uf@Ristnv=-`j~ z)L6Hn)$zJ`v4geOC+8pYL;ot$c?c#rKBFT|+z98kj4 zx{Unwm_OICU?gXJl3N~a)-7v17Aa76*f1k@-+~?;$n@phd&ooqK{HzZt*3-G;4Xwf zPsJxzP&3GLb;mP#%I^n`&jaq0jU|i z(cH;06Ghr_?70tDC^0{?sCrR@mRN`DY;}DJ_GtEx>Z&yZjuwgNZtaCJUqi8w%l$#s zT6O*OAuR?u+fnqb-V%ZyrddnYElc;!XGcc z$7nkiyEt0;gW^{!9h0VeY5KeAbk)b8D!K25mrlOQwX{q+#w|iwa5&FsbLi#GO<#Xi zmTA4P^BQSgI6uTu3u07qZZ>J+hw+Av{kvrprbu^1J_Y;=ofxa?T+RLtdI8>6VmJP2 z`BDeM-RrAxPlo5LI7uLU+-(r*)`^YiPIl8>m6RNWqD}IkRKqyh+beCl3V_d=ebVC0 zQ4aQ5Qn(hCMK;_zD}Qy;@=hyd-B_H`7i=~qJ=#P^DV_PYxI}#s?t{_hOT8=XpIjp<@?3em=^_ZeA1&|zc zI?$hl$`0u*W!aY29?#!SfMhg|nD~2-Cr6s5#!`6|x`lkq0dV;2voIg?#tr%9_gOrY z?9FpMz5i-KDo`}OG*ZukCqa@zilAX#LJWl3n=-@|Ye~{qNrG`oY2DWBBDjT`kF-`$ zk-zF#ySfV%K-*hwT{O5~$p+ZoH{zN}9XT1aXDMZI<1}{h=-TomU$M++ynE-!q!k9J z<#hwrJI@f#j{f!2hFSmHGotKPYSiGejR;u_`StPg!Q$BY*>WO3v{pbqIL{L(9!Tmp z7$v?jxxQ4t=lAYUe(rr0`DRUngIzn^Ub>AZ{Abhm@+w-o!zw-}uy}*PQC9KjNm(x7 zBMzU6ebyzBcqiHGcMuZ5(tSGmE@{*$bWiV}>afu&|C062juUyNE~(ygUGQNyQT!x-3)dO z3L~xvRWD(G;xLaR&G!4T{4-5MnfI*-Y;p`8A5U`oo{;d}oq*xJvosa@_6LC5`wjxw zUi?n*i+%>}r|gv@@(`%J*NJ-c3Qn!{D(xw~teUV}nt58t9;TBfHG}~KKlWjjWA0^+ zxUFH*JxXDNK#%bXb@Ax5@;xu}UBje#D|Xf{um0k;^3*y|?-d7AWrBo)7qbR>ooB6- z(BsHO(>MQFmm2dzZ|=IVrD$kW6Bx0gLo7qORo}KMin6^1Zx{c2uVl17TQco!!);}G zas<)~AESScWjGZOj-p?Vb^OSH*Y*=$EvO>cG>gjsSKS8$E6A>iklw&^q)y zgO0%J_)Arybdc39$thkwSKQ9{t{nu&vt40Q*TxwN(r(D{b*TFQUW-YNPEe&36m8)` zI50+j51;BEleJ`aFFJc9n)o49upMzSw>U#?ys5OPS}8($x~8L1V1OF;kWbltw-T7e zWsy9Z{$^r)6P;h{#?I~Q8Qp~(8MiVkhpK4)plsPPhQrdvYV` z)+(L*BALiAM4rUW^vp$O*L)SsnUuKb7HC2rP0OK7oxUL~19(%n@((0|%AyYWNhU9e zR)Wv#4uGrd1y;kHePy5hm}nfLN9x{_ttg85ZKBVH-@LZAL!s%U@V4p|8ubt&{u7aTQV=+ORrj zzO`D(zr*PC1Im_ZI9~1HhURehi)JnXMVhjQ`^&QlpYW_e zWy<0WZZUj1Gfc-%y;k07cWz_gby4!8LGDHD9c4&*b@uMv-5B=!Fb*-PvicRzk zkiVT?SW9>~j=kryt8Rwk>uC1m1<}?ApV>3zP5D0%2Q)9Ww80OKiHV-Shk_7JWGN(e z)*_9H>GOn!9`*T|yA{lV*5S|1L$-O}I_XhIK#qeno?~K$-)LnNE;fwTu@5(`CTDyZbCnpO%4=dar zlc+Y_BQ5%E4-QYwnxy2v9usQs;4Q$DKO2TjTcqJmE_*n#5#8mGv7 zEsFIaT{6Et*zPABu4%Z!>v9)M(zNDvQ|yV`l46*zgi-X{^1Cb`G`?v4FC5<2B6?tn zOE2MZRQN(o*-I0zsV{ug2Wzaq>==0--~ zi}(n?a3Qn`KcR?E!AvBeD8t~up@?Bq@ga+i&|`v7;6UVAK@0zvocuqbkR9D_naG0K zlpwg#3`h1@U1mb%fxjtbEh(v~P06aUg_^b-mPD)=QYpe=lyH;3lEF2Q~Z8D{^##1aO-j( zGlp}QeD(u~d+0OwH5{YziB|hdZRQx-3+$YSOEM{HCTgVV$lYjG8lsD><|@~)YtoU} z)EHWFOTlntY-suv;Sh3gWfnY`r2`SgNSWQ~;{+$Xk$V$WI}I0QvDIf^l1X8!?7{Xn zKwu=ofJIn27@6gmX>->{3rM^^<2(6*%RTXP=M(h6C?)PY`dixlOM>w4S@~E%dnh>v z;Y(bR-m1XQnMw41qk3LVh$iOz@f!f;)*d3BNcXej*EgiT<`xDz3ok-My8hj@4cd_8 zWjXlJh(Gv5d$b;spMOHZYO@%oFQim9p!4{zC9H3U$4R6)8;(K_qRk}_BkYPBdO4O8 zlZoKQd)+fAbPvkS#z2b&sI9MLY_>`>bx(J@II$KX77X`CvonmN{B6{dsX9uH$v!q9 zOCszBoaZ&a11TtamVznnu@+>lTI{}LS=%xE-I_x`CIFEW+6QZnNWwa#tb#h%y2SY= ziafY6A_zj7Q!%Ofu4XNi+!I5&cS%7U)JzF|-W_bwZ)-Mp!be`vG;$7IH5M_a z_S7q34PT~)EBQ`uUS~+^xCBgam54VFJ$Y$9%wWKWzu3m#$^O)jqdX>l9vF+lpe245j<@i@4h^pY=|gqa8q;ouhj5 z_0x9NuQZw|Zr*KXaC!pC$;WlY{;q7_hYKuBk*0a+WM+G5?tRa1B-wJTRCa1Bj8})@ zRU!k2*)@E~S zO-XSaQ2ZO_7q?K->srNz?_?mJjO`5QvSnb& z4zuu3{kHN~K{JmR%WDvQ)>%gdS*^w4IL*=XgdidJd&NJrC9yEL8`J9lxWP*#lA1YO@d{lVfAh6cK{%G~(N2qqPh0ds^6wV(` z>o^IbVAV?b;a2B?#qh6w1ZtqXwUCPR8~nyr#HuLK=gfz&kL$zZEcpce5^{b|^|9(W zjeM^Q7c`^hJLBD3;eip$7k?r2qX?_^kL&xspbf*2iSA18lR0Sof(3JiH0wY;$Rwa9 zfp+Vi&&k3j$KM}U9G4><@P7Xc&tOw&xVcyx7r2k6e((P}tRqABbgFf6y|d?x=rg}{ zd7#43AyFAR7S?klO79f!&$+?dI3DlEII_(xR10~Y#TPd>$Q5gOK;sLolr z9Ecduvl7Y<(S}s5_u}KhOq)iNxp@dndxU=M& z4rctQd5A@S!+2H?nI>(pB0r>&K0(u&8&%$xy|*BVy#2jWjm_2+^2{+}_oDxUq)Y=Z z*4qpPIF5|%Xn^@u z|DZAO4fI>CFpb7gz6LgSN3-#zh~4j{x#i2`!YExte+A&7k8H&yY|20H2Fz`s2@tZP z^KT$9mi$eUP%D$q+nL4~NWW-PAlO8(jAtg(0 zU%ZcO^>5{w*{L}Fq$)lMKV*JA+th0Vfwt?-_eaN6GFDD3ieCVM?gpH#%IL0DI*cx^ z{=&k2d1)ILP-hDebXGX;+arVKsDG0Cq!I}GHua!B<+?Yy^7OVO!jjhd7S>KEn?(E~ z!r+8T~+_vxBI zoGaxmm4r`r1RB&qaW#vRGwtZ2J>){;T{m4hYA1%}=k<1cktTpD;`_X9&E4PuuhCAP zp5+6qyjE#SWeN6!Q`fOS4oG<)G$g4`P?3?O{Yn*RRfppM;g`XLN(WNbM_YpYAecz8 z=63{Uh5sG`5az--)byICC>DjWO(2^jSD`7J&bx41yy3ajey*K5&gQ`!e;}LP9kL;C zsi0DNJ;(clBzBk;(1`>c&r1V{v@r=gtrco*wV-0rq)R2KP6Ke;Zy0JdkUE5teD+B9 zsj6hJ;XTS2!Ql%F!bEsju<@KH1rHmzHSCfPHv-;`Y41U$DcledGP4cN5(&Va@i+`tYyJG=LF%JW5jcf88+=hd zrZZ<^)0x_H6Xl9TUP!Z@z1_h^xa>>4nIjXdZSXepsGGv;L z5?|Xxs9yj-W**ztnB=W4GuSor>tsb9iMb@nXWfibnq94!KStlf7|j=9P|{s&(tLF9Pezuo~fo zEaP3P4R*05F)VcaqZs7^LB1&>FO6w^7~d$l-d`ZHCSrMovT7i9Dsgj>6=MQ_rSk>f-Ni1C4ie1Cezu$Jy1p zu?Bf&UnOi1)r`0MY*_AeLzwGPg=%xlME&wXuBh2TVOndVfuW?_gGQoQxQkd9bopHWRP?5f$YoC z8Yq{;E>SD?vv80{M#uBi*K1qy{-0Y zw~S!u((pXBU*A6}eTX5>{I`jZiNM)BVKwpTvv;zTP>(5aI@!%H`4#@V_Zd^K&bc9@ zA{XqB|Id&>?KarxGYowC-|+^Y6l_Jc{V`k>v@48In? zgtC!VI99?tUG66(^xl(DRY=Lq{yQd7sy#Kqw}&jZpA}s(tui)!ms4=)(#Yz3za2WL z*4zmaf@|J}wY~PNCjb*__k*~|MKN_R-h*X>cnCO>J=?Vm3qZn7J2h0QUWl5-r{vu% zmOIDTR`$-pJV?{)OXQ3}GGRwSqcaDT0PBfr#>qLOqWRQLYfOfVmHLWXH-#D6EC~#I z<_U5h-A!ugmK5VJQPOU{V+LGwcFXndR&3F9+>0&8;}!4y5I@aD8Z)JLKJbZYTC$7Y z_a_Rk#F*&r3-Mq4J5Ui3`KEeRwgTEWem0`;AmxlE=b(NM#gL4P>1>4P*VT!Dt3xUl zBU5f~IeJqJy^Mo3RNWC|RMgk|CFXHE@tjTUKgcj7sAL&1h=W=UZHGB9k?xYWwFtOm zB29*l{D^2Gs2*Jc2keu3VXD{!-LBwaV6;9KLYrISe!s*PwMg7ApRSL~l%ONphpc-h zA0mo?Tp|lKrdB_&3uNc6+3zf9juCoiab>zFuQhxWycQ@eQh(WrT-32YQKqF~gWJH5 z0mS+I5vb;J=U0t+E8u-^fE`qpuy$j)sWhE!KZ){>+@J(+46qMq7ptK-g)OGO zr8x<1n=86~GgVcspE!8B5UZzOjk@>#lglXq7PMtFHb=j4g%0 zR#Q||7Q2MMY+;*J_Hd*NPP-sY84sOpX zVFwCH{f;}mm0w%u4SEH4JVWv@io?Sv33lfVHhWnYS5{8b+sDV&T}@#@Lt>0o zU5`Tvf%21#y4vR(9hhsyEnaGE-aP5Ox5cW*=6l0<4GB?-i8z`G^bYOtG9vc$j#e(8 zolAHf4S}hyllm;Yn=UmYueoueeLlOoQO(}06;1uDdAcjn2I;s$2b>JYS$lM$0+dGe*wP5PghcL;mq8k zC8Ti3hGW3jR9f>-7SgI?fhu{0HslP^^l1)>2`pV-M3!t<&Bq>XqbXe%gjEq)Tfgye zRI{>CbvfD0F0*wZ3w0M>X7mk_W9}QOu$2Ih{@Xxe5B|N}f3u<=?K!ut&AH9E|z zr#)%E>>wUrbOa{}ozdi7QUkv=wl?}hMrXm1lT$;I4e3$5J5pb&V)MMJW^y0v z7%a1WU(nS3I|X|#((w|)mI!20JX1ra_(KSFy{vz8B0~GzWO%TzR7L+w@6#_t(&#G9 z^00`6z8X%}OG<)h!l|atUa2r>1cB>3Y8->(*+G(8V`QW!Wv2&e`A9-ig5MXWnr-E^kALJ= zuJjm%V!SWjnasa#v%tE-``BscEt?ZzVY7%o10O6+D&vhsalo&70;(yde?{)d; z_t+<^vwcE%yXK%`n2ymiDF~P--s=0ybi4MmIB4AUZ^#b{Y=4DL;A7bX0otN+)W@%T zA45-I@9$o+`-Kl_MgA}9*$PG^Z>RA*6&EQ;wB-2n8Nt^elh=55pi}!CpMGAoYkYko zR9gNiO4ipqB!7;-b6+^$%emLD3bZ&Fh3OL`tZhYTiUYsbfH^*Dsy^91Y$N*14N!+=MB9jn;oZ#T7f zyZXWVZ((_UV(lu$WH;Y#_%Yr>eGx?hUJW_$wFV2Q{bAzUy23 z1YaJq)}OtGN2bS(+~KvKChPEe340&#IcO1ROxDVO`mO$LX0d~Frb7FtHJnXYc|6aX z--_{+C1y?CW8++fv&!|RUPO07s(cGij7za!JC#l(TS91eafM(S+WtN+gEy!n-~WIk za`QT;H&*X#aw6>oBkU_9R4}7=sdTDB_Rkjl!nUtD2Jx$RCe& zKmPJh-9|*z(|=OBAlth*8JT|gIi8lk-zlc2*Uo<0`qs*>KHY~LI^G-GCS6>b?l!Sb zEZkOU^vZR3g}feT`dkL4xp%|?j?Zt^k4)-NhFxCDU-7$>?Z}co?@b1+ocj+bVn1#T z0iiRYM7<+|M8G##2L(Y7tHaDM5vm@a4~3p~F26j0lhzqlSl8*NbEMH-j|~xri1R>9 z5%f!By2hv@!|xWCy!0M>-_O_skeIpG(JL3772l4}D7P)lZGj)-{x81e{yNz#KvPfN z0{u2_p7)IUkD{lK2l_mgodyrZ?&JeD;a#i!wfOzDhjpI+Ax|7Me%dEOI~5Pj_45wX z-D~x+c%}R5PEdKWp)&H0^L5#}f5h9f_`%-G7d!`{jXvUAi_pH*d^FSK)&8ZOVVnH< z+mkc9FQT%M3xnTC-t%0T-KW7#oRSdRA)Z_!-(q|S}>i588Sg@dmp!b ztQ$geu;kQ@`2|?t!GU*3m+#uf?HyTL-dg=JmNxH?5I_I&n#cFdm9OwK(9_y$==D4Z zK%RX?++N659qH;zMu6`4&1$Fb;WZzE_g`cD{FoM?dfF5|ODsLV`eEm@JoY^v9i6?- zc`;TvzMu7dRr+VXd>FdzsRB7nR^x5B>MEfe2>jJ z!hUFh^2;gr`rFstPNgAGKGRI|KqrJ&o2LRi*jw_^oZ57@ zSmu7Jy=EBw>rEtO+ARA!oiwLv#-GcaewKK@lcIICZhy{W{!PUy z2H}ff=6h7@dFPej&w&0iWb(hH*ja+?=}!$`P;m1gRNCC-*f z6Ofn3Zhl23BsRU~;j~M()!5;fYqpt*(W0AI|C(eh4G9^(k=EF#R?<#QQISXtd40`$ z?K$(A71(pR?fKbux$WE|Ku1U4JVZx#{W1R&^)vN@*YZ=(^a*|Buk;Q5=+EsLB90G# z59iEx;=Xw$Gz07#`FTcW@t^yt1cu5r@s8>~NPz{voQHVfD~DXHbD}XBAgdkyzRYSx zF4;VYPZqaEGn3HNZ6j%=eS2m-k6H~8&Wh>Vk7=UJJM<$IEP4&rt3^EZpSllYr*6

lR87kRXGaG3Mo7nqBg`dvthG<<6Y_xL`ITw{CKHnk@RWo40`Eo1~SM z7@<7F1wtU&lbOO!LA`JxL)pX?BuQZ^Oo2@-&fk+~}ss$WXXDKnRnt5+sno+8@K!vJ+o%9_;N2C45_08nzo&{!wIU~}j1aaDnQ4dNBu@Y5Hp)t<_U$pp&qx`E!C<00Wy?J)6_2a(KJ zR^2fPQM}2~$us4`hXC$m)ws>r1CD^r$rGon`z;mM%!!Oa+LqPVf^N~lFLI_eY5s`! zl-m<0M)By!YCUv~CM`jpv#IhJf+%wJ5A?jfhMxqB_=nf6&Wp)$ahd~9aO3&@PBU)^XuYlxjI1%WBn~F!{P57=I@@#%y)W*f zww|>~k5tdTCMsUXD~-JUI=g|*(h7#-dyh@oPVGp8tFFpF_OM79-t%4y?M%tj=AQ|r z@3VN@jD3g~4Y<~uPM307vfe@{<7>S#G(B?DloROw(S{`a?wCz$hT7z<4CESPwP_KC z%O7M`^|zo4LZdY7d%I{oBI=0fr=r8PN^3W*WZ=i&E1j=0UGanBNG!;fjGA`aF2LPl z2SR3VH;chIkE~-1TV;MK8%RX~3;){5D-mK3Wj5b?sv-(U%Fo6#*JO^l4y&6qN^(YX zZw8-4=pFHP-Jdd7r}bJk7Vh2*VjdRW)dF5&DkUpRc^qDftosE5bl zajmw)&JY~&1>Lfp)ieWv>vo77B@{WOWe zn?_x5ahDY2?>8C>ziOTS$4uhr3BKUr=KEexj6WQD(FRMT-8;a}H?G6dFY9Bjx)?R3 zHi)RS9`PT>yHQnj=tDypUowL)-p7BECPKoS!7DIYG%Zwn5{N-vuWxsWghiS(X8v!(xCW zLeA?R25s<~N22C4Tn)Dd;j@ML$gieU*(~*1-AEWw^;IlPb)98@8nZ7k6=S+2Web~4 zl(HG0?zwSx)Sxq!?yYuo<69l$BA_%yMN;vbAB}532KXON8)*S%g18gdNDk=9*BA_) zh2rCt2J^XKH!_{6GF6Fh?~sMMrt+FHNn}M@p?~j>^XX-o(d81s$vyXRwhIN*Y*gD* zW!ojbvP3k5KCVEYibzV4zu*#`a{JqDs^N``ICZA!(6IGhVkeY7ylB(n`qDB0EIt0c)S35EHHP_oT|vY^4nXO z(mYM?6nhtA$^$-5ig*+~wU5rpexiPpxW!bc98Kl>O3Go5Sqn|9fG5Yw@EU)>nHUt? zWsl^bQvRt`%e#UJ8@Ox`_hQpnZ)aFJi+YysaG)Kp!7qni8>Xjam&MrUtG@one>qT# zru|!o{$Im*;gFcuG$GdQ%J@WnvZ%x;zbc(EBzwe^Z4{$%=ztJuQ!xB;#kNv=zS9%M z!6!iKGP(%yR)F5RoNxE{bj#)!b(5+%Xee#_#qnB5w63vZ`nKOV+V?u{^&KMKsV2p{ zp!R-k->LPo;xX~RAmChjdwJ`5k)WTn%}SuuMk=>yqf8U&K;nmIFQZg>*}pUAJrcU# zXQffzGbDY3F*aXVps26Ckqiv)0n0>lik|>~TYVf<*vi^Qh@U-+mP=Y?BM)-Ridz^i zANmd78RI;x!>*V!&k?f$1=}3IpG0Gm*Z$}Q*()Kb+_Sm(k*I1>O|eJI(@h-KGQDg+ z&JVK6{r186w=WEyF8?3RaxZhAvX)}*?QNno8OTd#EAfVA`hWvJ9jFxgj+3rful|6B z@3Rf>umVgnN2Phq+sUxJ71xw#0#w+F`k=_{XBEm0P-V#y0`EylOolGZfcR zM*LMKz6gh39gn{zDc-$ROnXz8v9!EYUTr8&5ifnJom$B0ny`UyJLc;xN@tc^HVyb< zuTy~@7kB?BVlLAxo}B_!1NTZ`Mt2-IQmpOOQo`=4dSV8^ZyyU#^^$XP@*^&Dc-#58 z@#7YBoMbb%|P7~6QmOY;juc%SAR3oKlPVkyLtzR3=;8e?t^Cfp`oZ0x&KUOf}CS1n9ErLb? z7E`PUfu|c%Oj3DL7cIa0o9DZ0n?8)P-!vE70B@d{0{Jm;>~NfLv{cmk!-YPV{(2MK z1Icg@{qXvJZdHcECV{GlWjrUo1K*Lfe`I_4VXtZ$^J7E zW*slMI94-#h>xwx@;=>NpK<7Q;l57vFsi`^jfh*1ov!N(D0lUE1qk3QkL zKVH0prt*bY7XdhxKyK-365TdYhcsRZ)Bey9tZQC5vuDXbeG?QdTpO)O5EUxKV_blH zy8DOrUe?1p_i6lb&Sqv?+$`O?KE?LdxBWZb1*$5Y#aWvoA(&!Zqf1}!bkbxb2gVC{ z%XrbcPDm-3_bsmbxk)Lx^T^mt(<^eap+d~3 zOE;$^t61MYcQ;9>>amLDny&)4bg|jjpa-55!ELwm^O>hAuVMV;w5bkz;YuHi+J+{$%zTE0} zEfYY%v2aB2WzvH4sRzF+Z&Jx8i3dWuQ}(rVTRtJBy3cambCDa<1%u#o!qgN@CWjoR z6)PNrDtJ1aOyH9gL2Q~(k>%k=28=`Y;f`ooZ!b!l|MyQr_&HYJ%^l~bMXEB}a=p$G zY_B+{{>lx^(}(ywFD?awzU@JaiOU((rOC8KwvP1k^MSJ{N*eCy316Y_=yV|1rBrdY z70y&>&nixO+-HgK%==x0=uf1fx*Mf#k<1%ukm|@67e|^)dqmcuf|QB3n&dCNe#de) zU{D)%FJiQ;Aws^b3V{kNz_L){`j5lot&m~Ei=Cfp<1oJA0e!W6J$6iM6@}9qfe>a5zrZ<}J&A5_9BWp1OJcuLy4@~cA zcw9yyj0O^=lB?~j)ow3dn(E3K&eUp)S?;$~s=CYJ4fa%(l#nXS3`dRLoY7bVTPkmn z?I-sU;Io0-j_H_FEkoL_SomeJ&^N5d_1x}p|KR{kS&h)IGdE2U?6|(wUbAy5aCwY; zN)&9^@q4@TQze*S{ldAg$~+p$r6We~auyiiqo7Em(O+n@tmNJ4Y*WatSepRD$P`8O_x1Ii`<~t z6m)nbukGb}98U+xV<^M1_*+957b)mRL<^zTYV9K37JW-++%e-CyjD>3P8lvt+h~zh zuD)b0&+iHQZT0)2KJn!*u$-Q`U9_Wexej|>nEjbZ+Cc)G8~l)-iiwlc?;HYUEBurE z?o1E;W-YZEttoQcodtW;(m-{)F~1zbZp8L2XJ5>AvAdTY|5@Q=hKa_{{HEVKN#K|9 z=cW@kzd!#5AQDx_7|nU}KK~s@-Jf=z&puZgmAM_PQ>8y4|AOiJi^ehXcnHuVN|~%G zoPIlgi+PJ@`wAfw;`{iQ*%bW z`!i8(3NLw^iGVhJPlfaN+9_hV5~5^3h~m?yR8-!}hpvA3B_Ey%7q%-rZLa=KlYb*S?M=|BF*@W(7+S*v&_8yye&H_@F$*mK4qd>($Oza?Su z<#^!ysw=V&prX*gX`h#NIy+LYl8D^zN|UHSy*FU%qZnFU5$ZtELZdKw?MbO=qmQn2 z7JILMk!n)V5WVF`I7;t=G=6m~Y8yxs@?a~GB6xSr+(9pRmc1wIiZYLTa2*M%IJc6i z8y*Cd&_0nUG9$rWWdHs`{fOcAbCfQ4X#`)rNnQ0k`vb}c0blRiy)ISrJdEDAcpi#8 zs#_|gB4l+V=k-^%lMS8juyyvf=8u@WRfd!->sX=9jb-Gnyc72Mc4Ol=F+sIIl@g?T zv*>J!)hC+0rB4y#>eY}J<|?BUBy}Im$c3q?Xo}kE`Fri&cR?2?6kZJZ`yEl}5GmrI z{x#OP>juk3<&{XkKS`>d-7X{sDUz+`HUiLo=w+gaP!q_Mz&Dvy9O0O0$NG_p)D(}Z zk`WSLNP+Z!08&7$zmB(6MqxP&Y}dP(46jSDKHyIP$ewepT-Pyza1V9U8r|#o)VSQS zXU7oXT2&G7_yZ&xo)nEgla4h!}8$)tc;TiE&RbCl0s8O=R;`lO9$(p1?YzmNty zpSw=AQ=G=oYR)6$=Jlk5wc|=dQ5AU1xc9MDT!r%o1mi+i^l(Rtp5M@d8*-ayNrWD> z!*2UBp2*I0($ze$Y4N!iq{>TBhto8Yquxr-cCGlpwjth?gs;ia6JhN<|2`e+esuWh zEnUQl_m@LFyIvEf>3J@M-i^On&0L5Dq2l4F4uFb8wvE@8mpN_;%$VSL`4bsVQPE%( z!qh!{VDFVhUWxr4I7lUi#yE9C;z zHJqwTa0|fGY|r(->!-s*=OtN9nlz+3bGEcSJP0+Uj$(HxxIQSwIb84I$qu`$&89Y=14Yzfa_ z-v9j#Uzv_Q4}ep=j-rqLxMO>=#b?VC7jX1Iv~+P|7@pm;Uj6$-3M$18W+}fr4tgBT zUz+Sf(DKTD{@&^+%cF^?dn+RH-)0uJ zznx*#`RM`r-D}@PjDrz5PedK#5dfKC0pmRm;n>a<^u6{!zqxZ=h4E~#J1)FpXWrps z3+hUAexcJhFr4)cbmY2%dAn1r!P|p){#MLfYFaY>_;>HuXj>_EefYFWKlcF8g<`SP zItP?$R2JWO`r(H{A=|PCaTpeHr{LSE$AoZ~bS3+*AQ*+&EEJA{8tVOu=cJHC?|4;R zEQG7cgOq90KHyp%Qr&Ik4jP=cyU+bP3P)xV7Tr6q6Wab(VE?m)Tu?EK_r2hP-+g%o zHNRiNDe+vPcjCwJv%QLK=~5l(pBq4#6W|AN%S8h`x5KdO`UJC6tvCLW>wTVMT}u>) zumlZzJRtj+-%gl2dqYN0#}MJ&Li7d3?<(7hz&VG0*9=-u3|KzGFel}JD+b12cgV)W z@2AWQ+u{>}mB#DA%jZ_OndZgTFTV#QWA7-ZE!)GT4^e#4WkW-0bqCtf1?#Ch17xJ7EA=P4XiUqxeIPEz2lj+rnMx{%#o5ETUBPV@D6n9v zUr`ecF>l801n4i}qjm+Zg306Px5<5Wr?c+81TKCj}9`&@uU>sPg5 z=^=>Zv5^q4wu9@|C!>pTbQbemYF1s)Y)-U)Bnbt8#_Z?t*q+^hAlV z*_&TX#BgKt2epxmHvBym_kiu`A#4iSxgASFfz4)_RpN{jjMTSCNc)r#LUICwW&2IR z=fJa zmK&IFCV0n!zVdN7mTlGKLyzRL?0@`VIA!GY&h`Mx*Yz38(2kHjLSL_@d_9I@6K}s- ziE%=P1;gCkydyvrWfw$Is||a;i-{gyH-)I}v~*P|&XDrK$x6g7om6waI_a+{fu6z^ zyw|Vng%rV1&CAL>Q1G$8@?oGQ9;<)Pq5ESu&}Ob&cI9Wr)T2N0K00V&UTwdc{0lWO zZsKK8OreIs3l!_Wc7=hKF=P3K3rFzd=7pC!!uvt=&H4JUL)_qS#52Hs&<4YlUWN9B zdE$4csr<-DOR%#LlaynxBnjIHt@0m-v1RqGUG_D42!7$AOHW}5&HSOOqq;WGCUiPo zf3$@trroXi+np2Qu6}z`IQod}h0v4lo6O)rjPGaGQ@Tih^!Szb7p@RIa&~MoRut;% zE>+Z(CBmM>=9oMNUHI)Kl+QlQfnlct%I_3uVwOezu<)=6e2mvt^)=Xsf|)&OKa}lZ ze``YRSky-%PX5Tr=aI{VOnYhEzPJ; zP6fCAovBVXt00In!RFvPHx$nq5MS=J#+%KrJyJ#Mh*ic{M=8{f;*Oq+;;o_FkYo|I zyEVUtcvv96xEXB&OV7Xk1QBsO@Xmdsc83iLO}cz&f4R|QJa59UvuX`7iz&*_=?=l^ z@yFbnGkHYsXiiUbqY+#`pk`~X<^}@>lT2A_cCeOv?C2{kWo-2`5}n&&gle?W+=6%c zz(+l2iOhl~7)#YS9 zCZpxXSCuX+;V+(?191Z)5S*;<;Svx8cS3H6{~Nl9O`}F9Rcam)=Yr?iZ#6AxMa@5L zp!D#Dz{NwBN{98}(AD1G()V^DE&s`(XY+@EF5>r2-{=sKe~I<(5oU0jN?_ zbM}4sh$$E)?Xk;>l*9MKMaq0O>iGHd+4EHcp)g@_l4aaH1juyhd=95VDQauN_l z_6KiGxP;otiCBAHK1nhB9iH3LuwsbBl@}wbV1qpo0p~@$| z?r8Dwi{!;`8B9M%zY=&%zd%yfTn>)oVhm-fD?C-z_?*pbjYm;|-Q}wy#=I`v;rYcD z_5YTCxOI$x{{a91|NjNS2_TdS8vx)HZ6vfp?s8P_Bcbs6#xQQ0G0YewIZ~)lX!wgQsTtZU@ zeA!%6Dm`(Fbl(-a!zDi$I=8B#S6vVcgLTfJvpAq(qd{?lj&f(syoNxuj>Dn4Xxc)=YXwmeL=Jj0P8yQ3?5% zSpPGJ=gyM%E?jd1hYb}erp*&X-=uEP)@O%NW<2(r#l&v>na+sml^2J}qJE9s5*9$y zNYbw=LpUBVx%P^&GYoP&opvwof{hEQHbTA@DAd>?Y8cG~ImMBTdP)=+sSW%cuhKTH+1|s+yl=CmepwNaUu@U<0{n>2YJkcjS`2k z(e$yTtD4xkTmK>40?YFA}`T}un*t;%G&KDwNh2~q@C=jK3yxu!f z9!%=^8DsfI(4t-y>(Uj8UF6jA>;wg`@N!!N{M|4!cV&)JT?pzuJ7nAVla4A(!Ly}- zqo|`FQPyq71R2Ya8I@uiyb*u-u~5GU@La|BML&!{oly}(zO%@^_g-puX#;^!r}VSO zV~ZyabSqy7JjlTUhn##j2*81%6feq11t#V`UMVJ|gIZ}6=|1i@Y-A_$tE&!S&)e1) zJN8iUfd$J%d`&2fscA3WFJs_cuIszkbI0(BEQ?V7dH}4}TLtW`*MdDO?C#aHuwids zZhK!xDHiCZF1}Rsh8v43SG`$hhrcZi2r`7IFkKQM`Om2&G`M}r_`u!WXi=uLNnSb) zZavZTIn)@1tshE#Zut1(i#DP#eKZK?@U4rU^%*Fha_D{-cLuiYzL_YyDjBn0b^HAx zxJ}&1Ea9Rmb7Jq2DvE#g2c5n^ zTpO)Wqt3FRME?6lkz_Aeuoirg>Zm04il6&FRK1^cpp6Q26(ix@yS~!lRyszp^O6^+ z*`$G5(OQXR22M(wcQ@Vkf?j!*s(vwNaBj$1Nn@VE-fR04QoAXr5F;yOHth!@i+61J zD%SX2{Zgr7bSD|uPGyUZo8w&ilVPdP0XY9>oI-YX5|-Z1D%PEIMjexl)(3-FINY_O z`FaWq86qk2T~;P=GfH46q^*S+sT{9?u#+Gqx4@c~55cU3u&UDcbbQs2H$S!%fvUPR z#VZqwgi@H))7_0exXU`-;Cq8DoPWx(jj#;Bvh;BC{7>6}*~XCgea$qn>*Gs(vn{%~ zRiMm0Sn-_PLB4-lKBJ90*Bqs`PfZe8(HDR0Y}y8oQh#^oYu$;*Iz+ED8!r+LbMlYQ zs^y>;?W`(SO&AP9dDiQbX2_BVMvvLB2V_OWM^TMO3B}ahO>=WLaN2BBx1^;PO1M7C zA9+%Qf2;j4adBqAD{4{RgI9ZyDPC(=DDZ{hR4TiG-AZKVc%-Ka?||-hBP*j*>KNa@ zzHaLCYqF>)1Z40p;L3~$^>=*P?}SK0(X!(vf`slk0>pggDeYGwvz&0aVWJ5I-kikS9H zm5Ey4F8^s7a}4;A=0V$~yfCZVrC3ChjXKoh8)e)Nz#4D;5l%537JsH|tq|Re-ZgZ- z;fqRg`m#)m^$T^FvPfraF6Us1&+3bhI6ly_T~%3*=>&v)Su>eS0mtp4tE^u~f>Uy- zthn74;G9et&*C|tA2rJ)(R3Fo?HgI9w*&)4T3d9fB%j=xGWMp*Q2{@dUtbcjO~AZ{ zj<7)cNPsWC>I^>$E{qOH>F3*n#`UJeEDam^xG=3Up%h_@6q)WSRdJ+`^N}Hq$H$8kVZhdq`OnPTe=$`9y%2fP(nfw6-57%Dxh$^=iZ(3Jv%$w zUhK`b4zVRDgtEIi~b7T?|BL*|oBDQ$=s0%PJyv6d}MM8$7 zx%dnB&W*CVyJ#h2iSlPn6`0Aw3^nu7i=~{wcXC-b85YBwS;(^hWEww4PA#p@5@jqs#N4Ow)+&<-zHDc&e zD|*aw1Cm&L6GX+HfL5#??}I^S=m~2eQ*TMc=WbD*Jg-oX|FE9r<625US2E`gJX1)4 zK?$lfJX#S;Xb+N45fpXUMR|MiWv> z$$nAOKCs%6;9g!=2EEy)Z*GpOAWb2ok?gOss7LaK%G^o-ESP@v`q^s$B#{(rYF}pw zi9O;u`N&g{PF*Akqs##vlA_n#FQf3UleFxwURy)t-`Arj;}AB{q5WvL*a^POH@D6o zy+>$POzsK~gyNd2%u64xI3U|1{MS^(9?geO-uxoeM;y?OroEdNj!i?WeRgp&3_6V6 zXug+D#Nd!S9T_3uCdu>ZYL*0%c>9zMiDNK9cf8^>`NdR%uYfvJyW4?CjXgN>Alr(# z`p~0!F0YR8EYX)OajV4kFR#nJO9?|9W};kv9ocyFw&$Pv*c`CE{NSh4muJ|0YAP&P zP!Agrf&CkHHiV9`&Z{ebt#GRm8<`4SGoE=+qZ1bX40Km2<<1wC!KeF&=x81#5u}Qy z;aY0;cu%4D&k9#5(Ds_DDc+4BtlPY|pPsIPz!hD2{^ta;seA{lLmBY-1gnxmk`<=s z6+3Bh><-GvO&To}WPx)V(=zr+A%tmvLxq)iD=eB_x|TLj3QtowrxcdeakA*C9-~tR z;3g$TMHAzO7(Q$k)yU`*LY4R(yrsfr_}5>b{TQm;L}r42dgC~O)tDC9W*AJm6X5G&NSyBZI5&UD4pX~Fva68i!(#@i zTa-V-&>Z7wwXY)!*w2Ayv^z5hU-BL-|Nb}}B521$%x=HJHy*57Dl#X4>%Q`&m~l?nV=q$LneM{gK&jvAw7_MGyIr3go7 zqv`A3(NLJOyfi|dh^WQ>y!oU55;v?EmOk`u0@<&5zE`8PYRpM1P%#tRn?b{!Zjcz~7 zd_Ok}TOGcqf9cj?iqfhtZ$pZ39Ibp@4to<^{5_j6{Ur?4RrTLr;W8kCn9^PrUM&Pj+53#;VGeG<3aq6 z=y2BVi=NAYuyj+o?$MVa@a=Rz&?oW~-Dq8&efFY^$hWjHa2*STkGp$EW*nm715IAZ z($kMb6;DTQ=EEFNR9Crh+IbSnxsHg0=|sUxrzvf+=1kC19aWKz?#6uMrl(uneX-}J zp;+Hjan$`Q`ebg4LJp2&jDgrge*LZ-wTz5f0kMswnYwaNNdk$B{7%!JiOe2^B&M^a;M zH#8Y+r|?WE3i&@+}VBkoffAXLRsD>3AY zmqj0Y?($ipQ>1Ezf2BomrsVhiw)5mKhu4z(DQx)LFp~;>`aoS8aQuw7 zh?TP=>Q{^dh5rc~|3jT^)r6Q$+`m5o(Ttx`wiACJ)*X;X6aRIwgUPv6}eUBH_0+a^S z+i-{Ekz1PkqQU=K6x-D!oj8N#o0-V#NvOW8Y^%821eQ^fuGf@0p(I+GeK4;Fm-=r_ zefU}pY8U)BV`Xj;;q_YwPL6a!M8=jD%1+i7xK%n+NZt<@RVw>Vvlf_>S<0_V!;#^rPx5SkwZQ)69C@r(7_P7$$2 zgmPgk4QhD4e?5M4M{JbQQn{oj-uUz7NcqSPMj6YKn z7>I%cj~RN|m3y&8wd_ki)@Oivl~sI#s-d4|#Qj=T9{xH$L8fMx4@{*QBjl8cc>ZsO z%0fapmXkT5tyh$WSqq~2V_kBf+UNHCd9p~LmL2{3X0Q)8?Gv0;GbksS+9E`ml%npAG;j5xzs==5S-+b5T%PEaWKU;PiWySdx3ODVMzp4lh_f z3=|1MCAPCgXVeNI@X30=s;C9}Et$%h$6$dz9BSXSSHsA3{J**%tPwbNdu2f|uMMUN z_Wec$Q3MDnTFuJk;x&6#M$+CewB|=PEIsc-2-tV^R(n(uCXem8x%a0K2TfBaAAOA= zBoa9u3vgs1Sv47+iOmjdq#V4ePUc12Ir&Z^oU#B1D|$tYlC5Cd-{(IY4?AM%JN#+? z^;_XTtHyk5&YnbNr9D~7gehcIk?vX=JqM?I4nIn_itv}GI=Q^{ZBU>b*hv?fL2Pq- zej7V&2^3vTbT%&?q33<=f@eks5$AKu-myLla<$bJlw|FQb2d9LBU%dcN232I{QEP- z%~4Z3b5zEwVf{BA)KiuWrba2 zAWz#~T>nlKp*%03e2JEwh+13x_4NV9EWF_{d`wlC;rvCBMuT=tcW3+#c1Nx{fJ`CB z@2JIeV)zkIm`@|K@xm zFBq6_Fc04-6)?CAz?1ms{?W68;3e~-ah>%O=255T&iYgZE;X-p(a|9s!%adHvONZN z2v@TUzJ;Mjh57gQ>am7pm!?M7YkZ%9*5+vSTfA?yq4#;|9KP;;-=syL2De#Sk56AQ zgB_B1L7THO$o}#1Q>+cu5LWuM%zW=La=nZ9-~Z@`qOAYuHokTz?s<794ZR-%HL{

nGv6U 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 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/complicatedProblem1.mat b/examples/smallscale/reference_solutions/complicatedProblem1.mat new file mode 100644 index 0000000000000000000000000000000000000000..c0caa1cef28bf7068a0678be310b330e1ee55624 GIT binary patch literal 6878 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 QF86gk@(Ypdu`1?&0Mt8YEC2ui literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/dantzig_problem1_smoothed_noisy.mat b/examples/smallscale/reference_solutions/dantzig_problem1_smoothed_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..a0944e4b70e06af314fadcdc4b16401543d69836 GIT binary patch literal 25264 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 Lbp-0dOSt+kq*qaF literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/lasso_problem1_noisy.mat b/examples/smallscale/reference_solutions/lasso_problem1_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..0837a1884dfdf3366da62a802c2661da0937c393 GIT binary patch literal 37672 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 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/ordered_asso_problem1_noisy.mat b/examples/smallscale/reference_solutions/ordered_asso_problem1_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..fcc58487b4321243654135e586d4d35017e245d6 GIT binary patch literal 45856 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 literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/traceLS_problem1_noisy.mat b/examples/smallscale/reference_solutions/traceLS_problem1_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..d84dc517793bdde9052159eb744da92b1168dc22 GIT binary patch literal 15333 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 YWKe&phTYnxSug$N*xnFE@vu?<3tU8uF#rGn literal 0 HcmV?d00001 diff --git a/examples/smallscale/reference_solutions/traceLS_problem2_noisy.mat b/examples/smallscale/reference_solutions/traceLS_problem2_noisy.mat new file mode 100644 index 0000000000000000000000000000000000000000..85cd1008b65c9f94fe6e14355a4086df8084cbea GIT binary patch literal 20072 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 R?{N}Z+kXShrvjev{{ePCL 1e-8 ); +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (lambda = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),lambda ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = struct('restart',-Inf,'tol',1e-13,'maxits',1000); +opts.errFcn = { @(f,primal) er(primal), ... + @(f,primal) f - obj_ref }; +tic; +[ x, out, optsOut ] = solver_L1RLS( A, b, lambda, x0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-7 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_LMI.m b/examples/smallscale/test_LMI.m new file mode 100644 index 0000000..8a098c8 --- /dev/null +++ b/examples/smallscale/test_LMI.m @@ -0,0 +1,134 @@ +%{ + Via smoothing, TFOCS can solve Linear Matrix Inequalities (LMI) + (these are equivalent to the dual of a SDP in standard form) + + min_y + s.t. A_0 + sum_i=1^M A_i * y(i) >= 0 + +where ">=" refers to the positive semi-definite cone. +"b" and "y" are vectors of length M, +and A_i (i = 0, 1, ..., M) +are symmetric or Hermian matrices of size N x N +(where M <= N^2 ) + +TFOCS assumes that all the A_i matrices (i=1,...,M; the A_0 matrix is +handled separately) are efficiently stored in one big "A" matrix +so that we can compactly replace the sum_i A_i * y(i) +with mat( A'*y ), where mat() is a reshaping operator. +The code below shows how we do this. + + See also test_SDP.m + + If we identify A0 with -C, then this is the dual of the SDP in standard form. + +%} + +% Add TFOCS to your path. + +randn('state',9243); rand('state',23432); + +N = 30; +M = round(N^2/4); + +symmetrize = @(X) (X+X')/2; +vec = @(X) X(:); +mat = @(y) reshape(y,N,N); + +Acell = cell(M,1); +for m = 1:M + Acell{m} = symmetrize( randn(N) ); +end + +% Put "Acell" into a matrix: +A = zeros(M,N^2); +for m = 1:M + A(m,:) = vec( Acell{m} ); +end +normA = normest(A,1e-2); + +b = randn(M,1); +y = randn(M,1); +A0 = mat(A'*y); % want A0 in range of A', otherwise infeasible or unbounded + +fileName = fullfile('reference_solutions','LMI'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + cvx_begin + variable y(M) + minimize( b'*y ) + subject to + A0 + mat(A'*y) >= 0 + cvx_end + y_cvx = y; + save(fullfile(pwd,fileName),'y_cvx'); + fprintf('Saved data to file %s\n', fileName); +end + +%% solve via TFOCS +mu = 1e-3; +opts = []; +opts.errFcn = @(f,d,y) norm( y - y_cvx)/norm(y_cvx); + +y0 = []; z0 = []; +[yy, out, opts ] = solver_sLMI( A0, A, b, mu, y0, z0, opts ); + +% Check that we are within allowable bounds +if out.err(end) < 1e-8 + disp('Everything is working'); +else + error('Failed the test'); +end + +%% Version with complex variables: +randn('state',9243); rand('state',23432); +N = 30; +M = round(N^2/4); +Acell = cell(M,1); +for m = 1:M + Acell{m} = symmetrize( randn(N) + 1i*randn(N) ); +end +A = zeros(M,N^2); +for m = 1:M + A(m,:) = vec( Acell{m} ); +end +normA = normest(A,1e-2); + +b = randn(M,1); % this should always be real +y = randn(M,1); % this should always be real +A0 = mat(A'*y); % this may be complex + +fileName = 'reference_solutions/LMI_complex'; +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + cvx_begin + variable y(M) + minimize( b'*y ) + subject to + A0 + mat(A'*y) == semidefinite(N) + cvx_end + y_cvx = y; + save(fullfile(pwd,fileName),'y_cvx'); + fprintf('Saved data to file %s\n', fileName); +end + +% and solve via TFOCS: +mu = 1e-3; +opts = []; +opts.errFcn = @(f,d,y) norm( y - y_cvx)/norm(y_cvx); +y0 = []; z0 = []; +[yy, out, opts ] = solver_sLMI( A0, A, b, mu, y0, z0, opts ); + +% Check that we are within allowable bounds +if out.err(end) < 1e-8 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_LinearProgram.m b/examples/smallscale/test_LinearProgram.m new file mode 100644 index 0000000..a50e22f --- /dev/null +++ b/examples/smallscale/test_LinearProgram.m @@ -0,0 +1,153 @@ +%{ + Via smoothing, TFOCS can solve a Linear Program (LP) in standard form: + +(P) min c'x s.t. x >= 0, Ax=b + + + This extends to SDP as well (see test_SDP.m ) +%} + +% Add TFOCS to your path. + +randn('state',9243); rand('state',234324'); + +N = 5000; +M = round(N/2); +x = randn(N,1)+10; +A = sprand(M,N,.01); +c = randn(N,1); +b = A*x; +if issparse(A) + normA = normest(A); +else + normA = norm(A); +end + +fileName = fullfile('reference_solutions','LP'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + cvx_begin + variable x(N) + minimize( c'*x ) + subject to + x >= 0 + A*x == b + cvx_end + x_cvx = x; + save(fullfile(pwd,fileName),'x_cvx'); + fprintf('Saved data to file %s\n', fileName); +end + +%% solve in TFOCS + +opts = []; +opts.errFcn = {@(f,d,x) norm( x - x_cvx )/norm(x_cvx )}; % optional +% opts.errFcn{end+1} = @(f,d,x) norm( A*x - b )/norm(b ); % optional +opts.restart = 2000; +opts.continuation = true; +x0 = []; +z0 = []; +mu = 1e-2; + +opts.stopCrit = 4; +opts.tol = 1e-4; +contOpts = []; % options for "continuation" settings +% (to see possible options, type "continuation()" ) +% By changing the options, you can get much better results sometimes, +% but here we use default choices for simplicity. +% contOpts.accel = false; +% contOpts.betaTol = 10; +% contOpts.finalTol = 1e-10; +contOpts.maxIts = 10; +contOpts.initialTol = 1e-3; + +opts.normA = normA; % this will help with scaling +[x,out,optsOut] = solver_sLP(c,A,b, mu, x0, z0, opts, contOpts); +% Check that we are within allowable bounds +if out.err(end) < 7e-2 + disp('Everything is working'); +else + error('Failed the test'); +end +%% plot +figure(2); +semilogy( out.err(:,1) ); + + +%% == TFOCS can also handle LPs with box constraints ========== +randn('state',9243); rand('state',234324'); + +N = 3000; +M = round(N/2); +x = randn(N,1)+10; +A = sprand(M,N,.01); +c = randn(N,1); +b = A*x; +% There is no longer an automatic x >= 0 bound +% (if you want to impose this, incorporate it into the lower bound) +l = -2*ones(N,1); % lower bound +u = 13*ones(N,1); % upper bound +if issparse(A) + normA = normest(A); +else + normA = norm(A); +end + +fileName = fullfile('reference_solutions','LP_box'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + cvx_begin + variable x(N) + cvx_precision best + minimize( c'*x ) + subject to + x >= l + x <= u + A*x == b + cvx_end + x_cvx = x; + save(fullfile(pwd,fileName),'x_cvx'); + fprintf('Saved data to file %s\n', fileName); +end + +%% solve box-constrained version in TFOCS + +opts = []; +opts.errFcn = {@(f,d,x) norm( x - x_cvx )/norm(x_cvx )}; % optional +opts.restart = 2000; +opts.continuation = true; +x0 = []; +z0 = []; +mu = 1e-2; + +opts.stopCrit = 4; +opts.tol = 1e-5; +contOpts = []; % options for "continuation" settings +contOpts.maxIts = 5; +contOpts.muDecrement = .8; +% contOpts.accel = false; + +opts.normA = normA; % this will help with scaling +[x,out,optsOut] = solver_sLP_box(c,A,b,l,u,mu, x0, z0, opts, contOpts); + +% Check that we are within allowable bounds +if out.err(end) < 2e-2 + disp('Everything is working'); +else + error('Failed the test'); +end + +%% plot box-constrained version +figure(2); +semilogy( out.err(:,1) ); + +%% close figures +close all + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_OrderedLASSO.m b/examples/smallscale/test_OrderedLASSO.m new file mode 100644 index 0000000..f87eaa3 --- /dev/null +++ b/examples/smallscale/test_OrderedLASSO.m @@ -0,0 +1,103 @@ +%{ + Tests the Ordered LASSO ( aka Ordered L1 regularized Least Squares) problem + + min_x sum(lambda*sort(abs(x),'descend')) + .5||A(x)-b||_2^2 + + lambda must be a vector in decreasing order, and all strictly positive + + Like the test_LASSO.m file, we will use the notation (A,b,x) + In the paper mentioned below, this corresponds to the notation (X,y,beta) + + Reference: + "Statistical Estimation and Testing via the Ordered l1 Norm" + by M. Bogdan, E. van den Berg, W. Su, and E. J. Candès, 2013 + http://www-stat.stanford.edu/~candes/OrderedL1/ + +see also test_sBPDN.m and test_LASSO.m + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); +%% +% Try to load the problem from disk +fileName = fullfile(tfocs_where,... + 'examples','smallscale','reference_solutions','ordered_asso_problem1_noisy'); +randn('state',34324); +rand('state',34324); +N = 1024; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +% give A unit-normed columns +A = bsxfun(@times,A,1./sqrt(sum(A.^2,1))); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + x = zeros(N,1); + T = randsample(N,K); + x(T) = randn(K,1); + + b_original = A*x; + snr = 30; % SNR in dB + sigma = 10^( (10*log10( norm(b_original)^2/N) - snr)/20 ); + b = myAwgn(b_original,snr); + x_original = x; + + lambda = 2*sigma*sqrt(2*log(N)); % for when A has unit-normed columns + % Now, we diverge from test_LASSO.m, and make lambda a vector +% lambda = fliplr( linspace(.5*lambda,2*lambda,N) )'; + lambda = .5*lambda + 1.5*lambda*rand(N,1); lambda = sort(lambda,'descend') + + x0 = zeros(N,1); + + % We cannot get the solution via CVX easily, so solve using our method + opts = struct('restart',-Inf,'tol',1e-13,'maxits',1000, 'printEvery',10); + [ x, out, optsOut ] = solver_OrderedLASSO( A, b, lambda, x0, opts ); + x_ref = x; + obj_ref = norm(A*x-b)^2/2 + sum(lambda(:).*sort(abs(x),'descend')); + + save(fileName,'x_ref','b','x_original','lambda',... + 'sigma','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref) + 1*( norm(x_ref)==0 ); +norm_x_orig = norm(x_original) + 1*( norm(x_original)==0 ); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mean(lambda) = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mean(lambda) ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = struct('restart',-Inf,'tol',1e-13,'maxits',1000, 'printEvery',10); +opts.errFcn = { @(f,primal) er(primal), ... + @(f,primal) f - obj_ref }; +tic; +[ x, out, optsOut ] = solver_OrderedLASSO( A, b, lambda, x0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. reference solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-7 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_SDP.m b/examples/smallscale/test_SDP.m new file mode 100644 index 0000000..e43ad91 --- /dev/null +++ b/examples/smallscale/test_SDP.m @@ -0,0 +1,143 @@ +%{ + Via smoothing, TFOCS can solve a Semi-definite Program (SDP) in standard form: + +(P) min s.t. X >= 0, A(X)=b + + + This extends to LP as well (see test_LinearProgram.m ) + We can also solve the dual of SDP, which is LMI. See test_LMI.m +%} + +% Add TFOCS to your path. + +randn('state',9243); rand('state',234324'); + +N = 80; +M = round(N^2/2); +X = randn(N); X = X*X'; +A = sprand(M,N^2,.005); +C = randn(N); C = C + C'; +vec = @(X) X(:); +mat = @(y) reshape(y,N,N); +% Important: we want each row of A to be the vectorized form of the +% symmetric matrix A_i. Otherwise, the dual problem makes no sense. +% Here, we'll ensure this symmetry. +% (BTW, due to how Matlab stores sparse arrays, we only want to make +% column operations. So we'll operate on A' and then transpose back) +At = A'; +for row = 1:M + At(:,row) = vec( mat(At(:,row)) + mat(At(:,row))' )/2; +end +A = At'; clear At; + +b = A*vec(X); + +normA = normest(A); + +fileName = fullfile('reference_solutions','SDP'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + cvx_begin sdp + variable X(N,N) symmetric + minimize trace( C*X ) + subject to + X >= 0 + A*vec(X) == b + cvx_end + X_cvx = X; + save(fullfile(pwd,fileName),'X_cvx'); + fprintf('Saved data to file %s\n', fileName); +end + +%% solve in TFOCS + +opts = []; +opts.errFcn = {@(f,d,X) norm( X - X_cvx,'fro')/norm(X_cvx,'fro' )}; % optional +% opts.errFcn{end+1} = @(f,d,X) norm( A*vec(X) - b )/norm(b); % optional +opts.restart = 2000; +opts.continuation = true; +x0 = []; +z0 = []; +mu = 1; + +opts.stopCrit = 4; +opts.tol = 1e-4; +contOpts = []; % options for "continuation" settings +% (to see possible options, type "continuation()" ) +% By changing the options, you can get much better results sometimes, +% but here we use default choices for simplicity. +% contOpts.accel = false; +% contOpts.betaTol = 10; +% contOpts.finalTol = 1e-10; + +opts.normA = normA; % this will help with scaling +[x,out,optsOut] = solver_sSDP(C,A,b, mu, x0, z0, opts, contOpts); +% Check that we are within allowable bounds +if out.err(end) < 5e-2 + disp('Everything is working'); +else + error('Failed the test'); +end +%% Here's another simple example, using complex valued variables +randn('state',9243); rand('state',234324'); + +N = 20; +M = round(N^2/1.3); +X = randn(N) + 1i*randn(N); X = X*X'; % ensure Hermitian and pos. semi-def. +A = sprandn(M,N^2,.01) + 1i*sprandn(M,N^2,.01); +C = randn(N)+1i*randn(N); C = C + C'; % ensure Hermitian +vec = @(X) X(:); +mat = @(y) reshape(y,N,N); +% Same as before... +At = A'; +for row = 1:M + At(:,row) = vec( mat(At(:,row)) + mat(At(:,row))' )/2; +end +A = At'; clear At; + +% Note: b should be real, but due to round errors it has as small imaginary part +b = real(A*vec(X)); + +normA = linop_normest(A); +fileName = 'reference_solutions/SDP_complex'; +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + cvx_begin sdp + variable X(N,N) hermitian % note the change from symmetric to hermitian + minimize real(trace( C*X )) % this should be real, but there are rounding errors + subject to + X >= 0 + real(A*vec(X)) == b + cvx_end + X_cvx = X; + save(fullfile(pwd,fileName),'X_cvx'); + fprintf('Saved data to file %s\n', fileName); +end + +% and solve with TFOCS +opts = []; +opts.errFcn = {@(f,d,X) norm( X - X_cvx,'fro')/norm(X_cvx,'fro' )}; +opts.continuation = true; +x0 = []; +z0 = []; +mu = 1e-3; +% opts.cmode = 'C2R'; % explicitly tell it that we are complex. not necessary though +opts.normA = normA; % this will help with scaling +opts.stopCrit = 4; +opts.tol = 1e-5; +[x,out,optsOut] = solver_sSDP(C,A,b, mu, x0, z0, opts, contOpts); + +% Check that we are within allowable bounds +if out.err(end) < 5e-2 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_TraceLS.m b/examples/smallscale/test_TraceLS.m new file mode 100644 index 0000000..b7e6e3a --- /dev/null +++ b/examples/smallscale/test_TraceLS.m @@ -0,0 +1,121 @@ +%{ +Test the trace-constrained least-squares problem with positive +semi-definite matrix variables + + minimize (1/2)*norm( A * X - b )^2 + lambda * trace( X ) + with the constraint that X is positive semi-definite ( X >= 0 ) +%} + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','traceLS_problem1_noisy'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + % Generate a new problem + + randn('state',sum('Trace')); + rand('state',sum('Trace2')); + + N = 30; R = 2; + + df = R*N; + oversample = 5; + Left = randn(M,R); + Right = Left; + k = round(oversample*df); + k = min( k, round(.8*N*N) ); + omega = randperm(N*N); + omega = sort(omega(1:k)).'; + + X_original = Left*Right'; % the "original" signal -- may not be optimal value + b_original = X_original(omega); + EPS = .001; % noise level + noise = EPS * randn(k,1); + b = b_original + noise; + lambda = 1e-2; + objective = @(X) sum_square( X(omega) - b )/2 + lambda*trace(X); + obj_original = objective(X_original); + + % get reference via CVX + cvx_begin + cvx_precision best + cvx_quiet true + variable Xcvx(N,N) + minimize objective(Xcvx) + subject to + Xcvx == semidefinite(N) + cvx_end + X_reference = Xcvx; % the nuclear norm minimizer + obj_reference = objective(X_reference); + fprintf('Difference between convex solution and original signal: %.2e\n', ... + norm( Xcvx - X_original, 'fro' ) ); + save(fileName,'X_original','X_reference','omega','b','obj_original',... + 'Left','EPS','b_original','R','obj_reference','lambda'); + fprintf('Saved data to file %s\n', fileName); + +end + +[M,N] = size(X_reference); +norm_X_reference = norm(X_reference,'fro'); +er_reference = @(x) norm(x-X_reference,'fro')/norm_X_reference; +objective = @(X) norm( X(omega) - b )^2/2 + lambda*trace(X); + +[omegaI,omegaJ] = ind2sub([M,N],omega); +mat = @(x) reshape(x,M,N); +vec = @(x) x(:); + +k = length(omega); +p = k/(M*N); +df = R*(M+N-R); +fprintf('%d x %d rank %d matrix, observe %d = %.1f x df = %.1f%% entries\n',... + M,N,R,k,k/df,p*100); +fprintf(' Trace norm solution and original matrix differ by %.2e\n',... + norm(X_reference-X_original,'fro')/norm_X_reference ); +%% Solve. No smoothing is necessary! +opts = struct('tol',1e-12); +opts.errFcn = @(f,x) er_reference(x); +x0 = []; +% opts.debug = true; + +% we need to tell it how to subsample. One method: +A = linop_subsample( {[N,N],[k,1]}, omega ); + +% another method: make a matrix with the correct +% sparsity pattern and let solver_TraceLS do it for us +% A = sparse( omegaI,omegaJ,ones(k,1),N,N); + +[x,out] = solver_TraceLS( A, b, lambda, x0, opts ); +epsilon = norm( x(omega) - b ); +h=figure(); +semilogy(out.err); + +% Check that we are within allowable bounds +if out.err(end) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end +%% We can solve this via a smoothed nuclear norm formulation +% But it will be slower! +% Also, it will not enforce the positive semi-definite constraint, +% so it is not exactly the same. + +testOptions = {}; +opts = []; +opts.errFcn = @(f,dual,x) er_reference(x); +opts.continuation = true; +opts.stopCrit = 4; +opts.tol = 1e-6; + +mu = .01; +X0 = 0; + +% [ x, out, opts ] = solver_sNuclearBP( {M,N,omega}, b, mu, X0, [], opts ); +[ x, out, opts ] = solver_sNuclearBPDN( {M,N,omega}, b, epsilon, mu, X0, [], opts ); + +%% +close(h) +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_all.m b/examples/smallscale/test_all.m new file mode 100644 index 0000000..4e0b185 --- /dev/null +++ b/examples/smallscale/test_all.m @@ -0,0 +1,30 @@ +%{ +Runs all tests in this directory. + +Recommended if you want to be sure that TFOCS is working 100% perfectly +on your system. + +The scripts will produce an error message if anything goes wrong. +So if you don't seen any error message in red text, then everything +is working. + +%} + +w = what; +fileList = w.m; +% and exclude the current file, otherwise we go into an infinite loop: +fileList = fileList( ~strcmpi( fileList, 'test_all.m' ) ); +tm_all = tic; +for k = 1:length(fileList) + + file = fileList{k}; % this has the .m extension, which we need to remove + eval( file(1:end-2) ); + +end +fprintf('\n\n\n-- Success!! --\n'); + +toc( tm_all ) + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_blockNorm.m b/examples/smallscale/test_blockNorm.m new file mode 100644 index 0000000..cc4d023 --- /dev/null +++ b/examples/smallscale/test_blockNorm.m @@ -0,0 +1,211 @@ +%{ + Tests two common block norms + + min_X ||X||_{1,p} +s.t. + || A(X) - b || <= eps + +where ||X||_{1,p} is sum_{i=1:m} norm(X(i,:),p) +and X is a m x n matrix. "p" is either 2 or Inf. +If n = 1, then for both p = 2 and p = Inf, this reduces +to the l1 norm. + +We may think of the columns of X as a new signal. The block +norm may be useful when we believe that these columns +are quite similar, e.g. when their supports overlap significantly. + +see also test_sBPDN.m + +%} +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Before running this, please add the TFOCS base directory to your path + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','blocknorm_smoothed_noisy'); +randn('state',34324); +rand('state',34324); +% N = 1024; +N = 128; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +d = 10; % number of columns of the signal matrix +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + % Our signal model is the following: + % for each column, half of the support (i.e. K/2 entries) + % is taken from the first 1:K elements, so these will likely + % have a strong overlap. + % The second half of the support is taken from the remaining + % elements, so will have less chance of overlap. + % (May not be 1/2, but that's the rough idea) + x = zeros(N,d); + K2 = round(.6*K); + for i = 1:d + T1 = randsample(K,K2); + T2 = randsample(N-K,K-K2) + K; + x(T1,i) = randn(K2,1); + x(T2,i) = randn(K-K2,1); + end + + b_original = A*x; + snr = 10; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original,'fro'); + x_original = x; + err = @(X) norm(X-x_original,'fro')/norm(x_original,'fro'); + + mu = .01*norm(x,Inf); + x0 = zeros(N,d); + + % get reference via CVX + + % How would we do if we did the estimation separately? + Xcvx_separate = zeros(N,d); + for i = 1:d + cvx_begin + cvx_precision best + cvx_quiet true + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0(:,i) ) + subject to + norm(A*xcvx - b(:,i) ) <= EPS/sqrt(d) + cvx_end + Xcvx_separate(:,i) = xcvx; + end + fprintf('Estimating each column separately, the error is %.2e\n',... + err(Xcvx_separate) ); + + + % Now, use p = 2 + p = 2; + dim = 2; + cvx_begin + cvx_precision best + cvx_quiet true + variable xcvx(N,d) + minimize sum(norms(xcvx,p,dim)) + mu/2*pow_pos( norm(xcvx-x0,'fro'), 2) + subject to + norm(A*xcvx - b,'fro' ) <= EPS + cvx_end + Xcvx_p2 = xcvx; + fprintf('Estimating using the block 1-2 norm, the error is %.2e\n',... + err(Xcvx_p2) ); + + % Now, use p = Inf + p = Inf; + dim = 2; + cvx_begin + cvx_precision best + cvx_quiet true + variable xcvx(N,d) + minimize sum(norms(xcvx,p,dim)) + mu/2*pow_pos( norm(xcvx-x0,'fro'), 2) + subject to + norm(A*xcvx - b,'fro' ) <= EPS + cvx_end + Xcvx_pInf = xcvx; + fprintf('Estimating using the block 1-Inf norm, the error is %.2e\n',... + err(Xcvx_pInf) ); + + + save(fileName,'Xcvx_separate','Xcvx_p2','Xcvx_pInf','b','x_original','mu',... + 'EPS','b_original','x0','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + +err = @(X) norm(X-x_original,'fro')/norm(x_original,'fro'); +fprintf('Block norm estimation: the error between cvx solution and orignal signal is...\n'); +fprintf(' Estimating each column separately, the error is %.2e\n',... + err(Xcvx_separate) ); +fprintf(' Estimating using the block 1-2 norm, the error is %.2e\n',... + err(Xcvx_p2) ); +fprintf(' Estimating using the block 1-Inf norm, the error is %.2e\n',... + err(Xcvx_pInf) ); + +%% Call the TFOCS solver for each column separately +disp('-- Testing TFOCS against the IPM (CVX) solution, each column separately --'); +X_separate = zeros(N,d); +for i = 1:d + er = @(x) norm(x-Xcvx_separate(:,i))/norm(Xcvx_separate(:,i)); + opts = []; + opts.restart = 500; + opts.errFcn = @(f,dual,primal) er(primal); + opts.maxIts = 1000; + opts.tol = 1e-8; + opts.printEvery = 500; + opts.fid = 0; + opts.restart = 100; + z0 = []; % we don't have a good guess for the dual + + [ x, out, optsOut ] = solver_sBPDN( A, b(:,i), EPS/sqrt(d), mu, x0(:,i), z0, opts ); + + fprintf('Error vs. IPM solution is %.2e\n',er(x) ); + X_separate(:,i) = x; + + % Check that we are within allowable bounds + if er(x) > 1e-6 + error('Failed the test'); + end +end +fprintf('Overall error vs. IPM solution is %.2e\n',... + norm(X_separate-Xcvx_separate,'fro')/norm(Xcvx_separate,'fro') ); + +%% Call the TFOCS solver for p = 2 +disp('-- Testing TFOCS against the IPM (CVX) solution, block norm, p = 2 --'); +er = @(x) norm(x-Xcvx_p2,'fro')/norm(Xcvx_p2,'fro'); +opts = []; +opts.restart = 200; +opts.errFcn = @(f,dual,primal) er(primal); +opts.maxIts = 1000; +opts.tol = 1e-8; +opts.printEvery = 50; +opts.fid = 1; +z0 = []; % we don't have a good guess for the dual + +AA = A; % doesn't work "out-of-the-box", because it thnkgs + % the domain of A is N x 1 matrices (it's really N x d). +AA = linop_matrix(A, 'r2r',d ); +[x,out,opts] = tfocs_SCD( prox_l1l2, { AA, -b }, prox_l2( EPS ), mu, x0, z0, opts ); + +fprintf('Error vs. IPM solution is %.2e\n',er(x) ); + +% Check that we are within allowable bounds +if er(x) < 1e-6 + disp('Everything is working'); +else + error('Failed the test'); +end +%% Call the TFOCS solver for p = Inf +disp('-- Testing TFOCS against the IPM (CVX) solution, block norm, p = Inf --'); +er = @(x) norm(x-Xcvx_pInf,'fro')/norm(Xcvx_pInf,'fro'); +opts = []; +opts.restart = 200; +opts.errFcn = @(f,dual,primal) er(primal); +opts.maxIts = 1000; +opts.tol = 1e-8; +opts.printEvery = 50; +opts.fid = 1; +z0 = []; % we don't have a good guess for the dual + +AA = A; % doesn't work "out-of-the-box", because it thnkgs + % the domain of A is N x 1 matrices (it's really N x d). +AA = linop_matrix(A, 'r2r',d ); +[x,out,opts] = tfocs_SCD( prox_l1linf(1), { AA, -b }, prox_l2( EPS ), mu, x0, z0, opts ); + +fprintf('Error vs. IPM solution is %.2e\n',er(x) ); +if er(x) < 3e-6 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_complicatedUsage.m b/examples/smallscale/test_complicatedUsage.m new file mode 100644 index 0000000..d75177a --- /dev/null +++ b/examples/smallscale/test_complicatedUsage.m @@ -0,0 +1,274 @@ +%{ +This file shows an example of using the full power of TFOCS. If you +are not familiar with basic usage of TFOCS, please see other demos +first. + +We make an example that uses: + -several variables (2), and the variables are matrices, not just vectors + -several constraints (4) + -affine operators (linear plus offset), and two of the operators + are matrix --> matrix operators + -debug mode + + +For small complicated examples like this, TFOCS is not necessarily +faster than software like CVX, since it takes more iterations than an +interior point method and there is some overhead in the TFOCS software +since the software is meant for flexibility rather than absolute speed. + +However, if you take any complicated problem and scale the size by a factor +of 100, then CVX won't be able to handle it at all. TFOCS will do +just fine (and it might even be less than 100x as slow, since now the overhead +is not significant). + + +The problem we will solve: + +min_{X1, X2} smooth1(X1)+smooth2(X2) + sum_{i=1}^4 g_i( A1_i*X1 + A2_i*X2 + B_i ) + +meaning that we have 2 variables (X1 and X2), both of which are matrices, +and each variable has its own smooth function. +For non-smooth and/or constraint terms (indexed by "i"), we have 4 functions +( i = 1,2,3,4) g_i, and each has it's own affine operator in X1 and X2. +So each of the 4 affine operators has three parts: the portion linear in X1, +the portion linear in X2, and the constant offset "B_i". + +To be explicit, the smooth functions are linear and quadratic, resp.: + +smooth1(X1) = dot( s1, X1 ) + 3.4 ("s1" is a matrix the same size as X1) +smooth2(X2) = dot( X2, X2 ) + dot( s2, X2 ) + 4.5 ("s2" is a matrix the same size as X2) + +and the non-smooth/constraint terms are: + +g_1(z) = indicator set of the positive orthant of R^10 +g_2(z) = ||z||_2 (usual Euclidean norm) in R^15 +g_3(z) = ||z||_1 (l1 norm) in R^{10 x 20 }. This views a 10 x 20 matrix as a 200 x 1 vector. +g_4(z) = indicator set of positive orthant of R^{20 x 22 }, i.e. each element must be >= 0 coordinate-wise + +The affine operators are picked arbitrary, but some of them (#3 and #4) have a range that +is a set of matrices, rather than a set of vectors, in order to make +this more interesting. + +%} + +fileName = fullfile('reference_solutions','complicatedProblem1'); +randn('state',29324); +rand('state',9332); +% rng(3481); % this only works on new versions of Matlab + +% -- Variables -- +% Two sets of variables, X1 (a matrix of size n1 x n2 ) and X2 (matrix of N1 x N2 ) +n1 = 10; n2 = 20; +N1 = 12; N2 = 18; +X1 = zeros( n1, n2 ); +X2 = zeros( N1, N2 ); + +% -- the smooth terms -- +% Note: the inner product used is the matrix inner product +% that induces the Frobenius norm. +s1 = randn(n1,n2); +smooth1 = smooth_linear( s1, 3.4 ); +s2 = randn(N1,N2); +smooth2 = smooth_quad( 1, s2, 4.5 ); +% and the same thing, but in a format that CVX likes: +smoothF = @(X1,X2) vec(X1)'*vec(s1) + 3.4 + vec(X2)'*vec(s2) + 4.5 + ... + sum_square(vec(X2))/2; + +% -- some proximal terms -- + +nProx = 4; +prox1 = proj_Rplus; +prox2 = proj_l2; % primal is norm( , 2) +prox3 = proj_linf(1); % this can take matrix varaibles... +prox4 = proj_Rplus; % this can take matrix variables... +% Sizes of proxes (i.e. sizes of dual variables, i.e. size of range of linear terms) +d1 = [ 10, 1 ]; +d2 = [ 15, 1 ]; +d3 = [ n1, n2 ]; +d4 = [ 20, 22 ]; + + +% -- and linear terms -- + +% for prox1: +const1 = randn(d1); +temp1 = randn( prod(d1), n1*n2); +A1_X1 = linop_compose( linop_matrix(temp1), linop_vec([n1,n2]) ); +temp2 = randn( prod(d1), N1*N2); +A1_X2 = linop_compose( linop_matrix(temp2), linop_vec([N1,N2]) ); + +A1 = @(X1,X2) temp1*vec(X1) + temp2*vec(X2) + const1; + +% for prox2: (matrix variable) +const2 = randn(d2); +temp1 = randn( prod(d2), n1*n2); +A2_X1 = linop_compose( linop_matrix(temp1), linop_vec([n1,n2]) ); +temp2 = randn( prod(d2), N1*N2); +A2_X2 = linop_compose( linop_matrix(temp2), linop_vec([N1,N2]) ); + +A2 = @(X1,X2) temp1*vec(X1) + temp2*vec(X2) + const2; + +% for prox3: +const3 = 0; +A3_X1 = 63.4; % this represents abstract scaling, i.e. any size input +A3_X2 = 0; % this reprsents the zero linear operator + +A3 = @(X1,X2) A3_X1*X1; + +% for prox4: (matrix variable) +const4 = randn(d4); +temp1 = randn( prod(d4), n1*n2); +A4_X1 = linop_compose( linop_matrix(temp1), linop_vec([n1,n2]) ); +rs = linop_adjoint( linop_vec(d4) ); +A4_X1 = linop_compose( rs, A4_X1 ); +mat1 = @(x) reshape( x, d4(1), d4(2) ); + +temp2 = randn( prod(d4), N1*N2); +A4_X2 = linop_compose( linop_matrix(temp2), linop_vec([N1,N2]) ); +rs = linop_adjoint( linop_vec(d4) ); +A4_X2 = linop_compose( rs, A4_X2 ); + +A4 = @(X1,X2) mat1( temp1*vec(X1) + temp2*vec(X2) ) + const4; + +% -- set the smoothing parameter -- +mu = 1; + +if exist([fileName,'.mat'],'file') + load(fileName); % contains X_CVX + fprintf('Loaded problem from %s\n', fileName ); +else + % Get reference solution in CVX + % First, get a solution to the smoothed version: + cvx_begin + variables X1(n1,n2) X2(N1,N2) + % it's important that we use norm(vec(...),1) and not just norm(...,1), + % otherwise CVX interprets this with the wrong implicit inner product + minimize( smoothF( X1, X2 ) + ... + norm( A2(X1,X2), 2 ) + norm( vec(A3(X1,X2)) , 1 ) + ... + mu*( sum_square(vec(X1)) + sum_square(vec(X2)) )/2 ) + subject to + A1(X1,X2) >= 0 % constraint is dual of prox1 + A4(X1,X2) >= 0 % constraint is dual of prox2 + cvx_end + X_CVX_smoothed{1} = X1; + X_CVX_smoothed{2} = X2; + + % Second, get a solution to the unsmoothed version + cvx_begin + variables X1(n1,n2) X2(N1,N2) + minimize( smoothF( X1, X2 ) + ... + norm( A2(X1,X2), 2 ) + norm( vec(A3(X1,X2)) , 1 ) ) + subject to + A1(X1,X2) >= 0 + A4(X1,X2) >= 0 + cvx_end + X_CVX{1} = X1; + X_CVX{2} = X2; + + save(fileName,'X_CVX', 'X_CVX_smoothed'); + fprintf('Saved data to file %s\n', fileName); +end + +% Verify constraint are satisfied +fprintf('Is A1(x1,x2) >= 0? The min element is %g\n', min( A1(X_CVX{1},X_CVX{2} )) ) +fprintf('Is A2(x1,x2) >= 0? The min element is %g\n', min(min(A4(X_CVX{1},X_CVX{2}))) ) +%% before running TFOCS, scale the problem perhaps? +% This is one way to see how big the norms are: +% nrm11 = linop_test( A1_X1 ); % 15.7 +% nrm12 = linop_test( A1_X2 ); % 16.6 +% +% nrm21 = linop_test( A2_X1 ); % 17.8 +% nrm22 = linop_test( A2_X2 ); % 18.6 +% +% nrm31 = linop_test( A3_X1 ); % 63.4 +% nrm32 = linop_test( A3_X2 ); % 0 +% +% nrm41 = linop_test( A4_X1 ); % 35.6 +% nrm42 = linop_test( A4_X2 ); % 34.6 + +%% now, run TFOCS. First, try it without continuation + +x0 = []; +z0 = []; +opts = struct('continuation',false,'maxits',1500,'debug',true); % using 'debug' mode to print out useful information +opts.printEvery = 50; + +% Pick a scaling factor "s" (optional: set to "1" to have no effect) +s = .5; % helps a little bit +% (note that if we scale prox3 by "s", then we multiply the corresponding +% affine part by "s", rather than divide them by "s", since prox3 +% is really for the dual and not the primal). + +mu = 1; +opts.errFcn{1} = @(f,d,p) norm( p{1}-X_CVX_smoothed{1}); % compare to smoothed reference solution +opts.errFcn{2} = @(f,d,p) norm( p{2}-X_CVX_smoothed{2}); + +[xAll,outParam,optsOut] = tfocs_SCD( {smooth1,smooth2}, ... + { A1_X1, A1_X2, const1; A2_X1, A2_X2, const2; ... + A3_X1*s, A3_X2*s, const3*s; A4_X1, A4_X2, const4 }, ... + {prox1,prox2,prox_scale(prox3,s),prox4},... + mu, x0, z0, opts ); + +mnConstraint1 = min(min( A1(xAll{1},xAll{2} ) ) ); +mnConstraint2 = min(min( A4(xAll{1},xAll{2} ) ) ); +fprintf('First constraint violated by: %g\n', mnConstraint1); +fprintf('Second constraint violated by: %g\n', mnConstraint2 ); + +% Check that we are within acceptable limits +er = sqrt(norm( xAll{1} - X_CVX_smoothed{1} )^2 + norm( xAll{2} - X_CVX_smoothed{2} )^2 )/... + sqrt( norm(X_CVX_smoothed{1})^2 + norm(X_CVX_smoothed{2})^2 ); % should be about .006 + +if er > 0.04 || mnConstraint1 < -.01 || mnConstraint2 < -.01 + error('Failed the test'); +else + disp('This test successfully passed'); +end + +%% run TFOCS with continuation +x0 = []; +z0 = []; +% type "tfocs" at the command to see possible options +opts = struct('continuation',true,'maxits',1500); +opts.printEvery = 50; +opts.tol = 1e-4; +opts.stopCrit = 4; +opts.printStopCrit = true; + +% type "continuation" at the command to see possible options +contOpts = struct( 'maxIts', 8 , 'muDecrement', 0.8 ); +% ask for increased accuracy on the final solve +contOpts.finalTol = 1e-5; + +s = .5; % helps a little bit +% (note that if we scale prox3 by "s", then we multiply the corresponding +% affine part by "s", rather than divide them by "s", since prox3 +% is really for the dual and not the primal). + +mu = 10; +opts.errFcn{1} = @(f,d,p) norm( p{1}-X_CVX{1}); % compare to unsmoothed reference solution +opts.errFcn{2} = @(f,d,p) norm( p{2}-X_CVX{2}); + +[xAll,outParam,optsOut] = tfocs_SCD( {smooth1,smooth2}, ... + { A1_X1, A1_X2, const1; A2_X1, A2_X2, const2; ... + A3_X1*s, A3_X2*s, const3*s; A4_X1, A4_X2, const4 }, ... + {prox1,prox2,prox_scale(prox3,s),prox4},... + mu, x0, z0, opts, contOpts ); + +mnConstraint1 = min(min( A1(xAll{1},xAll{2} ) ) ); +mnConstraint2 = min(min( A4(xAll{1},xAll{2} ) ) ); +fprintf('First constraint violated by: %g\n', mnConstraint1); +fprintf('Second constraint violated by: %g\n', mnConstraint2 ); + +% Check that we are within acceptable limits +er = sqrt(norm( xAll{1} - X_CVX{1} )^2 + norm( xAll{2} - X_CVX{2} )^2 )/... + sqrt( norm(X_CVX{1})^2 + norm(X_CVX{2})^2 ); % should be about .006 + +if er > 0.4 || mnConstraint1 < -.01 || mnConstraint2 < -.01 + error('Failed the test'); +else + disp('This test successfully passed'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/examples/smallscale/test_nuclearNorm.m b/examples/smallscale/test_nuclearNorm.m new file mode 100644 index 0000000..cd8da2f --- /dev/null +++ b/examples/smallscale/test_nuclearNorm.m @@ -0,0 +1,111 @@ +%{ + Tests nuclear norm minimization, + + min_X ||X||_* +s.t. + P(X) == b, or || P(X) - b || <= eps + +where X is a M x N matrix, and P is an observation operator + +The solvers solve a regularized version, using +||X||_* + mu/2*||X-X_0||_F^2 +You can turn on the continuation feature to reduce the effect of nonzero mu + +%} +nuclear_norm = @(x) sum(svd(x,'econ')); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','nuclearNorm_problem1_noiseless'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + randn('state',sum('NuclearNorm')); + rand('state',sum('NuclearNorm2')); + + M = 30; N = 30; R = 2; + + df = R*(M+N-R); + oversample = 5; + Left = randn(M,R); + Right = randn(N,R); + k = round(oversample*df); + k = min( k, round(.8*M*N) ); + omega = randperm(M*N); + omega = sort(omega(1:k)).'; + + X_original = Left*Right'; % the "original" signal -- may not be optimal value + b_original = X_original(omega); + EPS = 0; % noise level + b = b_original; + obj_original = nuclear_norm(X_original); + + % get reference via CVX + cvx_begin + cvx_precision best + variable Xcvx(M,N) + minimize norm_nuc(Xcvx) + subject to + Xcvx(omega) == b + cvx_end + X_reference = Xcvx; % the nuclear norm minimizer + obj_reference = nuclear_norm(X_reference); + + save(fileName,'X_original','X_reference','omega','b','obj_original',... + 'Left','Right','EPS','b_original','R','obj_reference'); + fprintf('Saved data to file %s\n', fileName); + +end + +[M,N] = size(X_reference); +norm_X_reference = norm(X_reference,'fro'); +er_reference = @(x) norm(x-X_reference,'fro')/norm_X_reference; +resid = @(x) norm(x(omega)-b)/norm(b); % change if b is noisy + +[omegaI,omegaJ] = ind2sub([M,N],omega); +mat = @(x) reshape(x,M,N); +vec = @(x) x(:); + +k = length(omega); +p = k/(M*N); +df = R*(M+N-R); +fprintf('%d x %d rank %d matrix, observe %d = %.1f x df = %.1f%% entries\n',... + M,N,R,k,k/df,p*100); +fprintf(' Nuclear norm solution and original matrix differ by %.2e\n',... + norm(X_reference-X_original,'fro')/norm_X_reference ); + +%% set some parameters + +testOptions = {}; +opts = []; +opts.alg = 'GRA'; +opts.errFcn = @(f,dual,x) norm( x - X_reference,'fro')/norm(X_reference,'fro'); + +mu = .001; +X0 = 0; + +% The Lipschitz bound is L = 1/mu +% opts.L0 = 1/mu; +[ x, out, opts ] = solver_sNuclearBP( {M,N,omega}, b, mu, X0, [], opts ); +% Check that we are within allowable bounds +if out.err(end) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end +%% test noisy version (just pretend there's a tiny amount of noise) +disp('== Noisy variant =='); +epsilon = 1e-6; % if there really were noise, this should be an estimate of the std +[ x, out, opts ] = solver_sNuclearBPDN( {M,N,omega}, b, epsilon, mu, X0, [], opts ); +% Check that we are within allowable bounds +if out.err(end) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_psdCompletion.m b/examples/smallscale/test_psdCompletion.m new file mode 100644 index 0000000..672973b --- /dev/null +++ b/examples/smallscale/test_psdCompletion.m @@ -0,0 +1,136 @@ +%{ +Test the trace-constrained least-squares problem with positive +semi-definite matrix variables + + minimize (1/2)*norm( A * X - b )^2 + with the constraint that X is positive semi-definite ( X >= 0 ) +and optionally, + the constraint trace(X) <= lambda + +%} + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','traceLS_problem2_noisy'); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + % Generate a new problem + randn('state',sum('Trace')+5); + rand('state',sum('Trace2')+5); + + N = 30; R = 2; + + df = R*N; + oversample = 5; + Left = randn(M,R); + Right = Left; + k = round(oversample*df); + k = min( k, round(.8*N*N) ); + omega = randperm(N*N); + omega = sort(omega(1:k)).'; + + X_original = Left*Right'; % the "original" signal -- may not be optimal value + b_original = X_original(omega); + EPS = .001; % noise level + noise = EPS * randn(k,1); % this destroys symmetry... + b = b_original + noise; + lambda = trace( X_original ); + objective = @(X) sum_square( X(omega) - b )/2; + obj_original = objective(X_original); + + % get references via CVX + cvx_begin + cvx_precision best + cvx_quiet true + variable Xcvx(N,N) + minimize objective(Xcvx) + subject to + Xcvx == semidefinite(N) + cvx_end + X_reference_noTraceConstraint = Xcvx; + fprintf('Difference between convex solution (no trace constraint) and original signal: %.2e\n', ... + norm( Xcvx - X_original, 'fro' ) ); + + cvx_begin + cvx_precision best + cvx_quiet true + variable Xcvx(N,N) + minimize objective(Xcvx) + subject to + Xcvx == semidefinite(N) + trace(Xcvx) <= lambda + cvx_end + X_reference = Xcvx; % the trace norm minimizer + obj_reference = objective(X_reference); + fprintf('Difference between convex solution (with trace constraint) and original signal: %.2e\n', ... + norm( Xcvx - X_original, 'fro' ) ); + save(fileName,'X_original','X_reference','X_reference_noTraceConstraint',... + 'omega','b','obj_original',... + 'Left','EPS','b_original','R','obj_reference','lambda'); + fprintf('Saved data to file %s\n', fileName); + +end + +[M,N] = size(X_reference); +norm_X_reference = norm(X_reference,'fro'); +er_reference = @(x) norm(x-X_reference,'fro')/norm_X_reference; +norm_X_reference2= norm(X_reference_noTraceConstraint,'fro'); +er_reference2 = @(x) norm(x-X_reference_noTraceConstraint,'fro')/norm_X_reference2; +objective = @(X) norm( X(omega) - b )^2/2; + +[omegaI,omegaJ] = ind2sub([M,N],omega); +mat = @(x) reshape(x,M,N); +vec = @(x) x(:); + +k = length(omega); +p = k/(M*N); +df = R*(M+N-R); +fprintf('%d x %d rank %d matrix, observe %d = %.1f x df = %.1f%% entries\n',... + M,N,R,k,k/df,p*100); +fprintf(' Trace norm solution and original matrix differ by %.2e\n',... + norm(X_reference-X_original,'fro')/norm_X_reference ); +%% Solve unconstrained version. No smoothing is necessary! +opts = struct('maxIts',500); +opts.errFcn{1} = @(f,x) er_reference2(x); % no trace constraint +opts.errFcn{2} = @(f,x) sum( abs(eig(x)) > 1e-5 ); % numerical rank +% tell it to use eigs instead of eig +% opts.largescale = true; % ( but not recommended) + +% opts.symmetrize = true; % another option + +A = sparse( omegaI,omegaJ,b,N,N); +[x,out] = solver_psdComp( A, opts ); +% Note: we do not expect to get zero error because there might be more +% than one optimal solution for this problem (since there are not +% so many constraints) +% Check that we have a feasible solution +fprintf('Objective is %.2e, min eigenvalue is %.2e\n', objective(x),... + min(eig(x)) ); +%% Solve trace constrained version. No smoothing necessary! +opts = struct('maxIts',1500,'tol',1e-9); +opts.errFcn{1} = @(f,x) er_reference(x); % no trace constraint +opts.errFcn{2} = @(f,x) trace(x)-lambda; +opts.errFcn{3} = @(f,x) sum( abs(eig(x)) > 1e-5 ); % numerical rank +% we can also symmetrize "omega", but it makes little difference, +% and gives slightly differen value than CVX, since CVX symmetrizes +% differently: +% opts.symmetrize = true; +A = sparse( omegaI,omegaJ,b,N,N); +[x,out] = solver_psdCompConstrainedTrace( A,lambda, opts ); + +h=figure(); +semilogy(out.err(:,1)); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end + +%% +close(h) +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_quadratic.m b/examples/smallscale/test_quadratic.m new file mode 100644 index 0000000..46356d7 --- /dev/null +++ b/examples/smallscale/test_quadratic.m @@ -0,0 +1,44 @@ +%% Tests the solvers on a simple unconstrained quadratic function + +%{ + Solve: + + minimize_x c'x + x'Dx/2 + + as an example of using TFOCS without the "SCD" interface + +%} + +randn( 'state', sum('quadratic test') ); +N = 100; +c = randn(N,1); +D = randn(N,N); +D = D * D' + .5*eye(N); +x_star = - D \ c; % the true answer +x0 = zeros(N,1); + +% Here's what you could do... +% f = @(x) c'*x + x'*D*x/2; +% grad_f = @(x) c + D*x; +% smoothF = @(x) wrapper_objective( f, grad_f, x ); + +% Here's a simpler way: +smoothF = smooth_quad(D,c); + +x_error = @(x) norm(x-x_star,Inf); + +opts = []; +opts.errFcn = { @(f,x) x_error(x)}; +opts.restart = 100; + +[ x, out, optsOut ] = tfocs( smoothF, [], [],x0, opts ); +% Check that we are within allowable bounds +if out.err(end) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_quadratic_constrained.m b/examples/smallscale/test_quadratic_constrained.m new file mode 100644 index 0000000..817dd7c --- /dev/null +++ b/examples/smallscale/test_quadratic_constrained.m @@ -0,0 +1,83 @@ +function test_quadratic_constrained +%% Tests the solvers on a simple constrained quadratic function +%{ + Solve: + + minimize_x c'x + x'Dx/2 + subject to ||x||_1 <= 10 + + as an example of using TFOCS without the "SCD" interface + (since the objective is smooth and we can project, there's + no need to smooth and solve the dual) + + It's also an example of using 2 objective functions + +%} + +%% Now, add in constraints + +randn( 'state', sum('quadratic test') ); +N = 100; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% CONSTRUCT THE TEST PROBLEM % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +c = randn(N,1); +D = randn(N,N); +D = D * D' + .5*eye(N); +s = svd(D); +% x_star = - D \ c; +% L = max(s); +% mu = min(s); +% if mu < eps, disp('WARNING: may have 0 eigenvalues'); end +% f_star = 0.5 * c' * x_star; +% n_star = norm( x_star ); +x0 = zeros(N,1); + +% f = @(x) c'*x + x'*D*x/2; +% grad_f = @(x) c + D*x; +% smoothF = @(x) wrapper_objective( f, grad_f, x ); +% -- or -- +% smoothF = smooth_quad(D,c); +% -- or (demonstrating how to use 2 objective functions) -- +f1 = @(x) c'*x; +f2 = @(x) x'*D*x/2; +g1 = @(x) c; +g2 = @(x) D*x; +smoothF = { @(x) wrapper_objective(f1,g1,x); @(x) wrapper_objective(f2,g2,x) }; +linearF = { 1 ; 1 }; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% SET UP THE TEST PARAMETERS % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +opts = []; +opts.tol = 1e-16; +opts.maxits = 3000; +opts.restart = 100; + +%%%%%%%%%%%%%%%%%%%%% +% ADD IN CONSTRAINT % +%%%%%%%%%%%%%%%%%%%%% +% projectorF = proj_simplex(10); % x >=0, sum(x) = 10 +projectorF = proj_l1(10); % l1 ball, radius 10 (e.g. norm(x,1) <= 10) + + +%%%%%%%%%%%%%%%%% +% RUN THE TESTS % +%%%%%%%%%%%%%%%%% +[ x, out, optsOut ] = tfocs( smoothF, linearF, projectorF,x0, opts ); +% Check that we are within allowable bounds + + + +function [v,gr] = wrapper_objective(f,g,x) +v = f(x); +if nargout > 1 + gr = g(x); +end +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBP.m b/examples/smallscale/test_sBP.m new file mode 100644 index 0000000..b5c3ec9 --- /dev/null +++ b/examples/smallscale/test_sBP.m @@ -0,0 +1,144 @@ +%{ + Tests basis pursuit + + min_x ||X||_1 +s.t. + A(X) == b + +The solvers solve a regularized version, using +||x||_1 + mu/2*||x-x_0||_2^2 + +This problem is a linear program, so if mu is sufficiently small, +the smoothing has exactly no effect. + +As a reference solution, we use either "x_ref" which is the interior-point +method solution, or we use "x_original", which is the original signal +used to generate the measurements b. In general, "x_original" is not +the solution to the minimization problem; but under certain circumstances, +such as when "x_original" is very sparse, it is the solution to the minimization problem. +This is the fundamental result from compressed sensing. +When this situation applies, "x_original" is the correct solution to machine precision, +and will be more accurate than even "x_ref". + +%} + +% Before running this, please add the TFOCS base directory to your path + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_smoothed_noiseless'); +randn('state',34324); + +% We don't want to store "A" +rand('state',34324); +N = 1024; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + % Generate a new problem + x = zeros(N,1); + T = randsample(N,K); + x(T) = randn(K,1); + + b = A*x; + EPS = 0; + b_original = b; + x_original = x; + + mu = .01*norm(x,Inf); + x0 = zeros(N,1); + % Note: with equality constraints, this is an LP, so for mu small + % enough (but > 0), we have exact relaxation. + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + A*xcvx == b + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +% er = er_ref; % error with reference solution (from IPM) +er = er_signal; % error from original signal +% obj_ref = norm(x_ref,1) + mu/2*norm(x_ref-x0)^2; +obj_ref = norm(x_original,1) + mu/2*norm(x_original-x0)^2; +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBP( A, b, mu, x0, z0, opts ); +time_TFOCS = toc; + +fprintf('--------------------------------------------------------\n'); +fprintf('Results:\n-original signal-\t-IPM solution- -TFOCS solution-\n'); +fprintf('---------------------+-----------------+----------------\n'); +fprintf('Number of nonzeros:\n\t%d\t\t%d\t\t%d\n',... + nnz(x_original),nnz(x_ref), nnz(x) ); +fprintf('Error vs. original, rel. l2 norm:\n\tN/A\t\t%.2e\t%.2e\n',... + er_signal(x_ref), er_signal(x) ); +er_signal1 = @(x) norm(x-x_original,Inf); +fprintf('Error vs. original, lInf norm:\n\tN/A\t\t%.2e\t%.2e\n',... + er_signal1(x_ref), er_signal1(x) ); +fprintf('Time to solve:\n\tN/A\t\t%.1fs\t\t%.1fs\n',... + time_IPM, time_TFOCS ); +fprintf('--------------------------------------------------------\n'); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-8 + disp('Everything is working'); +else + error('Failed the test'); +end +%% Here is how you can view the error history on a graph +figure(); +semilogy( out.err(:,1) ); +xlabel('iterations'); ylabel('error'); + +%% Here are some alternative ways to call it +opts = []; +opts.maxIts = 500; + +A_TFOCS = linop_matrix( A ); % not necessary, but one way to do it +[ x, out, optsOut ] = solver_sBP( A_TFOCS, b, mu, x0, z0, opts ); + +% We can also pass in function handles +Af = @(x) A*x; +At = @(y) A'*y; +A_TFOCS = linop_handles( [M,N], Af, At ); +[ x, out, optsOut ] = solver_sBP( A_TFOCS, b, mu, x0, z0, opts ); + +%% close plots +close all + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN.m b/examples/smallscale/test_sBPDN.m new file mode 100644 index 0000000..a6b3849 --- /dev/null +++ b/examples/smallscale/test_sBPDN.m @@ -0,0 +1,102 @@ +%{ + Tests basis pursuit de-noising + + min_x ||x||_1 +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version, using + ||x||_1 + mu/2*||x-x_0||_2^2 + +see also test_sBP.m + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); +%% +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_smoothed_noisy'); +randn('state',34324); +rand('state',34324); +N = 1024; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + x = zeros(N,1); + T = randsample(N,K); + x(T) = randn(K,1); + + b_original = A*x; + snr = 30; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + x_original = x; + + mu = .01*norm(x,Inf); + x0 = zeros(N,1); + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + norm(A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.restart = 500; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +opts.maxIts = 1000; +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBPDN( A, b, EPS, mu, x0, z0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-6 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN_W.m b/examples/smallscale/test_sBPDN_W.m new file mode 100644 index 0000000..840d859 --- /dev/null +++ b/examples/smallscale/test_sBPDN_W.m @@ -0,0 +1,149 @@ +%{ + Tests the "analysis" form of basis pursuit de-noising + + min_x ||Wx||_1 +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version, using + ||Wx||_1 + mu/2*||x-x_0||_2^2 + +see also test_sBP.m and test_sBPDN.m + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_W_problem1_smoothed_noisy'); +randn('state',34324); +rand('state',34324); + +N = 512; +M = round(N/2); +K = round(M/5); + +A = randn(M,N); +x = zeros(N,1); + +% introduce a sparsifying transform "W" +d = round(4*N); % redundant +Wf = @(x) dct(x,d); % zero-padded DCT +downsample = @(x) x(1:N,:); +Wt = @(y) downsample( idct(y) ); % transpose of W +W = Wf(eye(N)); % make an explicit matrix for CVX +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); + +else + + % Generate a new problem + + % the signal x consists of several pure tones at random frequencies + for k = 1:K + x = x + randn()*sin( rand()*pi*(1:N) + 2*pi*rand() ).'; + end + + + b_original = A*x; + snr = 40; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + x_original = x; + + mu = .01*norm(Wf(x),Inf); + x0 = zeros(N,1); + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(W*xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + norm(A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(W*x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr','d'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d\n', M, N ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +objF = @(x)norm(W*x,1) + mu/2*norm(x-x0).^2; +infeasF = @(x)norm(A*x-b) - EPS; +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f, ... + @(f,dual,primal) infeasF(primal) }; +opts.maxIts = 1000; +opts.tol = 1e-10; +% opts.normA2 = norm(A*A'); +% opts.normW2 = norm(W'*W); +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBPDN_W( A, W, b, EPS, mu, x0, z0, opts ); +time_TFOCS = toc; +fprintf('x is sub-optimal by %.2e, and infeasible by %.2e\n',... + objF(x) - obj_ref, infeasF(x) ); + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-4 + disp('Everything is working'); +else + error('Failed the test'); +end + + +%% Doing it "by hand" (for debugging), just for 200 iterations +objF = @(x)norm(W*x,1) + mu/2*norm(x-x0).^2; +infeasF = @(x)norm(A*x-b) - EPS; +er = er_ref; % error with reference solution (from IPM) +% er = er_signal; +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f, ... + @(f,dual,primal) infeasF(primal) }; +opts.maxIts = 200; +opts.printEvery = 10; +opts.tol = 1e-10; +z0 = []; +proxScale = sqrt(norm(W'*W)/norm(A*A')); +scale = 1; +prox = { prox_l2( EPS/scale ), proj_linf(proxScale) }; +affineF = {A/scale,-b/scale;W/proxScale,0}; +[ x, out, optsOut ] = tfocs_SCD( [], affineF, prox, mu, x0, z0, opts ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-2 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN_WW.m b/examples/smallscale/test_sBPDN_WW.m new file mode 100644 index 0000000..b3ce1d4 --- /dev/null +++ b/examples/smallscale/test_sBPDN_WW.m @@ -0,0 +1,130 @@ +%{ + Tests the extended "analysis" form of basis pursuit de-noising + + min_1 alpha*||W_1 x||_1 + beta*|| W_2 x ||_1 +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version + +see also test_sBP.m and test_sBPDN.m and test_sBPDN_W.m + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_WW_problem1_smoothed_noisy'); +randn('state',34324); +rand('state',34324); + +N = 512; +M = round(N/2); +K = round(M/5); + +A = randn(M,N); +x = zeros(N,1); + +% introduce a sparsifying transform "W" +d = round(4*N); % redundant +Wf = @(x) dct(x,d); % zero-padded DCT +downsample = @(x) x(1:N,:); +Wt = @(y) downsample( idct(y) ); % transpose of W +W = Wf(eye(N)); % make an explicit matrix for CVX + +% and add another transform: +d2 = round(2*N); +W2 = randn(d2,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + alpha = 1; + beta = 2; + + % the signal x consists of several pure tones at random frequencies + for k = 1:K + x = x + randn()*sin( rand()*pi*(1:N) + 2*pi*rand() ).'; + end + + + b_original = A*x; + snr = 40; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + x_original = x; + + mu = .01*norm(Wf(x),Inf); + x0 = zeros(N,1); + + % get reference via CVX + tic + cvx_begin + cvx_precision high + variable xcvx(N,1) + minimize alpha*norm(W*xcvx,1) + beta*norm(W2*xcvx,1) + ... + mu/2*sum_square(xcvx-x0) + subject to + norm(A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + objF = @(x)alpha*norm(W*x,1) +beta*norm(W2*x,1)+ mu/2*norm(x-x0).^2; + obj_ref = objF(x_ref); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr',... + 'd','d2','alpha','beta'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d\n', M, N ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +objF = @(x)alpha*norm(W*x,1) +beta*norm(W2*x,1)+ mu/2*norm(x-x0).^2; +infeasF = @(x)norm(A*x-b) - EPS; +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f, ... + @(f,dual,primal) infeasF(primal) }; +opts.maxIts = 2000; +opts.tol = 1e-10; +% opts.normA2 = norm(A*A'); +% opts.normW2 = norm(W'*W); +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBPDN_WW( A, alpha,W,beta,W2,b, EPS, mu, x0, z0, opts ); +time_TFOCS = toc; +fprintf('x is sub-optimal by %.2e, and infeasible by %.2e\n',... + objF(x) - obj_ref, infeasF(x) ); + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-3 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN_complex.m b/examples/smallscale/test_sBPDN_complex.m new file mode 100644 index 0000000..fe3bb4c --- /dev/null +++ b/examples/smallscale/test_sBPDN_complex.m @@ -0,0 +1,120 @@ +%{ + Tests basis pursuit de-noising + + min_x ||x||_1 +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version, using + ||x||_1 + mu/2*||x-x_0||_2^2 + +This version allows for complex measurements. + +see also test_sBPDN.m + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_smoothed_noisy_complex'); +randn('state',34324); +rand('state',34324); + +N = 1024; +M = round(N/2); +K = round(M/5); + +A = fft(eye(N)); % A is complex +rowsA = sort(randsample(N,M)); +A = A(rowsA,:); + +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + x = zeros(N,1); + T = randsample(N,K); + x(T) = randn(K,1); + + b_original = A*x; + snr = 30; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + x_original = x; + + mu = .01*norm(x,Inf); + x0 = zeros(N,1); + % Note: with equality constraints, this is an LP, so for mu small + % enough (but > 0), we have exact relaxation. + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + norm(A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +opts.maxIts = 750; + +% Option 1: +mode = 'r2c'; +% mode = 'c2c'; % do NOT use this: it solves a different problem! +AA = A; bb = b; Ahandles = linop_matrix(AA,mode); + +% Option 2: +% AA = [real(A); imag(A)]; bb = [real(b); imag(b)]; +% Ahandles = linop_matrix(AA); + +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBPDN( Ahandles, bb, EPS, mu, x0, z0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +if out.err(end,1) < 1e-3 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN_complex_2.m b/examples/smallscale/test_sBPDN_complex_2.m new file mode 100644 index 0000000..5e1cee1 --- /dev/null +++ b/examples/smallscale/test_sBPDN_complex_2.m @@ -0,0 +1,126 @@ +%{ + Tests basis pursuit de-noising + + min_x ||x||_1 +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version, using + ||x||_1 + mu/2*||x-x_0||_2^2 + +This version allows for complex measurements. + +Unlike test_sBPDN_complex, we also allow x to be complex. +For example, suppose we have a signal f that is the superposition +of a few tones. Then in the "synthesis" formulation, we make the +change-of-variables x = F f, where F is the FFT matrix. +If "PHI" is the measurement matrix, then the linear operator is +A = Phi*F', where F' = inv(F) is the IFFT. + +see also test_sBPDN.m, test_sBPDN_complex, test_sBPDN_W + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_smoothed_noisy_complex_2'); +randn('state',34324); +rand('state',34324); +N = 1024; +M = round(N/2); +M = round(M/5); +Phi = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); + F = fft(eye(N))/sqrt(N); + A = Phi * F'; +else + + % Generate a new problem + + F = fft(eye(N))/sqrt(N); + A = Phi * F'; + + % introduce a sparsifying transform "W" + f = zeros(N,1); + % the signal x consists of several pure tones at random frequencies + K = 5; % + for k = 1:K + f = f + randn()*sin( rand()*pi*(1:N) + 2*pi*rand() ).'; + end + x = fft(f)/sqrt(N); + + + b_original = A*x; + snr = 30; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + x_original = x; + + mu = .001*norm(x,Inf); + x0 = zeros(N,1); + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) complex + minimize norm(xcvx,1) + mu/2*sum_square_abs(xcvx-x0) + subject to + norm(A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square_abs(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, nnz(x_original) ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +opts.maxIts = 4500; +opts.printEvery = 50; +opts.tol = 1e-8; + +mode = 'c2c'; +AA = A; bb = b; Ahandles = linop_matrix(AA,mode); + +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBPDN( Ahandles, bb, EPS, mu, x0, z0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +if out.err(end,1) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN_nonnegative.m b/examples/smallscale/test_sBPDN_nonnegative.m new file mode 100644 index 0000000..5e5e8eb --- /dev/null +++ b/examples/smallscale/test_sBPDN_nonnegative.m @@ -0,0 +1,136 @@ +%{ + Tests non-negative basis pursuit + + min_x ||X||_1 +s.t. + || A(X) - b|| <= eps and x >= 0 + +The solvers solve a regularized version, using +||x||_1 + mu/2*||x-x_0||_2^2 + + +See also test_sBPDN.m and test_sBP_nonnegative.m + +%} + +% Before running this, please add the TFOCS base directory to your path + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_smoothed_noisy_nonnegative'); +randn('state',25442); +rand('state',2452); + +N = 1024; +M = round(N/2); +K = round(M/2); + +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + x = zeros(N,1); + T = randsample(N,K); + x(T) = .1+rand(K,1); % x >= 0 + + b = A*x; + noise = .1*norm(b)*randn(M,1)/sqrt(M); + EPS = .9*norm(noise); + b_original = b + noise; + x_original = x; + + mu = .01*norm(x,Inf); + x0 = zeros(N,1); + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + norm( A*xcvx - b ) <= EPS + xcvx >= 0 + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + % get reference via CVX, this time, without the x >= 0 constraing + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + norm( A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_pureBP = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','x_pureBP'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +norm_x_pureBP = norm(x_pureBP); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; % err w.r.t. CVX +er_pureBP = @(x) norm(x-x_pureBP)/norm_x_pureBP; % err w.r.t. CVX w.o. nonneg constraint +er_signal = @(x) norm(x-x_original)/norm_x_orig; % err w.r.t. signal +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by:\t\t\t\t%.2e (mu = %.2e)\n', ... + norm(x_pureBP - x_original)/norm(x_original),mu ); +fprintf('\tl1-norm solution, with x >=0 constraint, and original signal differ by:\t%.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +disp('Note: because of the noise, we do not expect to get zero error'); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from CVX) +opts = []; +% To see all possible options, run "tfocs()" +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f, ... + @(f,dual,primal) nnz(primal) }; +opts.maxIts = 400; +z0 = []; % we don't have a good guess for the dual + +opts.nonneg = true; % tell it to add the x >= 0 constraint + +tic; +x = solver_sBPDN( A, b, EPS, mu, x0, z0, opts ); +time_TFOCS = toc; + +fprintf('for (original signal, IPM solution, TFOCS solution),\n NNZ:\n\t%d\t\t%d\t\t%d\n',... + nnz(x_original),nnz(x_ref), nnz(x) ); +fprintf(' error vs. original, rel. l2 norm:\n\t%.2e\t%.2e\t%.2e\n',... + 0, er_signal(x_ref), er_signal(x) ); +er_signal1 = @(x) norm(x-x_original,Inf); +fprintf(' error vs. original, lInf norm:\n\t%.2e\t%.2e\t%.2e\n',... + 0, er_signal1(x_ref), er_signal1(x) ); +fprintf(' time to solve:\n\tN/A\t\t%.1fs\t\t%.1fs\n',... + time_IPM, time_TFOCS ); + +% Check that we are within allowable bounds +if er(x) < .01 + disp('Everything is working'); +else + error('Failed the test'); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBPDN_withContinuation.m b/examples/smallscale/test_sBPDN_withContinuation.m new file mode 100644 index 0000000..e94a465 --- /dev/null +++ b/examples/smallscale/test_sBPDN_withContinuation.m @@ -0,0 +1,147 @@ +%{ + Tests basis pursuit de-noising + + min_x ||x||_1 +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version, using + ||x||_1 + mu/2*||x-x_0||_2^2 + +in this file, we will use continuation to eliminate +the effect of the "mu" term. + +see also test_sBPDN.m + +%} + +% Before running this, please add the TFOCS base directory to your path + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_noisy'); +randn('state',34324); +rand('state',34324); +N = 1024; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + x = zeros(N,1); + T = randsample(N,K); + x(T) = randn(K,1); + + b_original = A*x; + snr = 30; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + x_original = x; + + mu = .01*norm(x,Inf); + x0 = zeros(N,1); + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + subject to + norm(A*xcvx - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.restart = 500; +opts.errFcn = { @(f,dual,primal) er(primal) }; +opts.maxIts = 1000; +opts.countOps = true; +% To see more possible options, run the command "tfocs" + +z0 = []; % we don't have a good guess for the dual +tic; + + +% -- with continuation: +opts.continuation = true; + +% when using continuation, a good stopping criteria is this one: +opts.stopCrit = 4; opts.tol = 1e-6; +opts.printStopcrit = 1; % this will display the value used in the stopping criteria calculation +[ x, out, optsOut ] = solver_sBPDN( A, b, EPS, mu, x0, z0, opts); + + + +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); +% Test we are within bounds: +if out.err(end,1) < 1e-3 + disp('Everything is working'); +else + error('Failed the test'); +end +%% Advanced options for continuation +% To see all possible options, run "continuation" with no inputs: +continuation() + +continuationOptions = []; + +% for example, we can make sure that we only take at most 200 +% iterations (except for the very last outer iteration): +continuationOptions.innerMaxIts = 200; + +% and instead of just 3 iterations (the default), let's do 5 iterations: +continuationOptions.maxIts = 5; + +% and decrease the tolerance of the outer loop +continuationOptions.tol = 1e-5; + +opts.printEvery = Inf; % to suppress output from inner iterations +continuationOptions.verbose = true; % "true" by default + +% if you want mu to decrease: +continuationOptions.muDecrement = 0.8; + +[ x, out, optsOut ] = solver_sBPDN( A, b, EPS, mu, x0, z0, opts, continuationOptions); + +% Test we are within bounds: +if out.err(end,1) < 1e-3 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sBP_nonnegative.m b/examples/smallscale/test_sBP_nonnegative.m new file mode 100644 index 0000000..663c58d --- /dev/null +++ b/examples/smallscale/test_sBP_nonnegative.m @@ -0,0 +1,149 @@ +%{ + Tests non-negative basis pursuit + + min_x ||X||_1 +s.t. + A(X) == b and x >= 0 + +The solvers solve a regularized version, using +||x||_1 + mu/2*||x-x_0||_2^2 + +%} + +% Before running this, please add the TFOCS base directory to your path + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','basispursuit_problem1_smoothed_noiseless_nonnegative'); +randn('state',34324); +rand('state',34324); + +N = 1024; +M = round(N/2); +K = round(M/2); + +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + x = zeros(N,1); + T = randsample(N,K); + x(T) = .1+rand(K,1); + + b = A*x; + EPS = 0; + b_original = b; + x_original = x; + + mu = .01*norm(x,Inf); + x0 = zeros(N,1); + % Note: with equality constraints, this is an LP, so for mu small + % enough (but > 0), we have exact relaxation. + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + A*xcvx == b + xcvx >= 0 + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + % get reference via CVX, this time, without the x >= 0 constraing + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + A*xcvx == b + cvx_end + time_IPM = toc; + x_pureBP = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','x_pureBP'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +norm_x_pureBP = norm(x_pureBP); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_pureBP = @(x) norm(x-x_pureBP)/norm_x_pureBP; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by:\t\t\t\t%.2e (mu = %.2e)\n', ... + norm(x_pureBP - x_original)/norm(x_original),mu ); +fprintf('\tl1-norm solution, with x >=0 constraint, and original signal differ by:\t%.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +% er = er_signal; % error from original signal +% er = er_pureBP; % the "pureBP" solution did not have x>=0 +opts = []; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f, ... + @(f,dual,primal) nnz(primal) }; +opts.restart = 1000; +z0 = []; % we don't have a good guess for the dual +tic; + +projectionF = {proj_Rn; proj_Rplus }; + +scaleA = 1/norm(A); +[x,out,optsOut] = tfocs_SCD( prox_l1, { linop_compose(A,scaleA), -b*scaleA; 1,0 }, projectionF, mu, x0, z0, opts ); + + + +time_TFOCS = toc; + +fprintf('for (original signal, IPM solution, TFOCS solution),\n NNZ:\n\t%d\t\t%d\t\t%d\n',... + nnz(x_original),nnz(x_ref), nnz(x) ); +fprintf(' error vs. original, rel. l2 norm:\n\t%.2e\t%.2e\t%.2e\n',... + 0, er_signal(x_ref), er_signal(x) ); +er_signal1 = @(x) norm(x-x_original,Inf); +fprintf(' error vs. original, lInf norm:\n\t%.2e\t%.2e\t%.2e\n',... + 0, er_signal1(x_ref), er_signal1(x) ); +fprintf(' time to solve:\n\tN/A\t\t%.1fs\t\t%.1fs\n',... + time_IPM, time_TFOCS ); + +% Test we are within bounds: +if out.err(end,1) < 1e-6 + disp('Everything is working'); +else + error('Failed the test'); +end +%% As of version 1.0d, the solver_sBP.m file can handle x >= 0 constraints +% This version will be more efficient, since it calls a special +% l1 + non-negative operator, rather than splitting it up. + +opts.nonneg = true; % tell it to add the x >= 0 constraint +[x,out,optsOut] = solver_sBP( A, b, mu, x0, z0, opts ); + +% Test we are within bounds: +if out.err(end,1) < 1e-5 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sDantzig.m b/examples/smallscale/test_sDantzig.m new file mode 100644 index 0000000..0e26e92 --- /dev/null +++ b/examples/smallscale/test_sDantzig.m @@ -0,0 +1,147 @@ +%{ + Tests the Dantzig Selector + + min_x ||x||_1 +s.t. + || D*A'*(A*x - b) || <= delta + +The solvers solve a regularized version, using + ||x||_1 + mu/2*||x-x_0||_2^2 + +see also test_sBPDN.m + +%} + +% Before running this, please add the TFOCS base directory to your path +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','dantzig_problem1_smoothed_noisy'); +randn('state',34324); +rand('state',34324); +N = 1024; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + normA2 = norm( (A'*A) )^2; + x = zeros(N,1); + T = randsample(N,K); + x(T) = randn(K,1); + + + b_original = A*x; + snr = 30; % SNR in dB + b = myAwgn(b_original,snr); + z = b - b_original; + sigma = std(z); % estimate of standard deviation + + % compute D and delta so that original signal is + % feasible with probability 1 - alpha + alpha = 0.1; + Anorms = sqrt(sum(A.^2))'; + nTrials = min(4*N,400); + w = randn(M,nTrials); + supAtz = sort(max( (A'*w) ./Anorms(:,ones(1,nTrials)))); + thresh = supAtz(round(nTrials*(1-alpha))); % empirical + d = thresh*sigma*Anorms; % a vector, for Dantzig solvers + if all(d > 1e-10) + delta = mean(d); + D = delta./d; % watch out for division by 0 + else + D = 1; + delta = 0; + end + normA2 = norm( diag(D)*(A'*A) )^2; +% clear d alpha w thresh nTrials + + x_original = x; + + mu = .05*norm(x,Inf); + x0 = zeros(N,1); + % Note:this is an LP, so for mu small + % enough (but > 0), we have exact relaxation. + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(N,1) + minimize norm(xcvx,1) + mu/2*sum_square(xcvx-x0) + subject to + norm(D.*(A'*(A*xcvx - b)),Inf ) <= delta + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = norm(x_ref,1) + mu/2*sum_square(x_ref-x0); + + save(fileName,'x_ref','b','x_original','mu',... + 'delta','D','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.restart = 500; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +opts.maxIts = 2000; +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sDantzig( {A,D}, b, delta, mu, x0, z0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-4 + disp('Everything is working'); +else + error('Failed the test'); +end +%% plot error +semilogy( out.err(:,1) ) +hold all + +%% Call the solver with W = I +% This is not usually recommended, since it is not necessary +% and creates extra dual variables +opts.restart = 2000; +W = linop_scale(1); % identity +[ x, out, optsOut ] = solver_sDantzig_W( {A,D}, W, b, delta, mu, x0, z0, opts ); +% Check that we are within allowable bounds +if out.err(end,1) < 1e-1 + disp('Everything is working'); +else + error('Failed the test'); +end + +%% close all figures +close all + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_sDantzig_3methods.m b/examples/smallscale/test_sDantzig_3methods.m new file mode 100644 index 0000000..2f23dc5 --- /dev/null +++ b/examples/smallscale/test_sDantzig_3methods.m @@ -0,0 +1,146 @@ +%{ + Tests the Dantzig Selector + + min_x ||x||_1 +s.t. + || D*A'*(A*x - b) || <= delta + +The solvers solve a regularized version, using + ||x||_1 + mu/2*||x-x_0||_2^2 + +see also test_sDantzig.m + +This demo shows three formulations of the Dantzig selector +Instead of calling a pre-built solver, we show how to call +tfocs_SCD directly. + +%} + +% Before running this, please add the TFOCS base directory to your path + +% Try to load the problem from disk +mu = 0; +fileName = fullfile('reference_solutions','dantzig_problem1_smoothed_noisy'); +randn('state',34324); +rand('state',34324); +N = 1024; +M = round(N/2); +K = round(M/5); +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + disp('Please run test_sDantzig.m to setup the file'); +end + + +[M,N] = size(A); +K = nnz(x_original); +norm_x_ref = norm(x_ref); +norm_x_orig = norm(x_original); +er_ref = @(x) norm(x-x_ref)/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*x-b)/norm(b); % change if b is noisy + +fprintf('\tA is %d x %d, original signal has %d nonzeros\n', M, N, K ); +fprintf('\tl1-norm solution and original signal differ by %.2e (mu = %.2e)\n', ... + norm(x_ref - x_original)/norm(x_original),mu ); + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.restart = 500; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +opts.maxIts = 1000; +opts.printEvery = 100; +z0 = []; % we don't have a good guess for the dual + + +%% Method 1: use the epigraph of the l_infinity norm as the cone +% Note: this is equivalent to calling: +% [ x, out, optsOut ] = solver_sDantzig( {A,D}, b, delta, mu, x0, z0, opts ); + +DAtb = D.*(A'*b); +DD = @(x) D.*(x); +objectiveF = prox_l1; +affineF = {linop_matrix(diag(D)*(A'*A)), -DAtb }; +dualproxF = prox_l1( delta ); +[ x, out, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts ); +x1 = x; +out1 = out; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-3 + disp('Everything is working'); +else + error('Failed the test'); +end +%% Method 2: use the LP formulation +% Instead of the constraint ||Ax-b||_infty <= delta +% (where "A" is really DA'A), +% think of it as +% -(Ax-b) + delta >= 0 +% (Ax-b) + delta >= 0 + +objectiveF = prox_l1; +affineF = {linop_matrix(-diag(D)*(A'*A)), DAtb + delta;... + linop_matrix( diag(D)*(A'*A)), -DAtb + delta; }; + +dualproxF = { proj_Rplus; proj_Rplus }; +[ x, out, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts ); +x2 = x; +out2 = out; + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-3 + disp('Everything is working'); +else + error('Failed the test'); +end +%% Method 3: put objective into constraint +% This is the trick we do with solver_sDantzig_W, to deal with ||Wx||_1 +% Instead of minimizing ||x||_1, we minimize t, +% with the constraint that ||x||_1 <= t +% This version has some scaling considerations -- if we +% are careful about scaling, the dual problem will be solved much faster. + +% Note: we still have to deal with the original constraints. We +% can use either method 1 or method 2 above. Here, we'll use +% method 1. + + +objectiveF = []; % tfocs_SCD recognizes this special objective +normA2 = norm( diag(D)*A'*A )^2; +scale = 1/sqrt(normA2); +affineF = {linop_matrix(diag(D)*(A'*A)), -DAtb; linop_scale(1/scale), 0 }; +dualproxF = { prox_l1(delta); proj_linf(scale) }; +[ x, out, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts ); +x3 = x; +out3 = out; + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-2 + disp('Everything is working'); +else + error('Failed the test'); +end +%% plot +figure; +semilogy( out1.err(:,1) ); +hold all +semilogy( out2.err(:,1) ); +semilogy( out3.err(:,1) ); +legend('Method 1 (epigraph cone)','Method 2 (LP)','Method 3 (W=I)' ); + +%% close all plots +close all + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/examples/smallscale/test_sTV.m b/examples/smallscale/test_sTV.m new file mode 100644 index 0000000..3d70ca5 --- /dev/null +++ b/examples/smallscale/test_sTV.m @@ -0,0 +1,118 @@ +%{ + Tests total-variation problem + + min_x ||x||_TV +s.t. + || A(x) - b || <= eps + +The solvers solve a regularized version + +see also test_sBP.m and test_sBPDN_W.m + +requires the image processing toolbox for this demo +(but the TFOCS solver does not rely on this toolbox) + +See also the TV examples in examples/largescale + +%} + +% Before running this, please add the TFOCS base directory to your path + +myAwgn = @(x,snr) x + ... + 10^( (10*log10(sum(abs(x(:)).^2)/length(x(:))) - snr)/20 )*randn(size(x)); + +% Try to load the problem from disk +fileName = fullfile('reference_solutions','tv_problem1_smoothed_noisy'); +randn('state',245); +rand('state',245); +n = 32; +n1 = n; +n2 = n-1; % testing the code with non-square signals +N = n1*n2; +M = round(N/2); +A = randn(M,N); +if exist([fileName,'.mat'],'file') + load(fileName); + fprintf('Loaded problem from %s\n', fileName ); +else + + % Generate a new problem + + n = max(n1,n2); + x = phantom(n); + x = x(1:n1,1:n2); + x_original = x; + + mat = @(x) reshape(x,n1,n2); + vec = @(x) x(:); + + + b_original = A*vec(x_original); + snr = 40; % SNR in dB + b = myAwgn(b_original,snr); + EPS = norm(b-b_original); + + tv = linop_TV( [n1,n2], [], 'cvx' ); + + mu = .005*norm( tv(x_original) ,Inf); + x0 = zeros(n1,n2); + + % get reference via CVX + tic + cvx_begin + cvx_precision best + variable xcvx(n1,n2) + minimize tv(xcvx) + mu/2*sum_square(vec(xcvx)-vec(x0) ) + subject to + norm(A*vec(xcvx) - b ) <= EPS + cvx_end + time_IPM = toc; + x_ref = xcvx; + obj_ref = tv(x_ref) + mu/2*sum_square(vec(x_ref)-vec(x0) ); + + save(fileName,'x_ref','b','x_original','mu',... + 'EPS','b_original','obj_ref','x0','time_IPM','snr'); + fprintf('Saved data to file %s\n', fileName); + +end + +imshow( [x_original, x_ref] ); + +[M,N] = size(A); +[n1,n2] = size(x_original); +norm_x_ref = norm(x_ref,'fro'); +norm_x_orig = norm(x_original,'fro'); +er_ref = @(x) norm(vec(x)-vec(x_ref))/norm_x_ref; +er_signal = @(x) norm(x-x_original)/norm_x_orig; +resid = @(x) norm(A*vec(x)-b)/norm(b); % change if b is noisy + + +%% Call the TFOCS solver +er = er_ref; % error with reference solution (from IPM) +opts = []; +opts.restart = 1000; +opts.errFcn = { @(f,dual,primal) er(primal), ... + @(f,dual,primal) obj_ref - f }; +opts.maxIts = 1000; + +W = linop_TV( [n1,n2] ); +normW = linop_TV( [n1,n2], [], 'norm' ); +opts.normW2 = normW^2; +z0 = []; % we don't have a good guess for the dual +tic; +[ x, out, optsOut ] = solver_sBPDN_W( A, W, b, EPS, mu, vec(x0), z0, opts ); +time_TFOCS = toc; + +fprintf('Solution has %d nonzeros. Error vs. IPM solution is %.2e\n',... + nnz(x), er(x) ); + +% Check that we are within allowable bounds +if out.err(end,1) < 1e-4 + disp('Everything is working'); +else + error('Failed the test'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/examples/smallscale/test_variousSolvers.m b/examples/smallscale/test_variousSolvers.m new file mode 100644 index 0000000..ba11ae8 --- /dev/null +++ b/examples/smallscale/test_variousSolvers.m @@ -0,0 +1,70 @@ +%{ +Solves a simple quadratic problem +(with optimal solution known in closed-form) +with all the various solvers + + +the quadratic problem is: + +min_x c'x + x'Px/2, P > 0, hence the optimal solution is x = -inv(Q)*c + +On a simple quadratic example like this, when there is no projection, +all the solver behave similarly. So this script is more useful for debugging +to make sure that all solvers are working + +%} + +%% Make a problem +N = 100; +randn('state',2334); +P = randn(N); P = P*P' + .1*eye(N); % ensure P is symmetric positive definite +c = randn(N,1); + +x_ref = -P\c; + +%% Solve with TFOCS +f = smooth_quad( P, c ); +affine = []; +projector = []; +x0 = zeros(N,1); +opts = []; +opts.printEvery = 500; +opts.tol = 1e-8; +opts.printStopCrit = true; +opts.restart = 350; % use this since the problem is strongly convex +opts.errFcn = @(f,x) norm( x-x_ref)/norm(x_ref); +opts.maxIts = 750; + +% The 'N83' and 'AT' methods can take advantage when you know +% the exact value of the strong convexity parameter +% (and the Lipschitz constant). In this case, do not +% use restart. But we won't test this right now, +% since it is unusual that you have information +% about the strong convexity constant. +% opts.restart = Inf; +% opts.mu = min(eig(P)); % strong convexity parameter. N83 can make use of this +% opts.Lexact = max(eig(P)); + +solverList = { 'GRA', 'AT', 'LLM', 'N07', 'N83', 'TS' }; +% solverList = { 'GRA', 'AT', 'N83', 'TS' }; +% solverList = { 'LLM','N07'}; % these take two steps per iteration + +figure(1); clf; + +for k = 1:length( solverList ) + solver = solverList{k}; + opts.alg = solver; + fprintf('\nSolver: %s\n\n', solver ); + [x,out,optsOut] = tfocs( f, affine, projector, x0, opts ); + semilogy( out.err ); + hold all +end +legend( solverList ); + + +%% close all figures +close all + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/linop_TV.m b/linop_TV.m new file mode 100644 index 0000000..77a1bf8 --- /dev/null +++ b/linop_TV.m @@ -0,0 +1,199 @@ +function op = linop_TV( sz, variation, action ) + +%LINOP_TV 2D Total-Variation (TV) linear operator. +% OP = LINOP_TV( SZ ) returns a handle to a TFOCS linear operator that +% implements the total variation linear operator on an M x N grid; +% that is, to be applied to matrices of size [M,N]. +% By default, it expects to operate on M*N x 1 vectors +% but if SZ = {M,N}, then expects to operate on M x N matrices +% +% N.B. In this form, OP does not calculate the TV norm but rather +% a vector of size M*N x 1 such that TV(X) = norm( OP(X,1), 1 ). +% +% TV = LINOP_TV( X ) returns ||X||_TV if X is bigger than 2 x 2 +% +% OP = LINOP_TV( SZ, VARIANT ) +% if VARIANT is 'regular' (default), +% assumes zeros on the boundary (Dirichlet boundary conditions) +% if VARIANT is 'circular', +% assumes circularity, so x(:,N+1) - x(:,N) +% is calculated by x(:,1) - x(:,N) (and similarly +% for row differences) [when SZ={M,N}] +% +% [...] = LINOP_TV(SZ, VARIATION, ACTION ) +% if ACTION is 'handle', returns a TFOCS function handle (default) +% if ACTION is 'cvx', returns a function handle suitable for CVX +% if ACTION is 'matrix', returns the explicit TV matrix +% (real part corresponds to horizontal differences, +% imaginary part correspond to vertical differences) +% if ACTION is 'norm', returns an estimate of the norm +% +% TODO: check circular case for non-square domains +% + +error(nargchk(1,3,nargin)); +if nargin < 2 || isempty(variation), variation = 'regular'; end +if nargin < 3 || isempty(action), action = 'handle'; end + +CALCULATE_TV = false; +if numel(sz) > 4 + CALCULATE_TV = true; + X = sz; + sz = size(X); +end + +if iscell(sz) + n1 = sz{1}; + n2 = sz{2}; +else + n1 = sz(1); + n2 = sz(2); +end + +% Setup the Total-Variation operators +mat = @(x) reshape(x,n1,n2); + +if strcmpi(action,'matrix') || strcmpi(action,'cvx') + switch lower(variation) + case 'regular' + e = ones(max(n1,n2),1); + e2 = e; + e2(n2:end) = 0; + J = spdiags([-e2,e], 0:1,n2,n2); + I = eye(n1); + Dh = kron(J,I); % horizontal differences, sparse matrix + % see also blkdiag + + e2 = e; + e2(n1:end) = 0; + J = spdiags([-e2,e], 0:1,n1,n1); + I = eye(n2); + Dv = kron(I,J); % vertical differences, sparse matrix + case 'circular' + e = ones(max(n1,n2),1); + e2 = e; +% e2(n2:end) = 0; + J = spdiags([-e2,e], 0:1,n2,n2); + J(end,1) = 1; + I = eye(n1); + Dh = kron(J,I); % horizontal differences, sparse matrix + % see also blkdiag + + e = ones(max(n1,n2),1); + e2 = e; +% e2(n1:end) = 0; + J = spdiags([-e2,e], 0:1,n1,n1); + J(end,1) = 1; + I = eye(n2); + Dv = kron(I,J); % vertical differences, sparse matrix + end + if strcmpi(action,'matrix') + op = Dh + 1i*Dv; + else + % "norms" is a CVX function, but we can over-load it (see sub-function below) + op = @(X) sum( norms( [Dh*X(:), Dv*X(:)]' ) ); + end + return; +end + +switch lower(variation) + case 'regular' + Dh = @(X) vec( [diff(X,1,2), zeros(n1,1)] ); + diff_h = @(X) [zeros(n1,1),X(:,1:end-1)] - [X(:,1:end-1),zeros(n1,1) ]; + Dv = @(X) vec( [diff(X,1,1); zeros(1,n2)] ); + diff_v = @(X) [zeros(1,n2);X(1:end-1,:)] - [X(1:end-1,:);zeros(1,n2) ]; + % sometimes diff_v is much slower than diff_h + % We can exploit data locality by working with transposes + diff_v_t = @(Xt) ([zeros(n2,1),Xt(:,1:end-1)] - [Xt(:,1:end-1),zeros(n2,1)])'; + case 'circular' + % For circular version, 2 x 2 case is special. +% error('not yet implemented'); + Dh = @(X) vec( [diff(X,1,2), X(:,1) - X(:,end)] ); + % diff_h needs to be checked + diff_h = @(X) [X(:,end),X(:,1:end-1)] - X; + % diff_v needs to be checked + Dv = @(X) vec( [diff(X,1,1); X(1,:) - X(end,:) ] ); + diff_v = @(X) [X(end,:);X(1:end-1,:)] - X; + diff_v_t = @(Xt) ([Xt(:,end),Xt(:,1:end-1)] - Xt)'; + otherwise + error('Bad variation parameter'); +end +if iscell(sz) + Dh_transpose = @(X) diff_h(mat(X)) ; +% Dv_transpose = @(X) diff_v(mat(X)) ; + Dv_transpose = @(X) diff_v_t(mat(X)') ; % faster +else + Dh_transpose = @(X) vec( diff_h(mat(X)) ); +% Dv_transpose = @(X) vec( diff_v(mat(X)) ); + Dv_transpose = @(X) vec( diff_v_t(mat(X)') ); % faster +end + +TV = @(x) ( Dh(mat(x)) + 1i*Dv(mat(x)) ); % real to complex +TVt = @(z) ( Dh_transpose(real(z)) + Dv_transpose(imag(z)) ); + +if CALCULATE_TV + op = norm( TV(X), 1 ); + return; +end + +if strcmpi(action,'norm') + % to compute max eigenvalue, I use a vector + % that is very likely to be the max eigenvector: + % matrix with every entry alternating -1 and 1 + even = @(n) ~( n - 2*round(n/2) ); % returns 1 if even, 0 if odd + Y = zeros( n1 + even(n1), n2 + even(n2) ); + nn = numel(Y); + Y(:) = (-1).^(1:nn); + Y = Y(1:n1,1:n2); + op = norm( TV(Y) )/norm(Y(:)); + + % Nearly equivalent to: + % norm(full( [real(tv); imag(tv)] ) ) + % where tv is the matrix form +else + if iscell(sz) + szW = { [n1,n2], [n1*n2,1] }; + else + szW = sz(1)*sz(2); % n1 * n2 + szW = [szW,szW]; + end + op = @(x,mode)linop_tv_r2c(szW,TV,TVt,x,mode); +end + +function y = linop_tv_r2c( sz, TV, TVt, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = TV( realcheck( x ) ); + case 2, y = realcheck( TVt( x ) ); +end + +function y = realcheck( y ) +if ~isreal( y ), + error( 'Unexpected complex value in linear operation.' ); +end + + +function y = norms( x, p, dim ) +sx = size(x); +if nargin < 3, dim = 1; end +if nargin < 2 || isempty(p), p = 2; end +if isempty(x) || dim > length(sx) || sx(dim)==1 + p = 1; +end +switch p, + case 1, + y = sum( abs( x ), dim ); + case 2, + y = sqrt( sum( x .* conj( x ), dim ) ); + case Inf, + y = max( abs( x ), [], dim ); + case {'Inf','inf'} + y = max( abs( x ), [], dim ); + otherwise, + y = sum( abs( x ) .^ p, dim ) .^ ( 1 / p ); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_TV3D.m b/linop_TV3D.m new file mode 100644 index 0000000..1fcc1fe --- /dev/null +++ b/linop_TV3D.m @@ -0,0 +1,197 @@ +function op = linop_TV3D( sz, variation, action ) + +%LINOP_TV3D 3D Total-Variation (TV) linear operator. +% OP = LINOP_TV3D( SZ ) returns a handle to a TFOCS linear operator that +% implements the total variation linear operator on an M x N x P grid; +% that is, to be applied to volume stacks of size [M,N,P]. +% By default, it expects to operate on M*N*P x 1 vectors +% but if SZ = {M,N,P}, then expects to operate on M x N x P matrices +% +% TV = LINOP_TV3D( X ) returns ||X||_TV if X is bigger than 2 x 2 x 2 +% +% OP = LINOP_TV3D( SZ, VARIANT ) +% if VARIANT is 'regular' (default), +% ... TODO +% if VARIANT is 'circular', +% ... TODO +% +% [...] = LINOP_TV3D(SZ, VARIATION, ACTION ) +% if ACTION is 'handle', returns a TFOCS function handle (default) +% if ACTION is 'cvx', returns a function handle suitable for CVX +% if ACTION is 'matrix', returns the explicit TV matrix +% (real part corresponds to horizontal differences, +% imaginary part correspond to vertical differences) +% if ACTION is 'norm', returns an estimate of the norm +% +% Contributed by Mahdi Hosseini (mahdi.hosseini@mail.utoronto.ca) +% Has not been extensively tested + +error(nargchk(1,3,nargin)); +if nargin < 2 || isempty(variation), variation = 'regular'; end +if nargin < 3 || isempty(action), action = 'handle'; end + +CALCULATE_TV = false; +if numel(sz) > 6 + CALCULATE_TV = true; + X = sz; + sz = size(X); +end +nDim = numel(sz); + +if iscell(sz) + n1 = sz{1}; + n2 = sz{2}; + n3 = sz{3}; +else + n1 = sz(1); + n2 = sz(2); + n3 = sz(3); +end + +% Setup the Total-Variation operators +mat = @(x) reshape(x,n1,n2,n3); + +if strcmpi(action,'matrix') || strcmpi(action,'cvx') + I1 = eye(n1); + I2 = eye(n2); + I3 = eye(n3); + switch lower(variation) + case 'regular' + e = ones(max([n1,n2,n3]),1); + e2 = e; + e2(n2:end) = 0; + J = spdiags([-e2,e], 0:1,n2,n2); + Dh = kron(I3,kron(J,I1)); % horizontal differences, sparse matrix + % see also blkdiag + + e2 = e; + e2(n1:end) = 0; + J = spdiags([-e2,e], 0:1,n1,n1); + Dv = kron(I3,kron(I2,J)); % vertical differences, sparse matrix + + e2 = e; + e2(n3:end) = 0; + J = spdiags([-e2,e], 0:1,n3,n3); + Dd = kron(J,kron(I2,I1)); % Depth differences, sparse matrix + case 'circular' + e = ones(max([n1,n2,n3]),1); + e2 = e; + % e2(n2:end) = 0; + J = spdiags([-e2,e], 0:1,n2,n2); + J(end,1) = 1; + Dh = kron(I3, kron(J,I1)); % horizontal differences, sparse matrix + % see also blkdiag + + e2 = e; + % e2(n1:end) = 0; + J = spdiags([-e2,e], 0:1,n1,n1); + J(end,1) = 1; + Dv = kron(I3, kron(I2,J)); % vertical differences, sparse matrix + + e2 = e; + % e2(n1:end) = 0; + J = spdiags([-e2,e], 0:1,n3,n3); + J(end,1) = 1; + Dd = kron(J, kron(I2,I1)); % vertical differences, sparse matrix + end + if strcmpi(action,'matrix') + op = [Dh;Dv;Dd]; + else + % "norms" is a CVX function + op = @(X) sum( norms( [Dh*X(:), Dv*X(:), Dd*X(:)]' ) ); + end + return; +end + +switch lower(variation) + case 'regular' + Dh = @(X) vec( [diff(X,1,2), zeros(n1,1,n3)] ); + Dv = @(X) vec( [diff(X,1,1); zeros(1,n2,n3)] ); + Dd = @(X) [vec(diff(X,1,3)); vec(zeros(n1,n2,1))]; + + diff_h = @(X) [zeros(n1,1,n3),X(:,1:end-1,:)] - ... + [X(:,1:end-1,:),zeros(n1,1,n3)]; + diff_v = @(X) [zeros(1,n2,n3);X(1:end-1,:,:)] - ... + [X(1:end-1,:,:);zeros(1,n2,n3)]; + diff_d = @(X) mat([vec(zeros(n1,n2,1));vec(X(:,:,1:end-1))] - ... + [vec(X(:,:,1:end-1));vec(zeros(n1,n2,1))]); + case 'circular' + % For circular version, 2 x 2 case is special. + % error('not yet implemented'); + Dh = @(X) vec( [diff(X,1,2), X(:,1,:) - X(:,end,:)] ); + Dv = @(X) vec( [diff(X,1,1); X(1,:,:) - X(end,:,:)] ); + Dd = @(X) [vec(diff(X,1,3)); vec(X(:,:,1) - X(:,:,end))]; + % diff_h needs to be checked + diff_h = @(X) [X(:,end,:),X(:,1:end-1,:)] - X; + % diff_v needs to be checked + diff_v = @(X) [X(end,:,:);X(1:end-1,:,:)] - X; + % diff_d needs to be checked + diff_d = @(X) mat([vec(X(:,:,end));vec(X(:,:,1:end-1))]) - X; + otherwise + error('Bad variation parameter'); +end +if iscell(sz) + Dh_transpose = @(X) diff_h(mat(X)) ; + Dv_transpose = @(X) diff_v(mat(X)) ; + Dd_transpose = @(X) diff_d(mat(X)) ; +else + Dh_transpose = @(X) vec( diff_h(mat(X)) ); + Dv_transpose = @(X) vec( diff_v(mat(X)) ); + Dd_transpose = @(X) vec( diff_d(mat(X)) ); +end + +%% TV & TVt Definitions +TV = @(x) [Dh(mat(x)); Dv(mat(x)); Dd(mat(x))]; + +firstThird = @(x) x(1: n1*n2*n3); +secondThird= @(x) x(n1*n2*n3+1: 2*n1*n2*n3); +thirdThird= @(x) x(2*n1*n2*n3+1: 3*n1*n2*n3); +TVt = @(z) ( Dh_transpose(firstThird(z)) +... + Dv_transpose(secondThird(z)) +... + Dd_transpose(thirdThird(z))); + +%% +if CALCULATE_TV + op = norm( TV(X), 1 ); + return; +end + +if strcmpi(action,'norm') + % to compute max eigenvalue, I use a vector + % that is very likely to be the max eigenvector: + % matrix with every entry alternating -1 and 1 + even = @(n) ~( n - 2*round(n/2) ); % returns 1 if even, 0 if odd + Y = zeros( n1 + even(n1), n2 + even(n2), n3 + even(n3) ); + nn = numel(Y); + Y(:) = (-1).^(1:nn); + Y = Y(1:n1,1:n2,1:n3); + op = norm( TV(Y) )/norm(Y(:)); + + % Nearly equivalent to: + % norm(full( [real(tv); imag(tv)] ) ) + % where tv is the matrix form +else + if iscell(sz) + szW = { [n1,n2,n3], [n1*n2*n3,1] }; + else + szW = prod(sz); % n1 * n2 + szW = [nDim*szW, nDim*szW]; + end + op = @(x,mode)linop_tv_r2c(szW,TV,TVt,x,mode); +end + +function y = linop_tv_r2c( sz, TV, TVt, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = TV( realcheck( x ) ); + case 2, y = realcheck( TVt( x ) ); +end + +function y = realcheck( y ) +if ~isreal( y ), + error( 'Unexpected complex value in linear operation.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_adjoint.m b/linop_adjoint.m new file mode 100644 index 0000000..7a2511d --- /dev/null +++ b/linop_adjoint.m @@ -0,0 +1,22 @@ +function op = linop_adjoint( A ) +%LINOP_ADJOINT Computes the adjoint operator of a TFOCS linear operator +%op = LINOP_ADJOINT( A ) +% Returns a function handle to a linear operator that is the adjoint of +% the operator supplied. + +op = @(y,mode)linop_adjoint_impl( A, y, mode ); + +function y = linop_adjoint_impl( A, x, mode ) +switch mode, + case 0, + y = A(x,0); + y([1,2]) = y([2,1]); + case 1, + y = A(x,2); + case 2, + y = A(x,1); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_compose.m b/linop_compose.m new file mode 100644 index 0000000..f050dd8 --- /dev/null +++ b/linop_compose.m @@ -0,0 +1,84 @@ +function op = linop_compose( varargin ) +%LINOP_COMPOSE Composes two TFOCS linear operators +%OP = LINOP_COMPOSE( OP1, OP2, ..., OPN ) +% Constructs a TFOCS-compatible linear operator from the composition of +% two or more linear operators and/or matrices. That is, +% OP(x,1) = OP1(OP2(...(OPN(x,1),...,1),1) +% OP(x,2) = OPN(...(OP2(OP1(x,2),2)...,2) +% If matrices are supplied, they must be real; to include complex +% matrices, convert them first to linear operators using LINOP_MATRIX. + +if nargin == 0, + error( 'Not enough input arguments.' ); +end +sz = { [], [] }; +for k = 1 : nargin, + tL = varargin{k}; + if isempty(tL) || ~isa(tL,'function_handle') && ~isnumeric(tL) || ndims(tL) > 2, + error( 'Arguments must be linear operators, scalars, or matrices.' ); + elseif isnumeric(tL), + if ~isreal(tL), + error( 'S or scalar arguments must be real.' ); + elseif numel(tL) == 1, + tL = linop_scale( tL ); + else + tL = linop_matrix( tL ); + end + varargin{k} = tL; + end + try + tsz = tL([],0); + catch + error( 'Arguments must be linear operators, scalars, or matrices.' ); + end + if isempty(tsz) % i.e. output of linop_identity is [] + tsz = { [], [] }; + end + if isnumeric(tsz), + tsz = { [tsz(2),1], [tsz(1),1] }; + end + + % convert [n1;n2] to [n1,n2] if necessary: + for kk = 1:2 + %if iscolumn( tsz{kk} ) + %tsz{kk} = tsz{kk}.'; + %end + tsz{kk} = tsz{kk}(:).'; + end + + if ~isempty(sz{1}) && ~isempty(tsz{2}) && ~isequal(tsz{2},sz{1}), + for kk = 1:min( numel(tsz{2}), numel( sz{1} ) ) + fprintf('Found incompatible sizes: %d ~= %d\n', tsz{2}(kk), sz{1}(kk) ); + end + error( 'Incompatible dimensions in linear operator composition.' ); + end + if ~isempty(tsz{1}), + sz{1} = tsz{1}; + end + if isempty(sz{2}), + sz{2} = tsz{2}; + end +end +if nargin == 1, + op = varargin{1}; +else + op = @(x,mode)linop_compose_impl( varargin, sz, x, mode ); +end + +function y = linop_compose_impl( ops, sz, y, mode ) +switch mode, + case 0, + y = sz; + case 1, + for k = numel(ops) : -1 : 1, + y = ops{k}( y, 1 ); + end + case 2, + for k = 1 : numel(ops), + y = ops{k}( y, 2 ); + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_dot.m b/linop_dot.m new file mode 100644 index 0000000..4a582ff --- /dev/null +++ b/linop_dot.m @@ -0,0 +1,49 @@ +function op = linop_dot( A, adj ) +%LINOP_DOT Linear operator formed from a dot product. +% OP = LINOP_DOT( A ) returns a handle to a TFOCS linear operator +% whose forward operation is OP(X) = TFOCS_DOT( A, X ). +% OP = LINOP_DOT( A, 1 ) returns the adjoint of that operator. + +switch class( A ), + case 'double', + sz = { size(A), [1,1] }; + case 'cell', + A = tfocs_tuple(A); + sz = { tfocs_size(A), [1,1] }; + case 'tfocs_tuple', + sz = { tfocs_size(A), [1,1] }; + otherwise, + error( 'First input must be a matrix or cell array of matrices.' ); +end +if nargin == 2 && adj, + sz = { sz{2}, sz{1} }; + op = @(x,mode)linop_dot_adjoint( sz, A, x, mode ); +else + op = @(x,mode)linop_dot_forward( sz, A, x, mode ); +end + +function y = linop_dot_forward( sz, A, x, mode ) +switch mode, + case 0, + y = sz; + case 1, + y = tfocs_dot( A, x ); + case 2, + if ~isreal(x), error( 'Unexpected complex input.' ); end + y = A * x; +end + +function y = linop_dot_adjoint( sz, A, x, mode ) +switch mode, + case 0, + y = sz; + case 1, + if ~isreal(x), error( 'Unexpected complex input.' ); end + y = A * x; + case 2, + y = tfocs_dot( A, x ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_fft.m b/linop_fft.m new file mode 100644 index 0000000..61ad9ff --- /dev/null +++ b/linop_fft.m @@ -0,0 +1,192 @@ +function op = linop_fft( N, bigN, cmode, width ) +%LINOP_FFT Fast Fourier transform linear operator. +% OP = LINOP_FFT( N ) +% returns a function handle that computes the 1D FFT. +% If given a matrix, it operates on each column separately (i.e. it +% does NOT automatically switch to a 2D FFT). +% +% By default, it assumes the input the FFT is real, and the output +% is therefore complex and conjugate-symmetric +% +% OP = LINOP_FFT( N, M ) +% will zero-pad the input so that it is size M. Currently, the code +% requires M > N, which is typical in applications. +% The forward linear operator takes a vector of dimension N and returns a +% vector of dimension M; the adjoint takes a M-vector and returns a N-vector. +% By default, M = N. +% +% OP = LINOP_FFT( N, M, CMODE ) +% specifies the complex mode of the linear operator. The choices are: +% 'r2c' (default) : real input and complex (conjugate-symmetric) output +% 'c2c' : complex input and outpuyt +% 'r2r' : real input and real output. The real output +% retains the same number of degrees of freedom because it exploits +% the redudancy in the conjugate-symmetry of the complex output of the fft. +% +% OP = LINOP_FFT( N, M, CMODE, WIDTH ) +% specifies that the domain is the space of N x WIDTH matrices +% (and the range is M x WIDTH ). +% +% new in TFOCS v1.3 + +% Wed, Jan 4 2011 + +error(nargchk(1,4,nargin)); + +% the naming convention "bigN" refers to the fact that we usually +% will pad the input with zeros to take an oversampled FFT. +% In this case, "bigN" is the size of the zero-padded input, +% and hence also the size of the output. +if nargin < 2 || isempty(bigN), bigN = N; end +if bigN < N, error('cannot yet handle bigN < N, but there is no inherent limitiation'); end +if nargin < 3 || isempty(cmode), + cmode = 'r2c'; + warning('TFOCS:FFTdefaultMode','In linop_fft, using default ''r2c'' mode for fft; this will take real-part of input only!'); +end +if nargin < 4 || isempty(width), width = 1; end +sz = { [N,width], [bigN,width] }; + +% allow normalization to make it orthogonal? To do so, divide forward mode by sqrt(bigN) + +switch lower(cmode) + case 'c2c' + op = @(x,mode)linop_fft_c2c( sz, N, bigN, x, mode ); + case 'r2c' + op = @(x,mode)linop_fft_r2c( sz, N, bigN, x, mode ); + case 'r2r' + n2 = bigN/2; + even = (n2 == round(n2) ); % find if n is even or odd + if ~even + n2 = (bigN+1)/2; + end + op = @(x,mode)linop_fft_r2r( sz, N, bigN, n2, even, x, mode ); + otherwise + error('bad input for cmode: must be "c2c", "r2c", or "r2r"'); +end + +function y = linop_fft_c2c(sz, N, bigN, x, mode) +switch mode, + case 0, y = sz; + case 1, y = fft(x,bigN); % input of size N. Norm is bigN + case 2, + y = bigN*ifft(x); % do NOT use ifft(x,N) because we want to truncate AFTER the ifft, not before + y = y(1:N,:); +end + +function y = linop_fft_r2c(sz, N, bigN, x, mode) +switch mode, + case 0, y = sz; + case 1, + if ~isreal(x), + x = real(x); +% x = real(x+conj(x))/2; % another possibility + end + y = fft(x,bigN); + case 2, + y = bigN*ifft(x,'symmetric'); + y = y(1:N,:); +end + + +function y = linop_fft_r2r(sz, N, bigN, n2, even, x, mode) +switch mode, + case 0, y = sz; + case 1, + error(sizeCheck( x, mode, sz )); + if ~isreal(x), + x = real(x); + end + z = fft(x,bigN); % output is of size bigN + y = real(z); + if even + % y( (n2+2):bigN, : ) = imag( z( 2:n2, : ) ); % convention "A" (not orthogonal) + y( (n2+2):bigN, : ) = -imag( z( n2:-1:2, : ) ); % convention "B" (orthogonal) + y( n2+1, :) = y( n2+1, :)/sqrt(2); + else + % y( (n2+1):bigN, : ) = imag( z( 2:n2, : ) ); % convention "A" + y( (n2+1):bigN, : ) = -imag( z( n2:-1:2, : ) );% convention "B" + end + y = sqrt(2)*y; % the sqrt(2) is so adjoint=inverse + y(1,:) = y(1,:)/sqrt(2); + case 2, + error(sizeCheck( x, mode, sz )); + assert( isreal(x) ); + y = complex(x); % reserve the memory for y + n = bigN; + if even + % y(2:n2, : ) = x(2:n2, : ) + 1i*x((n2+2):n, : ); % convention "A" + y(2:n2, : ) = ( x(2:n2, : ) - 1i*x(n:-1:(n2+2), : ) )/sqrt(2); % convention "B" + + % We can skip this (see the "trick" mentioned below) + % y(n:-1:n2+2, : )= conj( y(2:n2,:) ); + else + % y(2:n2, : ) = x(2:n2, : ) + 1i*x((n2+1):n, : ); % convention "A" + y(2:n2, : ) = ( x(2:n2, : ) - 1i*x(n:-1:(n2+1), : ) )/sqrt(2); % convention "B" + + % We can skip this (see the "trick" mentioned below) + % y(n:-1:n2+1, : )= conj( y(2:n2, :) ); + end + % Note: we are using a trick. We have commented out + % the lines that set y(n2+2:n) (or n2+1:n), because + % the ifft, when using the 'symmetric' option, + % will not look at these entries. + + y = bigN*ifft(y,'symmetric'); + y = y(1:N,:); +end + +function ok = sizeCheck( x, mode, sz ) +szX = size(x); +szY = sz{mode}; +if numel(szX) == numel(sz{1}) && all( szX == szY ) + ok = []; +else + ok = 'Dimensions mismatch in linop_fft; please check your input size. '; + if numel(szX) == 2 + if szX(1) == szY(2) && szX(2) == szY(1) + ok = strcat(ok,' Looks like you have a row vector instead of a column vector'); + end + end +end + +%{ +The conjugate-to-real transformation as a matrix. +(we design this for conjugate-symmetry, but you must make sure + that it still does the same operations when the input + is not conjugate-symmetric) + +n = 10; +even = n/2 == round(n/2) +P = zeros(n); +P(1,1) = 1; % DC component +if even, d = n/2 - 1; +P(d+2,d+2) = 1; % Nyquist component +else, d = (n-1)/2; end + +I = eye(d)/sqrt(2); +J = fliplr(I); + +first=2:d+1; +if even, second=d+3:n; else, second=d+2:n; end + +% Convention "A" +P(first, first) = I; % upper left block +P(second,first) = -1i*I; % lower left block + +P(first, second) = J; % upper right block +P(second,second) = 1i*J; % lower right block + +% or... this makes P' = inv(P). Convention "B" + +P(first, first) = I; % upper left block +P(second,first) = 1i*J; % lower left block. now, J, not I + +P(first, second) = J; % upper right block +P(second,second) = -1i*I; % lower right block. now, I, not J +%} + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license informatio diff --git a/linop_handles.m b/linop_handles.m new file mode 100644 index 0000000..015adcb --- /dev/null +++ b/linop_handles.m @@ -0,0 +1,89 @@ +function op = linop_handles( sz, Af, At, cmode ) +%LINOP_HANDLES Linear operator from user-supplied function handles. +%OP = LINOP_HANDLES( SZ, AF, AT, CMODE ) +% Constructs a TFOCS-compatible linear operator from separate function +% handles that compute the forward and adjoint operations. The first +% argument, SZ, gives the size of the linear operator; and the forward +% and adjoint handles are AF and AT, respectively. +% +% If the inputs and outputs are simple vectors, then SZ can take the +% standard Matlab form [M,N], where N is the input length and M is the +% output length. If the input or output is a matrix or array, then SZ +% should take the form { S_in, S_out }, where S_in is the size of the +% input and S_out is the size of the output. +% +% If the input or output space of the operator is complex, then the +% CMODE string must be supplied, which describes the forward operation: +% 'R2R': real input and output +% 'R2C': real input, complex output +% 'C2R': complex input, real output +% 'C2C': complex input, complex output +% If CMODE is not supplied, then 'R2R' is assumed. If the operator +% detects a complex input or output when it is not expected, then an +% error results. Therefore, you must make sure that your operators +% return real values when they are expected to do so. + +error(nargchk(3,4,nargin)); +if numel( sz ) ~= 2, + error( 'Size must have two elements.' ); +elseif isnumeric( sz ), + sz = { [sz(2),1], [sz(1),1] }; +elseif ~isa( sz, 'cell' ), + error( 'Invalid operator size specification.' ); +end +if ~isa( Af, 'function_handle' ) || ~isa( At, 'function_handle' ), + error( 'Second and third arguments must be function handles.' ); +end +if nargin < 4 || isempty( cmode ), + cmode = 'R2R'; +elseif ~ischar( cmode ) || size( cmode, 1 ) ~= 1, + error( 'Fourth argument must be a string.' ); +else + cmode = upper( cmode ); +end + +switch cmode, + case 'R2R', op = @(x,mode)linop_handles_r2r( sz, Af, At, x, mode ); + case {'R2C','R2CC'}, op = @(x,mode)linop_handles_r2c( sz, Af, At, x, mode ); + case {'C2R','CC2R'}, op = @(x,mode)linop_handles_c2r( sz, Af, At, x, mode ); + case 'C2C', op = @(x,mode)linop_handles_c2c( sz, Af, At, x, mode ); + otherwise, + error( 'Invalid complex mode: %s', cmode ); +end + +function y = linop_handles_r2r(sz, Af, At, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = realcheck( Af( realcheck( x ) ) ); + case 2, y = realcheck( At( realcheck( x ) ) ); +end + +function y = linop_handles_r2c(sz, Af, At, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = Af( realcheck( x ) ); + case 2, y = realcheck( At( x ) ); +end + +function y = linop_handles_c2r(sz, Af, At, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = realcheck( Af( x ) ); + case 2, y = At( realcheck( x ) ); +end + +function y = linop_handles_c2c(sz, Af, At, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = Af( x ); + case 2, y = At( x ); +end + +function y = realcheck( y ) +if ~isreal( y ), + error( 'Unexpected complex value in linear operation.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_matrix.m b/linop_matrix.m new file mode 100644 index 0000000..53e8f8c --- /dev/null +++ b/linop_matrix.m @@ -0,0 +1,93 @@ +function op = linop_matrix( A, cmode, width ) + +%LINOP_MATRIX Linear operator, assembled from a matrix. +% If A is a real matrix, OP = LINOP_MATRIX( A ) returns a handle to a +% TFOCS linear operator that uses that matrix to implement its size, +% forward, and adjoint operations. +% +% If A is a complex matrix, OP = LINOP_MATRIX( A, MODE ) returns a +% handle to a TFOCS linear operator that uses A to implement its size, +% forward, and linear operator. MODE is a string that tells it how to +% handle its complex inputs/outputs: +% 'R2R': real input, real output (real matrices only) +% 'R2C': real input, complex output +% 'C2R': complex input, real output +% 'C2C': complex input, complex output +% If the operator detects complex input when it is not expecting it, it +% will issue an error. +% +% OP = LINOP_MATRIX( A, MODE, WIDTH ) +% further specifies that A operates on matrices with WIDTH number +% of columns (and output also has WIDTH columns). Default is 1. +% +% Typically, end users do not need to call this routine. You may supply +% matrices directly to TFOCS, and it will call this as needed. + +if ~isnumeric( A ) || ndims( A ) > 2, + error( 'First input must be a matrix.' ); +end +if nargin < 3, width = 1; end +if numel( A ) == 1, + sz = { [], [] }; +else + sz = { [ size(A,2), width ], [ size(A,1), width ] }; +end +if nargin < 2 || isempty( cmode ), + if ~isreal( A ), + error( 'A real/complex mode must be supplied for complex matrices.' ); + end + cmode = 'R2R'; +elseif ~ischar( cmode ) || size( cmode, 1 ) ~= 1, + error( 'Complex mode must be a string.' ); +else + cmode = upper(cmode); +end +switch cmode, + case 'R2R', + if ~isreal( A ), + error( 'An "R2R" operator requires a real matrix.' ); + end + op = @(x,mode)linop_matrix_r2r( sz, A, x, mode ); + case 'R2C', op = @(x,mode)linop_matrix_r2c( sz, A, x, mode ); + case 'C2R', op = @(x,mode)linop_matrix_c2r( sz, A, x, mode ); + case 'C2C', op = @(x,mode)linop_matrix_c2c( sz, A, x, mode ); + otherwise, + error( 'Unexpected complex mode string: %s', op ); +end + +function y = linop_matrix_r2r( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = A * realcheck( x ); + case 2, y = A' * realcheck( x ); +end + +function y = linop_matrix_r2c( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = A * realcheck( x ); + case 2, y = real( A' * x ); +end + +function y = linop_matrix_c2r( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = real( A * x ); + case 2, y = A' * realcheck( x ); +end + +function y = linop_matrix_c2c( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = A * x; + case 2, y = A' * x; +end + +function y = realcheck( y ) +if ~isreal( y ), + error( 'Unexpected complex value in linear operation.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_normest.m b/linop_normest.m new file mode 100644 index 0000000..96fe9df --- /dev/null +++ b/linop_normest.m @@ -0,0 +1,79 @@ +function [ nxe, cnt ] = linop_normest( op, cmode, tol, maxiter ) + +%LINOP_NORMEST Estimates the operator norm. +% EST = LINOP_NORMEST( OP ) estimates the induced norm of the operator: +% || OP || = max_{||x||<=1} ||OP(X)|| +% using a simple power method similar to the MATLAB NORMEST functon. +% +% When called with a single argument, LINOP_TEST begins with a real +% initial vector. To test complex operators, use the two-argument +% version LINOP_NORMEST( OP, cmode ), where: +% cmode = 'R2R': real input, real output +% cmode = 'R2C': real input, complex output +% cmode = 'C2R': imag input, imag output +% cmode = 'C2C': complex input, complex output +% +% LINOP_NORMEST( OP, CMODE, TOL, MAXITER ) stops the iteration when the +% relative change in the estimate is less than TOL, or after MAXITER +% iterations, whichever comes first. +% +% [ EST, CNT ] = LINOP_NORMEST( ... returns the estimate and the number +% of iterations taken, respectively. + +if isnumeric( op ), + op = linop_matrix( op, 'C2C' ); +elseif ~isa( op, 'function_handle' ), + error( 'Argument must be a function handle or matrix.' ); +end +x_real = true; +if nargin >= 2 && ~isempty( cmode ), + switch upper( cmode ), + case { 'R2R', 'R2C' }, x_real = true; + case { 'C2R', 'C2C' }, x_real = false; + otherwise, error( 'Invalid cmode: %s', cmode ); + end +end +if nargin < 3 || isempty( tol ), + tol = 1e-8; +end +if nargin < 4 || isempty( maxiter ), + maxiter = 50; +end +sz = op([],0); +if iscell( sz ), + sz = sz{1}; +elseif ~isempty(sz) + sz = [sz(2),1]; +else + % if the input is the identity, it may not have a size associated with it, + % so current behavior is to try an arbitrary size: + sz = [50,1]; +end +cnt = 0; +nxe = 0; +while true, + if nxe == 0, + if x_real, + xx = randn(sz); + else + xx = randn(sz) + 1j*randn(sz); + end + nxe = sqrt( tfocs_normsq( xx ) ); + end + yy = op( xx / max( nxe, realmin ), 1 ); + nye = sqrt( tfocs_normsq( yy ) ); + xx = op( yy / max( nye, realmin ), 2 ); + nxe0 = nxe; + nxe = sqrt( tfocs_normsq( xx ) ); + if abs( nxe - nxe0 ) < tol * max( nxe0, nxe ), + break; + end + cnt = cnt + 1; + if cnt >= maxiter, + break; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_reshape.m b/linop_reshape.m new file mode 100644 index 0000000..963e483 --- /dev/null +++ b/linop_reshape.m @@ -0,0 +1,34 @@ +function op = linop_reshape( sz_in, sz_out ) + +%LINOP_RESHAPE Linear operator to perform reshaping of matrices. +% op = linop_reshape( sz_in, sz_out ) creates a linear operator that +% uses the matlab 'reshape' function to reshape between sz_in and sz_out. +% Both sz_in and sz_out must be vectors with d elements (for dimension d). +% Assumes the number of elements does not change. +% +% +% Contributed by Graham Coleman, graham.coleman@upf.edu +% See also linop_vec +error( nargchk(nargin,2,2)); +if ~isnumeric(sz_in) || ~isnumeric(sz_out) + error('sz_in and sz_out must be arrays'); +elseif numel(sz_in) ~= numel(sz_out) + error('sz_in and sz_out must have the same number of elements (usually 2)'); +elseif prod( sz_in(:) ) ~= prod( sz_out(:) ) + error('The number of elements cannot change from sz_in to sz_out'); +end +sz_in = sz_in(:).'; +sz_out = sz_out(:).'; + +op = @(x,mode) gkcop_reshape_impl( sz_in, sz_out, x, mode ); + +function y = gkcop_reshape_impl( sz_in, sz_out, x, mode ) +switch mode, + case 0, y = { sz_in, sz_out }; + case 1, y = reshape( x, sz_out ); + case 2, y = reshape( x, sz_in ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_scale.m b/linop_scale.m new file mode 100644 index 0000000..91ab0b3 --- /dev/null +++ b/linop_scale.m @@ -0,0 +1,62 @@ +function op = linop_scale( scale, sz ) + +%LINOP_SCALE Scaling linear operator. +% OP = LINOP_SCALE( scale ) returns a handle to a TFOCS linear operator +% whose forward and adjoint operators are OP(X) = scale * X. +% "scale" must be a real scalar +% +% OP = LINOP_SCALE( scale, size ) gives the scaling operator +% an explicit size. "sz" can be: +% - the empty matrix "[]" (default), which means the size is not +% yet defined; the TFOCS software will attempt to automatically +% determine the size later. +% - a vector [N1,N2] which implies the domain is the set of N1 x N2 matrices +% ( it is also possible to use [N1,N2,N3,....], which +% means the domain is a set of multi-dimensional arrays ) +% - a scalar N, which is equivalent to [N,1], i.e. the set of N x 1 vectors +% Because this is simple scaling, the range will have the same size +% as the domain. +% +% See also linop_compose + +if ~isnumeric( scale ) && numel( scale ) ~= 1, + error( 'Argument must be a scalar.' ); +elseif ~isreal( scale ), + error( 'Argument must be real.' ); +end +if nargin < 2, sz = []; end +if isempty(sz) + szCell = { [], [] }; +elseif isnumeric(sz) + if numel(sz) <= 1, sz = [sz, 1 ]; end + % ensure that "sz" is a row vector + sz = sz(:).'; + szCell = { sz, sz }; +else + error('bad type for size input'); +end + +if scale == 1, + op = @linop_identity; +else + op = @(x,mode)linop_scale_impl( szCell, scale, x, mode ); +end + +function y = linop_scale_impl( sz, scale, y, mode ) +if mode == 0, + y = sz; +else + y = scale * y; +end + +function OK = isSquare( sz ) +OK = true; +for j = 1:length( sz{1} ) + if sz{1}(j) ~= sz{2}(j) + OK = false; break; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_spot.m b/linop_spot.m new file mode 100644 index 0000000..7f72b58 --- /dev/null +++ b/linop_spot.m @@ -0,0 +1,81 @@ +function op = linop_spot( A, cmode ) + +%LINOP_SPOT Linear operator, assembled from a SPOT operator. +% If A is a real operator, OP = LINOP_SPOT( A ) returns a handle to a +% TFOCS linear operator that uses that object to implement its size, +% forward, and adjoint operations. +% +% If A is a complex operator, OP = LINOP_SPOT( A, MODE ) returns a +% handle to a TFOCS linear operator that uses A to implement its size, +% forward, and linear operator. MODE is a string that tells it how to +% handle its complex inputs/outputs: +% 'R2R': real input, real output (real matrices only) +% 'R2C': real input, complex output +% 'C2R': complex input, real output +% 'C2C': complex input, complex output +% If the operator detects complex input when it is not expecting it, it +% will issue an error. + +if ~isa( A, 'opSpot' ), + error( 'First input must be a SPOT operator.' ); +end +sz = { [ size(A,2), 1 ], [ size(A,1), 1 ] }; +if nargin < 2 || isempty( cmode ), + if ~isreal( A ), + error( 'A real/complex mode must be supplied for complex matrices.' ); + end + cmode = 'R2R'; +elseif ~ischar( cmode ) || size( cmode, 1 ) ~= 1, + error( 'Complex mode must be a string.' ); +else + cmode = upper(cmode); +end +switch cmode, + case 'R2R', + if ~isreal( A ), + error( 'An "R2R" operator requires a real matrix.' ); + end + op = @(x,mode)linop_matrix_r2r( sz, A, x, mode ); + case 'R2C', op = @(x,mode)linop_matrix_r2c( sz, A, x, mode ); + case 'C2R', op = @(x,mode)linop_matrix_c2r( sz, A, x, mode ); + case 'C2C', op = @(x,mode)linop_matrix_c2c( sz, A, x, mode ); + otherwise, + error( 'Unexpected complex mode string: %s', op ); +end + +function y = linop_matrix_r2r( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = A * realcheck( x ); + case 2, y = A' * realcheck( x ); +end + +function y = linop_matrix_r2c( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = A * realcheck( x ); + case 2, y = real( A' * x ); +end + +function y = linop_matrix_c2r( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = real( A * x ); + case 2, y = A' * realcheck( x ); +end + +function y = linop_matrix_c2c( sz, A, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = A * x; + case 2, y = A' * x; +end + +function y = realcheck( y ) +if ~isreal( y ), + error( 'Unexpected complex value in linear operation.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_subsample.m b/linop_subsample.m new file mode 100644 index 0000000..069c6b1 --- /dev/null +++ b/linop_subsample.m @@ -0,0 +1,161 @@ +function op = linop_subsample( sz, omega, SYMMETRIC ) +%LINOP_SUBSAMPLE Subsampling linear operator. +%OP = LINOP_SUBSAMPLE( SZ, OMEGA ) +% vector and matrix subsampling. Depending on SZ and OMEGA, +% this can do row-sampling (e.g. a partial FFT) +% or it can sample specific entries of a matrix (e.g. matrix completion) +% To specify matrix subsampling, set sz = { [n1,n2], [length(omega),1] } +% where the input matrix has size n1 x n2 +% +%OP = LINOP_SUBSAMPLE( OMEGA ) +% works if OMEGA is a sparse matrix, whose nonzero entries +% specify the entries to sample. +% +%OP = LINOP_SUBSAMPLE( ..., SYMMETRIC ) +% If SYMMETRIC is true, then +% forces the domain to be the space of symmetric matrices +% +% Example with FFT: +% FFT = linop_handles([N,N], @(x)fft(x)/sqrt(N), @(x)sqrt(N)*real(ifft(x)) ,'R2C'); +% and to make a row sub-sampled FFT (M <= N): +% rows = randperm(M); +% A = linop_compose( linop_subsample([M,N], rows ), FFT ); + + +% Designed to be used with a fft or dct +% Do we need a separate oversampling version? +% Also, make linop_DCT and linop_FFT ? +% The reason we might want this is that for linop_FFT, +% people will probably use idct as the transpose -- this is not +% correct, due to scaling + +% Help documentation: TBD +% Constructs a TFOCS-compatible linear operator from separate function +% handles that compute the forward and adjoint operations. The first +% argument, SZ, gives the size of the linear operator; and the forward +% and adjoint handles are AF and AT, respectively. +% +% If the inputs and outputs are simple vectors, then SZ can take the +% standard Matlab form [N,M], where N is the input length and M is the +% output length. If the input or output is a matrix or array, then SZ +% should take the form { S_in, S_out }, where S_in is the size of the +% input and S_out is the size of the output. +% + +error(nargchk(1,3,nargin)); +if nargin < 3 + SYMMETRIC = false; +end +if nargin == 1 + omega = sz; + sz = { size(omega), [nnz(omega),1] }; +elseif nargin ==2 && issparse(sz) + SYMMETRIC = omega; + omega = sz; + sz = { size(omega), [nnz(omega),1] }; +end + +if numel( sz ) ~= 2, + error( 'Size must have two elements.' ); +elseif isnumeric( sz ), + sz = { [sz(2),1], [sz(1),1] }; +elseif ~isa( sz, 'cell' ), + error( 'Invalid operator size specification.' ); +end + +dom = sz{1}; +n1 = dom(1); n2 = dom(2); +ran = sz{2}; + +% There are several possibilities. Let x be a point in the domain +%{ + x is a vector. Then omega should be a vector. + We return x(omega), resp. + + x is a matrix. + omega is a vector + This is ambiguous: does the user want x(omega,:) or x(omega)? + omega is a matrix with 2 columns, then assume it is [I,J] + We convert it to linear indices. + omega is a general matrix, more than 2 columns + Not sure what the user means; report an error. + omega is a sparse matrix + We find it's entries, and use those + +%} + +if n2 == 1 + % x is a vector, not a matrix. Simple. +% op = @(x,mode) linop_subsample_vector( sz, omega, x, mode ); + % Allow it to vectorize along rows: + op = @(x,mode) linop_subsample_row( sz, omega, x, mode ); +else + % trickier case. + if issparse(omega) + ind = find(omega); + [I,J] = ind2sub( sz{1}, ind ); +% op = @(x,mode) linop_subsample_matrix( sz, ind, I, J, SYMMETRIC, x, mode ); + elseif isvector(omega) + ind = omega; + [I,J] = ind2sub( sz{1}, ind ); +% op = @(x,mode) linop_subsample_matrix( sz, omega, SYMMETRIC,x, mode ); + elseif size(omega,2) == 2 + ind = sub2ind( sz{1}, omega(:,1), omega(:,2) ); + I = omega(:,1); + J = omega(:,2); +% op = @(x,mode) linop_subsample_matrix( sz, ind, SYMMETRIC,x, mode ); + else + error('omega is not an acceptable size; perhaps you meant it to be sparse?'); + end + % Make sure 'ind' is a column vector + ind = ind(:); + op = @(x,mode) linop_subsample_matrix( sz, ind, I, J, SYMMETRIC, x, mode ); + +end + + +function y = linop_subsample_vector(sz, omega, x, mode ) +switch mode, + case 0, y = sz; + case 1, + if ~isequal( sz{1}, size(x) ), error('input wrong size for vector subsampling'); end + y = x(omega); + case 2, + y = zeros( sz{1} ); + y(omega) = x; +end +function y = linop_subsample_row(sz, omega, x, mode ) +switch mode, + case 0, y = sz; + case 1, + if ~isequal( sz{1}(1), size(x,1) ), error('input wrong size for vector subsampling'); end + y = x(omega,:); + case 2, + y = zeros( sz{1} ); + y = zeros( sz{1}(1), size(x,2) ); + y(omega,:) = x; +end +function y = linop_subsample_matrix(sz, omega, indI, indJ,SYMMETRIC, x, mode ) +switch mode, + case 0, y = sz; + case 1 + S = []; + S.type = '()'; + S.subs = {omega}; + y = subsref(x,S); + case 2, + dom = sz{1}; n1 = dom(1); n2 = dom(2); + y = sparse( indI, indJ, x, n1, n2 ); + if SYMMETRIC + % in future, might update this + % e.g. force omega to only refer to lower part of matrx, + % and then do the update y = y + tril(y,-1)' + y = (y+y')/2; + end +end + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_test.m b/linop_test.m new file mode 100644 index 0000000..4ab8ba6 --- /dev/null +++ b/linop_test.m @@ -0,0 +1,206 @@ +function varargout = linop_test( op, cmode, maxits ) + +%LINOP_TEST Performs an adjoint test on a linear operator. +% LINOP_TEST( OP ) attempts to verify that a linear operator OP obeys +% the inner product test: = for all x, y. OP must be a +% TFOCS linear operator with hard-coded size information; that is, +% OP([],0) must return valid size info. +% +% When called with a single argument, LINOP_TEST creates real test +% vectors for X and Y. To test complex operators, use the two-argument +% version LINOP_TEST( OP, cmode ), where: +% cmode = 'R2R': real input, real output +% cmode = 'R2C': real input, complex output +% cmode = 'R2CC': real input, conjugate-symmetric complex output +% cmode = 'C2R': complex input, real output +% cmode = 'CC2R': conjugate-symmetric complex input, real output +% cmode = 'C2C': complex input, complex output +% +% The conjugate-symmetric options follow the symmetry conventions of Matlab's FFT. +% +% LINOP_TEST( OP, CMODE, MAXITS ) performs MAXITS iterations of the +% test loop. MAXITS=25 is the default. +% +% myNorm = LINOP_TEST(...) returns an estimate of the myNorm of +% the linear operator. + +error(nargchk(1,3,nargin)); +if isnumeric( op ), + if nargin < 2 || isempty( cmode ) + disp('warning: for matrix inputs, this assumes the cmode is ''C2C'' unless you specify otherwise'); + cmode = 'C2C'; + end + op = linop_matrix( op, cmode ); +end +y_conjSymmetric = false; +x_conjSymmetric = false; +if nargin < 2 || isempty( cmode ), + x_real = true; + y_real = true; +else + switch upper( cmode ), + case 'R2R', x_real = true; y_real = true; + case 'R2C', x_real = true; y_real = false; + case 'R2CC', x_real = true; y_real = false; y_conjSymmetric = true; + case 'C2R', x_real = false; y_real = true; + case 'CC2R', x_real = false; y_real = true; x_conjSymmetric = true; + case 'C2C', x_real = false; y_real = false; + case 'CC2CC', x_real = false; y_real = false; + x_conjSymmetric = true; + y_conjSymmetric = true; % this is probably never going to happen though + otherwise, error( 'Invalid cmode: %s', cmode ); + end +end +if nargin < 3 || isempty(maxits), + maxits = 25; +end +sz = op([],0); +if ~iscell(sz) + sz = { [sz(2),1], [sz(1),1] }; +elseif isempty(sz{1}) + disp('warning: could not detect the size; this often happens when using a scalar input, which represents scaling'); + if isempty( sz{2} ) + disp(' Proceeding under the assumption that the domain is 1D'); + sz{1} = [1,1]; + sz{2} = [1,1]; + else + disp(' Proceeding under the assumption that the domain equals the range'); + sz{1} = sz{2}; + end +elseif isempty(sz{2}) + disp('warning: could not detect the size; this often happens when using a scalar input, which represents scaling'); + disp(' Proceeding under the assumption that the domain equals the range'); + sz{2} = sz{1}; +end +nf = 0; +na = 0; +errs = zeros(1,maxits+1); +nxe = 0; nye = 0; +for k = 1 : maxits, + + % + % The adjoint test + % + + if x_real, + x = randn(sz{1}); + else + x = randn(sz{1})+1j*randn(sz{1}); + if x_conjSymmetric + x = make_conj_symmetrix( x ); + end + end + + if y_real, + y = randn(sz{2}); + else + y = randn(sz{2})+1j*randn(sz{2}); + if y_conjSymmetric + y = make_conj_symmetrix( y ); + end + end + + nx = myNorm(x); + Ax = op(x,1); + nf = max( nf, myNorm(Ax)/nx ); + Ax_y = tfocs_dot( Ax, y ); + + ny = myNorm(y); + Ay = op(y,2); + na = max( na, myNorm(Ay) / ny ); + Ay_x = tfocs_dot( x, Ay ); + + errs(k) = abs(Ax_y-Ay_x)/(nx*ny); + + % + % The myNorm iteration + % + + if nxe == 0, + if x_real, + xx = randn(sz{1}); + else + xx = randn(sz{1}) + 1j*randn(sz{1}); + if x_conjSymmetric + xx = make_conj_symmetrix( xx ); + end + end + nxe = myNorm(xx); + end + yy = op(xx/nxe,1); + nye = max(realmin,myNorm(yy)); + xx = op(yy/nye,2); + nxe = myNorm(xx); + +end + +% +% Use the estimated singular vectors for a final adjoint est +% + +if nxe > 0, + Ax_y = tfocs_dot( op(xx,1), yy ); + Ay_x = tfocs_dot( op(yy,2), xx ); + errs(end) = abs(Ax_y-Ay_x) / (nxe*nye); +end + +% +% Display the output +% + +nmax = max(nye,nxe); +myNorm_err = abs(nye-nxe) / nmax; +peak_err = max(errs) / nmax; +mean_err = mean(errs) / nmax; +rc = { 'complex', 'real', 'complex symmetric' }; +fprintf( 'TFOCS linear operator test:\n' ); +fprintf( ' Input size: [' ); fprintf( ' %d', sz{1} ); fprintf( ' ], %s\n', rc{x_real+1+2*x_conjSymmetric} ); +fprintf( ' Output size: [' ); fprintf( ' %d', sz{2} ); fprintf( ' ], %s\n', rc{y_real+1+2*y_conjSymmetric} ); +fprintf( 'After %d iterations:\n', maxits ); +fprintf( ' myNorm estimates (forward/adjoint/error): %g/%g/%g\n', nye, nxe, myNorm_err ); +fprintf( ' Gains: forward %g, adjoint %g\n', nf, na ); +fprintf( ' Inner product error:\n' ); +fprintf( ' Mean (absolute/relative): %g/%g\n', mean(errs), mean_err ); +fprintf( ' Peak (absolute/relative): %g/%g\n', max(errs), peak_err ); +fprintf( ' (inner product errors should 1e-10 or smaller)\n'); + +good = true; +if myNorm_err/max( nye, nxe ) > 1e-4 + fprintf(' Detected mismatch in forward/adjoint norm estimates. This is potentially a bad sign. Check your implementation\n'); + good = false; +end +if mean_err > 1e-8 + fprintf(' The mean error (relative) is high. This is a bad sign. Check your implementation\n'); + good = false; +end +if good + fprintf(' Allowing for some roundoff error, there are no obvious errors. This is good.\n'); +end + +if nargout > 0 + varargout{1} = mean([nye,nxe]); +end + + +% Improvement as suggested by Graham Coleman +% Allows for 3D arrays. +% This also changes default behavior of 2D arrays +% to now use the Frobenius norm instead of spectral norm. +% This is wise, since it's a much quicker computation. +function y = myNorm(x) +y = norm( x(:) ); + +function y = make_conj_symmetrix( y ) +ny = size( y, 1 ); +y(1,:) = real(y(1,:)); % DC component is 0 +if round(ny/2) == ny/2 % even + y(ny/2+1,:) = real(y(ny/2+1,:)); % Nyquist component is 0 + y( ny:-1:(ny/2+2) ) = conj( y(2:ny/2) ); +else % odd + y( ny:-1:((ny+1)/2+1) ) = conj( y(2:((ny+1)/2)) ); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/linop_vec.m b/linop_vec.m new file mode 100644 index 0000000..6e2a61b --- /dev/null +++ b/linop_vec.m @@ -0,0 +1,40 @@ +function op = linop_vec( sz ) +%LINOP_VEC Matrix to vector reshape operator +%OP = LINOP_VEC( SZ ) +% Constructs a TFOCS-compatible linear operator that reduces a matrix +% variable to a vector version using column-major order. +% This is equivalent to X(:) +% The transpose operator will reshape a vector into a matrix. +% +% The input SZ should of the form [M,N] where [M,N] describe the +% size of the matrix variable. The ouput vector will be of +% length M*N. If SZ is a single entry, then M = N is assumed. +% For advanced usage with multidimensional arrays, +% use the more general linop_reshape function instead. +% +% To do the reverse operation (from vector to matrix), +% use this function together with linop_adjoint. +% +% See also linop_reshape + +if numel(sz) > 2, error('must supply a 2-entry vector'); end +if numel(sz) == 1, sz = [sz(1),sz(1)]; end + +% Switch conventions of the size variable: +sz = { [sz(1),sz(2)], [sz(1)*sz(2),1] }; + +op = @(x,mode)linop_handles_vec( sz, x, mode ); + +function y = linop_handles_vec(sz, x, mode ) +switch mode, + case 0, y = sz; + case 1, y = x(:); + case 2, + MN = sz{1}; M = MN(1); N = MN(2); + y = reshape( x, M, N ); + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/mexFiles/makeMex.m b/mexFiles/makeMex.m new file mode 100644 index 0000000..166bf00 --- /dev/null +++ b/mexFiles/makeMex.m @@ -0,0 +1,57 @@ +function makeMex() +% Script to install mex files + +% Change directory, so all mex files are in the mexFiles/ subdirectory +here = pwd; +cd( fullfile(tfocs_where,'mexFiles') ); +if exist('OCTAVE_VERSION','builtin') + octave = true; + compileFunction = @compileForOctave; +else + octave = false; + compileFunction = @compileForMatlab; +end + + +% -- Compile all the mex files -- + +% For the ordered L1 norm, used in prox_OL1.m: +compileFunction('proxAdaptiveL1Mex.c'); + + + + +% Change directory back to wherever we started from +cd(here); + +end + + + + +% -- subroutines -- +function compileForMatlab( inputFile ) +mex(inputFile) +end + +function compileForOctave( inputFile ) +if ~isunix, + disp('warning, this will probably fail if not on linux; please edit makeMex.m yourself'); +end +% To compile oct or mex files with octave, you must have the octave function "mkoctfile" +% e.g. (for ubuntu 10.04, using octave 3.2): sudo apt-get install octave3.2-headers +% for other versions of ubuntu and/or octave, try packages like +% octave-pkg-dev or liboctave-dev +system(sprintf('mkoctfile --mex %s', inputFile ) ); + +% rm the .c ending +if length(inputFile) > 2 && inputFile(end-1:end)=='.c' + inputFile = inputFile(1:end-2); +end +system(sprintf('rm %s.o', inputFile ) ); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/mexFiles/proxAdaptiveL1Mex.c b/mexFiles/proxAdaptiveL1Mex.c new file mode 100644 index 0000000..3951eab --- /dev/null +++ b/mexFiles/proxAdaptiveL1Mex.c @@ -0,0 +1,133 @@ +/* This code from http://www-stat.stanford.edu/~candes/OrderedL1/ + * "Statistical Estimation and Testing via the Ordered L1 Norm" + * by M. Bogdan, E. van den Berg, W. Su, and E. J. Candès + * 2013 + * Released under the BSD 3-term license + * See license.txt + * */ + + +#include "mex.h" +#include + +/* Input: Vectors y, lambda, both non-negative and in decreasing order. */ + + +int evaluateProx(double *y, double *lambda, double *x, size_t n); + +/* ----------------------------------------------------------------------- */ +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +/* ----------------------------------------------------------------------- */ +{ mxArray *vectorY; + mxArray *vectorLambda; + mxArray *vectorX; + mwSize n, i, j, jMax, k; + double *y; + double *lambda; + double *x; + + /* Exit if no parameters are given */ + if (nrhs == 0) + { return ; + } + + /* Check for proper number of arguments */ + if (nrhs != 2) mexErrMsgTxt("Two input arguments expected."); + if (nlhs > 2) mexErrMsgTxt("Too many output arguments."); + + /* Extract the arguments */ + vectorY = (mxArray *)prhs[0]; + vectorLambda = (mxArray *)prhs[1]; + + /* Both vectors should be real, double, n-by-1 */ + n = mxGetNumberOfElements(vectorY); /* For code symmetry */ + if ((!mxIsDouble(vectorY)) || (mxIsComplex(vectorY)) || + (mxGetM(vectorY) != n) || (mxGetN(vectorY) != 1) || + (mxGetNumberOfDimensions(vectorY) != 2)) + { mexErrMsgTxt("First input argument should be a real n-by-1 vector of type double.\n"); + } + + if ((!mxIsDouble(vectorLambda)) || (mxIsComplex(vectorLambda)) || + (mxGetM(vectorLambda) != n) || (mxGetN(vectorLambda) != 1) || + (mxGetNumberOfDimensions(vectorLambda) != 2)) + { mexErrMsgTxt("Second input argument should be a real n-by-1 vector of type double.\n"); + } + + + /* --- DO NOT CHECK DECREASING ORDER AND SIGN PATTERNS --- */ + + + /* Allocate output argument */ + vectorX = mxCreateDoubleMatrix(n,1,mxREAL); + plhs[0] = vectorX; + + /* Get data pointers */ + lambda = mxGetPr(vectorLambda); + y = mxGetPr(vectorY); + x = mxGetPr(vectorX); + + + /* Solve prox function */ + evaluateProx(y,lambda,x,n); + + return ; +} + + + +/* ----------------------------------------------------------------------- */ +int evaluateProx(double *y, double *lambda, double *x, size_t n) +/* ----------------------------------------------------------------------- */ +{ double d; + double *s = NULL; + double *w = NULL; + size_t *idx_i = NULL; + size_t *idx_j = NULL; + size_t i,j,k; + int result = 0; + + /* Allocate memory */ + s = (double *)malloc(sizeof(double) * n); + w = (double *)malloc(sizeof(double) * n); + idx_i = (size_t *)malloc(sizeof(size_t) * n); + idx_j = (size_t *)malloc(sizeof(size_t) * n); + + if ((s != NULL) && (w != NULL) && (idx_i != NULL) && (idx_j != NULL)) + { + k = 0; + for (i = 0; i < n; i++) + { + idx_i[k] = i; + idx_j[k] = i; + s[k] = y[i] - lambda[i]; + w[k] = s[k]; + + while ((k > 0) && (w[k-1] <= w[k])) + { k --; + idx_j[k] = i; + s[k] += s[k+1]; + w[k] = s[k] / (i - idx_i[k] + 1); + } + + k++; + } + + for (j = 0; j < k; j++) + { d = w[j]; if (d < 0) d = 0; + for (i = idx_i[j]; i <= idx_j[j]; i++) + { x[i] = d; + } + } + } + else + { result = -1; + } + + /* Deallocate memory */ + if (s != NULL) free(s); + if (w != NULL) free(w); + if (idx_i != NULL) free(idx_i); + if (idx_j != NULL) free(idx_j); + + return result; +} diff --git a/mexFiles/proxAdaptiveL1Mex.mex b/mexFiles/proxAdaptiveL1Mex.mex new file mode 100755 index 0000000000000000000000000000000000000000..320b46f13b400974754743b07eed2de0d335f296 GIT binary patch literal 16581 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 literal 0 HcmV?d00001 diff --git a/mexFiles/proxAdaptiveL1Mex.mexa64 b/mexFiles/proxAdaptiveL1Mex.mexa64 new file mode 100755 index 0000000000000000000000000000000000000000..4d050e019e0bbc0c078a380041e46b59341ee2c7 GIT binary patch literal 9146 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 literal 0 HcmV?d00001 diff --git a/private/linop_identity.m b/private/linop_identity.m new file mode 100644 index 0000000..35f6315 --- /dev/null +++ b/private/linop_identity.m @@ -0,0 +1,10 @@ +function y = linop_identity( x, mode ) + +%LINOP_IDENTITY Internal TFOCS routine. +% Implements the identity linear operator. + +y = x; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/linop_stack.m b/private/linop_stack.m new file mode 100644 index 0000000..8a5354b --- /dev/null +++ b/private/linop_stack.m @@ -0,0 +1,297 @@ +function [ op, inp_dims, otp_dims ] = linop_stack( linearF, inp_dims, otp_dims, DO_DEBUG ) + +%LINOP_STACK Stacked linear operators. +% OP = LINOP_STACK( linearF ), where linearF is a cell vector or cell +% matrix, returns a function handle for a linear operator that accepts +% TFOCS_TUPLE objects as input or output, as appropriate, and applies +% the various linear operators in block matrix fashion. +% +% If linearF has more than one row, then the output in its forward mode +% or its input in adjoint mode is a TFOCS_TUPLE object. If linearF has +% more than one column, then the output in its adjoint mode or its input +% in forward mode is a TFOCS_TUPLE object. + +if nargin < 4 || isempty(DO_DEBUG), DO_DEBUG = false; end + +if ~isa( linearF, 'cell' ), + error( 'First argument must be a cell array.' ); +end +[ m, n ] = size( linearF ); +if nargin < 2 || isempty( inp_dims ), + inp_dims = cell( 1, n ); +end +if nargin < 3 || isempty( otp_dims ), + otp_dims = cell( 1, m ); +end +rescan = zeros(2,0); +debugPrintf('----- DEBUG INFO: Size of linear matrix (and offsets) ---- \n'); +% debugPrintf('---------------------------------------------------------- \n'); +for j = 1 : n, debugPrintf('-----------------------------------+'); end +debugPrintf('\n'); +% old_inp_d = {}; +for i = 1 : m, + otp_d = otp_dims{i}; + for j = 1 : n, + inp_d = inp_dims{j}; + lF = linearF{i,j}; + sZ = []; + if isempty(lF), + elseif isa( lF, 'function_handle' ), + sZ = lF([],0); + elseif ~isnumeric( lF ), + error( 'Entries should be real matrices or linear operators.' ); + elseif ~isreal(lF), % Why? we now handle A: C --> C + error( 'Matrix entries must be real.' ); + elseif numel(lF) > 1, + sZ = size(lF); + linearF{i,j} = linop_matrix( lF ); % Jan 2012, check this + elseif lF == 0, + linearF{i,j} = []; + else + if lF == 1, +% linearF{i,j} = @(x,mode)x; + linearF{i,j} = @linop_identity; + else + linearF{i,j} = @(x,mode)lF*x; + end + if ~isempty(otp_d), + sZ = { otp_d, otp_d }; + elseif ~isempty(inp_d), + sZ = { inp_d, inp_d }; + else + rescan(:,end+1) = [i;j]; + end + end + if isempty( sZ ), + printSizes( inp_d , otp_d ); % if DO_DEBUG is true, this will print + if j < n, debugPrintf(' |'); end + continue; + elseif isnumeric( sZ ), % This should never be triggered, unless offset is empty + % June 2011: + % If this is the offset term, then we allow for a matrix (rather than vector) + % offset, as long as the size of the linear portion has been specified: + if j == n && j > 1 + sZ_old = linearF{i,j-1}([],0); + if ~isempty( sZ_old) && all( sZ_old{2} == sZ ) + % We may have a matrix + sZ = { [1,1], sZ }; + % So, re-define linearF{i,j} not to be linop_matrix but rather the constant function + else + sZ = { [sZ(2),1], [sZ(1),1] }; + end + else + sZ = { [sZ(2),1], [sZ(1),1] }; + end + end + if isempty(inp_d), + inp_d = sZ{1}; +% elseif ~isequal(inp_d,sZ{1}) && ~isempty( sZ{1} ) % adding Oct 12. Jan 2012, is this right? inp_d was already defined.... +% elseif ~isempty( old_inp_d ) && ~isempty( sZ{1} ) && ~isequal( old_inp_d, sZ{1} ) +% if j > 1 +% for jj = 1:(j-1) +% sZ_old = linearF{i,jj}([],0); +% if isempty( sZ_old ) +% fprintf( 2, ... +% 'TFOCS message: About to throw an error: may be because element (%d,%d) of \n',i,jj); +% fprintf( 2, ... +% ' linear operator matrix does not have an explicit size\n' ); +% end +% end +% end +% error( 'Incompatible dimensions in element (%d,%d) of the linear operator matrix', i, j ); + end + inp_dims{j} = inp_d; +% if ~isempty( inp_d ), old_inp_d = inp_d; end + + if isempty(otp_d), + otp_d = sZ{2}; + elseif ~isequal(otp_d,sZ{2}), + if isequal( fliplr(otp_d), sZ{2} ) + fprintf('\nThe sizes match if you switch some rows/columns. Double-check your offsets are column vectors\n\n'); + end + error( 'Incompatible dimensions in element (%d,%d) of the linear operator matrix', i, j ); + end + + + printSizes( inp_d, otp_d ); % if DO_DEBUG is true, this will print + if j < n, debugPrintf(' |'); end + + end + otp_dims{i} = otp_d; + + debugPrintf('\n'); + +end +debugPrintf('---------------------------------------------------------- \n'); + +% +% In some cases, we cannot resolve the dimensions on the first pass: +% specifically, those entries that represent scalar scaling operations. +% In those cases, we know that the input and output dimensions must be the +% same, but we may not have yet determined either in the first pass. So +% we rescan those entries until all ambiguities are resolved or until no +% further progress is made. +% + +while ~isempty(rescan), + rescan_o = rescan; + rescan = zeros(2,0); + for ij = rescan, + i = ij(1); j = ij(2); + lF = linearF{i,j}; + if isnumeric(lF) && numel(lF) == 1, + if isempty(inp_dims{j}), + if isempty(otp_dims{i}), + rescan(:,end+1) = [i;j]; + continue; + else + inp_dims{j} = otp_dims{i}; + end + elseif isempty(otp_dims{i}), + otp_dims{i} = inp_dims{j}; + elseif ~isequal( inp_dims{i}, otp_dims{j} ), + error( 'Incompatible dimensions in element (%d,%d) of the linear operator matrix', i, j ); + end + if DO_DEBUG + fprintf('Affine term (%d,%d) has size:', i, j ); + printSizes( inp_dims{j}, otp_dims{i} ); + end + end + end + % Prevent infinite loops + if numel(rescan) == numel(rescan_o), + break; + end +end +debugPrintf('---------------------------------------------------------- \n'); + +if m == 1 && n == 1, + op = linearF{1,1}; + inp_dims = inp_dims{1}; + otp_dims = otp_dims{1}; + if isempty(op), + op = @linop_identity; + end +elseif m == 1, + otp_dims = otp_dims{1}; + op = @(x,mode)linop_stack_row( linearF, n, { inp_dims, otp_dims }, x, mode ); +elseif n == 1, + inp_dims = inp_dims{1}; + op = @(x,mode)linop_stack_col( linearF, m, { inp_dims, otp_dims }, x, mode ); +else + op = @(x,mode)linop_stack_mat( linearF, [m,n], { inp_dims, otp_dims }, x, mode ); +end + + +% ------- Internal subfunctions ------------ +% These functions can see the workspace variables of the main function +function debugPrintf( varargin ) +if DO_DEBUG + fprintf( varargin{:} ); +end +end + +function printSizes( inp_d, otp_d ) +if DO_DEBUG + % Print size of domain + if isempty( inp_d ) + fprintf(' ( ? )'); + else + if length( inp_d ) == 1, inp_d = [inp_d, 1 ]; end + fprintf(' ('); + for kk = 1:length(inp_d)-1, fprintf('%4d x ', inp_d(kk) ); end + fprintf('%4d )', inp_d(end) ); + end + % Print size of range + fprintf(' --> '); + if isempty( otp_d ) + fprintf('( ? )'); + else + if length( otp_d ) == 1, otp_d = [otp_d, 1 ]; end + fprintf('('); + for kk = 1:length(otp_d)-1, fprintf('%4d x ', otp_d(kk) ); end + fprintf('%4d )', otp_d(end) ); + end +end +end + + +end % end of main program + +% ------- External subfunctions ------------ +function y = linop_stack_row( linearF, N, dims, x, mode ) +switch mode, + case 0, + y = dims; + case 1, + y = 0; + x = cell( x ); + for j = 1 : N, + lF = linearF{j}; + if ~isempty(lF), y = y + lF(x{j},1); end + end + case 2, + y = cell(1,N); + for j = 1 : N, + lF = linearF{j}; + if ~isempty(lF), y{j} = lF(x,2); else y{j} = 0*x; end + end + y = tfocs_tuple( y ); +end +end + +function y = linop_stack_col( linearF, N, dims, x, mode ) +switch mode, + case 0, + y = dims; + case 1, + y = cell(1,N); + for j = 1 : N, + lF = linearF{j}; + if ~isempty(lF), y{j} = lF(x,1); else y{j} = 0*x; end + end + y = tfocs_tuple( y ); + case 2, + y = 0; + x = cell( x ); + for j = 1 : N, + lF = linearF{j}; + if ~isempty(lF), y = y + lF(x{j},2); end + end +end +end + +function y = linop_stack_mat( linearF, sZ, dims, x, mode ) +switch mode, + case 0, + y = dims; + case 1, + x = cell( x ); + y = cell( 1, sZ(1) ); + for i = 1 : sZ(1), + ans = 0; + for j = 1 : sZ(2), + lF = linearF{i,j}; + if ~isempty(lF), ans = ans + lF(x{j},1); end + end + y{i} = ans; + end + y = tfocs_tuple( y ); + case 2, + x = cell( x ); + y = cell( 1, sZ(2) ); + for j = 1 : sZ(2), + ans = 0; + for i = 1 : sZ(1), + lF = linearF{i,j}; + if ~isempty(lF), ans = ans + lF(x{i},2); end + end + y{j} = ans; + end + y = tfocs_tuple( y ); +end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/print_cell_size.m b/private/print_cell_size.m new file mode 100644 index 0000000..20a0f89 --- /dev/null +++ b/private/print_cell_size.m @@ -0,0 +1,40 @@ +function print_cell_size( c , fid, offsetPossible ) +% c should be a cell array + +if nargin < 2 || isempty(fid), fid = 1; end +if nargin < 3 || isempty(offsetPossible), offsetPossible = false; end + +if ~iscell(c) + fprintf(fid,'\tcomponent 1: '); +% d = size(c); + d = c; + for k = 1:(length(d)-1) + fprintf(fid,'%4d x ',d(k) ); + end + fprintf(fid,'%4d\n', d(k+1) ); + fprintf(fid,' (but input to printCellSize should have been a cell array)\n'); + return; +else + for j = 1:length(c) + if j == length(c) && offsetPossible && all(size( c{j} ) == [1,2] ) ... + && all( c{j} == [1,1] ) + fprintf(fid,'\tcomponent %2d is fixed (i.e. an offset)\n', j ); + else + fprintf(fid,'\tcomponent %2d: ', j ); + if isempty( c{j} ) + fprintf(fid,'size not yet determined\n'); + else + d = c{j}; + if length(d) < 2, d = [d,1]; end % this case shouldn't arise... + for k = 1:(length(d)-1) + fprintf(fid,'%4d x ',d(k) ); + end + fprintf(fid,'%4d\n', d(k+1) ); % bug, Feb 29 2012: change d(k) to d(k+1) + end + end + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/private/prox_stack.m b/private/prox_stack.m new file mode 100644 index 0000000..08bd4dd --- /dev/null +++ b/private/prox_stack.m @@ -0,0 +1,64 @@ +function op = prox_stack( varargin ) + +% OP = PROJ_STACK( P1, P2, P3, ..., PN ) +% "Stacks" N proximity functions P1, P2, P3, ..., PN together, to create +% a single proximity function that operates on an N-tuple. Returns a +% function handle ready to be used in + +args = varargin; +while isa( args, 'cell' ) && numel( args ) == 1, + args = args{1}; +end +if isempty( args ), + op = @proj_Rn; +elseif isa( args, 'function_handle' ), + op = args; +elseif ~isa( args, 'cell' ), + error( 'Expected one or more projector function handles.' ); +else + op = @(varargin)proj_stack_impl( args, varargin{:} ); +end + +function [ v, x ] = proj_stack_impl( proj, y, t ) + +np = numel(proj); +no = nargout > 1; +ni = nargin > 2; +y = cell( y ); +if no, + v = zeros( 1, np ); + x = cell( 1, np ); +else + v = 0; +end +if ni && numel(t) == 1, + t = t * ones(1,np); +end +switch 2 * no + ni, +case 0, % 1 input, 1 output + for k = 1 : np, + v = v + proj{k}( y{k} ); + end +case 1, % 2 inputs, 1 output + for k = 1 : np, + v = v + proj{k}( y{k}, t(k) ); + end +case 2, % 1 input, 2 outputs + for k = 1 : np, + [ v(k), x{k} ] = proj{k}( y{k} ); + end +case 3, % 2 inputs, 2 outputs + v = zeros( 1, np ); + x = cell( 1, np ); + for k = 1 : np, + [ v(k), x{k} ] = proj{k}( y{k}, t(k) ); + end +end +if no, + v = sum( v ); + x = tfocs_tuple( x ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/size_ambig.m b/private/size_ambig.m new file mode 100644 index 0000000..e4ca632 --- /dev/null +++ b/private/size_ambig.m @@ -0,0 +1,26 @@ +function ans = size_ambig( sz ) + +switch class( sz ), + case 'double', + ans = isempty( sz ); + case 'cell', + if isa( sz{1}, 'function_handle' ), + ans = false; % it's not ambiguous as long as it's a valid function... + elseif isempty( sz ), + ans = true; + else + ans = false; + for k = 1 : numel( sz ), + if size_ambig( sz{k} ), + ans = true; + break; + end + end + end + otherwise, + ans = true; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/size_compat.m b/private/size_compat.m new file mode 100644 index 0000000..53b65da --- /dev/null +++ b/private/size_compat.m @@ -0,0 +1,57 @@ +function [ a, sX ] = size_compat( sX, sY ) +a = true; +switch class( sX ), + case 'double', + if isempty( sX ) || all( sX == 0 ), + sX = sY; + elseif isempty( sY ) || all( sY == 0 ), + elseif ~isequal( sX, sY ), + + % Feb 29, 2012. Special case: + % One represents the size a x b x c, where c = 1 + % The other is a x b (since Matlab often automatically squeezes + % 3D arrays to 2D if the 3rd dimension is a singletone) +% if (length(sX) >= 3 && length(sX) == length(sY)+1 && sX(end)==1) || ... +% (length(sY) >= 3 && length(sY) == length(sX)+1 && sY(end)==1) + % do nothing + % March 2012, a better fix (also due to Graham Coleman) + if min( length(sX),length(sY) ) >= 2 + truncA = cutTrailingOnes(sX); + truncB = cutTrailingOnes(sY); + a = isequal( truncA, truncB ); + else + a = false; + end + end + case 'cell', + if ~isa( sY, 'cell' ) || numel( sX ) ~= numel( sY ) || isa( sX{1}, 'function_handle' ) && ~isequal( sX, sY ), + a = false; + elseif isa( sX{1}, 'function_handle' ), + a = isequal( sX, sY ); + else + for k = 1 : numel( sX ), + [ ta, sX{k} ] = size_compat( sX{k}, sY{k} ); + a = a && ta; + end + end + otherwise, + a = isequal( sX, sY ); +end +if ~a, + sX = []; +end + +function y = cutTrailingOnes( x ) +%cuts the final ones of a vector, and columnize + +lastNotOne = find( x(:)~=1, 1, 'last' ); + +%do not cut before 2nd position +lastNotOne = max( 2, lastNotOne ); + +y = x(1:lastNotOne); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/private/smooth_stack.m b/private/smooth_stack.m new file mode 100644 index 0000000..9c28f08 --- /dev/null +++ b/private/smooth_stack.m @@ -0,0 +1,43 @@ +function op = smooth_stack( varargin ) + +% OP = SMOOTH_STACK( S1, S2, S3, ..., SN ) +% "Stacks" N smooth functions S1, S2, S3, ..., SN together to create +% a single smooth function that operates on an N-tuple. Returns a +% function handle ready to be used in TFOCS. + +args = varargin; +while isa( args, 'cell' ) && numel( args ) == 1, + args = args{1}; +end +if isempty( args ), + op = @smooth_zero; +elseif isa( args, 'function_handle' ), + op = args; +elseif ~isa( args, 'cell' ), + error( 'Expected one or more smooth function handles.' ); +else + op = @(varargin)smooth_stack_impl( args, varargin{:} ); +end + +function [ f, g ] = smooth_stack_impl( smooth, x ) + +np = numel(smooth); +x = cell( x ); +if nargout > 1, + f = zeros( 1, np ); + g = cell( 1, np ); + for k = 1 : np, + [ f(k), g{k} ] = smooth{k}( x{k} ); + end + f = sum( f ); + g = tfocs_tuple( g ); +else + f = 0; + for k = 1 : np, + f = f + smooth{k}( x{k} ); + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/solver_apply.m b/private/solver_apply.m new file mode 100644 index 0000000..6fe5254 --- /dev/null +++ b/private/solver_apply.m @@ -0,0 +1,13 @@ +function varargout = solver_apply( ndxs, func, varargin ) +global tfocs_count___ + +%SOLVER_APPLY Internal TFOCS routine. +% A wrapper function used to facilitate the counting of function calls, and +% to standardize the number of output arguments for the algorithms. + +[ varargout{1:max(nargout,1)} ] = func( varargin{:} ); +tfocs_count___(ndxs) = tfocs_count___(ndxs) + 1; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/tfocs_backtrack.m b/private/tfocs_backtrack.m new file mode 100644 index 0000000..f232288 --- /dev/null +++ b/private/tfocs_backtrack.m @@ -0,0 +1,37 @@ +% TFOCS_BACKTRACK +% Backtracking helper script. + +% Quick exit for no backtracking +if beta >= 1, break; end % SRB changing == to >= + +% Quick exit if no progress made +xy = x - y; +xy_sq = tfocs_normsq( xy ); +if xy_sq == 0, localL = Inf; break; end + +% Compute Lipschitz estimate +if backtrack_simple, + if isinf( f_x ), + f_x = apply_smooth( A_x ); + end + q_x = f_y + tfocs_dot( xy, g_y ) + 0.5 * L * xy_sq; + localL = L + 2 * max( f_x - q_x, 0 ) / xy_sq; + backtrack_simple = abs( f_y - f_x ) >= backtrack_tol * max( abs( f_x ), abs( f_y ) ); +else + if isempty( g_Ax ), + [ f_x, g_Ax ] = apply_smooth( A_x ); + end + localL = 2 * tfocs_dot( A_x - A_y, g_Ax - g_Ay ) / xy_sq; +end + +% Exit if Lipschitz criterion satisfied, or if we hit Lexact +backtrack_steps = backtrack_steps + 1; +if localL <= L || L >= Lexact, break; end +if ~isinf( localL ), + L = min( Lexact, localL ); +elseif isinf( localL ), localL = L; end +L = min( Lexact, max( localL, L / beta ) ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/tfocs_cleanup.m b/private/tfocs_cleanup.m new file mode 100644 index 0000000..3f35689 --- /dev/null +++ b/private/tfocs_cleanup.m @@ -0,0 +1,62 @@ +%SOLVER_CLEANUP TFOCS helper script +% Performs the final set of operations for our templated solvers: performs +% final calculations, prints final outputs, cleans up unused data. + +if saddle, + if isempty(g_Ax), + [ f_x, g_Ax ] = apply_smooth(A_x); + end + out.dual = get_dual( g_Ax ); + % Oct 13: make it compatible: + if isa( out.dual, 'tfocs_tuple') + out.dual = cell( out.dual ); + end +elseif isinf(f_x), + f_x = apply_smooth(A_x); +end +if isinf( C_x ), + C_x = apply_projector( x ); +end +if fid && printEvery, + fprintf( fid, 'Finished: %s\n', status ); +end +out.niter = n_iter; +out.status = status; +d.niter = 'Number of iterations'; +if saveHist, + out.f(n_iter) = maxmin * ( f_x + C_x ); + out.f(n_iter+1:end) = []; + out.normGrad(n_iter+1:end) = []; + out.stepsize(n_iter+1:end) = []; + out.theta(n_iter+1:end,:) = []; + if countOps, + out.counts(n_iter+1:end,:) = []; + end + if ~isempty(errFcn), + out.err(n_iter+1:end,:) = []; + end + d.f = 'Objective function history'; + d.normDecr = 'Decrement norm'; + d.stepsize = 'Stepsize'; + d.theta = 'Acceleration parameter history'; + if countOps, + d.counts = strvcat(... + 'k x 4 arry, with columns [F,G,A,P] where',... + 'F: Number of function evaluations of the smooth function',... + 'G: Number of gradient evaluations of the smooth function',... + 'A: Number of calls to the linear operator and its transpose',... + 'N: Number of calls to the nonsmooth function (w/o projection)',... + 'P: Number of calls to the projection operator' ); + end + if ~isempty(errFcn) + d.err = 'Error, determined by evaluating the user-supplied error function'; + end +end +out.description = d; +if countOps, + clear global tfocs_count___ +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/tfocs_initialize.m b/private/tfocs_initialize.m new file mode 100644 index 0000000..4b15eb9 --- /dev/null +++ b/private/tfocs_initialize.m @@ -0,0 +1,598 @@ +%SOLVER_INITIALIZE TFOCS helper script +% Performs the initializations common to all of the first-order solvers + +% Process the options structure and string/value pairs. First, we replace the +% default values with any values specified by the user in the 'opts' structure. +% We ignore any fields in the 'opts' structure that do not match ours, so that +% users can re-use the opts structure for other purposes. + +odef = struct( ... + 'maxIts', Inf ,... + 'maxCounts', Inf ,... + 'countOps', false ,... % adjusted if maxCounts < Inf + 'saveHist', true ,... + 'adjoint', false ,... + 'saddle', false ,... + 'tol', 1e-8 ,... + 'errFcn', {{}} ,... + 'stopFcn', {{}} ,... + 'printEvery', 100 ,... + 'maxmin', 1 ,... + 'beta', 0.5 ,... + 'alpha', 0.9 ,... % See below for special CG stuff + 'L0', 1 ,... % See below + 'Lexact', Inf ,... + 'mu', 0 ,... + 'fid', 1 ,... + 'stopCrit', 1 ,... % the TFOCS paper used stopCrit = 2 + 'alg', 'AT' ,... + 'restart', Inf ,... + 'printStopCrit', false,... + 'cntr_reset', 50 ,... % how often to explicitly recompute A*x and A*y (set to Inf if you want ) + 'cg_restart', Inf ,... % for CG only + 'cg_type', 'pr' ,... % for CG only + 'debug', false .... + ); + +% Calling the solver with a no arguments returns the default options structure +if nargin < 1 || ( nargin ==1 && isstruct( smoothF ) ) + opts = odef; + % remove the "CG" options for now, since they are undocumented + opts = rmfield(opts,'cg_restart'); + opts = rmfield(opts,'cg_type'); + % if any default options are passed in (e.g. by tfocs_SCD), add those now: + if ( nargin ==1 && isstruct( smoothF ) ) + for f = fieldnames( smoothF )' + opts.( f{1} ) = smoothF.( f{1} ); + end + end + + out = []; + x = opts; + if nargout == 0, + disp('====== TFOCS default options ======='); +% disp('Format is fieldname: { [default] ''Usage type. +% Description''}'); + + % add some helpful description + desc = []; + desc.alg = 'Algorithm, e.g. GRA for gradient descent. Values: AT, GRA, LLM, N07, N83, TS'; + desc.maxIts = 'Basic. Maximum number of allowed iterations'; + desc.saveHist = 'Basic. Record history of all iterations'; + desc.restart = 'Basic. Restart parameter. Make negative for "no-regress" feature'; + desc.tol = 'Basic. Tolerance for stopping criteria'; + desc.L0 = 'Basic. Initial estimate of Lipschitz constant'; + desc.Lexact = 'Basic. Known bound of Lipschitz constant'; + desc.printEvery = 'Basic. How often to print info to the terminal; set to 0 or Inf to suppress output'; + desc.maxmin = 'Basic. +1 for convex minimization, -1 for concave maximization'; + + desc.errFcn = 'Medium. User-specified error function. See user guide'; + desc.beta = 'Medium. Backtracking parameter, in (0,1). No line search if >= 1'; + desc.alpha = 'Medium. Line search increase parameter, in (0,1)'; + + + desc.maxCounts = 'Advanced. Vector that fine-tunes various types of iteration limits; same form as countOps'; + desc.countOps = 'Advanced. Record number of linear multiplies, etc.; form [fcn, grad, linear, nonsmth, proj]'; + desc.mu = 'Advanced. Strong convexity parameter.'; + desc.fid = 'Advanced. File id, e.g. via fopen. All output is sent to this file'; + desc.stopFcn = 'Advanced. User-supplied stopping criteria. See user guide'; + desc.stopCrit = 'Advanced. Controls which stopping criteria to use; 1,2, 3 or 4.'; + desc.printStopCrit = 'Advanced. Controls whether to display the value used in the stopping criteria'; + desc.cntr_reset = 'Advanced. Controls how often to reset some numerical computatios to avoid roundoff'; + desc.debug = 'Advanced. Turns on more useful error messages'; + + desc.adjoint = 'Internal.'; + desc.saddle = 'Internal. Used by TFOCS_SCD'; + + + + disp( opts ); + disp('====== Description of TFOCS options ======='); + disp( desc ); + end + return % from this script only; the calling function does not return +end + +% [smoothF, affineF, projectorF, x0, opts ] = deal(varargin{:}); +% Check for incorrect types +F_types = {'function_handle','cell','double','single'}; +assert( ismember(class(smoothF),F_types),'smoothF is of wrong type' ); +assert( ismember(class(affineF),F_types),'affineF is of wrong type' ); +assert( ismember(class(projectorF),F_types),'projectorF 3 is of wrong type' ); +if nargin >= 4 + x0_types = {'cell','double','single','tfocs_tuple'}; + assert( ismember(class(x0),x0_types),'x0 is of wrong type' ); +end +if nargin >= 5 && ~isempty(opts) + assert( ismember(class(opts),{'struct'}),'opts is of wrong type' ); +end + +% Some parameters defaults depend on whether the user supplies other +% options. These will be updated later +L0_default = odef.L0; +odef.L0 = Inf; + +alpha_default = odef.alpha; % typically 0.9 +alpha_default_CG = 1; +odef.alpha = Inf; + + + +% Process the options structure, merging it with the default options +% (merge "opts" into "odef") +def_fields = fieldnames(odef)'; % default options +if nargin > 4 && ~isempty(opts), + use_fields = zeros(size(def_fields)); + opt_fields = fieldnames(opts)'; % user-supplied options + for k = opt_fields, + k = k{1}; + ndx = find(strcmpi(def_fields,k)); + if ~isempty(ndx) + if ~isempty(opts.(k)) && ~use_fields(ndx), + odef.(def_fields{ndx}) = opts.(k); + end + use_fields(ndx) = use_fields(ndx) + 1; + else + % Warn if the field is not found in our options structure + warnState = warning('query','backtrace'); + warning off backtrace + warning('TFOCS:OptionsStructure',' Found extraneous field "%s" in options structure', k ); + warning(warnState); + end + end + % Warn if fields appear twice due to capitalization; e.g., maxIts/maxits + if any(use_fields>1), + warnState = warning('query','backtrace'); + warning off backtrace + warning('TFOCS:OptionsStructure',' Some fieldnames of the options structure are identical up to capitalization: unpredictable behavior'); + warning(warnState); + end +end + +% Remove unnecessary options +if ~strcmpi( odef.alg, 'cg' ) + odef = rmfield( odef, 'cg_restart' ); + odef = rmfield( odef, 'cg_type' ); + % update our list of fieldnames + def_fields = fieldnames(odef)'; +end + +% If opts.alpha wasn't supplied, use a default value: +if isinf(odef.alpha) + if strcmpi( odef.alg, 'cg' ) + odef.alpha = alpha_default_CG; + else + odef.alpha = alpha_default; + end +end + +% If opts.printEvery is Inf, set it to 0 +if isinf(odef.printEvery) + odef.printEvery = 0; +end + +% The default value of L0 is set to Lexact, if it is supplied +if isinf(odef.L0), + if isinf(odef.Lexact), + if odef.beta >= 1, + error( 'For a fixed step size, L0 or Lexact must be supplied.' ); + end + odef.L0 = L0_default; + else + odef.L0 = odef.Lexact; + end +end +% If maxCounts is set, set countOps to true +if any(odef.maxCounts 1, + error( 'Multidimensional arrays are not permitted.' ); + end + if numel(affineF) == 1, + identity_linop = affineF == 1; + affineF = { linop_scale( affineF ) }; + else + identity_linop = false; + affineF = { linop_matrix( affineF ) }; + end +elseif isa( affineF, 'function_handle' ), + affineF = { affineF }; +elseif ~isa( affineF, 'cell' ), + error( 'Invalid affine operator specification.' ); +end + +% If adjoint mode is specified, temporarily transpose the affineF cell +% array so that the rows and columns match the number of smooth functions +% and projectors, respectively. Then verify size compatibility. +if adjoint, affineF = affineF'; end +[ m_aff, n_aff ] = size( affineF ); +% if all( m_aff ~= n_smooth + [0,1] ) || n_proj && all( n_aff ~= n_proj + [0,1] ), +% error( 'The affine operation matrix has incompatible dimensions.' ); +% May 16, 2011: making error messages more informative +if all( m_aff ~= n_smooth + [0,1] ) + if fid + fprintf(fid,'Detected error: inputs are of wrong size\n'); + fprintf(fid,' Found %d smooth functions, so expect that many primal variables\n',... + n_smooth ); + fprintf(fid,' So the affine operator should have %d (or %d if there is an offset) entries\n',... + n_smooth, n_smooth+1 ); + fprintf(fid,' but instead found %d affine operators\n', m_aff ); + fprintf(fid,' Perhaps you need the transpose of the affine operator?\n'); + end + error( 'The affine operation matrix has incompatible dimensions.' ); +elseif n_proj && all( n_aff ~= n_proj + [0,1] ), + if fid + fprintf(fid,'Detected error: inputs are of wrong size\n'); + fprintf(fid,' Found %d nonsmooth functions, so expect %d or %d sets of affine operators\n',... + n_proj, n_proj, n_proj + 1 ); + fprintf(fid,' but instead found %d affine operators\n', n_aff ); + end + error( 'The affine operation matrix has incompatible dimensions.' ); +elseif n_proj == 0, + n_proj = max( 1, m_aff - 1 ); +end +inp_dims = cell( 1, n_proj ); +otp_dims = cell( 1, n_smooth ); + +% If an additional affine portion of the objective has been specified, +% create an additional smooth function to contain it. +if m_aff == n_smooth + 1, + offset = true; % note: saddle_ndxs is 1:n_smooth + otp_dims{end+1} = [1,1]; + smoothF{end+1} = smooth_linear( maxmin ); + n_smooth = n_smooth + 1; + for k = 1 : n_proj, + offX = affineF{end,k}; + if isempty(offX), + affineF{end,k} = 0; + elseif isa(offX,'function_handle'), + if adjoint, pos = 'row'; else pos = 'column'; end + error( 'The elements in the last %s must be constants.', pos ); + elseif isnumeric(offX) && numel(offX) == 1 && offX == 0, + affineF{end,k} = 0; + elseif nnz(offX), + affineF{end,k} = linop_dot( affineF{end,k}, adjoint ); + % add a case if ~nnz(offX)... Jan 2012 + elseif ~nnz(offX) + affineF{end,k} = 0; + end + end +else + offset = false; +end + +% If an affine offset has been specified, integrate those offsets into +% each smooth function, then remove that portion of the array. +if n_aff == n_proj + 1, + for k = 1 : n_smooth, + offX = affineF{k,end}; + if isempty(offX), + continue; + elseif isa(offX,'function_handle'), + if adjoint, pos = 'column'; else pos = 'row'; end + error( 'The elements in the last %s must be constant matrices.', pos ); + elseif isnumeric(offX) && numel(offX) == 1 && offX == 0, + continue; + else + otp_dims{k} = size(offX); + if nnz(offX), + smoothF{k} = @(x)smoothF{k}( x + offX ); + end + end + end + n_aff = n_aff - 1; + affineF(:,end) = []; +end + + +% -- Todo: +% If opts.debug = true, then before calling linop_stack, +% we should print a message showing the sizes of everything +% (this is useful when the linear portion is entered as "1" or "[]" +% and we have automatically determined the size ). + +% Transpose back, if necessary; then check dimensions +if adjoint, + affineF = affineF'; + [ linearF, otp_dims, inp_dims ] = linop_stack( affineF, otp_dims, inp_dims, debug ); + linearF = linop_adjoint( linearF ); +else + [ linearF, inp_dims, otp_dims ] = linop_stack( affineF, [], [], debug ); +end +identity_linop = isequal( linearF, @linop_identity ); % doesn't always catch identities +square_linop = identity_linop || isequal( inp_dims, otp_dims ); +adj_arr = [0,2,1]; +if countOps, + apply_linear = @(x,mode)solver_apply( 3, linearF, x, mode ); +else + apply_linear = linearF; +end + +% +% Smooth function, pass 2: integrate scale, counting +% + +smoothF = smooth_stack( smoothF ); +if maxmin < 0, + smoothF = tfunc_scale( smoothF, -1 ); +end +if countOps, + apply_smooth = @(x)solver_apply( 1:(1+(nargout>1)), smoothF, x ); +else + apply_smooth = smoothF; +end + +% +% Projector, pass 2: supply default, stack it up, etc. +% + +if isempty( projectorF ), + n_proj = 0; + projectorF = proj_Rn; +else + projectorF = prox_stack( projectorF ); +end +if countOps, + apply_projector = @(varargin)solver_apply( 4:(4+(nargout>1)), projectorF, varargin{:} ); +else + apply_projector = projectorF; +end + +% +% Initialize the op counts +% + +if countOps, + global tfocs_count___ + tfocs_count___ = [0,0,0,0,0]; + maxCounts = maxCounts(:)'; +end + +% +% Construct the common initial values +% + +L = L0; +theta = Inf; +f_v_old = Inf; +x = []; A_x = []; f_x = Inf; C_x = Inf; g_x = []; g_Ax = []; +restart_iter = 0; +warning_lipschitz = 0; +backtrack_simple = true; +backtrack_tol = 1e-10; +backtrack_steps = 0; + +% +% Try to determine the size of the input, and construct the initial point. +% + +% Attempt 1: From x0 itself +zero_x0 = true; +if ~isempty( x0 ), + if isa( x0, 'tfocs_tuple' ) + x0 = cell(x0); + end + if isa( x0, 'cell' ), + n_x0 = numel( x0 ); + if n_x0 == 1, + x0 = x0{1}; + else + x0 = tfocs_tuple( x0 ); + end + else + n_x0 = 1; + end + if n_proj && n_proj ~= n_x0, + error( 'Size conflict detected between the projector and x0.' ); + end + zero_x0 = ~nnz(x0); +% Attempt 2: From the linear operator dimensions +elseif ~size_ambig( inp_dims ), + x0 = tfocs_zeros( inp_dims ); +elseif ~size_ambig( otp_dims ), + A_x = tfocs_zeros( otp_dims ); + x0 = apply_linear( A_x, 2 ); +end +if isempty( x0 ), + error( 'Could not determine the dimensions of the problem. Please supply an explicit value for x0.' ); +end +x = x0; +if isinf( C_x ), + C_x = apply_projector( x ); + if isinf( C_x ), + zero_x0 = false; + [ C_x, x ] = apply_projector( x, 1 ); + end +end +if isempty( A_x ), + if identity_linop || zero_x0 && square_linop, + A_x = x; + elseif ~zero_x0 || size_ambig( otp_dims ), % Jan 2012: todo: give size_ambig the 'offset' information + A_x = apply_linear( x, 1 ); % celldisp( size(A_x) ) + else + A_x = tfocs_zeros( otp_dims ); + end +end + +% New, Jan 2012 +if debug + if ~adjoint, str1 = 'primal'; str2 = 'dual'; + else, str1 = 'dual'; str2 = 'primal'; + end + fprintf(fid,'------- DEBUG INFO -----------\n'); + offsetPossible = offset && ~adjoint; + fprintf(fid,'Size of %s variable, via method 1\n', str1); + print_cell_size( size(x0), fid, offsetPossible); + fprintf(fid,'Size of %s variable, via method 2\n', str1); + print_cell_size( inp_dims, fid, offsetPossible); + + offsetPossible = offset && adjoint; + fprintf(fid,'Size of %s variable, via method 1\n', str2); + print_cell_size( size(A_x), fid, offsetPossible); + fprintf(fid,'Size of %s variable, via method 2\n', str2); + print_cell_size( otp_dims, fid, offsetPossible); + fprintf(fid,'------------------------------\n'); +end +% Added Dec 2012, check for bad sizes that are not integers +if ~isequal( otp_dims, tfocs_round(otp_dims) ) + error('Output dimensions must be integer valued'); +end +if ~isequal( inp_dims, tfocs_round(inp_dims) ) + error('Input dimensions must be integer valued'); +end + +% Final size check +[ isOK1, inp_dims ] = size_compat( size(x0), inp_dims ); +[ isOK2, otp_dims ] = size_compat( size(A_x), otp_dims ); +if ~isOK1 || ~isOK2, + if debug + if ~isOK1 + fprintf(fid,'Debug message: size of %s variables did not line up\n',str1); + end + if ~isOK2 + fprintf(fid,'Debug message: size of %s variables did not line up\n',str2); + end + end + error( 'Could not determine the dimensions of the problem. Please supply an explicit value for x0.' ); +end +[ f_x, g_Ax ] = apply_smooth( A_x ); +if isinf( f_x ), + error( 'The initial point lies outside of the domain of the smooth function.' ); +end +% Adding Feb 2011: +if isa(g_Ax,'tfocs_tuple') + get_dual = @(g_Ax) get( g_Ax, saddle_ndxs ); +else + get_dual = @(g_Ax) g_Ax; +end + +% Theta advancement function +% if mu > 0 && Lexact > mu +if mu > 0 && ~isinf(Lexact) && Lexact > mu, % fixed Dec 2011 + theta_scale = sqrt(mu / Lexact); + theta_scale = ( 1 - theta_scale ) / ( 1 + theta_scale ); + advance_theta = @(theta_old,L,L_old) min(1,theta_old*theta_scale); +else + advance_theta = @(theta_old,L,L_old) 2/(1+sqrt(1+4*(L/L_old)/theta_old.^2)); +end + +% Preallocate the arrays for the output structure +out.alg = alg; +out.algorithm = algorithm; +if ~isempty(errFcn) && ~iscell(errFcn), + errFcn = { errFcn }; +end +if ~isempty(stopFcn) && ~iscell(stopFcn), + stopFcn = { stopFcn }; +end +errs = zeros(1,length(errFcn)); +if nargout == 1, + saveHist = false; +end +if saveHist, + [ out.f, out.normGrad, out.stepsize, out.theta ] = deal( zeros(0,1) ); + if countOps, + out.counts = zeros(0,length(tfocs_count___)); + end + if ~isempty(errFcn), + out.err = zeros(0,length(errs)); + end +end +if saddle, + out.dual = []; +end +n_iter = 0; +status = ''; + +% Initialize the iterate values +y = x; z = x; +A_y = A_x; A_z = A_x; +C_y = C_x; C_z = C_x; +f_y = f_x; f_z = f_x; +g_y = g_x; g_z = g_x; +g_Ay = g_Ax; g_Az = g_Ax; +norm_x = sqrt( tfocs_normsq( x ) ); + +% for recomputing linear operators +cntr_Ay = 0; +cntr_Ax = 0; + +% Special setup for constant step sizes +if beta >= 1, + beta = 1; + alpha = 1; +end + +% Print the opening text +if fid && printEvery, + fprintf(fid,'%s\n',algorithm); + fprintf(fid,'Iter Objective |dx|/|x| step'); + if countOps, fprintf( fid, ' F G A N P' ); end + if ~isempty(errFcn) + nBlanks = max( [0, length(errFcn)*9 - 9] ); + fprintf( fid, ' errors%s',repmat(' ',nBlanks,1) ); + end + if printStopCrit, fprintf( fid, ' stopping criteria' ); end + fprintf( fid, '\n' ); + fprintf(fid,'----+----------------------------------' ); + if countOps, fprintf( fid, '+-------------------------------' ); end +% if ~isempty(errFcn), fprintf( fid, '+-------------------' ); end + if ~isempty(errFcn) + fprintf( fid, '+%s', repmat('-',1+length(errFcn)*9,1) ); + end + + + if printStopCrit, fprintf( fid, '+%s', repmat('-',19,1) ); end + + + fprintf(fid,'\n'); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/private/tfocs_iterate.m b/private/tfocs_iterate.m new file mode 100644 index 0000000..0ac3bab --- /dev/null +++ b/private/tfocs_iterate.m @@ -0,0 +1,284 @@ +%SOLVER_ITERATE TFOCS helper script +% Performs the iterate processing common to all of the first-order solvers +% +% Major inputs: x, x_old, xy , A_x, A_y, g_Ax, g_Ay +% (does not need g_x or g_y really) + +% Test for positive stopping criteria +n_iter = n_iter + 1; +norm_x = sqrt( tfocs_normsq( x ) ); +norm_dx = sqrt( tfocs_normsq( x - x_old ) ); +% We also handle legacy stopping criteria used in the paper: +if stopCrit == 2 && beta >= 1 + % xy_sq not already computed + xy = x - y; + xy_sq = tfocs_normsq( xy ); +end +if isnan( f_y ), + status = 'NaN found -- aborting'; +elseif (stopCrit == 1) && norm_dx == 0 + if n_iter > 1 + status = 'Step size tolerance reached (||dx||=0)'; + end +elseif (stopCrit == 1) && norm_dx < tol * max( norm_x, 1 ), + status = 'Step size tolerance reached'; +elseif (stopCrit == 2) && L*sqrt(xy_sq) < tol, + status = 'Step size tolerance reached'; +elseif n_iter == maxIts, + status = 'Iteration limit reached'; +elseif countOps && any( tfocs_count___ >= maxCounts ), + status = 'Function/operator count limit reached'; +elseif backtrack_steps > 0 && xy_sq == 0, + status = 'Unexpectedly small stepsize'; +end + +% for stopCrit 3, we need the new and old dual points +if stopCrit == 3 || stopCrit == 4 + if ~saddle, error('stopCrit = {3,4} requires a saddle point problem'); end + if exist('cur_dual','var') + old_dual = cur_dual; + else + old_dual = []; + end +end + +% +% For stopping criteria or algorithm control, we assume that the user +% needs the objective function value, but does not wish to do any more +% computation than necessary. So we will use the function value for y +% instead of x if that is cheaper to obtain. So here we determine what +% the additional computational costs will be, and choose the path that +% minimizes them, favoring x in the case of a tie. +% + +% if isempty(status) && ( ~isempty(stopFcn) || restart < 0 || stopCrit == 3 || stopCrit ==4 ), +if (isempty(status) || ~isempty(findstr(status,'limit')) ) ... + && ( ~isempty(stopFcn) || restart < 0 || stopCrit == 3 || stopCrit ==4 ), + comp_x = [ isinf(f_x), saddle*~isempty(stopFcn)*isempty(g_Ax), isinf(C_x) ]; + comp_y = [ isinf(f_y), saddle*~isempty(stopFcn)*isempty(g_Ay), isinf(C_y) ]; + if sum(comp_x) <= sum(comp_y), + if comp_x(2), [f_x,g_Ax] = apply_smooth(A_x); + elseif comp_x(1), f_x = apply_smooth(A_x); end + if comp_x(3), C_x = apply_projector(x); end + cur_pri = x; cur_dual = g_Ax; + f_v = maxmin*(f_x+C_x); + else + if comp_y(2), [f_y,g_Ay] = apply_smooth(A_y); + elseif comp_y(1), f_y = apply_smooth(A_y); end + if comp_y(3), C_y = apply_projector(y); end + cur_pri = y; cur_dual = g_Ay; + f_v = maxmin*(f_y+C_y); + end + for err_j = 1 : numel(stopFcn), + if saddle, + stop = stopFcn{err_j}(f_v,cur_pri, get_dual(cur_dual) ); + else + stop = stopFcn{err_j}(f_v,cur_pri); + end + if stop + status = sprintf('Reached user''s supplied stopping criteria no. %d',err_j); + end + end +end + +% Now we can apply stopCrit 3 if it has been requested: +if (stopCrit == 3 || stopCrit == 4 ) + if ~isempty( old_dual ) && ~isempty( cur_dual ) + d_dual = sqrt(tfocs_normsq( old_dual - cur_dual )); + if isnumeric( cur_dual ), + norm_cur = tfocs_normsq( cur_dual ); + norm_old = tfocs_normsq( old_dual ); + else + norm_cur = 0; + norm_old = 0; + for j = 1:max( [1, numel(cur_dual)-1] ) + norm_cur = norm_cur + tfocs_normsq( cur_dual{j} ); + norm_old = norm_old + tfocs_normsq( old_dual{j} ); + end + end + norm_cur = sqrt(norm_cur); + norm_old = sqrt(norm_old); + else + d_dual = Inf; + norm_cur = 0; + norm_old = 0; + end + + % Note: it is common for the duals to be stuck at zero for quite a few + % iterations at the beginning. In stopCrit = 4 mode, we will not + % terminate if both the current and old dual are zero. + + if stopCrit == 4 + % for "4", we look at relative change, not absolute change + if norm_cur > 10*eps && norm_old > 10*eps + d_dual = d_dual / norm_cur; + else + % The dual vectors are zero, so do not terminate. + % This is equivalent to defining 0/0 = Inf; + d_dual = Inf; + end + end + if d_dual < tol + status = 'Step size tolerance reached'; + end +end + + + +% +% Data collection. Since this is used only for algorithm analysis and not +% for algorithm control, we are free to make additional computations here +% without adding them to our total algorithm cost. We prefer to track the +% x sequence in this case, since it is what we will ultimately choose as +% the solution at the end of the algorithm. +% + +will_print = fid && printEvery && ( ~isempty( status ) || ~mod( n_iter, printEvery ) ); +if saveHist || will_print, + f_x_save = f_x; + g_Ax_save = g_Ax; + if ~isempty(errFcn) && saddle, + if isempty(g_Ax), + [ f_x, g_Ax ] = smoothF( A_x ); + end + out.dual = get_dual( g_Ax ); + end + if isinf(f_x), + f_x = smoothF( A_x ); + end + if isinf( C_x ), + C_x = projectorF( x ); + end + f_w = maxmin * ( f_x + C_x ); + if ~isempty(errFcn) && iscell(errFcn) + for err_j = 1 : numel(errFcn), + if saddle, + errs(err_j) = errFcn{err_j}(f_w,x,out.dual); + else + errs(err_j) = errFcn{err_j}(f_w,x); + end + end + end + f_x = f_x_save; + g_Ax = g_Ax_save; +end + +% Register a warning if the step size suggests a Lipschitz violation +if isempty(status) && ( beta < 1 && backtrack_simple && localL > Lexact ), + warning_lipschitz = true; +end + +% Print status +if will_print, + if warning_lipschitz, + warning_lipschitz = false; + bchar = 'L'; + elseif backtrack_simple, + bchar = ' '; + else + bchar = '*'; + end + fprintf( fid, '%-4d| %+12.5e %8.2e %8.2e%c', ... + n_iter, f_w, norm_dx / max( norm_x, 1 ), 1 / L, bchar ); + if countOps, + fprintf( fid, '|' ); + fprintf( fid, ' %5d', tfocs_count___ ); + end + if ~isempty(errFcn), + if countOps, + fprintf( fid, ' ' ); + end + fprintf( fid, '|' ); + fprintf( fid, ' %8.2e', errs ); + end + + + if printStopCrit + % Display the number used to determine stopping + + switch stopCrit + case 1 + if exist('norm_dx','var') && exist('norm_x','var') + stopResid = norm_dx/max( norm_x,1); + else + stopResid = Inf; + end + case 2 + stopResid = L*sqrt(xy_sq); + case {3,4} + if exist('d_dual','var') + stopResid = d_dual; + end + case Inf + % do nothing + stopResid = 0; + otherwise + error('Bad stopCrit value'); + end + + if ~isempty(errFcn) || countOps + fprintf( fid, ' ' ); + end + fprintf( fid, '|' ); + fprintf( fid, ' %8.2e', stopResid ); + end + + fprintf( fid, '\n'); +end + +% Save history, extending arrays if needed +if saveHist, + if length(out.f) < n_iter && isempty(status), + csize = min(maxIts,length(out.f)+1000); + out.f(end+1:csize,1) = 0; + out.theta(end+1:csize,1) = 0; + out.stepsize(end+1:csize,1) = 0; + out.normGrad(end+1:csize,1) = 0; + if countOps, + out.counts(end+1:csize,:) = 0; + end + if ~isempty(errs), + out.err(end+1:csize,:) = 0; + end + end + out.f(n_iter) = f_w; + out.theta(n_iter) = theta; + out.stepsize(n_iter) = 1 / L; + out.normGrad(n_iter) = norm_dx; + if countOps, + out.counts(n_iter,:) = tfocs_count___; + end + if ~isempty(errFcn), + out.err(n_iter,:) = errs; + end +end + +% Exit if positive stopping criteria met +if ~isempty( status ), + break; +end + +% Restart acceleration if necessary +backtrack_steps = 0; +% "No regress" feature: test was (maxmin*f_v > f_v_old) +% This worked for minimization, but not for maximization! Dec 15 2010. +% Fixed it. +% two changes: (1) test is now ( maxmin*f_v > maxmin*f_v_old) +% (2) to reset f_v_old, set to maxmin*Inf, not just +Inf +if n_iter - restart_iter == abs(round(restart)) || (restart < 0 && maxmin*f_v > maxmin*f_v_old), + restart_iter = n_iter; + backtrack_simple = true; + theta = Inf; + y = x; A_y = A_x; f_y = f_x; g_Ay = g_Ax; g_y = g_x; C_y = C_x; + z = x; A_z = A_x; f_z = f_x; g_Az = g_Ax; g_z = g_x; C_z = C_x; + f_v_old = maxmin*Inf; % important! +% continue; +elseif restart < 0, + f_v_old = f_v; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + + diff --git a/private/tfocs_prox.m b/private/tfocs_prox.m new file mode 100644 index 0000000..b17d0e1 --- /dev/null +++ b/private/tfocs_prox.m @@ -0,0 +1,113 @@ +function op = tfocs_prox( f, prox_f, VECTOR_SCALAR ) +% OP = TFOCS_PROX( F, PROX_F ) +% combines F and PROX_F into the appropriate TFOCS-compatible object. +% +% F is any function (with known proximity operator), +% and PROX_F is its proximity operator, defined as: +% +% PROX_F( Y, t ) = argmin_X F(X) + 1/(2*t)*|| X - Y ||^2 +% +% To use this, please see the file PROX_L1 as an example +% +% The basic layout of a file like PROX_L1 is as follows: +% ( for the function F(X) = q*||X||_1 ) +% +% function op = prox_l1(q) +% op = tfocs_prox( @f, @prox_f ) +% +% function v = f(x) +% ... this function calculates the function f ... +% end +% function v = prox_f(y,t) +% ... this function calculates the prox-function to f ... +% end +% end +% +% Note: in the above template, the "end" statements are very important. +% +% OP = TFOCS_PROX( F, PROX_F, 'vector') +% will signal the routine that it is OK to allow "vector" stepsizes +% (this corresponds to solving +% PROX_F( Y, T ) = argmin_X F(X) + 1/2( X-Y )'*diag(1./T)*( X-Y ) +% where T is a vector of the same size as X ). +% ... = TFOCS_PROX( F, PROX_F, 'scalar' ) +% will throw an error if a vector stepsize is attempted. This is the default. +% +% Also, users may wish to test their smooth function +% with the script TEST_NONSMOOTH +% +% See also prox_l1, test_nonsmooth + +if nargin < 3 || isempty(VECTOR_SCALAR) + VECTOR_SCALAR = 'scalar'; +end + +if strcmpi(VECTOR_SCALAR,'scalar') +% op = @fcn_impl; % comment this out and use octave-compatible version below: +% op = @(x,t)fcn_impl(f,prox_f,x,t); % this doesn't work: requires 2 inputs always + op = @(varargin)fcn_impl(f,prox_f,varargin{:}); +elseif strcmpi(VECTOR_SCALAR,'vector') +% op = @fcn_impl_vector; % comment this out and use octave-compatible version below: +% op = @(x,t)fcn_impl_vector(f,prox_f,x,t); % this doesn't work: requires 2 inputs always + op = @(varargin)fcn_impl_vector(f,prox_f,varargin{:}); +else + error('bad option for VECTOR_SCALAR parameter'); +end + +function [ v, x ] = fcn_impl(f,prox_f,x, t ) + if nargin < 3, + error( 'Not enough arguments.' ); + end + if nargin == 4, + if numel(t) ~= 1, error('The stepsize must be a scalar'); end + x = prox_f(x,t); + elseif nargout == 2, + error( 'This function is not differentiable.' ); + end + v = f(x); +end + +function [ v, x ] = fcn_impl_vector(f,prox_f,x, t ) + if nargin < 3, + error( 'Not enough arguments.' ); + end + if nargin == 4, + x = prox_f(x,t); + elseif nargout == 2, + error( 'This function is not differentiable.' ); + end + v = f(x); +end + +% - Octave incompatible version: - +% function [ v, x ] = fcn_impl(x, t ) +% if nargin < 1, +% error( 'Not enough arguments.' ); +% end +% if nargin == 2, +% if numel(t) ~= 1, error('The stepsize must be a scalar'); end +% x = prox_f(x,t); +% elseif nargout == 2, +% error( 'This function is not differentiable.' ); +% end +% v = f(x); +% end +% +% function [ v, x ] = fcn_impl_vector(x, t ) +% if nargin < 1, +% error( 'Not enough arguments.' ); +% end +% if nargin == 2, +% x = prox_f(x,t); +% elseif nargout == 2, +% error( 'This function is not differentiable.' ); +% end +% v = f(x); +% end + + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/private/tfocs_round.m b/private/tfocs_round.m new file mode 100644 index 0000000..1617426 --- /dev/null +++ b/private/tfocs_round.m @@ -0,0 +1,10 @@ +function y = tfocs_round(x) +if iscell( x ), + y = cellfun( @tfocs_round, x, 'UniformOutput', false ); +else + y = round( x ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/private/tfocs_smooth.m b/private/tfocs_smooth.m new file mode 100644 index 0000000..1b17a47 --- /dev/null +++ b/private/tfocs_smooth.m @@ -0,0 +1,45 @@ +function op = tfocs_smooth( fcn ) +% OP = TFOCS_SMOOTH(FCN) +% is a wrapper designed to facilitate users writing their own +% smooth functions. +% +% To use this, please see the file SMOOTH_HUBER as an example +% +% The basic layout of a file like SMOOTH_HUBER is as follows: +% +% function op = smooth_huber(mu) +% op = tfocs_smooth( @huber_impl ) +% +% function [f,g] = huber_impl(x) +% ... this function calculates the function, f, +% and the gradient, g, of x ... +% end +% end +% +% Note: in the above template, the "end" statements are very important. +% +% +% Also, users may wish to test their smooth function +% with the script TEST_SMOOTH +% +% See also smooth_huber, test_smooth, private/tfocs_smooth, smooth_handles + + +op = @fcn_impl; + +function [ v, g ] = fcn_impl(x, t ) + if nargin == 2, + error( 'Proximity minimization not supported by this function.' ); + end + if nargout == 2 + [v,g] = fcn(x); + else + v = fcn(x); + end +end + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. \ No newline at end of file diff --git a/private/tfocs_zeros.m b/private/tfocs_zeros.m new file mode 100644 index 0000000..c8bf8f0 --- /dev/null +++ b/private/tfocs_zeros.m @@ -0,0 +1,54 @@ +function z = tfocs_zeros( y ) +switch class( y ), + case 'double', + % SRB: this was the old code. This function was intended + % to be called as tfocs_zeros( size(y) ) + % But, for packSVD, I'd prefer to call it as tfocs_zeros(y) + % so that we can look at the type of y and pass it off + % accordingly (see 'packSVD' section below). + % This would break old code. But, it appears that all + % calls of this use the { [n1,n2], [m1,m2] } form of sz + % (as opposed to the [m1,n1] convention), so + % I don't think this will break anything. + + % Old code: + % z = zeros( y ); + + % New code: + % Try to catch cases where we thing y = [m,n] is a size: +% if numel(y) == 2 && isint(y(1)) && isint(y(2)) + % March 2011, fixing this to work with 3D arrays + % If you want to use 4D arrays, you must modify this in a similar + % fasion; we don't do that before hand since it makes it more likely + % that the wrong case is picked. + if (numel(y) == 2 || numel(y) == 3) && all(isint(y)) + z = zeros( y ); + else + z = zeros( size(y) ); + end + + case 'cell', + if isa( y{1}, 'function_handle' ), + z = y{1}( y{2:end} ); + else + for k = 1 : numel(y), + y{k} = tfocs_zeros( y{k} ); + end + z = tfocs_tuple( y ); + end + case 'packSVD' + %z = packSVD>tfocs_zeros(y); + z = packSVD_zeros(y); + otherwise + error('TFOCS_ZEROS: cannot handle this type of object'); +end + +% do NOT use "isinteger" because that tests for the integer data type. +function h = isint(y) +h = abs( round(y) - y ) < 10*abs(y)*eps; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + + diff --git a/proj_0.m b/proj_0.m new file mode 100644 index 0000000..2cb3ec5 --- /dev/null +++ b/proj_0.m @@ -0,0 +1,63 @@ +function op = proj_0(offset) + +%PROJ_0 Projection onto the set {0} +% OP = PROJ_0 returns an implementation of the indicator +% function for the set including only zero. +% +% OP = PROJ_0( c ) returns an implementation of the +% indicator function of the set {c} +% If c is a scalar, this is interpreted as c*1 +% where "1" is the all ones object of the appropriate size. +% +% See also prox_0.m, proj_Rn.m and smooth_constant.m, +% which are the Fenchel conjugates of this function. + +if nargin == 0 + op = @proj_0_impl; +else + op = @(varargin) proj_0_impl_q( offset, varargin{:} ); +end + +function [ v, x ] = proj_0_impl( x, t ) +v = 0; +switch nargin, + case 1, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif any( x(:) ), + v = Inf; + end + case 2, + % "t" variable has no effect + x = 0*x; + otherwise, + error( 'Not enough arguments.' ); +end +function [ v, x ] = proj_0_impl_q( c, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + end + if isscalar(c) + if any( x(:) - c ) + v = Inf; + end + elseif any( x(:) - c(:) ), + v = Inf; + end + case 3, + % "t" variable has no effect + if isscalar(c) && ~isscalar(x) + x = c*ones( size(x) ); + else + x = c; + end + + otherwise, + error( 'Not enough arguments.' ); +end +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_Rn.m b/proj_Rn.m new file mode 100644 index 0000000..99e4948 --- /dev/null +++ b/proj_Rn.m @@ -0,0 +1,14 @@ +function op = proj_Rn + +%PROJ_RN "Projection" onto the entire space. +% OP = PROX_RN returns an implementation of the set Rn. Use +% this function to specify a model with no nonsmooth component. It is +% identical to both PROX_0 and SMOOTH_CONSTANT( 0 ). +% The projection onto Rn is just the identity. +% Dual: proj_0.m + +op = smooth_constant( 0 ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_Rplus.m b/proj_Rplus.m new file mode 100644 index 0000000..1cb078e --- /dev/null +++ b/proj_Rplus.m @@ -0,0 +1,62 @@ +function op = proj_Rplus( scale ) + +%PROJ_RPLUS Projection onto the nonnegative orthant. +% OP = PROJ_RPLUS returns an implementation of the indicator +% function for the nonnegative orthant. +% OP2 = PROJ_RPLUS( scale ) returns the indicator function +% of the scaled nonnegative orthant: that is, +% OP2( x ) = OP( scale * x ). +% Because the nonnegative orthant is a cone, scaling has no +% effect if scale > 0. If scale < 0, the result is an +% indicator function for the nonpositive orthant, which is +% also the conjugate of the original. If scale == 0, then +% the result is the zero function. +% Dual: proj_Rplus(-1) +% +% See also proj_psd.m, the matrix-analog of this function + +if nargin == 0, + op = @proj_Rplus_impl; +elseif ~isa( scale, 'double' ) || numel( scale ) ~= 1 || ~isreal( scale ), + error( 'The argument must be a real scalar.' ); +elseif scale > 0, + op = @proj_Rplus_impl; +elseif scale < 0, + op = @proj_Rminus_impl; +else + op = proj_Rn; +end + +function [ v, x ] = proj_Rplus_impl( x, t ) +v = 0; +switch nargin, + case 1, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif any( x(:) < 0 ), + v = Inf; + end + case 2, + x = max( x, 0 ); + otherwise, + error( 'Not enough arguments.' ); +end + +function [ v, x ] = proj_Rminus_impl( x, t ) +v = 0; +switch nargin, + case 1, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif any( x(:) > 0 ), + v = Inf; + end + case 2, + x = min( x, 0 ); + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_affine.m b/proj_affine.m new file mode 100644 index 0000000..be268ae --- /dev/null +++ b/proj_affine.m @@ -0,0 +1,85 @@ +function op = proj_affine( A, b, R ) +%PROJ_AFFINE(A, b) Projection onto the affine set +% { x : A*x == b } +% +% Warning! For large dimensions, this may be costly. +% In this case, use proj_0 and explicitly seprate +% out the affine term (this is the motivation behind +% TFOCS). +% +% For efficiency, this function does a single costly +% Cholesky decomposition when instatiated; subsequent +% calls are cheap. +%PROJ_AFFINE(A, b, R) +% uses the user-provided R for the Cholesky factorization +% of A*A', i.e., R'*R = A*A' and R is upper triangular. +% +% If A is a tight frame, e.g., A*A' = alpha*I for some +% scalar alpha>0, then the projection is efficient, but it is +% not automatically recognized. The user should +% provide R = sqrt(alpha)*eye(m) (where A is m x n) +% and then the code will be efficient. +% +% N.B. If the system of equations is overdetermined, +% then the set is just a single point. +% +% See also proj_boxAffine and proj_singleAffine + +error(nargchk(2,3,nargin)); +if nargin < 3 + if size(A,1) > 1e4 + warning('proj_affine:largeSize','Cholesky decomposition might take a large for such large matrices'); + end + [R,p] = chol(A*A'); +else + p = 0; + % Assuming that if user provides R, then it is not over-determiend +end +if p > 0 + % A*A' is rank deficient, i.e., system is over-determined + % So, there is just a unique point + warning('proj_affine:overdetermined','The system is over-determined so the set is a single point'); + xStar = A\b; + op = @(varargin) proj_point( xStar, varargin{:} ); +else + op = @(varargin) proj_affine_internal( A, R, b, varargin{:} ); +end + +function [v,x] = proj_point( xStar, y, t ) +v = 0; +switch nargin + case 2 + if nargout == 2 + error('This function is not differentiable.'); + end + % Check if we are feasible + if norm( y - xStar ) > 1e-13 + v = Inf; + end + case 3 + % The projection is simple: + x = xStar; + otherwise + error( 'Wrong number of arguments.' ); +end + +function [v,x] = proj_affine_internal( A, R, b, y, t ) +v = 0; +switch nargin + case 4 + if nargout == 2 + error('This function is not differentiable.'); + end + % Check if we are feasible + if norm( A*y - b ) > 1e-13 + v = Inf; + end + case 5 + x = y + A'*( R\( R'\( b - A*y )) ); + otherwise + error( 'Wrong number of arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_box.m b/proj_box.m new file mode 100644 index 0000000..9435766 --- /dev/null +++ b/proj_box.m @@ -0,0 +1,46 @@ +function op = proj_box(l,u) +%PROJ_BOX Projection onto box constraints. +%PROJ_BOX(l,u) Projection onto the box { l <= x <= u } +% If l or u is the empty matrix [], then the constraint is not +% enforced (e.g. PROJ_BOX([],1) is the set { x <= 1 }, +% and PROJ_BOX(0) is the set { 0 <= x } ) +% The parameters l and u may also be vectors or matrixes +% of the same size as x. +% +% OP = PROJ_BOX returns an implementation of this projection. +% +% Dual function: prox_boxDual.m +% See also prox_boxDual + +% warning: doesn't check to ensure l <= u + +error(nargchk(1,2,nargin)); +if nargin < 2, u = []; end +%op = @proj_Rplus_impl; +% bugfix, March 2011: +op = @(varargin)proj_Rplus_impl(l,u,varargin{:}); + +function [ v, x ] = proj_Rplus_impl( l, u, x, t ) +v = 0; +switch nargin, + case 3, + if nargout == 2, + error( 'This function is not differentiable.' ); + end + if any( x < l ) || any( x > u ) + v = Inf; + end + case 4, + if ~isempty(l) + x = max( x, l ); + end + if ~isempty(u) + x = min( x, u ); + end + otherwise, + error( 'Wrong number of arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_boxAffine.m b/proj_boxAffine.m new file mode 100644 index 0000000..aeb8acc --- /dev/null +++ b/proj_boxAffine.m @@ -0,0 +1,111 @@ +function op = proj_boxAffine( a, l, u, alpha ) +%PROJ_BOXAFFINE(a,l,u,alpha) Projection onto the box { l <= x <= u } intersected +% with the constraint a'*x == alpha +% If l or u is the empty matrix [], then the constraint is not +% enforced. "a" must be included (otherwise, use proj_box.m). +% The parameters l and u may also be vectors or matrixes +% of the same size as x. +% The offset "alpha" is optional (default is 0) +% +% OP = PROJ_BOXAFFINE returns an implementation of this projection. +% +% See also proj_box, proj_affine, proj_singleAffine, prox_boxDual +error(nargchk(3,4,nargin)); +if isempty(l), l = -Inf(size(a)); end +if isempty(u), u = Inf(size(a)); end +if nargin < 4 || isempty(alpha), alpha = 0; end +% % Not the most efficient, but least amount of coding work: +% if isequal(size(l),[1,1]) && numel(a) > 1 +% l = l*ones(size(a)); +% end +% if isequal(size(u),[1,1]) && numel(a) > 1 +% u = u*ones(size(a)); +% end +op = @(varargin) proj_box_affine(a,l,u, alpha, varargin{:} ); + + +function [v,x] = proj_box_affine( a, l, u, alpha, y, t ) +v = 0; +switch nargin + case 5 + if nargout == 2 + error('This function is not differentiable.'); + end + % Check if we are feasible + if any( y < l ) || any( y > u ) || abs( tfocs_dot(y,a) ) > 1e-13 + v = Inf; + end + case 6 + scalarL = ( numel(l) == 1 ); + scalarU = ( numel(u) == 1 ); + + n = length(y); + if numel(y) > n + % It should work, but no 100% guarantees +% warning('TFOC:proj_boxAffine','Not extensively tested for matrix inputs; use at your own risk!'); + n = numel(y); + end + projBox = @(x) max( l, min( u, x ) ); + % Turning points for constraints = l (l for lower) + T1 = (y-l)./a; + % Turning points for constraints = u (u for upper) + T2 = (y-u)./a; + T = sort(union(T1(:),T2(:))); + lwrBound = 1; + uprBound = 2*n; + for i = 1:ceil(log2(2*n)) + indx = round( (lwrBound+uprBound)/2 ); + beta = T(indx); + + % Our trial solution + x = projBox( y - beta*a ); + % Refine beta on the support (ignore constraints): see if it satisifies constraints + S = find( x > l & x < u ); + S1 = x==l; + S2 = x==u; + + % Given the fixed points, we subtract these off, and resolve the + % plain affine projection problem on the active set + % The affine projection solun is always x = y - betaEst*a + % for some betaEst. + +% betaEst = (a(S)'*y(S) + a(S2)'*u(S2) + a(S1)'*l(S1) )/(a(S)'*a(S)); + % Or, so that we can allow u and l to be scalars, + if scalarL, al = sum(a(S1))*l; else al = a(S1)'*l(S1); end + if scalarU, au = sum(a(S2))*u; else au = a(S2)'*u(S2); end + betaEst = (a(S)'*y(S) + au + al - alpha )/(a(S)'*a(S)); + + + % Check if bestEst is in the admissible range + % e.g. if betaEst > beta, is it less than T(indx+1)? and vice-versa + if betaEst > beta + if indx == 2*n || betaEst < T(indx+1) + break; + else + lwrBound = indx + 1; % we need to increase beta + end + else + if indx == 1 || betaEst > T(indx-1) + break; + else + uprBound = indx - 1; % we need to decrease beta + end + end + end + x = projBox( y - betaEst*a ); + S = find( x > l & x < u ); + S1 = x==l; + S2 = x==u; +% betaEst = (a(S)'*y(S) + a(S2)'*u(S2) + a(S1)'*l(S1) )/(a(S)'*a(S)); + % Or, so that we can allow u and l to be scalars, + if scalarL, al = sum(a(S1))*l; else al = a(S1)'*l(S1); end + if scalarU, au = sum(a(S2))*u; else au = a(S2)'*u(S2); end + betaEst = (a(S)'*y(S) + au + al -alpha )/(a(S)'*a(S)); + x = projBox( y - betaEst*a ); + otherwise + error( 'Wrong number of arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_conic.m b/proj_conic.m new file mode 100644 index 0000000..fc166a7 --- /dev/null +++ b/proj_conic.m @@ -0,0 +1,48 @@ +function op = proj_conic() +%PROJ_CONIC +% Returns an operator implementing projection onto +% the second order cone, aka (real) Lorentz cone +% aka ice-cream cone +% That is, +% { x : norm( x(1:end-1) , 2) <= x(end) } +% +% The cone is often written as +% { (x,t) : ||x|| <= t } +% so note that in this implementation, "t" is +% inferred from the final coordinate of x. +% +% Contributed by Joself Salmon 2013 + +op = @proj_conic_impl; + +function [ v, x ] = proj_conic_impl( x, t ) +v = 0; +[n,m]=size(x); +sum_part=sqrt( sum(x(1:n-1).^2) ); +xn=(x(n)); + +switch nargin, + case 1, + if nargout == 2 + error( 'This function is not differentiable.' ); + elseif ( (sum_part)>xn ) + v = Inf; + end + case 2, + + if ( ((sum_part) -abs(xn))> 0 ) + x=1/2*(1+xn/sum_part)*[x(1:n-1);sum_part]; + + elseif ( (sum_part)<(xn) ) + x=x; + elseif ( (sum_part)<(-xn) ) + x=zeros(n,m); + + end + + otherwise, + error( 'Not enough arguments.' ); +end +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_l1.m b/proj_l1.m new file mode 100644 index 0000000..8a0e0be --- /dev/null +++ b/proj_l1.m @@ -0,0 +1,42 @@ +function op = proj_l1( q ) +%PROJ_L1 Projection onto the scaled 1-norm ball. +% OP = PROJ_L1( Q ) returns an operator implementing the +% indicator function for the 1-norm ball of radius q, +% { X | norm( X, 1 ) <= q }. Q is optional; if omitted, +% Q=1 is assumed. But if Q is supplied, it must be a positive +% real scalar. +% Dual: prox_linf.m +% See also: prox_linf, prox_l1, proj_linf + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +op = @(varargin)proj_l1_q(q, varargin{:} ); + +function [ v, x ] = proj_l1_q( q, x, t ) +v = 0; +switch nargin, +case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( x(:), 1 ) > q, + v = Inf; + end +case 3, + s = sort(abs(nonzeros(x)),'descend'); + cs = cumsum(s); + % ndx = find( cs - (1:numel(s))' .* [ s(2:end) ; 0 ] >= q, 1 ); + ndx = find( cs - (1:numel(s))' .* [ s(2:end) ; 0 ] >= q+2*eps(q), 1 ); % For stability + if ~isempty( ndx ) + thresh = ( cs(ndx) - q ) / ndx; + x = x .* ( 1 - thresh ./ max( abs(x), thresh ) ); % May divide very small numbers + end +otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_l2.m b/proj_l2.m new file mode 100644 index 0000000..c7f7d4e --- /dev/null +++ b/proj_l2.m @@ -0,0 +1,83 @@ +function op = proj_l2( q ) + +%PROJ_L2 Projection onto the scaled 2-norm ball. +% OP = PROJ_L2( Q ) returns an operator implementing the +% indicator function for the 2-norm ball of size q, +% { X | norm( X, 2 ) <= q }. Q is optional; if omitted, +% Q=1 is assumed. But if Q is supplied, it must be a positive +% real scalar. +% Dual: prox_l2.m +% See also: prox_l2.m + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) + error( 'Argument must be a real scalar.' ); +end +if numel(q) == 1 + if q <= 0, error('Argument must be positive'); end + op = @(varargin)proj_l2_q( q, varargin{:} ); +else + if any( abs(q) < 10*eps ), error('Weight "q" must be nonzero'); end + warning('TFOCS:experimental','Using experimental feature of TFOCS'); + + op = @(varargin)proj_l2_qVec( q, varargin{:} ); +end + +function [ v, x ] = proj_l2_q( q, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( x(:), 'fro' ) > q, % GKC fix 2013 (for > 2D arrays) + v = Inf; + end + case 3, + nrm = norm(x(:),'fro'); % fixing, Feb '11, and GKC fix 2013 + if nrm > q + x = x .* ( q / nrm ); + end + otherwise, + error( 'Not enough arguments.' ); +end + +% -- experimental version for when q is a vector -- +function [ v, x ] = proj_l2_qVec( q, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( x./q, 'fro' ) > 1, + v = Inf; + end + case 3, + nrm = norm(x./q,'fro'); + if nrm > 1 + + % We know x is of the form x0./( 1 + lambda*D2 ) + % for some lambda > 0, but we don't have an easy + % way to know what lambda is. So, treat this as + % a 1D minimization problem to find lambda. + D = 1./(q); + D2 = D.^2; + Dx = D.*x; + +% lMax = max( abs(x./D2) )*sqrt(numel(x)); + lMax = 1.2*norm( abs(x./D2),'fro'); % a tighter bound + fmin_opts = optimset( 'TolX', 1e-10 ); +% MaxFunEvals: 500 +% MaxIter: + [lOpt,val,exitflag,output] = fminbnd( @(l) (norm(Dx./(1+l*D2),'fro')-1)^2, 0, lMax,fmin_opts); + if val > 1e-3, error('Proj_l2 failed to converge'); end + x = x./( 1 + lOpt*D2 ); + end + + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_l2group.m b/proj_l2group.m new file mode 100644 index 0000000..9b96d31 --- /dev/null +++ b/proj_l2group.m @@ -0,0 +1,79 @@ +function op = proj_l2group( q,group_indices ) +%PROJ_L2GROUP Projection onto the intersection of scaled 2-norm balls. +% OP = PROJ_L2GROUP( Q, GROUP_INDICES ) returns an operator implementing the +% indicator function for the intersection of 2-norm ball of size q_k, +% i.e. intersection_k B_k +% where B_k = { X | norm( X(indK) ) <= q(k) } +% and indK is indexed by group_indices. Group_indices keeps track +% of the last index in each index set. +% The index sets must be non-overlapping. +% For example, if K=2, and ind1 = 1:10 and ind2 = 11:20, +% then group_indices = [10,20]. +% Q must be a vector of length K or a vector of length N. +% +% With contributions from Joseph Salmon +% +% See also: proj_l2.m + +if isempty(q), q = 1; end +% Make sure q is a column vector +if size(q,1) == 1 && size(q,2) > 1, q = q.'; end +q = expand_q(q,group_indices); +op = @(varargin)proj_l2_q( q,group_indices, varargin{:} ); +end + +function [ v, x ] = proj_l2_q( q,group_indices, x, t ) +v = 0; +nrm=group_norm(x,group_indices); + +switch nargin, + case 3, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( nrm, 'inf') > q, + v = Inf; + end + case 4, + x = x .* min(1,( q ./ nrm )); + otherwise, + error( 'Not enough arguments.' ); +end +end + + +function y=group_norm(x,group_indices) +% the groups are indexed by their end point + K=length(group_indices); + y=zeros(size(x)); + j=1; % start of the group + for k=1:K + end_point=group_indices(k); + y(j:end_point)=norm(x(j:end_point)); + j=end_point+1; + end +end + +function Q = expand_q(q,group_indices) +% If we have 2 groups, each of size 10 +% (group_indices = [10,20]), then q should +% be a vector of size 20. But it's more convenient +% for the user to pass in a vector of size 2, so this function +% will convert it... +n = group_indices(end); +K = length(group_indices); +if length(q) < n + Q = ones(n,1); + j=1; % start of the group + for k=1:K + end_point=group_indices(k); + Q(j:end_point) = q(k); + j=end_point+1; + end +else + Q = q; +end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_linf.m b/proj_linf.m new file mode 100644 index 0000000..9e72811 --- /dev/null +++ b/proj_linf.m @@ -0,0 +1,36 @@ +function op = proj_linf( q ) + +%PROJ_LINF Projection onto the scaled infinity norm ball. +% OP = PROJ_LINF( Q ) returns an operator implementing the +% indicator function for the infinity norm ball of size q, +% { X | norm( X, Inf ) <= q }. Q is optional; if omitted, +% Q=1 is assumed. But if Q is supplied, it must be a positive +% real scalar. +% Dual: prox_l1.m +% See also: prox_l1, prox_linf, proj_l1 + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +op = @(varargin)proj_linf_q( q, varargin{:} ); + +function [ v, x ] = proj_linf_q( q, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( x(:), Inf ) > q, + v = Inf; + end + case 3, + x = x ./ max( 1, abs( x / q ) ); + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_linfl2.m b/proj_linfl2.m new file mode 100644 index 0000000..490a609 --- /dev/null +++ b/proj_linfl2.m @@ -0,0 +1,64 @@ +function op = proj_linfl2( q ) + +%PROJ_LINFL2 Projection of each row onto the scaled l2 norm ball. +% OP = PROJ_LINFL2( Q ) returns an operator implementing the +% indicator function for the set of l2 norm ball of size q, +% { X | for all rows i, norm( X(i,:),2) <= q }. Q is optional; if omitted, +% Q=1 is assumed. But if Q is supplied, it must be a positive +% real scalar. +% Dual: prox_l1l2.m +% See also: prox_l1, prox_linf, proj_l1 + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end + +% In r2007a and later, we can use bsxfun instead of the spdiags trick +vr=version('-release'); +if str2num(vr(1:4)) >= 2007 + op = @(varargin)proj_linfl2_q_bsxfun( q, varargin{:} ); +else + % the default, using spdiags + op = @(varargin)proj_linfl2_q( q, varargin{:} ); +end + +function [ v, x ] = proj_linfl2_q( q, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( x(:), Inf ) > q, + v = Inf; + end + case 3, + % Compute the norms of the rows + m = size(x,1); + nrms = sqrt( sum( abs(x).^2 , 2 ) ); + % Scale the rows using left diagonal multiplication + x = spdiags( min(1,q./nrms), 0, m, m )*x; + otherwise, + error( 'Not enough arguments.' ); +end + +function [ v, x ] = proj_linfl2_q_bsxfun( q, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif norm( x(:), Inf ) > q, + v = Inf; + end + case 3, + nrms = sqrt( sum( abs(x).^2 , 2 ) ); + bsxfun( @times, x, min(1,q./nrms) ); + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_max.m b/proj_max.m new file mode 100644 index 0000000..b729531 --- /dev/null +++ b/proj_max.m @@ -0,0 +1,36 @@ +function op = proj_max( q ) + +%PROJ_max Projection onto the scaled max-function ball. +% OP = PROJ_MAX( Q ) returns an operator implementing the +% indicator function for the max-function ball of size q, +% { X | max( X(:) ) <= q }. Q is optional; if omitted, +% Q=1 is assumed. But if Q is supplied, it must be a +% real scalar (negative is OK). +% Dual: prox_l1pos.m +% See also: prox_l1pos, prox_linf, proj_l1, proj_linf + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 + error( 'Argument "q" must be a real scalar.' ); +end +op = @(varargin)proj_linf_q( q, varargin{:} ); + +function [ v, x ] = proj_linf_q( q, x, t ) +v = 0; +switch nargin, + case 2, + if nargout == 2, + error( 'This function is not differentiable.' ); + elseif max( x(:) ) > q, + v = Inf; + end + case 3, + x = min( x, q ); + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_maxEig.m b/proj_maxEig.m new file mode 100644 index 0000000..37615ca --- /dev/null +++ b/proj_maxEig.m @@ -0,0 +1,141 @@ +function op = proj_maxEig( q, LARGESCALE ) + +%PROJ_MAXEIG Projection onto the set of matrices with max eigenvalue less than or equal to q +% OP = PROJ_MAXEIG( q ) returns a function that implements the +% indicator for matrices with spectral norm less than q. +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% This function is similar proj_spectral.m but assumes that +% the input is real symmetric. For positive semi-definite inputs, +% this function is equivalent to proj_spectral +% OP = PROJ_MAXEIG( ..., largescale) will switch to using +% eigs instead of svd or eig, if largescale==true. This is usually +% beneficial for large, sparse variables. +% +% Dual: prox_trace(q) +% (if domain is pos. semidefinite matrices, then prox_trace(q) +% is also the dual, and is more efficient than prox_nuclear(q) ). +% +% See also prox_trace, prox_nuclear, proj_spectral, prox_spectral + +% Sept 1, 2012 +if nargin < 2 || isempty(LARGESCALE) + LARGESCALE = false; +end + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +% vectorFunction = proj_linf( q ); % for proj_spectral.m +vectorFunction = proj_max( q ); + +if LARGESCALE + % clear the persistent values: + proj_maxEig_eigs_q(); + op = @(varargin)proj_maxEig_eigs_q( q,vectorFunction, varargin{:} ); +else + op = @(varargin)proj_maxEig_eig_q( q,vectorFunction, varargin{:} ); +end + + +function [ v, X ] = proj_maxEig_eig_q( q,eproj, X, t ) +SP = issparse(X); +v = 0; +if nargin > 3 && t > 0, + if SP, X = full(X); end % svd, eig, and norm require full matrices + [V,D] = eig(X); % just in case X is sparse + [dum,D] = eproj(diag(D),q); % not q*t, since we are just projecting... + X = V*diag(D)*V'; + if SP, X = sparse(X); end +else + nrm = max(eig(X)); + if nrm > q + v = Inf; + end +end + +% --------------- largescale functions: use eigs or svds ----------------------- +function [ v, X ] = proj_maxEig_eigs_q( q,eproj, X, t ) +persistent oldRank +persistent nCalls +persistent V +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; V=[]; return; end +if isempty(nCalls), nCalls = 0; end +SP = issparse(X); +v = 0; +if nargin > 3 && t > 0, + + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + [M,N] = size(X); + + ok = false; + opts = []; + opts.tol = 1e-10; + if isreal(X) + opts.issym = true; + SIGMA = 'LA'; % not 'LM' like proj_spectral + else + SIGMA = 'LR'; + end + while ~ok + K = min( [K,M,N] ); + if K > min(M,N)/2 || K > (min(M,N)-2) || min(M,N) < 20 + [V,D] = eig(full((X+X')/2)); + ok = true; + else + [V,D] = eigs( X, K, SIGMA, opts ); + ok = (min(abs(diag(D))) < q) || ( K == min(M,N) ); + end + if ok, break; end + K = 2*K; + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-3; + end + end + oldRank = length(find(diag(D) > q)); % no abs here + + [dum,D_proj] = eproj(diag(D),q); + % we want to keep the singular vectors that we haven't discovered + % small = X - V*D*V' + % large = V*D_proj*V' + % and add together to get X - V*(D-Dproj)*V' + + X = X - V*diag(diag(D)-D_proj)*V'; + + if SP, X = sparse(X); end +else +% if SP + opts = struct('tol',1e-8); + if isreal(X) + opts.issym = true; + SIGMA = 'LA'; % not 'LM' like proj_spectral + else + SIGMA = 'LR'; + end + K = 1; + D = eigs( X, K, SIGMA , opts ); + nrm = max(D); +% else +% nrm = max(eig(X)); +% end + if nrm > q + v = Inf; + end +end + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/proj_nuclear.m b/proj_nuclear.m new file mode 100644 index 0000000..db9ce70 --- /dev/null +++ b/proj_nuclear.m @@ -0,0 +1,56 @@ +function op = proj_nuclear( q ) + +%PROJ_NUCLEAR Projection onto the set of matrices with nuclear norm less than or equal to q. +% OP = PROJ_NUCLEAR( q ) returns a function that implements the +% indicator for matrices with nuclear norm less than q. +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% This function is like proj_psdUTrace.m but does not assume +% that inputs are square Hermitian positive semidefinite matrices. +% +% This version uses a dense svd decomposition; future versions +% of TFOCS will take advantage of low-rank and/or sparse structure. +% Dual function: prox_spectral.m +% See also prox_spectral, proj_psdUTrace.m, proj_simplex.m, prox_nuclear.m + +% June 26, 2012 + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +q = proj_simplex( q ); +op = @(varargin)proj_nuclear_q( q, varargin{:} ); + +function [ v, X ] = proj_nuclear_q( eproj, X, t ) + +VECTORIZE = false; +% Input must be a matrix + +% In proj_psdUTrace.m, we allow vector inputs because +% we can reshape them (since we have square matrices). +% For nuclear norm, inputs can be rectangular matrices, +% so we do not know how to correctly reshape a vector in +% this case. + +% if size(X,1) ~= size(X,2) +% n = sqrt(length(X)); +% X = reshape(X, n, n ); +% VECTORIZE = true; +% end +v = 0; +if nargin > 2 && t > 0, + [U,D,V] = svd(X,'econ'); + [dum,D] = eproj(diag(D),t); + tt = D > 0; + X = U(:,tt)*diag(D(tt))*V(:,tt)'; +% if VECTORIZE, X = X(:); end +elseif any(svd(X)<0), + v = Inf; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/proj_psd.m b/proj_psd.m new file mode 100644 index 0000000..34e17ec --- /dev/null +++ b/proj_psd.m @@ -0,0 +1,138 @@ +function op = proj_psd( LARGESCALE, isReal, K ) + +% PROJ_PSD Projection onto the positive semidefinite cone. +% OP = PROJ_PSD() returns a function that implements +% the projection onto the semidefinite cone: +% X = argmin_{min(eig(X))>=0} norm(X-Y,'fro') +% +% OP = PROJ_PSD( LARGESCALE ) +% performs the same computation, but in a more efficient +% manner for the case of sparse (and low-rank) matrices +% +% OP = PROJ_PSD( LARGESCALE, isReal ) +% also includes the constraint that X is real-valued if isReal is true. +% +% This function is self-dual. +% See also proj_Rplus.m, the vector analog of this function + +% in the future, we might include this nonconvex version: +% OP = PROJ_PSD( LARGESCALE, isReal, k ) +% only returns at most a rank k matrix +% + +if nargin == 0 || isempty(LARGESCALE), LARGESCALE = false; end +if nargin < 2 || isempty(isReal), isReal = false; end +if nargin < 3, K = Inf; end + +if ~LARGESCALE + op = @(varargin)proj_psd_impl(isReal, varargin{:} ); +else + proj_psd_largescale(); % reset any counters + op = @(varargin)proj_psd_largescale( K, isReal, varargin{:} ); +end + + +function [ v, X ] = proj_psd_impl( isReal, X, t ) +if nargin > 2 && t > 0, + v = 0; + X = full(X+X'); % divide by 2 later + if isReal, X = real(X); end + [V,D]=eig(X); % we don't yet take advantage of sparsity here + D = max(0.5*diag(D),0); + tt = D > 0; + V = bsxfun(@times,V(:,tt),sqrt(D(tt,:))'); + X = V * V'; +else + s = eig(full(X+X'))/2; + if min(s) < -8*eps*max(s), + v = Inf; + else + v = 0; + end +end + + +function [ v, X ] = proj_psd_largescale(Kignore,isReal, X, t ) +% Updated Sept 2012. The restriction to rank K "Kignore" has not been done yet +% (that is nonconvex) +persistent oldRank +persistent nCalls +persistent V +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; V=[]; return; end +if isempty(nCalls), nCalls = 0; end +SP = issparse(X); + +if nargin > 3 && t > 0, + v = 0; + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + + [M,N] = size(X); + EIG_TOL = 1e-10; + ok = false; + opts = []; + opts.tol = 1e-10; + if isreal(X) + opts.issym = true; + SIGMA = 'LA'; + else + SIGMA = 'LR'; % largest real part + end + X = (X+X')/2; + if isReal, X = real(X); end + while ~ok + K = min( [K,N] ); + if K > N/2 || K > N-2 || N < 20 + [V,D] = eig(full((X+X')/2)); + ok = true; + else + [V,D] = eigs( X, K, SIGMA, opts ); + ok = (min(real(diag(D))) < EIG_TOL) || ( K == N ); + end + if ok, break; end +% opts.v0 = V(:,1); % starting vector + K = 2*K; +% fprintf('Increasing K from %d to %d\n', K/2,K ); + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-3; + end + end + D = real( diag(D) ); + oldRank = length(find( D > EIG_TOL )); + + tt = D > EIG_TOL; + V = bsxfun(@times,V(:,tt),sqrt(D(tt,:))'); + X = V * V'; + if SP, X = sparse(X); end +else + opts.tol = 1e-10; + if isreal(X) + opts.issym = true; + SIGMA = 'SA'; + else + SIGMA = 'SR'; % smallest real part + end + K = 1; % we only want the smallest + X = full(X+X'); % divide by 2 later + if isReal, X = real(X); end + d = eigs(X, K, SIGMA, opts ); + d = real(d)/2; + + if d < -10*eps + v = Inf; + else + v = 0; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/proj_psdUTrace.m b/proj_psdUTrace.m new file mode 100644 index 0000000..02199e0 --- /dev/null +++ b/proj_psdUTrace.m @@ -0,0 +1,193 @@ +function op = proj_psdUTrace( q, LARGESCALE, force_real, maxK ) + +%PROJ_PSDUTRACE Projection onto the positive semidefinite cone with fixed trace. +% OP = PROJ_PSDUTRACE( q ) returns a function that implements the +% indicator for the cone of positive semidefinite (PSD) matrices with +% fixed trace: { X | min(eig(X+X'))>=0, trace(0.5*(X+X'))<=q +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% +% OP = PROJ_PSDUTRACE( q, LARGESCALE) will use a Lanczos-based Eigenvalue +% decomposition if LARGESCALE==true, eitherwise it uses a dense +% matrix decomposition +% +% OP = PROJ_PSDUTRACE( q, LARGESCALE, forceReal ) will also include the +% constraint that X is a real matrix if forceReal is true. +% +% OP = PROJ_PSDUTRACE( q, LARGESCALE, forceReal, maxK ) will only +% use the Lanczos-solver if it expects fewer than maxK eigenvalues +% +% CALLS = PROJ_PSDUTRACE( 'reset' ) +% resets the internal counter and returns the number of function calls +% +% This version uses a dense eigenvalue decomposition; future versions +% of TFOCS will take advantage of low-rank and/or sparse structure. +% +% If the input to the operator is a vector of size n^2 x 1, it will +% be automatically reshaped to a n x n matrix. In this case, +% the output will also be of length n^2 x 1 and not n x n. +% +% Note: proj_simplex.m (the vector-analog of this function) +% Duals: the dual function is prox_maxEig, which also requires +% PSD inputs. The function prox_spectral(q,'sym') is also equivalent +% to prox_maxEig if given a PSD input. +% See also prox_maxEig, prox_spectral, proj_simplex + +% Feb 15 2013, adding support for eigs calculations + + +if nargin == 1 && strcmpi(q,'reset') + op = prox_trace_impl; + return; +end + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +if nargin < 2, LARGESCALE = []; end +if nargin < 3 + force_real = false; +end +if nargin < 4 || isempty(maxK) + maxK = 100; +end +if ~isempty( LARGESCALE ) && LARGESCALE + op = @(varargin)proj_psdUTrace_q_eigs( q, maxK, force_real, varargin{:} ); +else + op = @(varargin)proj_psdUTrace_q( q, force_real, varargin{:} ); +end + +function [ v, X ] = proj_psdUTrace_q_eigs( lambda, maxK, force_real, X, t ) +persistent oldRank +persistent nCalls +persistent V +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; V=[]; return; end +if isempty(nCalls), nCalls = 0; end +if isempty(maxK), maxK = size(X,1); end + + +VECTORIZE = false; +if size(X,1) ~= size(X,2) + %error('proj_psdUTrace requires a square matrix as input'); + n = sqrt(length(X)); + X = reshape(X, n, n ); + VECTORIZE = true; +end +v = 0; +X = full(0.5*(X+X')); % added 'full' Sept 5 2012 +if force_real % added Nov 23 2012 + X = real(X); +end +if nargin > 4 && t > 0 + + opts = []; + opts.tol = 1e-10; + if force_real + opts.issym = true; + SIGMA = 'LA'; % get largest eigenvalues (NOT in magnitude) + else + SIGMA = 'LR'; % should be real anyhow (to 1e-10). get largest real part + end + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + N = size(X,1); + ok = false; + ctr = 0; + FEASIBLE = false; + while ~ok + ctr = ctr + 1; + K = min( K, N ); + if K > N/2 || K > maxK + [V,D] = eig(X); ok = true; + D = diag(D); + break; + end +% opts.tol = min(max(opts.tol,1e-10),1e-7); + + [V,D] = eigs( X, K,SIGMA, opts ); + D = diag(D); + delta = ( sum(D) - lambda ) / K; + ok = min(D) < delta; + + if ok, break; end + + % Can we do an early return? maybe we are already feasible + if sum(D) + (N-K-1)*min(D) < lambda +% disp('Point is feasible! exiting'); + FEASIBLE = true; + break; + end + + % otherwise, increase K + + K = 2*K; + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-3; + end + end + if FEASIBLE + oldRank = N; + else + smplx = proj_simplex(lambda); + [dum,D] = smplx(D,1); + spprt = find(D); + D = diag(D(spprt)); + V = V(:,spprt); + X = V*D*V'; + oldRank = length(spprt); + end + + + nCalls = nCalls + 1; + + if VECTORIZE, X = X(:); end +elseif any(eig(X)<0) || trace(X) > lambda + v = Inf; +end + + + +function [ v, X ] = proj_psdUTrace_q( lambda, force_real, X, t ) +persistent nCalls +if nargin == 0, v = nCalls; nCalls = []; return; end +if isempty(nCalls), nCalls = 0; end + +eproj = proj_simplex( lambda ); + +VECTORIZE = false; +if size(X,1) ~= size(X,2) + %error('proj_psdUTrace requires a square matrix as input'); + n = sqrt(length(X)); + X = reshape(X, n, n ); + VECTORIZE = true; +end +v = 0; +X = full(0.5*(X+X')); % added 'full' Sept 5 2012 +if force_real % added Nov 23 2012 + X = real(X); +end +if nargin > 3 && t > 0, + nCalls = nCalls + 1; + [V,D]=eig(X); + [dum,D] = eproj(diag(D),t); + tt = D > 0; + V = bsxfun(@times,V(:,tt),sqrt(D(tt,:))'); + X = V * V'; + if VECTORIZE, X = X(:); end +elseif any(eig(X)<0) || trace(X) > lambda + v = Inf; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/proj_simplex.m b/proj_simplex.m new file mode 100644 index 0000000..ccbe2df --- /dev/null +++ b/proj_simplex.m @@ -0,0 +1,61 @@ +function op = proj_simplex( q ) + +%PROJ_SIMPLEX Projection onto the simplex. +% OP = PROJ_SIMPLEX( Q ) returns an nonsmooth function that +% represents the scaled simplex { x | x >= 0, sum(x) <= q }. +% Q is optional; if not supplied, it defaults to 1. If it is +% supplied, it must be a real positive scalar. +% +% See also proj_psdUTrace.m (the matrix-analog of this function) + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +op = @(varargin)proj_simplex_q( q,varargin{:} ); + +function [ v, x ] = proj_simplex_q( q, x, t ) +v = 0; + +% We have two options when input x is a matrix: +% Does the user want to treat it as x(:), i.e. "vectorize" it ? +% Or does the user want to treat each column separately? +% Most other functions (e.g. l1, linf) treat it as x(:) +% so that will be the default. However, we leave it +% as a hard-coded option so that the user can change it +% if they want. +VECTORIZE = true; + +if nargin > 2 && t > 0, + if any( x(:) < 0 ) || any( sum( x ) > q ), + if VECTORIZE + s = sort( x(:), 'descend' ); + else + s = sort( x, 'descend' ); + end + if q < eps(s(1)) + error('Input is scaled so large compared to q that accurate computations are difficult'); + % since then cs(1) = s(1) - q is not even guaranteed to be + % smaller than q ! + end + if size(x,2) == 1 || VECTORIZE + cs = ( cumsum(s) - q ) ./ ( 1 : numel(s) )'; + ndx = nnz( s > cs ); + x = max( x - cs(ndx), 0 ); + else + % vectorized for several columns + cs = diag( 1./( 1 : size(s,1) ) )*( cumsum(s) - q ); + ndx = sum( s > cs ); + ndx = sub2ind( size(x), ndx, 1:size(x,2) ); + x = max( x - repmat(cs(ndx),size(x,1),1), 0 ); + end + end +% Sept 6 2012, adding factor of 100 in the line below: +elseif any( x(:) < 0 ) || any( abs( sum(x) / q - 1 ) > sqrt(numel(x)) * 100 *eps ) + v = Inf; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_singleAffine.m b/proj_singleAffine.m new file mode 100644 index 0000000..eddc05f --- /dev/null +++ b/proj_singleAffine.m @@ -0,0 +1,50 @@ +function op = proj_singleAffine( a, beta, ineq ) +%PROJ_SINGLEAFFINE(a,beta) Projection onto the affine set { x : a'*x == beta } +% The parameter a may be a vector or matrix +% of the same size as x. By default, beta is 0 +%PROJ_SINGLEAFFINE(a,beta,ineq) +% Projection onto the affine set { x : a'*x >= beta } +% if ineq == 1, and onto { x : a'*x <= beta } if ineq == -1 +% Setting ineq == 0 (default) uses { x : a'*x == beta } +% +% For matrix variables, this interprets a'*x as a(:)'*x(:), e.g., +% the standard dot product +% +% OP = PROJ_SINGLEAFFINE returns an implementation of this projection. +% +% For more than one affine constraint, see proj_affine.m +% +% See also proj_boxAffine and proj_affine + +error(nargchk(1,3,nargin)); +if nargin < 2, beta = 0; end +if isempty(beta), beta = 0; end +if nargin < 3 || isempty(ineq), ineq = 0; end +op = @(varargin) proj_affine_internal(a,beta,ineq, varargin{:} ); + + +function [v,x] = proj_affine_internal( a, beta, ineq, y, t ) +v = 0; +switch nargin + case 4 + if nargout == 2 + error('This function is not differentiable.'); + end + % Check if we are feasible + if abs( tfocs_dot(y,a) - beta ) > 1e-13 + v = Inf; + end + case 5 + dt = tfocs_dot(a,y); + if (ineq==0) || ( ineq==1 && dt < beta ) || (ineq==-1 && dt > beta) + x = y - ( dt - beta )/tfocs_dot(a,a) * a; + else + x = y; + end + otherwise + error( 'Wrong number of arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/proj_spectral.m b/proj_spectral.m new file mode 100644 index 0000000..f561935 --- /dev/null +++ b/proj_spectral.m @@ -0,0 +1,226 @@ +function op = proj_spectral( q, SYM_FLAG, LARGESCALE ) + +%PROJ_SPECTRAL Projection onto the set of matrices with spectral norm less than or equal to q +% OP = PROJ_SPECTRAL( q ) returns a function that implements the +% indicator for matrices with spectral norm less than q. +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% This function is like proj_psdUTrace.m but does not assume +% that inputs are square Hermitian positive semidefinite matrices. +% OP = PROJ_SPECTRAL( q, 'sym' ) or OP = PROJ_SPECTRAL( 'eig' ) +% will instruct the code that the matrix is Hermitian and +% therefore the relations between singular- and eigen-values +% are well known, and the code will use the more efficient +% eigenvalue decomposition (instead of the SVD). +% OP = PROJ_SPECTRAL( ..., largescale) will switch to using +% svds (or PROPACK, if installed and in the path) or eigs +% instead of svd or eig, if largescale==true. This is usually +% beneficial for large, sparse variables. +% +% Dual: prox_nuclear(q) +% (if domain is pos. semidefinite matrices, then prox_trace(q) +% is also the dual, and is more efficient than prox_nuclear(q) ). +% +% See also prox_nuclear, proj_linf, proj_nuclear, prox_spectral, proj_maxEig + +% Sept 1, 2012 +if nargin < 3 || isempty(LARGESCALE) + LARGESCALE = false; +end + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +vectorFunction = proj_linf( q ); +if nargin >= 2 && ( ... + ~isempty(strfind(lower(SYM_FLAG),'eig')) || ... + ~isempty(strfind(lower(SYM_FLAG),'sym')) ) + if LARGESCALE + % clear the persistent values: + proj_spectral_eigs_q(); + op = @(varargin)proj_spectral_eigs_q( q,vectorFunction, varargin{:} ); + else + op = @(varargin)proj_spectral_eig_q( q,vectorFunction, varargin{:} ); + end +else + if LARGESCALE + % clear the persistent values: + proj_spectral_svds_q(); + op = @(varargin)proj_spectral_svds_q( q,vectorFunction, varargin{:} ); + else + op = @(varargin)proj_spectral_svd_q( q,vectorFunction, varargin{:} ); + end +end + +function [ v, X ] = proj_spectral_svd_q( q,eproj, X, t ) +sx = size( X ); +SP = issparse(X); +if length( sx ) > 2, + X = reshape( X, prod(sx(1:end-1)), sx(end) ); +end +v = 0; +if nargin > 3 && t > 0, + if SP, X = full(X); end % svd, eig, and norm require full matrices + [U,D,V] = svd( X, 'econ' ); + [dum,D] = eproj(diag(D),q); % not q*t, since we are just projecting... + X = U*diag(D)*V'; + if SP, X = sparse(X); end + X = reshape( X, sx ); +else + if SP, + [nrm,cnt] = normest(X,1e-3); + else + nrm = norm(X); + end + if nrm > q + v = Inf; + end +end + +function [ v, X ] = proj_spectral_eig_q( q,eproj, X, t ) +SP = issparse(X); +v = 0; +if nargin > 3 && t > 0, + if SP, X = full(X); end % svd, eig, and norm require full matrices + [V,D] = eig(X); % just in case X is sparse + [dum,D] = eproj(diag(D),q); % not q*t, since we are just projecting... + X = V*diag(D)*V'; + if SP, X = sparse(X); end +else + if SP, [nrm,cnt] = normest(X,1e-3); + else nrm = norm(X); + end + if nrm > q + v = Inf; + end +end + +% --------------- largescale functions: use eigs or svds ----------------------- +function [ v, X ] = proj_spectral_eigs_q( q,eproj, X, t ) +persistent oldRank +persistent nCalls +persistent V +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; V=[]; return; end +if isempty(nCalls), nCalls = 0; end +SP = issparse(X); +v = 0; +if nargin > 3 && t > 0, + + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + [M,N] = size(X); + + ok = false; + opts = []; + opts.tol = 1e-10; + if isreal(X) + opts.issym = true; + end + while ~ok + K = min( [K,M,N] ); + [V,D] = eigs( X, K, 'LM', opts ); + ok = (min(abs(diag(D))) < q) || ( K == min(M,N) ); + if ok, break; end + K = 2*K; + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-3; + end + if K > min(M,N)/2 + [V,D] = eig(full((X+X')/2)); + ok = true; + end + end + oldRank = length(find(abs(diag(D)) > q)); + + [dum,D_proj] = eproj(diag(D),q); + % we want to keep the singular vectors that we haven't discovered + % small = X - V*D*V' + % large = V*D_proj*V' + % and add together to get X - V*(D-Dproj)*V' + + X = X - V*diag(diag(D)-D_proj)*V'; + + if SP, X = sparse(X); end +else + if SP, [nrm,cnt] = normest(X,1e-3); + else nrm = norm(X); + end + if nrm > q + v = Inf; + end +end + + +function [ v, X ] = proj_spectral_svds_q( q,eproj, X, t ) +persistent oldRank +persistent nCalls +persistent V +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; V=[]; return; end +if isempty(nCalls), nCalls = 0; end +SP = issparse(X); +v = 0; +if nargin > 3 && t > 0, + + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + [M,N] = size(X); + + ok = false; + opts = []; + opts.tol = 1e-10; % the default in svds + opt = []; + opt.eta = eps; % makes compute_int slow + opt.delta = 10*opt.eta; + while ~ok + K = min( [K,M,N] ); + if exist('lansvd','file') + [U,D,V] = lansvd(X,K,'L',opt ); + else + [U,D,V] = svds(X,K,'L',opts); + end + ok = (min(abs(diag(D))) < q) || ( K == min(M,N) ); + if ok, break; end + K = 2*K; + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-3; + end + if K > min(M,N)/2 + [U,D,V] = svd( full(X), 'econ' ); + ok = true; + end + end + oldRank = length(find(abs(diag(D)) > q)); + + [dum,D_proj] = eproj(diag(D),q); + X = X - U*diag(diag(D)-D_proj)*V'; + + if SP, X = sparse(X); end +else + if SP, [nrm,cnt] = normest(X,1e-3); + else nrm = norm(X); + end + if nrm > q + v = Inf; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/prox_0.m b/prox_0.m new file mode 100644 index 0000000..ad3c93b --- /dev/null +++ b/prox_0.m @@ -0,0 +1,13 @@ +function op = prox_0 + +%PROX_0 The zero proximity function: +% OP = PROX_0 returns an implementation of the function OP(X) = 0. Use +% this function to specify a model with no nonsmooth component. It is +% identical to both PROJ_Rn and SMOOTH_CONSTANT( 0 ). +% Dual: proj_0.m + +op = smooth_constant( 0 ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_Ol1.m b/prox_Ol1.m new file mode 100644 index 0000000..738d983 --- /dev/null +++ b/prox_Ol1.m @@ -0,0 +1,87 @@ +function op = prox_Ol1( lambda ) +%PROX_OL1 Ordered L1 norm. +% OP = PROX_L1( lambda ) implements the nonsmooth function +% OP(X) = sum(lambda.*sort(abs(X),'descend')) +% where lambda is strictly positive and sorted in decreasing order, +% in which case this function is a norm (and hence convex). +% If lambda is a scalar, it will be expanded to all elements, but in this +% case it is equivalent to the (scaled) usual l1 norm. +% +% Notice: this function uses mex files. Some pre-compiled binaries +% for common systems are included; if these do not work for you, +% then please install yourself. In the mexFiles/ subdirectory, +% run the file "makeMex.m" +% Reference: +% http://www-stat.stanford.edu/~candes/OrderedL1/ +% "Statistical Estimation and Testing via the Ordered L1 Norm" +% by M. Bogdan, E. van den Berg, W. Su, and E. J. Candès +% 2013 +% +% See also prox_l1.m, makeMex.m + +if nargin == 0, + lambda = 1; +elseif ~isnumeric( lambda ) || ~isreal( lambda ) || any( lambda <= 0 ) + error( 'Argument lambda must have all positive entries.' ); +end +if ~issorted(flipud(lambda(:))) + error( 'Argument lambda must be sorted in decreasing order.'); +end +if numel(lambda)==1 + warning('TFOCS:prox_OL1','When lambda is a scalar, we recommend prox_l1.m instead pf prox_OL1.m'); +end + + +% The mex file is in the child directory mexFiles/ +% Check for its existence. First, add the right paths +addpath(fullfile(tfocs_where,'mexFiles')); +if 3 ~= exist('proxAdaptiveL1Mex','file') + makeMex; + % check that it worked + if 3 ~= exist('proxAdaptiveL1Mex','file') + disp('Compilation of mex files for prox_OL1.m failed; please report this error'); + end +end + +f = @(x) sum( lambda(:) .* sort(abs(x(:)), 'descend') ); +prox_f = @(x,t) proxOrderedL1(x,t.*lambda); + +op = tfocs_prox( f, prox_f , 'vector' ); % Allow vector stepsizes + + +end + +% -- subroutines -- +function x = proxOrderedL1(y,lambda) + % Normalization + lambda = lambda(:); + y = y(:); + sgn = sign(y); + [y,idx] = sort(abs(y),'descend'); + + % Simplify the problem + k = find(y > lambda,1,'last'); + + % Compute solution and re-normalize + n = numel(y); + x = zeros(n,1); + + if (~isempty(k)) + v1 = y(1:k); + if numel(lambda) > 1 + v2 = lambda(1:k); + else + v2 = lambda*ones(k,1); % if lambda is a scalar, implicity make it lambda*ones(size(y)) + end + v = proxAdaptiveL1Mex(v1,v2); + x(idx(1:k)) = v; + end + + % Restore signs + x = sgn .* x; +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_boxDual.m b/prox_boxDual.m new file mode 100644 index 0000000..f5c03f1 --- /dev/null +++ b/prox_boxDual.m @@ -0,0 +1,82 @@ +function op = prox_boxDual(l,u,scale) +%PROX_BOXDUAL Dual function of box indicator function { l <= x <= u } +%PROX_BOXDUAL(l,u) Dual function of box indicator function { l <= x <= u } +% If l or u is the empty matrix [], then the constraint is not +% enforced (e.g. PROJ_BOXDUAL([],1) is the set { x <= 1 }, +% and PROJ_BOXDUAL(0) is the set { 0 <= x } ) +% The parameters l and u may also be vectors or matrixes +% of the same size as x. +% +% OP = PROJ_BOXDUAL returns an implementation of this projection. +% +% ... = PROJ_BOXDUAL( l, u, -1 ) +% will return an implementation of the projection composed +% with the operator x --> -x. This is the form that TFOCS +% expects for the "conjnegeF" term. +% +% See also proj_box, proj_Rplus + + +error(nargchk(1,3,nargin)); +if nargin < 3, scale = 1; +else + if ~isscalar(scale) || ( scale ~= 1 && scale ~= -1 ) + error('"scale" must be either +1 or -1'); + end +end +if nargin < 2, u = []; end +% check that l <= u +if ~isempty(l) && ~isempty(u) + if ~all( l <= u ) + error('for box constraints, need l <= u '); + end +end +if isempty(l), l = -Inf; end +if isempty(u), u = Inf; end + +op = @(varargin)proj_RplusDual_impl(l,u,scale,varargin{:}); + +function [ v, xOut ] = proj_RplusDual_impl( l, u, scale, x, t ) + if scale ~= 1 + x = scale*x; + end +switch nargin, + case 4, + if nargout == 2, + error( 'This function is not differentiable.' ); + end + v = sum(sum(max( l.*x, u.*x ))); + case 5, + xOut = zeros( size(x) ); + if ~isempty(l) + indx1 = find( x < t*l ); + if isscalar(l) + xOut( indx1 ) = x( indx1 ) - t*l; + else + xOut( indx1 ) = x( indx1 ) - t*l( indx1 ); + end + end + if ~isempty(u) + indx2 = find( x > t*u ); + if isscalar(u) + xOut( indx2 ) = x( indx2 ) - t*u; + else + xOut( indx2 ) = x( indx2 ) - t*u( indx2 ); + end + end + % and implicity, if l/t < x < u/t, then xOut is 0 + + if scale ~= 1 + xOut = scale*xOut; + end + + v = sum(sum( max( l.*xOut, u.*xOut ) )); +% v = sum(sum( max( [l.*xOut, u.*xOut] ) )); + otherwise, + error( 'Wrong number of arguments.' ); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_dualize.m b/prox_dualize.m new file mode 100644 index 0000000..1004ba1 --- /dev/null +++ b/prox_dualize.m @@ -0,0 +1,94 @@ +function op = prox_dualize( dualProx, NEG ) +%PROX_DUALIZE Define a proximity function by its dual +% OP = PROX_DUALIZE( dualOp ) returns an operator implementing the +% dual of the function dualProx. You can verify they are duals +% via test_proxPair( dualOp, OP ). +% +% OP = PROX_DUALIZE( dualOp, 'neg' ) +% OP = PROX_DUALIZE( dualOp, 'negative' ) +% will return the scaled dual of dualOp; that is, +% dualOp(x) and OP(-x) are duals. +% The negative is useful because this is the version TFOCS +% expects for the SCD formulation. +% For 1-homogenous functions (e.g. norms), this has no effect, +% since ||x|| = ||-x||. +% +% Warning: if you can calculate the dual function explicitly, +% it is likely more computationally efficient to do so, rather +% than rely on this code. This code requires some tricks, some +% of which can sometimes be expensive; also, it will call dualOp +% so it is at least as slow as dualOp; and it may require high +% precision from dualOp. This code can break down if dualOp +% is not numerically stable. + +if nargin < 2, NEG = ''; end +if strcmpi(NEG,'neg') || strcmpi(NEG,'negative') + op = @(varargin)dualize(dualProx,-1,varargin{:} ); +else + op = @(varargin)dualize(dualProx,1,varargin{:} ); +end + +function [ v, x ] = dualize( dualProx, scale, x, t ) +vec = @(x) x(:); +myDot = @(x,y) x(:)'*y(:); +if scale == -1 + x = -x; +end +switch nargin, + case 3 + if nargout == 2, + error( 'This function is not differentiable.' ); + else + % This case is a bit tricky... + % If the function is non-differentiable, then standard exact + % penalty function results tell us that for a sufficiently + % small stepsize, we can remove the effect of smoothing. + % In other cases, we don't have an exact value, but we hope this + % is a reasonable approximation. + +% t = 1e-15; +% [~,x2] = dualProx( x/t, 1/t ); +% v = myDot(x,x2) - dualProx( x2 ); + + % However, some functions break down when 1/t is huge + % So we will slowly decrease it + vOld = Inf; + t = 1e-5; + ok = false; + iter = 0; + while ~ok && t > eps + [~,x2] = dualProx( x/t, 1/t ); + v = myDot(x,x2) - dualProx( x2 ); + if abs(v-vOld)/max( 1e-10, abs(v) ) < 1e-4 + % due to exact penalty, we expect that + % for t < t_cutoff, v=vOld up to machine accuracy + ok = true; + else + t = t/10; + vOld = v; + iter = iter + 1; + %fprintf('%d and v is %.2e\n', iter, v ); + end + end + + end + + case 4 + % This is exact. + [ignore,x2] = dualProx( x/t, 1/t ); + x1 = x - t*x2; % Moreau's identity, equation (8.1) in the user guide + v = myDot(x1,x2) - dualProx( x2 ); + + % If we think it is an indicator function, then round down to zero: + if abs(v) < 100*eps, + v = 0; + end + x = scale*x1; + + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_hinge.m b/prox_hinge.m new file mode 100644 index 0000000..e64f77b --- /dev/null +++ b/prox_hinge.m @@ -0,0 +1,76 @@ +function op = prox_hinge( q , r, y) + +%PROX_HINGE Hinge-loss function. +% OP = PROX_HINGE( q , r, y ) implements the nonsmooth function +% OP(X) = q * sum( max( r - y.*x, 0 ) ). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar. +% R is also optional; if omitted, R = 1 is assumed. R may be any real number. +% Y is also optional; if omitted, Y = 1 is assumed. Y may be any scalar +% or vector of the same size as X +% Dual: prox_hingeDual.m +% +% See also PROX_HINGEDUAL + +if nargin < 3 + y = []; +elseif ~isempty(y) && ( ~isnumeric(y) || ~isreal(y) ) + error( 'Argument 3 must be a real vector'); +end +if nargin < 2 + r = 1; +elseif ~isnumeric( r ) || ~isreal( r ) %|| numel( r ) ~= 1 + error( 'Argument 2 must be real.' ); +end +if nargin < 1 + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument 1 must be positive.' ); +end + +if isempty(y) + op = tfocs_prox( @(x)hinge(x,r,q), @(x,t)prox_hinge(x,t,r,q) ); +else + ry = r./y; + op = tfocs_prox( @(x)hingeY(x,r,q,y), @(x,t)prox_hingeY(x,t,r,q,ry,y) ); +end + + +% -- the actual functions -- + +function v = hingeY(x,r,q,y) + if ~isscalar(r), assert( numel(r) == numel(x),'r is wrong size' ); end + if ~isscalar(y), assert( numel(y) == numel(x),'y is wrong size'); end + v = q*sum( max( r(:) - y(:).*x(:), 0 ) ); +end +function v = hinge(x,r,q) + if ~isscalar(r), assert( numel(r) == numel(x),'r is wrong size' ); end + v = q*sum( max( r(:) - x(:), 0 ) ); +end + + +% PROX_F( Y, t ) = argmin_X F(X) + 1/(2*t)*|| X - Y ||^2 +function x = prox_hinge(x,t,r,q) + tq = t * q; + x = r + (x-r).*( x > r ) + (x + tq - r).*( x + tq < r ); +end +%{ + in the q = r = t = 1 case, the prox is: +prox(x) = { x, if x > 1 + 1, if x <= 1, and x > 0 + x + q, if x <= 0 +%} + +function x = prox_hingeY(x,t,r,q,ry,y) + tq = t * q; + x = ry + (x-ry).*( y.*x > r ) + (x + tq*y - ry).*( y.*(x + tq*y) < r ); +end + + +end + +% Added Feb, 2011; modified Dec, 2011 + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_hingeDual.m b/prox_hingeDual.m new file mode 100644 index 0000000..cb97d19 --- /dev/null +++ b/prox_hingeDual.m @@ -0,0 +1,97 @@ +function op = prox_hingeDual( q , r, y) + +%PROX_HINGEDUAL Dual function of the Hinge-loss function. +% OP = PROX_HINGEDUAL( q , r , y) implements the nonsmooth function +% that is dual to the Hinge-loss function f, where +% f(x) = q * sum( max( r - y.*x, 0 ) ). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar. +% R is also optional; if omitted, R = 1 is assumed. R may be any real scalar. +% +% There is a simple form for the dual and its proximity operator. +% In the case q = r = 1, the dual is: +% f^*(y) = { +y, if y >= -1 and y <= 0 +% { +Inf, otherwise +% +% See also PROX_HINGE +% +% Note: if the primal is PROX_HINGE( q, r, y ) +% then TFOCS expects conjnegF to be PROX_HINGEDUAL( q, r, -y) +% (the -y is because the conjugate should be composed +% with the function x --> -x ) + +if nargin < 3 + y = []; +elseif ~isempty(y) && ( ~isnumeric(y) || ~isreal(y) ) + error( 'Argument 3 must be a real vector'); +end +if nargin < 2 || isempty(r) + r = 1; +elseif ~isnumeric( r ) || ~isreal( r ) %|| numel( r ) ~= 1 + error( 'Argument must be real.' ); +end +if nargin < 1 || isempty(q) + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end + +if isempty(y) + if isscalar(r) + sumXR = @(x) r*sum(x); + else + sumXR = @(x) x'*r; + end + op = tfocs_prox( @(x)hingeDual(x,q,sumXR), @(x,t)prox_hingeDual(x,t,r,q) ); +else + ry = r./y; + qy = -q.*abs(y); + sy = sign(y); + if isscalar(r) && isscalar( y ) + sumXR = @(x) ry*sum(x); + else + sumXR = @(x) x'*ry; + end + op = tfocs_prox( @(x)hingeDualY(x,sy,qy,sumXR), @(x,t)prox_hingeDualY(x,t,sy,ry,qy) ); +end + + +function v = hingeDual(x,q,sumXR) + feasible = ( x >= -q & x <= 0 ); + if any( ~feasible ) + v = Inf; + return; + end +% v = sum( x*r ); + v = sumXR( x ); +% v = sum( x*r.*( feasible ) + Inf.*( ~feasible ) ); % 0*Inf leads to NaN. bad! +end + +function v = hingeDualY(x,sy,qy,sumXR) +% feasible = ( sign(y).*x >= -q.*abs(y) & sign(y).*x <= 0 ); + feasible = ( sy.*x >= qy & sy.*x <= 0 ); % using precomputed vectors + if any( ~feasible ) + v = Inf; + return; + end +% v = sum( x.*ry ); + v = sumXR(x); +end + + +% PROX_F( Y, t ) = argmin_X F(X) + 1/(2*t)*|| X - Y ||^2 +function x = prox_hingeDual(x,t,r,q) + x = max( min( x - t*r, 0), -q ); +end +function x = prox_hingeDualY(x,t,sy,ry,qy) + x = sy.*max( min( sy.*(x - t*ry), 0), qy ); +end + + +end + +% Added Feb, 2011; support for y added Dec 2011 + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_l1.m b/prox_l1.m new file mode 100644 index 0000000..23f18cd --- /dev/null +++ b/prox_l1.m @@ -0,0 +1,54 @@ +function op = prox_l1( q ) + +%PROX_L1 L1 norm. +% OP = PROX_L1( q ) implements the nonsmooth function +% OP(X) = norm(q.*X,1). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar (or must be same size as X). +% Dual: proj_linf.m + +% Update Feb 2011, allowing q to be a vector +% Update Mar 2012, allow stepsize to be a vector + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || any( q < 0 ) || all(q==0) %|| numel( q ) ~= 1 + error( 'Argument must be positive.' ); +end + +% The following commented code works fine in Matlab, +% but doesn't work in Octave due to different conventions on nesting +% functions. We keep in in the comments since it may be useful +% as an example of building a prox function + +% op = tfocs_prox( @f, @prox_f , 'vector' ); % Allow vector stepsizes +% function v = f(x) +% v = norm( q(:).*x(:), 1 ); +% end +% function x = prox_f(x,t) +% tq = t .* q; % March 2012, allowing vectorized stepsizes +% s = 1 - min( tq./abs(x), 1 ); +% x = x .* s; +% end +% end % end of main file + +% This is Matlab and Octave compatible code +op = tfocs_prox( @(x)f(q,x), @(x,t)prox_f(q,x,t) , 'vector' ); +end + +% These are now subroutines, that are NOT in the same scope +function v = f(qq,x) + v = norm( qq(:).*x(:), 1 ); +end + +function x = prox_f(qq,x,t) + tq = t .* qq; % March 2012, allowing vectorized stepsizes + s = 1 - min( tq./abs(x), 1 ); + x = x .* s; +end + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_l1l2.m b/prox_l1l2.m new file mode 100644 index 0000000..3a69952 --- /dev/null +++ b/prox_l1l2.m @@ -0,0 +1,66 @@ +function op = prox_l1l2( q ) + +%PROX_L1L2 L1-L2 block norm: sum of L2 norms of rows. +% OP = PROX_L1L2( q ) implements the nonsmooth function +% OP(X) = q * sum_{i=1:m} norm(X(i,:),2) +% where X is a m x n matrix. If n = 1, this is equivalent +% to PROX_L1 +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be positive and real. +% If Q is a vector, it must be m x 1, and in this case, +% the weighted norm OP(X) = sum_{i} Q(i)*norm(X(i,:),2) +% is calculated. +% +% Dual: proj_linfl2.m +% See also proj_linfl2.m + +if nargin == 0, + q = 1; +% elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, +elseif ~isnumeric( q ) || ~isreal( q ) || any(q <= 0), + error( 'Argument must be positive.' ); +end +% In r2007a and later, we can use bsxfun instead of the spdiags trick +if exist('OCTAVE_VERSION','builtin') + vr = '2010'; +else + vr=version('-release'); +end +if str2num(vr(1:4)) >= 2007 + op = tfocs_prox( @(x)f(q,x), @(x,t)prox_f_bsxfun(q,x,t) ); +else + % the default, using spdiags + op = tfocs_prox( @(x)f(q,x), @(x,t)prox_f(q,x,t) ); +end + +function v = f(q,x) + if numel(q) ~= 1 && size(q,1) ~= size(x,1) + error('Weight must be a scalar or a column vector'); + end + v = sum( q.* sqrt(sum(x.^2,2)) ); +end + +function x = prox_f(q,x,t) + if nargin < 3, + error( 'Not enough arguments.' ); + end + % v = sqrt( tfocs_normsq( x ) ); + v = sqrt( sum(x.^2,2) ); + s = 1 - 1 ./ max( v ./ ( t .* q ), 1 ); + m = length(s); + x = spdiags(s,0,m,m)*x; +end +function x = prox_f_bsxfun(q,x,t) + if nargin < 3, + error( 'Not enough arguments.' ); + end + v = sqrt( sum(x.^2,2) ); + s = 1 - 1 ./ max( v ./ ( t .* q ), 1 ); + x = bsxfun( @times, x, s ); % Apr 24 2013. Should be faster +end + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_l1linf.m b/prox_l1linf.m new file mode 100644 index 0000000..8358b01 --- /dev/null +++ b/prox_l1linf.m @@ -0,0 +1,107 @@ +function op = prox_l1linf( q ) + +%PROX_L1LINF L1-LInf block norm: sum of L2 norms of rows. +% OP = PROX_L1LINF( q ) implements the nonsmooth function +% OP(X) = q * sum_{i=1:m} norm(X(i,:),Inf) +% where X is a m x n matrix. If n = 1, this is equivalent +% to PROX_L1 +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be positive and real. +% If Q is a vector, it must be m x 1, and in this case, +% the weighted norm OP(X) = sum_{i} Q(i)*norm(X(i,:),Inf) +% is calculated. + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || any(q <= 0), + error( 'Argument must be positive.' ); +end +op = tfocs_prox( @(x)f(x,q), @(x,t)prox_f(x,t,q), 'vector' ); +end % end of main function + +function v = f(x,q) + if numel(q) ~= 1 && size(q,1) ~= size(x,1) + error('Weight must be a scalar or a column vector'); + end + v = sum( q.* max(abs(x),[],2) ); +end + +function x = prox_f(x,t,q) + if nargin < 2, + error( 'Not enough arguments.' ); + end + + [n,d] = size(x); + dim = 2; + + % Option 1: explicitly call prox_linf on the rows: +% for k= 1:n +% if isscalar(q), qk = q; +% else, qk = q(k); +% end +% x(k,:) = prox_linf_q( qk, x(k,:).', t ).'; +% end +% return; + + + +% Option 2: vectorize the call. By far, more efficient than option 1 + s = sort( abs(x), dim, 'descend' ); + cs = cumsum(s,dim); + s = [s(:,2:end), zeros(n,1)]; + + + ndx1 = zeros(n,1); + ndx2 = zeros(n,1); + + if isscalar(q), + tq = repmat( t*q, n, d ); + else + tq = repmat( t*q, 1, d ); + end + Z = cs - s*diag(1:d); + Z = ( Z >= tq ); + Z = Z.'; + + + % Not sure how to vectorize the find. + % One option is to use the [i,j] = find(...) form, + % but that's also extra work, since we can't just find the "first". + % So for now, making a for loop + for k = 1:n + ndxk = find( Z(:,k), 1 ); + if ~isempty(ndxk) + ndx1(k) = ndxk; + ndx2(k) = ndxk; + else + ndx1(k) = 1; % value doesn't matter + ndx2(k) = Inf; + end + end + indx_cs = sub2ind( [n,d], (1:n)', ndx1 ); + tau = (cs(indx_cs) - tq(:,1))./ndx2; + tau = repmat( tau, 1, d ); + tau_noZeros = tau; + tau_noZeros( ~x ) = 1; + x = x .* ( tau ./ max( abs(x), tau_noZeros ) ); + + +end + +% Some old code: +% function x = prox_linf_q( q, x, t ) +% +% s = sort( abs(nonzeros(x)), 'descend' ); +% cs = cumsum(s); +% ndx = find( cs - (1:numel(s))' .* [s(2:end);0] >= t * q, 1 ); +% if ~isempty( ndx ), +% tau = ( cs(ndx) - t * q ) / ndx; +% x = x .* ( tau ./ max( abs(x), tau ) ); +% else +% x = 0; +% end +% end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_l1pos.m b/prox_l1pos.m new file mode 100644 index 0000000..f1e3e1e --- /dev/null +++ b/prox_l1pos.m @@ -0,0 +1,38 @@ +function op = prox_l1pos( q ) +%PROX_L1POS L1 norm, restricted to x >= 0 +% OP = PROX_L1( q ) implements the nonsmooth function +% OP(X) = norm(q.*X,1) + indicator_{ X >= 0 } +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar (or must be same size as X). + +% New in v1.0d + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || any( q < 0 ) || all(q==0) %|| numel( q ) ~= 1 + error( 'Argument must be positive.' ); +end + +op = tfocs_prox( @(x)f(x,q), @(x,t)prox_f(x,t,q), 'vector'); +end + +function v = f(x,q) + if any( x(:) < 0 ) + v = Inf; + elseif isscalar(q) + v = q*sum( x(:) ); + else + v = sum( q(:).*x(:) ); + end +end + +% The proximity operator is a simplified version of shrinkage: +function x = prox_f(x,t,q) + x = max( 0, x - t*q ); +end + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_l2.m b/prox_l2.m new file mode 100644 index 0000000..b2412c5 --- /dev/null +++ b/prox_l2.m @@ -0,0 +1,96 @@ +function op = prox_l2( q ) + +%PROX_L2 L2 norm. +% OP = PROX_L2( q ) implements the nonsmooth function +% OP(X) = q * norm(X,'fro'). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar. +% If Q is a vector or matrix of the same size and dimensions as X, +% then this uses an experimental code to compute the proximity operator +% of OP(x) = norm( q.*X, 'fro' ) +% In the limit q --> 0, this function acts like prox_0 (aka proj_Rn) +% Dual: proj_l2.m +% See also proj_l2, prox_0, proj_Rn + +% Feb '11, allowing for q to be a vector +% This is complicated, so not for certain +% A safer method is to use a linear operator to scale the variables + +if nargin == 0, + q = 1; +% elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, +elseif ~isnumeric( q ) || ~isreal( q ) %|| any( q < 0 ) || all(q==0) + error( 'Argument must be positive.' ); +end +if isscalar(q) + if any( q <= 0 ) + error('Scaling argument must be positive, real scalar. If q=0, use prox_0 instead'); + end + op = @(varargin)prox_l2_q( q, varargin{:} ); +else + if all(q==0), error('Argument must be nonzero'); end + warning('TFOCS:experimental','Using experimental feature of TFOCS'); + op = @(varargin)prox_l2_vector( q, varargin{:} ); +end + +function [ v, x ] = prox_l2_q( q, x, t ) +if nargin < 2, + error( 'Not enough arguments.' ); +end +v = sqrt( tfocs_normsq( x ) ); +if nargin == 3, + s = 1 - 1 ./ max( v / ( t * q ), 1 ); + + x = x * s; + v = v * s; % version A +elseif nargout == 2, + error( 'This function is not differentiable.' ); +end +v = q * v; % version A + + +% --------- experimental code ----------------------- +function [ v, x ] = prox_l2_vector( q, x, t ) +if nargin < 2, + error( 'Not enough arguments.' ); +end +v = sqrt( tfocs_normsq( q.*x ) ); % version B +if nargin == 3, +%{ + we need to solve for a scalar variable s = ||q.*x|| + (where x is the unknown solution) + + we have a fixed point equation: + s = f(s) := norm( q.*x_k ) where x_k = x_0/( 1 + t*q/s ) + + to solve this, we'll use Matlab's "fzero" to find the zero + of the function F(s) = f(s) - s + + Clearly, we need s >= 0, since it is ||q.*x|| + + If q is a scalar, we can solve explicitly: s = q*(norm(x0) - t) + +%} + + xk = @(s) x./( 1 + t*(q.^2)/s ); + f = @(s) norm( q.*xk(s) ); +% F = @(s) f(s) - s; + tq2 = t*(q.^2); + F = @(s) norm( (q.*x)./( 1 + tq2/s ) ) - s; + [s,sVal] = fzero( F, 1); + if abs( sVal ) > 1e-4 + error('cannot find a zero'); + end + if s <= 0 + x = 0*x; + else + x = xk(s); + end + v = sqrt( tfocs_normsq( q.*x ) ); % version B +elseif nargout == 2, + error( 'This function is not differentiable.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_linf.m b/prox_linf.m new file mode 100644 index 0000000..24e2fcc --- /dev/null +++ b/prox_linf.m @@ -0,0 +1,42 @@ +function op = prox_linf( q ) + +%PROX_LINF L-infinity norm. +% OP = PROX_LINF( q ) implements the nonsmooth function +% OP(X) = q * norm( X(:), Inf ). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar. +% Dual: proj_l1.m +% See also proj_l1 + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +op = @(varargin)prox_linf_q( q, varargin{:} ); + +function [ v, x ] = prox_linf_q( q, x, t ) +if nargin < 2, + error( 'Not enough arguments.' ); +end +tau = norm( x(:), Inf ); +if nargin == 3, + s = sort( abs(nonzeros(x)), 'descend' ); % makes one big vector + + cs = cumsum(s); + ndx = find( cs - (1:numel(s))' .* [s(2:end);0] >= t * q, 1 ); + if ~isempty( ndx ), + tau = ( cs(ndx) - t * q ) / ndx; + x = x .* ( tau ./ max( abs(x), tau ) ); + else + x(:) = 0; % adding Oct 21 + tau = 0; + end +elseif nargout == 2, + error( 'This function is not differentiable.' ); +end +v = q * tau; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_max.m b/prox_max.m new file mode 100644 index 0000000..a3b7826 --- /dev/null +++ b/prox_max.m @@ -0,0 +1,53 @@ +function op = prox_max( q ) + +%PROX_MAX Entry-wise maximum element. +% OP = PROX_MAX( q ) implements the nonsmooth function +% OP(X) = q * max( X(:) ). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% then it must be a positive real scalar. +% Dual: proj_simplex.m (at least if X is a vector) +% See also proj_simplex, prox_linf, prox_linf + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +op = @(varargin)prox_lmax_q( q, varargin{:} ); + +function [ v, x ] = prox_lmax_q( q, x, t ) +if nargin < 2, + error( 'Not enough arguments.' ); +end +% We have two options when input x is a matrix: +% Does the user want to treat it as x(:), +% Or does the user want to treat each column separately? +% Most other functions (e.g. l1, linf) treat it as x(:) +% so that will be the default. However, we leave it +% as a hard-coded option so that the user can change it +% if they want. +% (Note: the dual function, proj_simplex, vectorizes it) +VECTORIZE = true; +% right now, we do not have the non-vectorized version implemented. + +tau = max( x(:) ); +if nargin == 3, + s = sort( nonzeros(x), 'descend' ); +% s = sort( x, 'descend' ); % 'nonzeros' does a x(:) operation + cs = cumsum(s); + ndx = find( cs - (1:numel(s))' .* [s(2:end);0] >= t * q, 1 ); + if ~isempty( ndx ), + tau = ( cs(ndx) - t * q ) / ndx; + x = x .* ( tau ./ max( x, tau ) ); + else + x(:) = 0; % adding Oct 21 + tau = 0; + end +elseif nargout == 2, + error( 'This function is not differentiable.' ); +end +v = q * tau; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_maxEig.m b/prox_maxEig.m new file mode 100644 index 0000000..b4d59d1 --- /dev/null +++ b/prox_maxEig.m @@ -0,0 +1,62 @@ +function op = prox_maxEig( q ) +%PROX_MAXEIG Maximum eigenvalue of a real symmetric matrix +% OP = PROX_MAXEIG( q ) implements the nonsmooth function +% OP(X) = q * max(eig(X)). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a real scalar. +% +% Note: if X is positive semi-definite, then max(eig(X)) == norm(eig(X),Inf) +% and hence prox_maxEig(q) is equivalent to prox_spectral(q,'eig') +% +% This implementation uses a naive approach that does not exploit any +% a priori knowledge that X is low rank or sparse. Future +% implementations of TFOCS will be able to handle low-rank matrices +% more effectively. +% +% Dual: proj_psdUTrace.m +% See also proj_psdUTrace, prox_max, prox_spectral, proj_nuclear + +% Note: it would be possible to use eigs for sparse matrices, +% but that requires a little bit more work than it did for proj_spectral. + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 %|| q <= 0, + error( 'Argument must be a real scalar.' ); +end +op = @(varargin)prox_spectral_eig_q( q, varargin{:} ); + +function [ v, X ] = prox_spectral_eig_q( q, X, t ) +% Assumes X is square and symmetric +% Therefore, all singular values are just absolute values +% of the eigenvalues. +if nargin < 2, + error( 'Not enough arguments.' ); +end +if size(X,1) ~= size(X,2) + error('prox_spectral: variable must be a square matrix'); +end +if norm( X - X', 'fro' ) > 1e-10*norm(X,'fro') + error('Input must be Hermitian'); +end +X = (X+X')/2; % Matlab will make it exactly symmetric + +if nargin == 3 && t > 0, + [V,S] = eig(full(X)); +% op = prox_linf(q); % for prox_spectral + op = prox_max(q); + s = diag(S); + [dummy,s] = op(s,t); + tau = max(s); + X = V*diag(s)*V'; +else + if nargout == 2 + error( 'This function is not differentiable.' ); + end + tau = max(eig(X)); +end +v = q * tau; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_nuclear.m b/prox_nuclear.m new file mode 100644 index 0000000..4ff1adf --- /dev/null +++ b/prox_nuclear.m @@ -0,0 +1,140 @@ +function op = prox_nuclear( q, LARGESCALE ) + +%PROX_NUCLEAR Nuclear norm. +% OP = PROX_NUCLEAR( q ) implements the nonsmooth function +% OP(X) = q * sum(svd(X)). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% +% OP = PROX_NUCLEAR( q, LARGESCALE ) +% uses a Lanczos-based SVD if LARGESCALE == true, +% otherwise it uses a dense matrix SVD +% +% CALLS = PROX_NUCLEAR( 'reset' ) +% resets the internal counter and returns the number of function +% calls +% +% This implementation uses a naive approach that does not exploit any +% a priori knowledge that X and G are low rank or sparse. Future +% implementations of TFOCS will be able to handle low-rank matrices +% more effectively. +% Dual: proj_spectral.m +% See also prox_trace.m and proj_spectral.m + +if nargin == 1 && strcmpi(q,'reset') + op = prox_nuclear_impl; + return; +end + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +if nargin < 2, LARGESCALE = []; end + +% clear the persistent values: +prox_nuclear_impl(); + +op = @(varargin)prox_nuclear_impl( q, LARGESCALE, varargin{:} ); + +end % end of main function + +function [ v, X ] = prox_nuclear_impl( q, LARGESCALE, X, t ) +persistent oldRank +persistent nCalls +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; return; end +if isempty(nCalls), nCalls = 0; end + +ND = (size(X,2) == 1); +% ND = ~ismatrix(X); +if ND, % X is a vector, not a matrix, so reshape it + sx = size(X); + X = reshape( X, prod(sx(1:end-1)), sx(end) ); +end + +if nargin > 3 && t > 0, + + if ~isempty(LARGESCALE) % inherited from parent + largescale = LARGESCALE; + else + largescale = ( numel(X) > 100^2 ) && issparse(X); + end + tau = q*t; + nCalls = nCalls + 1; + +% fprintf('ranks: '); + if ~largescale + [U,S,V] = svd( full(X), 'econ' ); + else + % Guess which singular value will have value near tau: + [M,N] = size(X); + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + + ok = false; + opts = []; + opts.tol = 1e-10; % the default in svds + opt = []; + opt.eta = eps; % makes compute_int slow +% opt.eta = 0; % makes reorth slow + opt.delta = 10*opt.eta; + while ~ok + K = min( [K,M,N] ); + if exist('lansvd','file') + [U,S,V] = lansvd(X,K,'L',opt ); + else + [U,S,V] = svds(X,K,'L',opts); + end + ok = (min(diag(S)) < tau) || ( K == min(M,N) ); +% fprintf('%d ',K ); + if ok, break; end +% K = K + 5; + K = 2*K; + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-1; + end + if K > min(M,N)/2 +% disp('Computing explicit SVD'); + [U,S,V] = svd( full(X), 'econ' ); + ok = true; + end + end + oldRank = length(find(diag(S) > tau)); + end + s = diag(S) - tau; + tt = s > 0; + s = s(tt,:); + +% fprintf('\n')'; +% fprintf('rank is %d\n', length(tt) ); + + % Check to make sure this doesn't break existing code... + if isempty(s), +% X(:) = 0; % this line breaks the packSVD version + X = tfocs_zeros(X); + else + X = U(:,tt) * bsxfun( @times, s, V(:,tt)' ); + end +else + s = svd(full(X)); % could be expensive! +end + +v = q * sum(s); +if ND, + X = reshape( X, sx ); +end + +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_scale.m b/prox_scale.m new file mode 100644 index 0000000..351aed3 --- /dev/null +++ b/prox_scale.m @@ -0,0 +1,54 @@ +function op = prox_scale( proxF, scale ) + +%PROX_SCALE Scaling a proximity/projection function. +% PSCALE = PROX_SCALE( PROXF, s ) is the proximity function formed +% by multiplying the input by the real value SCALE, then calling the +% function PROXF. In other words, +% PSCALE( y ) = PROXF( s * y ). +% In three-argument form, [v,x]=PSCALE(y,t) finds the minimizer of +% PROXF( s * x ) + (0.5/t)*||x-y||_2^2 +% +% Why scale the input, and not the output? When the proximity function +% is a norm, the two choices are equivalent when SCALE >= 0, since +% ||s*x|| = abs(s)*||x||. However, for *indicator* functions, scaling +% the output is useless. Scaling the input grows or shrinks the domain +% of PSCALE by choosing SCALE<1 or SCALE>1, respectively. +% +% Note that when constructing our equivalence we assume a particular +% form for the proximity minimization. More generally, PROJ_SCALE will +% preserve equivalence if 0.5*||x-y||^2 is replaced with D(x-y), as +% long as D() satisfies the following homogeneity property: +% D(s*z) = s^2*D(z) +% If this is not the case, you will not be able to use the TFOCS +% internal scaling with this function. + +if ~isa( proxF, 'function_handle' ), + error( 'The first argument must be a function handle.' ); +elseif ~isa( scale, 'double' ) || ~isreal( scale ) || numel( scale ) ~= 1 || scale == 0, + error( 'The second argument must be a nonzero real scalar.' ); +elseif scale == 1, + op = proxF; +else + op = @(varargin)prox_scale_impl( proxF, scale, varargin{:} ); +end + +function varargout = prox_scale_impl( proxF, s, y, t ) +no = max(nargout,1); +if nargin < 4 || t == 0, + [ varargout{1:no} ] = proxF( s * y ); +else + % For three-input mode, we wish to simulate + % x = argmin_x projectorF(s*x) + (0.5/t)*||x-y||^2 + % Setting z = s * x, we have + % z = argmin_z projectorF(z) + (0.5/(s^2*t))*||z-s*y||^2 + % Therefore, we have + % [v,z] = projectorF( s * y, t * s^2 ); x = z / s; + [ varargout{1:no} ] = proxF( s * y, s^2 * t ); +end +if no > 1, + varargout{2} = varargout{2} / s; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_spectral.m b/prox_spectral.m new file mode 100644 index 0000000..e51520f --- /dev/null +++ b/prox_spectral.m @@ -0,0 +1,105 @@ +function op = prox_spectral( q, SYM_FLAG ) +%PROX_SPECTRAL Spectral norm, i.e. max singular value. +% OP = PROX_SPECTRAL( q ) implements the nonsmooth function +% OP(X) = q * max(svd(X)). +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% +% OP = PROX_SPECTRAL( q , 'sym' ) or OP = PROX_SPECTRAL( q, 'eig' ) +% will instruct the code that the matrix is Hermitian and +% therefore the relations between singular- and eigen-values +% are well known, and the code will use the more efficient +% eigenvalue decomposition (instead of the SVD). +% +% This implementation uses a naive approach that does not exploit any +% a priori knowledge that X is low rank or sparse. Future +% implementations of TFOCS will be able to handle low-rank matrices +% more effectively. +% +% Dual: proj_nuclear.m +% See also proj_nuclear, prox_linf, prox_maxEig + +% Note: it would be possible to use eigs for sparse matrices, +% but that requires a little bit more work than it did for proj_spectral. + + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +if nargin >= 2 && ( ... + ~isempty(strfind(lower(SYM_FLAG),'eig')) || ... + ~isempty(strfind(lower(SYM_FLAG),'sym')) ) + op = @(varargin)prox_spectral_eig_q( q, varargin{:} ); +else + op = @(varargin)prox_spectral_impl( q, varargin{:} ); +end + +function [ v, X ] = prox_spectral_impl( q, X, t ) +if nargin < 2, + error( 'Not enough arguments.' ); +end + +if nargin == 3 && t > 0, + [U,S,V] = svd( full(X), 'econ' ); + s = diag(S); + tau = s(1); + + cs = cumsum(s); + ndx = find( cs - (1:numel(s))' .* [s(2:end);0] >= t * q, 1 ); + if ~isempty( ndx ), + tau = ( cs(ndx) - t * q ) / ndx; + s = s .* ( tau ./ max( abs(s), tau ) ); + end + X = U*diag(s)*V'; +else + if nargout == 2 + error( 'This function is not differentiable.' ); + end + if issparse(X) + tau = normest(X); + else + tau = norm(X); + end +end +v = q * tau; + +function [ v, X ] = prox_spectral_eig_q( q, X, t ) +% Assumes X is square and symmetric +% Therefore, all singular values are just absolute values +% of the eigenvalues. +if nargin < 2, + error( 'Not enough arguments.' ); +end +if size(X,1) ~= size(X,2) + error('prox_spectral: variable must be a square matrix'); +end +if norm( X - X', 'fro' ) > 1e-10*norm(X,'fro') + error('Input must be Hermitian'); +end +X = (X+X')/2; % Matlab will make it exactly symmetric + +if nargin == 3 && t > 0, + [V,S] = eig(full(X)); + op = prox_linf(q); + s = diag(S); + [dummy,s] = op(s,t); +% tau = max(s); % for SVD, s >= 0 so we can do max. Not so for eig. + tau = norm(s,Inf); + X = V*diag(s)*V'; +else + if nargout == 2 + error( 'This function is not differentiable.' ); + end + if issparse(X) + tau = normest(X); + else + tau = norm(X); + end +end +v = q * tau; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/prox_trace.m b/prox_trace.m new file mode 100644 index 0000000..b18f37c --- /dev/null +++ b/prox_trace.m @@ -0,0 +1,139 @@ +function op = prox_trace( q, LARGESCALE, isReal ) + +%PROX_TRACE Nuclear norm, for positive semidefinite matrices. Equivalent to trace. +% OP = PROX_TRACE( q ) implements the nonsmooth function +% OP(X) = q * sum(svd(X)) = q*tr(X) ( X >= 0 assumed ) +% Q is optional; if omitted, Q=1 is assumed. But if Q is supplied, +% it must be a positive real scalar. +% This function is a combination of the proximity function of the trace +% and projection onto the set of symmetric/Hermitian matrices. +% +% OP = PROX_TRACE( q, LARGESCALE ) +% uses a Lanczos-based Eigenvalue decomposition if LARGESCALE == true, +% otherwise it uses a dense matrix Eigenvalue decomposition +% +% OP = PROX_TRACE( q, LARGESCALE, isReal ) +% also projects onto the set of real matrices if isReal=true. +% +% CALLS = PROX_TRACE( 'reset' ) +% resets the internal counter and returns the number of function +% calls +% +% This implementation uses a naive approach that does not exploit any +% a priori knowledge that X and G may be low rank (plus sparse). Future +% implementations of TFOCS will be able to handle low-rank matrices +% more effectively. +% Dual: proj_spectral(q,'symm') +% See also proj_spectral, prox_nuclear + +if nargin == 1 && strcmpi(q,'reset') + op = prox_trace_impl; + return; +end + +if nargin == 0, + q = 1; +elseif ~isnumeric( q ) || ~isreal( q ) || numel( q ) ~= 1 || q <= 0, + error( 'Argument must be positive.' ); +end +if nargin < 2, LARGESCALE = []; end +if nargin < 3 || isempty(isReal), isReal = false; end + +% clear the persistent values: +prox_trace_impl(); + +op = @(varargin)prox_trace_impl( q, LARGESCALE, isReal, varargin{:} ); +end + +function [ v, X ] = prox_trace_impl( q,LARGESCALE, isReal, X, t ) +persistent oldRank +persistent nCalls +persistent V +if nargin == 0, oldRank = []; v = nCalls; nCalls = []; V=[]; return; end +if isempty(nCalls), nCalls = 0; end + +if nargin >= 5 && t > 0, + + if ~isempty(LARGESCALE) + largescale = LARGESCALE; + else + largescale = ( numel(X) > 100^2 ) && issparse(X); + end + tau = q*t; + nCalls = nCalls + 1; + + if ~largescale + if isReal + [V,D] = eig(real(full((X+X')/2))); + else + [V,D] = eig(full((X+X')/2)); + end + else + + % Guess which eigenvalue value will have value near tau: + [M,N] = size(X); + X = (X+X')/2; + if isReal, X = real(X); end + if isempty(oldRank), K = 10; + else, K = oldRank + 2; + end + + ok = false; + opts = []; + opts.tol = 1e-10; + if isreal(X) + opts.issym = true; + SIGMA = 'LA'; + else + SIGMA = 'LR'; + end + % SIMGA = 'LM' (bug) prior to March 18 2012 + while ~ok + K = min( [K,M,N] ); + + if K > min(M,N)/2 + [V,D] = eig(full((X+X')/2)); + ok = true; + break; + end + + [V,D] = eigs( X, K, SIGMA, opts ); + ok = (min(diag(D)) < tau) || ( K == min(M,N) ); + if ok, break; end + K = 2*K; + if K > 10 + opts.tol = 1e-6; + end + if K > 40 + opts.tol = 1e-4; + end + if K > 100 + opts.tol = 1e-3; + end + + end + oldRank = length(find(diag(D) > tau)); + end + s = diag(D) - tau; + tt = s > 0; + s = s(tt,:); + + if isempty(s), + X = tfocs_zeros(X); + else + X = V(:,tt) * bsxfun( @times, s, V(:,tt)' ); + % And force it to be symmetric + X = (X+X')/2; + end + v = q * sum(s); + +else + v = q* trace( X + X' )/2; +end + +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_constant.m b/smooth_constant.m new file mode 100644 index 0000000..038249a --- /dev/null +++ b/smooth_constant.m @@ -0,0 +1,29 @@ +function op = smooth_constant( d ) + +%SMOOTH_CONSTANT Constant function generation. +% FUNC = SMOOTH_CONSTANT( D ) returns a function handle that provides +% a TFOCS-compatible implementation of the constant function F(X) = D. +% D must be a real scalar. The function can be used in both a smooth +% and a nonsmooth context. + +error(nargchk(1,1,nargin)); +if ~isa( d, 'double' ) || ~isreal( d ) || numel( d ) ~= 1, + error( 'Argument must be a real scalar.' ); +end +op = @(varargin)smooth_constant_impl( d, varargin{:} ); + +function [ v, g ] = smooth_constant_impl( v, x, t ) +switch nargin, + case 2, + if nargin > 1, + g = 0 * x; + end + case 3, + g = x; + otherwise, + error( 'Not enough arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_entropy.m b/smooth_entropy.m new file mode 100644 index 0000000..6f2d32e --- /dev/null +++ b/smooth_entropy.m @@ -0,0 +1,21 @@ +function op = smooth_entropy() +%SMOOTH_ENTROPY The entropy function -sum( x_i log(x_i) ) +op = @smooth_entropy_impl; + +function [ v, g ] = smooth_entropy_impl( x ) +if any( x < 0 ), + v = -Inf; + if nargout > 1, + g = NaN * ones(size(x)); + end +else + logx = log(max(x,realmin)); + v = - tfocs_dot( x, logx ); + if nargout > 1, + g = - logx - 1; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_handles.m b/smooth_handles.m new file mode 100644 index 0000000..e15ee7a --- /dev/null +++ b/smooth_handles.m @@ -0,0 +1,27 @@ +function op = smooth_handles( func, grad ) + +%SMOOTH_HANDLES Smooth function from separate f/g handles. +% OP = SMOOTH_HANDLES( func, grad ) constructs a TFOCS-compatible +% smooth function from separate handles for computing the function +% value and gradient. +% +% See also private/tfocs_smooth + +op = @(varargin)smooth_handles_impl( func, grad, varargin{:} ); + +function [ f, g ] = smooth_handles_impl( fop, gop, x, t ) +switch nargin, + case 3, + f = fop( x ); + if nargout > 1, + g = gop( x ); + end + case 4, + error( 'This function does not support proximity minimizaztion.' ); + case { 0, 1, 2 }, + error( 'Not enough input arguments.' ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_huber.m b/smooth_huber.m new file mode 100644 index 0000000..63f6f07 --- /dev/null +++ b/smooth_huber.m @@ -0,0 +1,47 @@ +function op = smooth_huber(tau ) + +%SMOOTH_HUBER Huber function generation. +% FUNC = SMOOTH_QUAD( TAU ) returns a function handle that implements +% +% FUNC(X) = 0.5 *( x.^2 )/tau if |x| <= tau +% = |x| - tau/2 if |x| > tau +% +% All arguments are optional; the default value is tau = 1. +% The Huber function has continuous gradient and is convex. +% +% The function acts component-wise. TAU may be either a scalar +% or a vector/matrix of the same size as X +% +% Does not support nonsmooth usage yet + +% Does not yet fully support tfocs_tuples + +if nargin == 0, + tau = 1; +end +% op = @(varargin) smooth_huber_impl(tau, varargin{:} ); % old method + +op = tfocs_smooth( @smooth_huber_impl ); + + +% function [ v, g ] = smooth_huber_impl(tau, x, t ) % old method +function [ v, g ] = smooth_huber_impl(x) + if nargin == 3, + error( 'Proximity minimization not supported by this function.' ); + end + if ~isscalar(tau) && ~size(tau) == size(x) + error('smooth_huber: tau must be a scalar or the same size as the variable'); + end + smallSet = ( abs(x) <= tau ); + v = smallSet.*( 0.5*(x.^2)./tau ) + (~smallSet).*( abs(x) - tau/2) ; + if nargout > 1 + g = sign(x).*min( 1, abs(x)./tau ); + end +end % new method + +end % new method + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_linear.m b/smooth_linear.m new file mode 100644 index 0000000..db272e9 --- /dev/null +++ b/smooth_linear.m @@ -0,0 +1,38 @@ +function op = smooth_linear( c, d ) + +%SMOOTH_LINEAR Linear function generation. +% FUNC = SMOOTH_LINEAR( C, D ) returns a function handle that provides a +% TFOCS-compatible implementation of a linear function: if +% [F,G] = FUNC(X), +% then F = TFOCS_DOT(C,X)+D and G = C. D is optional; if omitted, then +% D == 0 is assumed. But if it is supplied, D must be a real scalar. +% If C == 0, then this function is equivalent to SMOOTH_CONSTANT( D ). +% This function can be used in both smooth and non-smooth contexts. + +error(nargchk(1,2,nargin)); +if nargin < 2, + d = 0; +elseif ~isa( d, 'double' ) || ~isreal( d ) || numel( d ) ~= 1, + error( 'Second argument must be a real scalar.' ); +end +if nnz(c), + op = @(varargin)smooth_linear_impl( c, d, varargin{:} ); +else + op = smooth_constant( d ); +end + +function [ v, g ] = smooth_linear_impl( c, d, x, t ) +switch nargin, + case 3, + g = c; + case 4, + x = x - t * c; + g = x; + otherwise, + error( 'Not enough arguments.' ); +end +v = tfocs_dot( c, x ) + d; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_logLLogistic.m b/smooth_logLLogistic.m new file mode 100644 index 0000000..c8dd0b9 --- /dev/null +++ b/smooth_logLLogistic.m @@ -0,0 +1,41 @@ +function op = smooth_logLLogistic(y) +% SMOOTH_LOGLLOGISTIC Log-likelihood function of a logistic: sum_i( y_i mu_i - log( 1+exp(mu_i) ) ) +% OP = SMOOTH_LOGLLOGISTIC( Y ) +% returns a function that computes the log-likelihood function +% in a standard logistic regression model with independent entries. There +% are two classes y_i = 0 and y_i = 1 with +% +% prob(y_i = 1) = exp(mu_i)/(1 + exp(mu_i) +% +% so that the log-likelihood is given by +% +% log-likelihood(mu) = sum_i ( y_i mu_i - log(1+ exp(mu_i)) ) +% +% where mu is the parameter of the distribution (this is unknown, +% so it is the variable), and Y is a vector of observations. + +error(nargchk(1,1,nargin)); +op = tfocs_smooth( @smooth_logLlogistic_impl); + +function [ v, g ] = smooth_logLlogistic_impl( mu ) + + if length(mu) == 1, + mu = mu * ones(size(y)); + elseif size(mu) ~= size(y), + error('Parameters and data must be of the same size'), + end + + aux = 1 + exp(-abs(mu)); + v = tfocs_dot(y-1,mu.*(mu > 0)) ... + + tfocs_dot(y,mu.*(mu < 0)) ... + - tfocs_dot(ones(size(y)), log(aux)); + if nargout > 1, + g = y - ((mu > 0) + (mu <= 0).*exp(mu))./aux; + end +end + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_logLPoisson.m b/smooth_logLPoisson.m new file mode 100644 index 0000000..9c14ea3 --- /dev/null +++ b/smooth_logLPoisson.m @@ -0,0 +1,43 @@ +function op = smooth_logLPoisson(x) +% SMOOTH_LOGLPOISSON Log-likelihood of a Poisson: sum_i (-lambda_i + x_i * log( lambda_i) ) +% OP = SMOOTH_LOGLPOISSON( X ) +% returns a function that computes the log-likelihood function +% of independent Poisson random variables with parameters lambda_i: +% +% log-likelihood(lambda) = sum_i ( -lambda_i + x_i * log( lambda_i) ) +% +% where LAMBDA is the parameter of the distribution (this is unknown, +% so it is the variable), and X is a vector of observations. +% +% Note: the constant term in the log-likelihood is omitted. + +error(nargchk(1,1,nargin)); +op = tfocs_smooth( @smooth_llPoisson_impl ); + +function [ v, g ] = smooth_llPoisson_impl( lambda ) + + if length(lambda) == 1, + lambda = lambda * ones(size(x)); + elseif size(lambda) ~= size(x), + error('Parameters and data must be of the same size'), + end + + if any( lambda < 0 ), + v = -Inf; + if nargout > 1, + g = NaN * ones(size(x)); + end + else + loglambda = log(max(lambda,realmin)); + v = - tfocs_dot(lambda, ones(size(x))) + tfocs_dot( x, loglambda); + if nargout > 1, + g = -1 + x./lambda; + end + end +end + +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_logdet.m b/smooth_logdet.m new file mode 100644 index 0000000..0f7b9c3 --- /dev/null +++ b/smooth_logdet.m @@ -0,0 +1,127 @@ +function op = smooth_logdet(q,C) +% SMOOTH_LOGDET The -log( det( X ) ) function. +% (Note the minus sign) +% FUNC = SMOOTH_LOGDET( q ) returns a function handle that +% provides a TFOCS-compatible implementation of the funciton +% -q*log( det( X ) ) +% +% FUNC = SMOOTH_LOGDET( q, C ) represents +% -q*log( det( X ) ) + < C, X >, where C is symmetric/Hermitian +% +% X must be symmetric/Hermitian and positive definite, +% and q must be a positive real number (if not provided, +% the default value is q = 1). +% +% N.B. it is the user's responsibility to ensure +% that X is Hermitian and pos. def., since +% automatically checking is expensive. +% +% This function is differentiable, but the gradient +% is not Lipschitz on the domain X > 0 +% +% This function does support proximity operations, and so +% it may be used as a nonsmooth function. +% However, the input must be symmetric positive definite +% (and if C is used, then it must also be > tC ) + +% SRB: have not yet tested this. +% SRB: I think we CAN compute the proximity operator to logdet. +% Will implement this in prox_logdet +if nargin < 1, q = 1; end +if nargin < 2, C = []; end +if ~isreal(q) || q <= 0 + error('First argument must be real and positive'); +end + +%op = @smooth_logdet_impl; +if isempty(C) + op = @(varargin)smooth_logdet_impl( q, varargin{:} ); +else + op = @(varargin)smooth_logdet_impl_C( q, C, varargin{:} ); +end + +function [ v, g ] = smooth_logdet_impl( q, x, t ) +if size(x,1) ~= size(x,2) + error('smooth_logdet: input must be a square matrix'); +end +switch nargin + case 2 + % the function is being used in a "smooth" fashion + %v = -log(det(x)); + v = -2*q*sum(log(diag(chol(x)))); % chol() takes half the time as det() + % and it is easier to avoid overflow errors + % since we sum the logs. + % Also, chol() will warn if not pos. def. + if nargout > 1 + g = -q*inv(x); + % it would be nice to make g a function handle + % that calculates g(y) = -x\y + end + + + case 3 + % the function is being used in a "nonsmooth" fashion + % i.e. return g = argmin_g -q*log(det(g)) + 1/(2t)||g-x||^2 + [V,D] = eig(x); + d = diag(D); + if any(d<=0), + v = Inf; + g = nan(size(x)); + return; +% error('log_det requires a positive definite point'); + end + l = ( d + sqrt( d.^2 + 4*t*q ) )/2; + g = V*diag(l)*V'; + v = -q*sum(log(l)); + otherwise + error('Wrong number of arguments'); +end + + +function [ v, g ] = smooth_logdet_impl_C( q, C, x, t ) +if size(x,1) ~= size(x,2) + error('smooth_logdet: input must be a square matrix'); +end +if size(C,1) ~= size(C,2) + error('smooth_logdet: input must be a square matrix'); +end +switch nargin + case 3 + % the function is being used in a "smooth" fashion + %v = -log(det(x)); + v = -2*q*sum(log(diag(chol(x)))); % chol() takes half the time as det() + % and it is easier to avoid overflow errors + % since we sum the logs. + % Also, chol() will warn if not pos. def. + v = v + tfocs_dot( C, x ); + if nargout > 1 + g = -q*inv(x) + C; + % it would be nice to make g a function handle + % that calculates g(y) = -x\y + end + + + case 4 + % the function is being used in a "nonsmooth" fashion + % i.e. return g = argmin_g -q*log(det(g)) + 1/(2t)||g-x||^2 + x = x - t*C; % hope that this remains pos. def.! + [V,D] = eig(x); + d = diag(D); + if any(d<=0), + v = Inf; + g = nan(size(x)); + return; +% error('log_det requires a positive definite point'); + end + l = ( d + sqrt( d.^2 + 4*t*q ) )/2; + g = V*diag(l)*V'; + v = -q*sum(log(l)); + v = v + tfocs_dot( C, g ); + otherwise + error('Wrong number of arguments'); +end + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_logsumexp.m b/smooth_logsumexp.m new file mode 100644 index 0000000..ed2d716 --- /dev/null +++ b/smooth_logsumexp.m @@ -0,0 +1,21 @@ +function op = smooth_logsumexp() +% SMOOTH_LOGSUMEXP The function log(sum(exp(x))) +% returns a smooth function to calculate +% log( sum( exp(x) ) ) +% +% For a fancier version (with offsets), +% see also smooth_LogLLogistic.m + +op = @smooth_logsumexp_impl; + +function [ v, g ] = smooth_logsumexp_impl( x ) +expx = exp(x); +sum_expx = sum(expx(:)); +v = log(sum_expx); +if nargout > 1, + g = expx ./ sum_expx; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/smooth_quad.m b/smooth_quad.m new file mode 100644 index 0000000..4ccd935 --- /dev/null +++ b/smooth_quad.m @@ -0,0 +1,152 @@ +function op = smooth_quad( P, q, r, use_eig ) + +%SMOOTH_QUAD Quadratic function generation. +% FUNC = SMOOTH_QUAD( P, q, r ) returns a function handle that implements +% +% FUNC(X) = 0.5 * TFOCS_DOT( P * x, x ) + TFOCS_DOT( q, x ) + r. +% +% All arguments are optional; the default values are P=I, q=0, r=0. In +% particular, calling FUNC = SMOOTH_QUAD with no arguments yields +% +% FUNC(X) = 0.5 * TFOCS_NORMSQ( X ) = 0.5 * TFOCS_DOT( X, X ). +% +% If supplied, P must be a scalar, square matrix, or symmetric linear +% operator. Furthermore, it must be positive semidefinite (convex) or +% negative semidefinite (concave). TFOCS does not verify operator +% symmetry or definiteness; that is your responsibility. +% If P is a vector, then it assumed this is the diagonal part of P. +% Note: when P is diagonal, this function can compute a proximity +% operator. If P is zero, then smooth_linear should be called instead. +% When P is an explicit matrix, this can also act as a proimity operator +% but it may be slow, since it must invert (I+tP). True use_eig mode (see below) +% +% If P is a scaling matrix, like the identity or a multiple of the identity +% (say, P*x = 5*x), then specifying the scaling factor is sufficient (in +% this example, 5). If P is empty, then P=1 is assumed. +% +% FUNC = SMOOTH_QUAD( P, q, r, use_eig ) +% will perform a one-time (but expensive) eigenvalue decomposition +% of P if use_eig=true, which will speed up future iterations. In general, +% this may be significantly faster, especially if you run +% an algorithm for many iterations. +% This mode is only useful when P is a full matrix and when +% smooth_quad is used as a proximity operator; does not affect +% it when used as a smooth operator. New in v 1.3. +% +% See also smooth_linear.m + +if nargin == 0, + op = @smooth_quad_simple; + return +end +if isa( P, 'function_handle' ), + sz = P([],0); + if ~isequal( sz{1}, sz{2} ), + error( 'P must be square.' ); + end +elseif ~isnumeric( P ), + error( 'P must be a scalar, matrix, or linear operator.' ); +elseif isempty(P) + P = 1; +elseif ndims( P ) > 2 || (~isvector(P) && size( P, 1 ) ~= size( P, 2 ) ), + error( 'P must be a square matrix.' ); +end +if nargin < 2 || isempty( q ), + q = 0; +elseif numel(P) > 1 && numel(q) > 1 && length(q) ~= size(P,1), + error( 'Dimension mismatch between p and q.' ); +end +if nargin < 3 || isempty( r ), + r = 0; +elseif numel(r) > 1 || ~isreal( r ), + error( 'r must be a real scalar.' ); +end +if nargin < 4, use_eig = false; end + +if isnumeric( P ), + if isvector( P ) + if any(P) < 0 && any(P) < 0 + error(' P must be convex (minimization) or concave (maximization) but cannot be mixed'); + end + P = P(:); % make it a column vector + op = @(varargin)smooth_quad_diag_matrix( P, q, r, varargin{:} ); + else + P = 0.5 * ( P + P' ); + if use_eig + [V,DD] = eig(P); dd = diag(DD); + else + V = []; dd = []; + end + op = @(varargin)smooth_quad_matrix( P, q, r, V, dd, varargin{:} ); + end +else + op = @(varargin)smooth_quad_linop( P, q, r, varargin{:} ); +end + +function [ v, g ] = smooth_quad_matrix( P, q, r, V, dd, x, t ) +switch nargin + case 6, + g = P * x + q; + v = 0.5 * tfocs_dot( x, g + q ) + r; + case 7, + n = length(x); + + if ~isempty(V) && ~isempty(dd) + % Use the stored eigenvalue decomposition to improve speed + x = V*( (V'*(x-t*q))./(t*dd+ones(n,1)) ); + else + x = (eye(n) + t*P)\ (x-t*q); + end + g = x; % for this case, "g" is NOT the gradient + v = 0.5 * tfocs_dot( P*x + 2*q, x ) + r; +end +%if nargin == 5, + %error( 'Proximity minimization not supported by this function.' ); +%end +% Note: we don't support proximity minimization, but there is +% nothing that prevents it theoretically. You would need to know P^-1 +% (and ideally calculate it quickly). +% In particular, if P is diagonal, then it is easy. +%g = P * x + q; +%v = 0.5 * tfocs_dot( x, g + q ) + r; + + + +% Jan 10, 2011: this function isn't correct for case 5 +% function [ v, g ] = smooth_quad_diag_matrix( p, q, r, x, t ) +% switch nargin +% case 4, +% case 5, +% x = (1./(t*p+1)) .* x; +% end +% g = p .* x + q; +% v = 0.5 * tfocs_dot( x, g + q ) + r; +function [ v, g ] = smooth_quad_diag_matrix( p, q, r, x, t ) +switch nargin + case 4, + g = p .* x + q; + v = 0.5 * tfocs_dot( x, g + q ) + r; + case 5, + x = (1./(t*p+1)) .* (x-t*q); + g = x; % for this case, "g" is NOT the gradient + v = 0.5 * tfocs_dot( p.*x + 2*q, x ) + r; +end + +function [ v, g ] = smooth_quad_linop( P, q, r, x, t ) +if nargin == 5, + error( 'Proximity minimization not supported by this function.' ); +end +g = P( x, 1 ) + q; +v = 0.5 * tfocs_dot( x, g + q ) + r; + +function [ v, x ] = smooth_quad_simple( x, t ) +switch nargin, + case 1, + case 2, + x = (1/(t+1)) * x; +end +v = 0.5 * tfocs_normsq( x ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/solver_L1RLS.m b/solver_L1RLS.m new file mode 100644 index 0000000..8d77c86 --- /dev/null +++ b/solver_L1RLS.m @@ -0,0 +1,25 @@ +function [ x, odata, opts ] = solver_L1RLS( A, b, lambda, x0, opts ) +% SOLVER_L1RLS l1-regularized least squares problem, sometimes called the LASSO. +% [ x, odata, opts ] = solver_L1RLS( A, b, lambda, x0, opts ) +% Solves the l1-regularized least squares problem +% minimize (1/2)*norm( A * x - b )^2 + lambda * norm( x, 1 ) +% using the Auslender/Teboulle variant with restart. A must be a matrix +% or a linear operator, b must be a vector, and lambda must be a real +% positive scalar. The initial point x0 and option structure opts are +% both optional. +% +% Note: this formulation is sometimes referred to as "The Lasso" + +error(nargchk(3,5,nargin)); +if nargin < 4, x0 = []; end +if nargin < 5, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 100; +end + +[x,odata,opts] = tfocs( smooth_quad, { A, -b }, prox_l1( lambda ), x0, opts ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_LASSO.m b/solver_LASSO.m new file mode 100644 index 0000000..fd3785e --- /dev/null +++ b/solver_LASSO.m @@ -0,0 +1,27 @@ +function [ x, out, opts ] = solver_LASSO( A, b, tau, x0, opts ) +% SOLVER_LASSO Minimize residual subject to l1-norm constraints. +% [ x, out, opts ] = solver_LASSO( A, b, tau, x0, opts ) +% Solves the LASSO in the standard Tibshirani formulation, +% minimize (1/2)*norm(A*x-b)^2 +% s.t. norm(x,1) <= tau +% A must be a linear operator or matrix, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% +% Note: many people use "LASSO" to refer to the problem: +% minimize 1/2*norm(A*x-b)^2 + lambda*norm(x,1) +% In TFOCS, this version is implemented in "solver_L1RLS" +% (which stands for "L1 regularized Least Squares" ) + +% Supply default values +error(nargchk(3,5,nargin)); +if nargin < 4, x0 = []; end +if nargin < 5, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 100; end + +% Extract the linear operators +[x,out,opts] = tfocs( smooth_quad, { A, -b }, proj_l1( tau ), x0, opts ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_OrderedLASSO.m b/solver_OrderedLASSO.m new file mode 100644 index 0000000..e300af0 --- /dev/null +++ b/solver_OrderedLASSO.m @@ -0,0 +1,29 @@ +function [ x, odata, opts ] = solver_OrderedLASSO( A, b, lambda, x0, opts ) +% SOLVER_ORDEREDLASSO l1-regularized least squares problem, sometimes called the LASSO, +% [ beta, odata, opts ] = solver_L1RLS( X, y, lambda, beta0, opts ) +% Solves the l1-regularized least squares problem, using the ordered l1 norm, +% minimize (1/2)*norm( A * x - b )^2 + norm( lasso.*sort(abs(x),'descend'), 1 ) +% using the Auslender/Teboulle variant with restart. X must be a matrix +% or a linear operator, y must be a vector, and lambda must be a real +% positive vector in decreasing order. +% The initial point beta0 and option structure opts are both optional. +% Reference: +% "Statistical Estimation and Testing via the Ordered l1 Norm" +% by M. Bogdan, E. van den Berg, W. Su, and E. J. Candès, 2013 +% http://www-stat.stanford.edu/~candes/OrderedL1/ +% +% See also solver_L1RLS.m, solver_LASSO.m, prox_Ol1.m + +error(nargchk(3,5,nargin)); +if nargin < 4, x0 = []; end +if nargin < 5, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 100; +end + +[x,odata,opts] = tfocs( smooth_quad, { A, -b }, prox_Ol1( lambda ), x0, opts ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_TraceLS.m b/solver_TraceLS.m new file mode 100644 index 0000000..6840c6d --- /dev/null +++ b/solver_TraceLS.m @@ -0,0 +1,48 @@ +function [ x, odata, opts ] = solver_TraceLS( A, b, lambda, x0, opts ) +% SOLVER_TRACELS Unconstrained form of trace-regularized least-squares problem. +% [ x, odata, opts ] = solver_TraceLS( A, b, lambda, x0, opts ) +% Solves the trace-regularized least squares problem +% minimize (1/2)*norm( A * X - b )^2 + lambda * trace( X ) +% with the constraint that X is positive semi-definite. +% A must be a matrix or a linear operator, b must be a vector, +% and lambda must be a real positive scalar. +% The initial point x0 and option structure opts are +% both optional. +% +% If "A" is a sparse matrix, and nnz(Z) = length(b), then it is assumed +% that the nonzero entries correspond to samples, and then the +% corresponding sampling operator is used. +% +% If opts.largescale = true, then uses an iterative method +% to compute the eigenvalue decomposition used with prox_trace. +% +% See also solver_L1RLS (aka The Lasso) + + +% Added Feb 7, 2011 +error(nargchk(3,5,nargin)); +if nargin < 4, x0 = []; end +if nargin < 5, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 100; +end +if issparse(A) && nnz(A) == length(b) + A = linop_subsample(A); +end +if isfield( opts,'largescale') + prx = prox_trace( lambda, opts.largescale ); + opts = rmfield(opts,'largescale'); +else + prx = prox_trace( lambda ); +end +% Note: the proximity operator of trace is really a combination +% of the proximity operator of trace and the indicator +% set of positive semi-definite matrices. +% So we do not need to *explicitly* include the positive semi-definite constraint. + +[x,odata,opts] = tfocs( smooth_quad, { A, -b }, prx, x0, opts ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_psdComp.m b/solver_psdComp.m new file mode 100644 index 0000000..bffd282 --- /dev/null +++ b/solver_psdComp.m @@ -0,0 +1,66 @@ +function [ x, out, opts ] = solver_psdComp( Xinc, opts ) +% SOLVER_PSDCOMP Matrix completion for PSD matrices. +% [ x, out, opts ] = solver_psdComp( Xinc, opts ) +% Solves the PSD matrix completion problem +% minimize (1/2)*norm(X(ij)-vv).^2 +% s.t. X p.s.d +% where ij is a vector of indices corresponding to the known elements. +% The nonzero values of Xinc are assumed to be the known values; that +% is, all zero values are considered unknowns. In order to specify a +% known zero value, replace it with a very small value; e.g., 1e-100. +% +% Since X must be symmetric, the ij entries should have symmetry. If they +% are not specified symmetrically, then choose opts.symmetrize = true +% to force them to become symmetric. This will change the objective +% function. +% +% Set opts.largescale=true to use eigs() instead if eig(). +% +% See also solver_psdCompConstrainedTrace + +% Supply default values +error(nargchk(1,2,nargin)); +if nargin < 2, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 50; +end +if isfield( opts, 'symmetrize' ), + symmetrize = opts.symmetrize; + opts = rmfield(opts,'symmetrize'); +else + symmetrize = false; +end +if isfield( opts, 'largescale' ), + largescale = opts.largescale; + opts = rmfield(opts,'largescale'); +else + largescale = false; +end +[n,m] = size(Xinc); +if n ~= m, error( 'Input must be square.' ); end + +if norm(Xinc-Xinc','fro') > 1e-8*norm(Xinc,'fro') && symmetrize + Xinc = symmetrizeSparseMatrix(Xinc); + disp('symmetrizing input'); +end +linop = linop_subsample(Xinc); +vv = full( Xinc( find(Xinc) ) ); + +[x,out,opts] = tfocs( smooth_quad, { linop, -vv }, proj_psd(largescale), Xinc, opts ); + + +function X = symmetrizeSparseMatrix( X ) + % Some entries were only specified once, others were + % double-specified, so we don't know if we should + % divide by 1 or 2 + ind1 = find( tril(X) ); + ind2 = find( tril(X',-1) ); + X = tril(X) + tril( X', -1); + ind = intersect(ind1,ind2); + X(ind) = X(ind)/2; + X = X + tril(X,-1)'; + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/solver_psdCompConstrainedTrace.m b/solver_psdCompConstrainedTrace.m new file mode 100644 index 0000000..4e09483 --- /dev/null +++ b/solver_psdCompConstrainedTrace.m @@ -0,0 +1,84 @@ +function [ x, out, opts ] = solver_psdCompConstrainedTrace( Xinc, tr, opts ) +% SOLVER_PSDCOMPCONSTRAINEDTRACE Matrix completion with constrained trace, for PSD matrices. +% [ x, out, opts ] = solver_psdCompConstrainedTrace( Xinc, tr, opts ) +% Solves the PSD matrix completion problem +% minimize (1/2)*norm(X(ij)-vv).^2 +% s.t. X p.s.d and trace(X) = tr +% where ij is a vector of indices corresponding to the known elements. +% The nonzero values of Xinc are assumed to be the known values; that +% is, all zero values are considered unknowns. In order to specify a +% known zero value, replace it with a very small value; e.g., 1e-100. +% +% Since X must be symmetric, the ij entries should have symmetry. If they +% are not specified symmetrically, then choose opts.symmetrize = true +% to force them to become symmetric. This will change the objective +% function. +% +% See also solver_psdComp + +% Supply default values +error(nargchk(1,3,nargin)); +if nargin < 3, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 50; +end +if isfield( opts, 'symmetrize' ), + symmetrize = opts.symmetrize; + opts = rmfield(opts,'symmetrize'); +else + symmetrize = false; +end +if nargin < 2 || isempty(tr) + tr = 1; +end + +[n,m] = size(Xinc); +if n ~= m, error( 'Input must be square.' ); end +% If not symmetric, then make it symmetric +if norm(Xinc-Xinc','fro') > 1e-8*norm(Xinc,'fro') && symmetrize + Xinc = symmetrizeSparseMatrix(Xinc); + disp('symmetrizing input'); +end + +% -- This doesn't work so well -- +% Xinc = tril(Xinc); +% [ii,jj,vv] = find(Xinc); +% ij = sub2ind( [n,n], ii, jj ); +% linop = @(varargin)samp_op( n, ii, jj, ij, varargin{:} ); +% +% Xinc = Xinc + tril(Xinc,-1)'; + +% This is better: +linop = linop_subsample(Xinc); +vv = full( Xinc( find(Xinc) ) ); + +% Extract the linear operators +[x,out,opts] = tfocs( smooth_quad, { linop, -vv }, proj_psdUTrace(tr), Xinc, opts ); + +% we use a special sampling operator to tell it to make it symmetric +function y = samp_op( n, ii, jj, ij, X, mode ) +switch mode + case 0, + y = { [n,n], [length(ii),1] }; + case 1, + X = (X+X')/2; + y = full(X(ij)); %otherwise it is a sparse vector + case 2, + y = sparse( ii, jj, X, n, n ); + y = y + tril(y,-1)'; +end + +function X = symmetrizeSparseMatrix( X ) + % Some entries were only specified once, others were + % double-specified, so we don't know if we should + % divide by 1 or 2 + ind1 = find( tril(X) ); + ind2 = find( tril(X',-1) ); + X = tril(X) + tril( X', -1); + ind = intersect(ind1,ind2); + X(ind) = X(ind)/2; + X = X + tril(X,-1)'; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/solver_sBP.m b/solver_sBP.m new file mode 100644 index 0000000..fe74849 --- /dev/null +++ b/solver_sBP.m @@ -0,0 +1,70 @@ +function varargout = solver_sBP( A, b, mu, x0, z0, opts, varargin ) +% SOLVER_SBP Basis pursuit (l1-norm with equality constraints). Uses smoothing. +% [ x, out, opts ] = solver_sBP( A, b, mu, x0, z0, opts ) +% Solves the smoothed basis pursuit problem +% minimize norm(x,1) + 0.5*mu*(x-x0).^2 +% s.t. A * x == b +% by constructing and solving the composite dual +% maximize - g_sm(z) +% where +% g_sm(z) = sup_x -norm(x,1)-(1/2)*mu*norm(x-x0) +% A must be a linear operator or matrix, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% +% If "nonneg" is a field in "opts" and opts.nonneg is true, +% then the constraints are A * x == b AND x >= 0 + +% Supply default values +error(nargchk(3,7,nargin)); +if nargin < 4, x0 = []; end +if nargin < 5, z0 = []; end +if nargin < 6, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 400; end + + +% -- legacy options from original software -- +if isfield(opts,'lambda0') + opts = rmfield(opts,'lambda0'); +end +if isfield(opts,'xPlug') + opts = rmfield(opts,'xPlug'); +end +if isfield(opts,'solver') + svr = opts.solver; + opts = rmfield(opts,'solver'); + if isfield(opts,'alg') && ~isempty(opts.alg) + disp('Warning: conflictiong options for the algorithm'); + else + % if specified as "solver_AT", truncate: + s = strfind( svr, '_' ); + if ~isempty(s), svr = svr(s+1:end); end + opts.alg = svr; + end +end + + +nonneg = false; +if isfield(opts,'nonneg') + nonneg = opts.nonneg; + opts = rmfield(opts,'nonneg'); +end +if isfield(opts,'nonNeg') + nonneg = opts.nonNeg; + opts = rmfield(opts,'nonNeg'); +end + +if nonneg + % -- case: x >= 0 constraints + prox = prox_l1pos; +else + % -- case: no x >= 0 constraint + prox = prox_l1; +end +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( prox, { A, -b }, proj_Rn, mu, x0, z0, opts, varargin{:} ); + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sBPDN.m b/solver_sBPDN.m new file mode 100644 index 0000000..11504da --- /dev/null +++ b/solver_sBPDN.m @@ -0,0 +1,83 @@ +function varargout = solver_sBPDN( A, b, epsilon, mu, x0, z0, opts, varargin ) +% SOLVER_SBPDN Basis pursuit de-noising. BP with relaxed constraints. Uses smoothing. +% [ x, out, opts ] = solver_sBPDN( A, b, epsilon, mu, x0, z0, opts ) +% Solves the smoothed basis pursuit denoising problem +% minimize norm(x,1) + 0.5*mu*(x-x0).^2 +% s.t. norm(A*x-b,2) <= epsilon +% by constructing and solving the composite dual +% maximize - g_sm(z) - epsilon*norm(z,2) +% where +% gsm(z) = sup_x -norm(x,1)-(1/2)*mu*norm(x-x0) +% A must be a linear operator or matrix, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% +% If "nonneg" is a field in "opts" and opts.nonneg is true, +% then the constraints are norm(A*x-b,2) <= epsilon AND x >= 0 + +% Supply default values +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 400; end + +if epsilon < 0 + error('TFOCS error: epsilon is negative'); +end +if ~epsilon + error('TFOCS error: cannot handle epsilon = 0. Please call solver_sBP instead'); +elseif epsilon < 100*builtin('eps') + warning('TFOCS:badConstraint',... + 'TFOCS warning: epsilon is near zero; consider calling solver_sBP instead'); +end + + +% -- legacy options from original software -- +if isfield(opts,'lambda0') + opts = rmfield(opts,'lambda0'); +end +if isfield(opts,'xPlug') + opts = rmfield(opts,'xPlug'); +end +if isfield(opts,'solver') + svr = opts.solver; + opts = rmfield(opts,'solver'); + if isfield(opts,'alg') && ~isempty(opts.alg) + disp('Warning: conflictiong options for the algorithm'); + else + % if specified as "solver_AT", truncate: + s = strfind( svr, '_' ); + if ~isempty(s), svr = svr(s+1:end); end + opts.alg = svr; + end +end + + + + +nonneg = false; +if isfield(opts,'nonneg') + nonneg = opts.nonneg; + opts = rmfield(opts,'nonneg'); +end +if isfield(opts,'nonNeg') + nonneg = opts.nonNeg; + opts = rmfield(opts,'nonNeg'); +end + +if nonneg + % -- case: x >= 0 constraints + prox = prox_l1pos; +else + % -- case: no x >= 0 constraint + prox = prox_l1; +end +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( prox, { A, -b }, prox_l2( epsilon ), mu, x0, z0, opts, varargin{:} ); + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sBPDN_W.m b/solver_sBPDN_W.m new file mode 100644 index 0000000..d799361 --- /dev/null +++ b/solver_sBPDN_W.m @@ -0,0 +1,68 @@ +function varargout = solver_sBPDN_W( A, W, b, epsilon, mu, x0, z0, opts, varargin ) +% SOLVER_SBPDN_W Weighted BPDN problem. Uses smoothing. +% [ x, out, opts ] = solver_sBPDN_W( A, W, b, epsilon, mu, x0, z0, opts ) +% Solves the smoothed basis pursuit denoising problem +% minimize norm(Wx,1) + 0.5*mu*(x-x0).^2 +% s.t. norm(A*x-b,2) <= epsilon +% by constructing and solving the composite dual. +% A and W must be a linear operator or matrix, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% See also solver_sBPDN + +% Supply default values +error(nargchk(5,9,nargin)); +if nargin < 6, x0 = []; end +if nargin < 7, z0 = []; end +if nargin < 8, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 1000; end + +if epsilon < 0 + error('TFOCS error: epsilon is negative'); +end +% we don't have solver_sBP_W, so am building this into this function: +%if ~epsilon + %error('TFOCS error: cannot handle epsilon = 0. Please call solver_sBP_W instead'); +%elseif epsilon < 100*builtin('eps') + %warning('TFOCS:badConstraint',... + %'TFOCS warning: epsilon is near zero; consider calling solver_sBP instead'); +%end + +% Need to estimate the norms of A*A' and W*W' in order to be most efficient +if isfield( opts, 'noscale' ) && opts.noscale, + normA2 = 1; normW2 = 1; +else + normA2 = []; normW2 = []; + if isfield( opts, 'normA2' ), + normA2 = opts.normA2; + opts = rmfield( opts, 'normA2' ); + end + if isfield( opts, 'normW2' ), + normW2 = opts.normW2; + opts = rmfield( opts, 'normW2' ); + end +end +if isempty( normA2 ), + normA2 = linop_normest( A ).^2; +end +if isempty( normW2 ), + normW2 = linop_normest( W ).^2; +end + +% if ~isfield(opts,'L0') || isempty(opts.L0) +% opts.L0 = normA2/mu; % is this right? check +% end + +proxScale = sqrt( normW2 / normA2 ); +if epsilon > 0 + prox = { prox_l2( epsilon ), proj_linf(proxScale) }; +else + prox = { proj_Rn, proj_linf(proxScale) }; +end +W = linop_compose( W, 1 / proxScale ); +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( [], { A, -b; W, 0 }, prox, mu, x0, z0, opts, varargin{:} ); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sBPDN_WW.m b/solver_sBPDN_WW.m new file mode 100644 index 0000000..7e92fa4 --- /dev/null +++ b/solver_sBPDN_WW.m @@ -0,0 +1,77 @@ +function varargout = solver_sBPDN_WW( A, alpha, W1, beta, W2, b, epsilon, mu, x0, z0, opts, varargin ) +% SOLVER_SBPDN_WW BPDN with two separate (weighted) l1-norm terms. Uses smoothing. +% [ x, out, opts ] = solver_sBPDN_WW( A, alpha, W_1, beta, W_2, b, epsilon, mu, x0, opts ) +% Solves the smoothed basis pursuit denoising problem +% minimize alpha*norm(W_1 x,1) + beta*norm(W_2 x, 1) 0.5*mu*(x-x0).^2 +% s.t. norm(A*x-b,2) <= epsilon +% by constructing and solving the composite dual. +% A, W_1 and W_2 must be a linear operator or matrix, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% See also solver_sBPDN and solver_sBPDN_W + +% Supply default values +error(nargchk(8,12,nargin)); +if nargin < 9, x0 = []; end +if nargin < 10, z0 = []; end +if nargin < 11, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 5000; end + +if epsilon < 0 + error('TFOCS error: epsilon is negative'); +end +if ~epsilon + error('TFOCS error: cannot handle epsilon = 0. Please call solver_sBP instead'); +elseif epsilon < 100*builtin('eps') + warning('TFOCS:badConstraint',... + 'TFOCS warning: epsilon is near zero; consider calling solver_sBP instead'); +end + +% Need to estimate the norms of A*A' and W*W' in order to be most efficient +if isfield( opts, 'noscale' ) && opts.noscale, + normA2 = 1; normW12 = 1; normW22 = 1; +else + normA2 = []; normW12 = []; normW22 = []; + if isfield( opts, 'normA2' ) + normA2 = opts.normA2; + opts = rmfield( opts, 'normA2' ); + end + if isfield( opts, 'normW12' ) + normW12 = opts.normW12; + opts = rmfield( opts, 'normW12' ); + end + if isfield( opts, 'normW22' ) + normW22 = opts.normW22; + opts = rmfield( opts, 'normW22' ); + end +end +if isempty( normA2 ), + normA2 = linop_normest( A ).^2; +end +if isempty( normW12 ), + normW12 = linop_normest( W1 ).^2; +end +if isempty( normW22 ), + normW22 = linop_normest( W2 ).^2; +end +if isempty(alpha), + alpha = 1; +end +if isempty(beta), + beta = 1; +end + +proxScale1 = sqrt( normW12 / normA2 ); +proxScale2 = sqrt( normW22 / normA2 ); +prox = { prox_l2( epsilon ), ... + proj_linf( proxScale1 * alpha ),... + proj_linf( proxScale2 * beta ) }; +W1 = linop_compose( W1, 1 / proxScale1 ); +W2 = linop_compose( W2, 1 / proxScale2 ); +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( [], { A, -b; W1, 0; W2, 0 }, prox, mu, x0, z0, opts, varargin{:} ); + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sDantzig.m b/solver_sDantzig.m new file mode 100644 index 0000000..1130d25 --- /dev/null +++ b/solver_sDantzig.m @@ -0,0 +1,84 @@ +function varargout = solver_sDantzig( A, b, delta, mu, x0, z0, opts, varargin ) +% SOLVER_SDANTZIG Dantzig selector problem. Uses smoothing. +%[ x, out, opts ] = solver_sDantzig( A, b, delta, mu, x0, z0, opts ) +% Solves the smoothed Dantzig +% minimize norm(x,1) + (1/2)*mu*norm(x-x0).^2 +% s.t. norm(D.*(A'*(A*x-b)),Inf) <= delta +% by constructing and solving the composite dual +% maximize - g_sm(z) - delta*norm(z,1) +% where +% gsm(z) = sup_x -norm(x,1)-(1/2)*mu*norm(x-x0) +% A must be a linear operator, b must be a vector, and delta and mu +% must be positive scalars. Initial points x0 and z0 are optional. +% The standard calling sequence assumes that D=I. To supply a scaling, +% pass the cell array { A, D } instead of A. D must either be a scalar, +% a vector of weights, or a linear operator. + +% Supply default values +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end + +% -- legacy options from original software -- +if isfield(opts,'lambda0') + z0 = opts.lambda0; + opts = rmfield(opts,'lambda0'); +end +if isfield(opts,'xPlug') + x0 = opts.xPlug; + opts = rmfield(opts,'xPlug'); +end +if isfield(opts,'solver') + svr = opts.solver; + opts = rmfield(opts,'solver'); + if isfield(opts,'alg') && ~isempty(opts.alg) + disp('Warning: conflictiong options for the algorithm'); + else + % if specified as "solver_AT", truncate: + s = strfind( svr, '_' ); + if ~isempty(s), svr = svr(s+1:end); end + opts.alg = svr; + end +end + +% Extract the linear operators +D = []; +if isa( A, 'cell' ), + if length(A) > 1, D = A{2}; end + A = A{1}; +end +if isempty(D), + D = @(x)x; +elseif isa( D, 'double' ), + D = @(x)D.*x; +end +if isa( A, 'double' ), + A = linop_matrix(A); +end + +% Call TFOCS +objectiveF = prox_l1; +affineF = { @(y,mode)linear_DS( D, A, y, mode ), -D(A(b,2)) }; +dualproxF = prox_l1( delta ); +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts, varargin{:} ); + +% Implements x -> D*A'*A*x and its adjoint if A is a linop +function y = linear_DS( D, A, y, mode ) +switch mode, +case 0, + y = A([],0); + if iscell( y ), + y = { y{1}, y{1} }; + else + y = { [y(2),1], [y(2),1] }; + end +case 1, y = D(A(A(y,1),2)); +case 2, y = A(A(D(y),1),2); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sDantzig_W.m b/solver_sDantzig_W.m new file mode 100644 index 0000000..600067f --- /dev/null +++ b/solver_sDantzig_W.m @@ -0,0 +1,127 @@ +function varargout = solver_sDantzig_W( A,W, b, delta, mu, x0, z0, opts, varargin ) +% SOLVER_SDANTZIG_W Weighted Dantzig selector problem. Uses smoothing. +%[ x, out, opts ] = solver_sDantzig_W( A,W, b, delta, mu, x0, z0, opts ) +% Solves the smoothed Dantzig +% minimize norm(W*x,1) + (1/2)*mu*norm(x-x0).^2 +% s.t. norm(D.*(A'*(A*x-b)),Inf) <= delta +% by constructing and solving the composite dual +% +% A and W must be a linear operator, b must be a vector, and delta and mu +% must be positive scalars. Initial points x0 and z0 are optional. +% The standard calling sequence assumes that D=I. To supply a scaling, +% pass the cell array { A, D } instead of A. D must either be a scalar, +% a vector of weights, or a linear operator. +% +% Pass in the options "normA2" and "normW2" (which are ||A||^2 +% and ||W||^2 respectively) for best efficiency. +% +% See also solver_sDantzig + +% Supply default values +error(nargchk(5,9,nargin)); +if nargin < 6, x0 = []; end +if nargin < 7, z0 = []; end +if nargin < 8, opts = []; end + +if isfield(opts,'solver') + svr = opts.solver; + opts = rmfield(opts,'solver'); + if isfield(opts,'alg') && ~isempty(opts.alg) + disp('Warning: conflictiong options for the algorithm'); + else + % if specified as "solver_AT", truncate: + s = strfind( svr, '_' ); + if ~isempty(s), svr = svr(s+1:end); end + opts.alg = svr; + end +end + +% Extract the linear operators +D = []; +if isa( A, 'cell' ), + if length(A) > 1, D = A{2}; end + A = A{1}; +end +if isempty(D), + D = @(x)x; +elseif isa( D, 'double' ), + D = @(x)D.*x; +end +if isa( A, 'double' ), + % if "A" is not too rectangular, it is probably more efficient + % to compute A'*A once at the beginning and store it. + mn = min(size(A)); + mx = max(size(A)); + if mn >= .7*mx && mx < 1e5 + AA = @(y,mode)linear_DS_AA( D, A'*A, y, mode ); + A = linop_matrix(A); + else + A = linop_matrix(A); + AA = @(y,mode)linear_DS( D, A, y, mode ); + end +else + AA = @(y,mode)linear_DS( D, A, y, mode ); +end + +% Need to estimate the norms of A*A' and W*W' in order to be most efficient +if isfield( opts, 'noscale' ) && opts.noscale, + normA2 = 1; normW2 = 1; +else + normA2 = []; normW2 = []; + if isfield( opts, 'normA2' ), + normA2 = opts.normA2; + opts = rmfield( opts, 'normA2' ); + end + if isfield( opts, 'normW2' ), + normW2 = opts.normW2; + opts = rmfield( opts, 'normW2' ); + end +end +if isempty( normA2 ), + normA2 = linop_normest( A ).^2; +end +if isempty( normW2 ), + normW2 = linop_normest( W ).^2; +end + +% Call TFOCS +proxScale = sqrt( normW2 / normA2 ); +prox = { prox_l1( delta ); proj_linf(proxScale) }; +W = linop_compose( W, 1/proxScale); +affineF = {AA, -D(A(b,2)); W, 0 }; +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( [], affineF, prox, mu, x0, z0, opts, varargin{:} ); + + +% Implements x -> D*A'*A*x and its adjoint if A is a linop +function y = linear_DS( D, A, y, mode ) +switch mode, +case 0, + y = A([],0); + if iscell( y ), + y = { y{1}, y{1} }; + else + y = { [y(2),1], [y(2),1] }; + end +case 1, y = D(A(A(y,1),2)); +case 2, y = A(A(D(y),1),2); +end + +function y = linear_DS_AA( D, AA, y, mode ) +% similar to above, but expects AA to be an explicit Hermitian matrix +switch mode, +case 0, + y = size(AA); + if iscell( y ), + y = { y{1}, y{1} }; + else + y = { [y(2),1], [y(2),1] }; + end +case 1, y = D(AA*y); +case 2, y = AA*D(y); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sLMI.m b/solver_sLMI.m new file mode 100644 index 0000000..5cf7c32 --- /dev/null +++ b/solver_sLMI.m @@ -0,0 +1,101 @@ +function varargout = solver_sLMI( A0, A, b, mu, x0, z0, opts, varargin ) +% SOLVER_SLMI Generic linear matrix inequality problems (LMI is the dual of a SDP). Uses smoothing. +% [ y, out, opts ] = solver_sLMI( A0, A, b, mu, y0, z0, opts ) +% Solves the smoothed Linear-Matrix Inequality (LMI) problem +% +% minimize_y b'*y +% s.t. A0 + sum_i A_i y(i) >= 0 +% " >= 0 " indicates that a matrix is positive semi-definite. +% +% "A0" and must be a symmetric/Hermitian matrix, "b" must be a vector, and +% "A" must be a matrix (dense or sparse) +% with the convention that each row of A stores the vectorized symmetric/Hermitian +% matrix A_i, so that sum_i A_i y(i) can be written as mat(A'*y) +% (where mat() reshapes a vector into a square matrix) +% if "A" is a function, then in forward mode it should compute A'*y +% and in transpose mode it should compute A*X +% +% Note: A0 and A_i must be symmetric/Hermitian, but this function +% does not check for it, so the user must check. If you get +% unexpected errors, this may be the culprit. +% +% For maximum efficiency, the user should specify the spectral norm of A, +% via opts.normA +% (e.g. opts.normA = norm(A) or opts.normA = normest(A)) +% +% By default, this assumes variables are real. +% To allow y to be complex, either pass in a complex value for y0 +% or make sure A or A0 is complex, +% or specify opts.cmode = 'R2C' +% (note: cmode = 'C2C' is not supported) +% +% See also solver_sSDP + +% Supply default values +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 1000; end + +% Do we automatically re-scale "A"? +% (there are two dual variables, one corresponding to Ax==b +% and one corresponding to I*x >= 0, and the dual problem +% is most efficient if norm(I) = norm(A), +% hence we rescale A <-- A/norm(A) ) +if isfield( opts, 'noscale' ) && opts.noscale + % The user has forced us not to automatically rescale + normA = 1; +else + normA = []; + if isfield( opts, 'normA' ), + normA = opts.normA; + opts = rmfield( opts, 'normA' ); + end +end +if isempty( normA ), + normA = linop_normest( A ); +end +if isfield( opts, 'noscale' ) + opts = rmfield(opts,'noscale'); +end + + +N = size(A0,1); if N ~= size(A0,2), error('"A0" must be square and symmetric'); end +if ~isa(A,'function_handle') + % We need to tell TFOCS that we'll be using matrix variables + M = size(A,1); if size(A,2) ~= N^2, error('"A" has wrong number of columns'); end + sz = { [M,1], [N,N] }; % specify dimensions of domain and range of A + if isfield( opts, 'cmode' ) + cmode = opts.cmode; + else + if ~isempty(x0) && ~isreal(x0) + cmode = 'R2C'; + elseif ~isreal(A) || ~isreal(A0) + cmode = 'R2C'; % if A is complex, then X must be, in order to get real output + else + cmode = 'R2R'; % default assumption + end + end + vec = @(x) x(:); + mat = @(y) reshape(y,N,N); + A = linop_handles( sz, @(y) mat(A'*y), @(X)real(A*vec(X)), cmode); +end + + + +% Perform the re-scaling: +A = linop_compose( A, 1 / normA ); +A0 = A0/normA; + + +obj = smooth_linear(b); + +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, {A,A0}, {proj_psd}, mu, x0, z0, opts, varargin{:} ); + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sLP.m b/solver_sLP.m new file mode 100644 index 0000000..bec94ec --- /dev/null +++ b/solver_sLP.m @@ -0,0 +1,82 @@ +function varargout = solver_sLP( c, A, b, mu, x0, z0, opts, varargin ) +% SOLVER_SLP Generic linear programming in standard form. Uses smoothing. +% [ x, out, opts ] = solver_sLP( c, A, b, mu, x0, z0, opts ) +% Solves the smoothed standard form Linear Program (LP) +% minimize c'*x + 0.5*mu*||x-x0||_2^2 +% s.t. A * x == b and x >= 0 +% +% "c" and "b" must be vectors, and "A" must be a matrix (dense or sparse) +% or a function that computes A*x and A'*y (see help documentation). +% +% If the constraint "x >= 0" is not needed, then specify this by +% setting: +% opts.nonnegativity = false +% +% For maximum efficiency, the user should specify the spectral norm of A, +% via opts.normA +% (e.g. opts.normA = norm(A) or opts.normA = normest(A)) + +% Supply default values +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 1000; end + + +% Do we include the x >= 0 constraint? +NONNEG = true; +if isfield(opts,'nonnegativity') + if ~opts.nonnegativity + NONNEG = false; + end + opts = rmfield(opts,'nonnegativity'); +end + + +% Do we automatically re-scale "A"? +% (there are two dual variables, one corresponding to Ax==b +% and one corresponding to I*x >= 0, and the dual problem +% is most efficient if norm(I) = norm(A), +% hence we rescale A <-- A/norm(A) ) +if isfield( opts, 'noscale' ) && opts.noscale + % The user has forced us not to automatically rescale + normA = 1; +else + normA = []; + if isfield( opts, 'normA' ), + normA = opts.normA; + opts = rmfield( opts, 'normA' ); + end +end +if isempty( normA ), + normA = linop_normest( A ); +end +if isfield( opts, 'noscale' ) + opts = rmfield(opts,'noscale'); +end + +% Perform the re-scaling: +if isnumeric(A), A = A/normA; % do it once +else +A = linop_compose( A, 1 / normA ); +end +b = b/normA; + + +obj = smooth_linear(c); + +if ~NONNEG + % There is no x >= 0 constraint: + [varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, { A, -b }, proj_Rn, mu, x0, z0, opts, varargin{:} ); +else + % There is a x >= 0 constraint: + [varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, { 1,0;A,-b}, {proj_Rplus,proj_Rn}, mu, x0, z0, opts, varargin{:} ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sLP_box.m b/solver_sLP_box.m new file mode 100644 index 0000000..d3ca903 --- /dev/null +++ b/solver_sLP_box.m @@ -0,0 +1,83 @@ +function varargout = solver_sLP_box( c, A, b, l, u, mu, x0, z0, opts, varargin ) +% SOLVER_SLP_BOX Generic linear programming with box constraints. Uses smoothing. +% [ x, out, opts ] = solver_sLP_box( c, A, b, l, u, mu, x0, z0, opts ) +% Solves the smoothed standard form Linear Program (LP) with box-constraints +% minimize c'*x + 0.5*mu*||x-x0||_2^2 +% s.t. A * x == b and l <= x <= b +% +% "c" and "b" must be vectors, and "A" must be a matrix (dense or sparse) +% or a function that computes A*x and A'*y (see help documentation). +% "l" and "u" are vectors, or scalars (in which case they are a scalar +% times the vector of all ones ) +% +% If only "l" (or only "u") is needed, set "u" (or "l") to the empty matrix []. +% +% For maximum efficiency, the user should specify the spectral norm of A, +% via opts.normA +% (e.g. opts.normA = norm(A) or opts.normA = normest(A)) +% +% See also solver_sLP + +% Supply default values +error(nargchk(6,10,nargin)); +if nargin < 7, x0 = []; end +if nargin < 8, z0 = []; end +if nargin < 9, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 1000; end + + + +% Do we automatically re-scale "A"? +% (there are two dual variables, one corresponding to Ax==b +% and one corresponding to I*x >= 0, and the dual problem +% is most efficient if norm(I) = norm(A), +% hence we rescale A <-- A/norm(A) ) +if isfield( opts, 'noscale' ) && opts.noscale + % The user has forced us not to automatically rescale + normA = 1; +else + normA = []; + if isfield( opts, 'normA' ), + normA = opts.normA; + opts = rmfield( opts, 'normA' ); + end +end +if isempty( normA ), + normA = linop_normest( A ); +end +if isfield( opts, 'noscale' ) + opts = rmfield(opts,'noscale'); +end + +% Perform the re-scaling: +if isnumeric(A), A = A/normA; % do it once +else +A = linop_compose( A, 1 / normA ); +end +b = b/normA; + + +obj = smooth_linear(c); + +if isempty(l) && isempty(u) + % There is no x >= 0 constraint: + [varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, { A, -b }, proj_Rn, mu, x0, z0, opts, varargin{:} ); +elseif isempty(u) + % There is a x >= l constraint, i.e. x - l >= 0 + [varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, { 1,-l;A,-b}, {proj_Rplus,proj_Rn}, mu, x0, z0, opts, varargin{:} ); +elseif isempty(l) + % There is a x <= u constraint, i.e. -x + u >= 0 + [varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, {-1, u;A,-b}, {proj_Rplus,proj_Rn}, mu, x0, z0, opts, varargin{:} ); +else + % have both upper and lower box constraints + [varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, {1,-l;-1, u;A,-b}, {proj_Rplus,proj_Rplus,proj_Rn}, mu, x0, z0, opts, varargin{:} ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sNuclearBP.m b/solver_sNuclearBP.m new file mode 100644 index 0000000..5ab28bd --- /dev/null +++ b/solver_sNuclearBP.m @@ -0,0 +1,107 @@ +function varargout = solver_sNuclearBP( omega, b, mu, x0, z0, opts, varargin ) +% SOLVER_SNUCLEARBP Nuclear norm basis pursuit problem (i.e. matrix completion). Uses smoothing. +% [ x, out, opts ] = solver_sNuclearBP( omega, b, mu, X0, Z0, opts ) +% Solves the smoothed nuclear norm basis pursuit problem +% minimize norm_nuc(X) + 0.5*mu*norm(X-X0,'fro').^2 +% s.t. A_omega * x == b +% by constructing and solving the composite dual +% maximize - g_sm(z) +% where +% g_sm(z) = sup_x -norm(x,1)-(1/2)*mu*norm(x-x0) +% A_omega is the restriction to the set omega, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% +% The "omega" term may be in one of three forms: +% (1) OMEGA, a sparse matrix. Only the nonzero pattern is important. +% (2) {n1,n2,omega}, a cell, where [n1,n2] = size(X), and omega +% is the vector of linear indices of the observed set +% (3) {n1,n2,omegaI,omegaJ}, a cell. Similar to (2), except the set +% omega is now specified by subscripts. Specifically, +% omega = sub2ind( [n1,n2], omegaI, omegaJ) and +% [omegaI,omegaJ] = ind2sub( [n1,n2], omega ) +% +% If the options field "largeScale" is provided and set to true, then uses +% a Lanczos-based SVD + +% Supply default values +error(nargchk(3,7,nargin)); +if nargin < 4, x0 = []; end +if nargin < 5, z0 = []; end +if nargin < 6, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 50; +end + +if isempty(omega) + error( 'Sampling operator cannot be empty.' ); +elseif issparse(omega) + [omegaI,omegaJ] = find(omega); + [n1,n2] = size(omega); + omega_lin = sub2ind( [n1,n2], omegaI, omegaJ ); +elseif iscell(omega) + switch length(omega) + case 3, + [ n1, n2, omega_lin ] = deal( omega{:} ); + [omegaI,omegaJ] = ind2sub( [n1,n2], omega_lin ); + case 4 + [ n1, n2, omegaI, omegaJ ] = deal( omega{:} ); + omega_lin = sub2ind( [n1,n2], omegaI, omegaJ ); + otherwise + error( 'Incorrect format for the sampling operator.' ); + end +else + error( 'Incorrect format for the sampling operator.' ); +end +nnz = numel(omega_lin); +omega_lin = omega_lin(:); % make it a column vector +b = b(:); % make it a column vector +if ~isequal( size(b), [ nnz, 1 ] ), + error( 'Incorrect size for the sampled data.' ); +end + +% automatically do kicking on the dual variable: +if isempty(z0) + z0 = sparse(omegaI,omegaJ,b,n1,n2); + nY = linop_normest(z0,'R2R',1e-4,300); + z0 = b/nY; +end + +packSVDflag = isa(x0,'packSVD'); +% Note: packSVD is not supported, so has been removed +A = @(varargin)linop_nuclear( n1, n2, nnz, omega_lin, omegaI, omegaJ,packSVDflag, varargin{:} ); + +q=1; +if isfield(opts,'largeScale') && opts.largeScale + prox = prox_nuclear(q,true); +else + prox = prox_nuclear(q); +end +if isfield(opts,'largeScale'), opts = rmfield(opts,'largeScale'); end + +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( prox, { A, -b }, proj_Rn, mu, x0, z0, opts, varargin{:} ); + +% +% Implements the matrix sampling operator: X -> [X_ij]_{i,j\in\omega} +% +function y = linop_nuclear( n1, n2, nnzs, omega, omegaI, omegaJ, packSVDflag, x, mode ) +switch mode, + case 0, + y = { [n1,n2], [nnzs,1] }; + case 1, +% y = x(omega); + S = []; + S.type = '()'; + S.subs = {omega}; + y = subsref(x,S); + case 2, + y = sparse( omegaI, omegaJ, x, n1, n2 ); + if packSVDflag + y = packSVD(y); + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sNuclearBPDN.m b/solver_sNuclearBPDN.m new file mode 100644 index 0000000..3310c6b --- /dev/null +++ b/solver_sNuclearBPDN.m @@ -0,0 +1,78 @@ +function varargout = solver_sNuclearBPDN( omega, b, epsilon, mu, x0, z0, opts, varargin ) +% SOLVER_SNUCLEARBPDN Nuclear norm basis pursuit problem with relaxed constraints. Uses smoothing. +% [ x, out, opts ] = solver_sNuclearBPDN( omega, b, epsilon,mu, X0, Z0, opts ) +% Solves the smoothed nuclear norm basis pursuit problem +% minimize norm_nuc(X) + 0.5*mu*norm(X-X0,'fro').^2 +% s.t. ||A_omega * x - b || <= epsilon +% by constructing and solving the composite dual +% maximize - g_sm(z) +% where +% g_sm(z) = sup_x -norm(x,1)-(1/2)*mu*norm(x-x0) +% A_omega is the restriction to the set omega, and b must be a vector. The +% initial point x0 and the options structure opts are optional. +% +% The "omega" term may be in one of three forms: +% (1) OMEGA, a sparse matrix. Only the nonzero pattern is important. +% (2) {n1,n2,omega}, a cell, where [n1,n2] = size(X), and omega +% is the vector of linear indices of the observed set +% (3) {n1,n2,omegaI,omegaJ}, a cell. Similar to (2), except the set +% omega is now specified by subscripts. Specifically, +% omega = sub2ind( [n1,n2], omegaI, omegaJ) and +% [omegaI,omegaJ] = ind2sub( [n1,n2], omega ) + +% Supply default values +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end +if ~isfield( opts, 'restart' ), + opts.restart = 50; +end + +if isempty(omega) + error( 'Sampling operator cannot be empty.' ); +elseif issparse(omega) + [omegaI,omegaJ] = find(omega); + [n1,n2] = size(omega); + omega_lin = sub2ind( [n1,n2], omegaI, omegaJ ); +elseif iscell(omega) + switch length(omega) + case 3, + [ n1, n2, omega_lin ] = deal( omega{:} ); + [omegaI,omegaJ] = ind2sub( [n1,n2], omega_lin ); + case 4 + [ n1, n2, omegaI, omegaJ ] = deal( omega{:} ); + omega_lin = sub2ind( [n1,n2], omegaI, omegaJ ); + otherwise + error( 'Incorrect format for the sampling operator.' ); + end +else + error( 'Incorrect format for the sampling operator.' ); +end +nnz = numel(omega_lin); +if ~isequal( size(b), [ nnz, 1 ] ), + error( 'Incorrect size for the sampled data.' ); +end + +% TODO: see the new linop_subsample.m file +A = @(varargin)linop_nuclear( n1, n2, nnz, omega_lin, omegaI, omegaJ, varargin{:} ); +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( prox_nuclear, { A, -b }, prox_l2(epsilon), mu, x0, z0, opts, varargin{:} ); + +% +% Implements the matrix sampling operator: X -> [X_ij]_{i,j\in\omega} +% +function y = linop_nuclear( n1, n2, nnz, omega, omegaI, omegaJ, x, mode ) +switch mode, + case 0, + y = { [n1,n2], [nnz,1] }; + case 1, + y = x(omega); + case 2, + y = sparse( omegaI, omegaJ, x, n1, n2 ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/solver_sSDP.m b/solver_sSDP.m new file mode 100644 index 0000000..9af5bd0 --- /dev/null +++ b/solver_sSDP.m @@ -0,0 +1,104 @@ +function varargout = solver_sSDP( c, A, b, mu, x0, z0, opts, varargin ) +% SOLVER_SSDP Generic semi-definite programs (SDP). Uses smoothing. +% [ x, out, opts ] = solver_sSDP( C, A, b, mu, X0, z0, opts ) +% Solves the smoothed standard form Semi-Definite Program (SDP) +% minimize trace(C'*X) + 0.5*mu*||X-X0||_F^2 +% s.t. A * vec(X) == b and X >= 0 +% where X >= 0 indicates that X is positive semi-definite. +% +% "C" and must be a matrix, "b" must be a vector, and +% "A" must be a matrix (dense or sparse) +% or a function that computes A*x and A'*y (see help documentation). +% The operation b = A*vec(X) is equivalent to +% b_i = trace( A_i'*X ) for i = 1:size(A,1), where A_i is the symmetric +% matrix formed by reshaping the ith row of A. +% +% +% For maximum efficiency, the user should specify the spectral norm of A, +% via opts.normA +% (e.g. opts.normA = norm(A) or opts.normA = normest(A)) +% +% By default, this assumes variables are real. +% To allow X to be complex, either pass in a complex value for X0 +% or make sure A is complex, +% or specify opts.cmode = 'C2R' +% (note: cmode = 'C2C' is not supported) +% +% See also solver_sLMI + +% Supply default values +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end +if ~isfield( opts, 'restart' ), opts.restart = 1000; end + +% Do we automatically re-scale "A"? +% (there are two dual variables, one corresponding to Ax==b +% and one corresponding to I*x >= 0, and the dual problem +% is most efficient if norm(I) = norm(A), +% hence we rescale A <-- A/norm(A) ) +if isfield( opts, 'noscale' ) && opts.noscale + % The user has forced us not to automatically rescale + normA = 1; +else + normA = []; + if isfield( opts, 'normA' ), + normA = opts.normA; + opts = rmfield( opts, 'normA' ); + end +end +if isempty( normA ), + normA = linop_normest( A ); +end +if isfield( opts, 'noscale' ) + opts = rmfield(opts,'noscale'); +end + + +N = size(c,1); if N ~= size(c,2), error('"C" must be square and symmetric'); end +if ~isa(A,'function_handle') + % We need to tell TFOCS that we'll be using matrix variables + M = size(A,1); if size(A,2) ~= N^2, error('"A" has wrong number of columns'); end + sz = { [N,N], [M,1] }; % specify dimensions of domain and range of A + if isfield( opts, 'cmode' ) + cmode = opts.cmode; + else + if ~isempty(x0) && ~isreal(x0) + cmode = 'C2R'; + elseif ~isreal(A) || ~isreal(c) % bug fix, Tue Apr 26, 2011 + cmode = 'C2R'; % if A is complex, then X must be, in order to get real output + else + cmode = 'R2R'; % default assumption + end + end + vec = @(x) x(:); + mat = @(y) reshape(y,N,N); + A = linop_handles( sz, @(X)real(A*vec(X)), @(y) mat(A'*y),cmode); +end + + + +% Perform the re-scaling: +A = linop_compose( A, 1 / normA ); +b = b/normA; + + +obj = smooth_linear(c); + +[varargout{1:max(nargout,1)}] = ... + tfocs_SCD( obj, { 1,0;A,-b}, {proj_psd,proj_Rn}, mu, x0, z0, opts, varargin{:} ); + +% and undo the scaling by normA: +if nargout >= 2 && isfield( varargout{2},'dual' ) && normA ~= 1 + if isa( varargout{2}.dual,'tfocs_tuple') + varargout{2}.dual = tfocs_tuple( {varargout{2}.dual{1}, varargout{2}.dual{2}/normA }); + else + varargout{2}.dual{2} = varargout{2}.dual{2}/normA; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/test_nonsmooth.m b/test_nonsmooth.m new file mode 100644 index 0000000..4eacf1b --- /dev/null +++ b/test_nonsmooth.m @@ -0,0 +1,219 @@ +function varargout = test_nonsmooth( f, N, DOM ) +% TEST_NONSMOOTH Runs diagnostic tests to ensure a non-smooth function conforms to TFOCS conventions +% TEST_NONSMOOTH( F ) +% tests whether the function handle F works as a tfocs non-nonsmooth +% function object. +% +% Requirements: +% f = F(X) must return the value of the function at X. +% p = F(X,t) must return the proximity operator of F at X, +% i.e. p = argmin_y F(y) + 1/(2*t)||y-x||^2 +% +% For an example, see PROX_L1.M +% +% TEST_NONSMOOTH( F, N ) +% specifies the size of the domain space. If N is a scalar, +% then the domain space is the set of N x 1 vectors. +% If N is a matrix of the form [n1, n2], the the domain +% space is the set of n1 x n2 matrices. +% +% TEST_NONSMOOTH( ..., DOM ) +% specifies the domain of F. DOM can be of the form [a,b] +% which signifies the 1D set (a,b). +% +% OK = TEST_NONSMOTH(...) +% returns "true" if all tests are passed, and "false" otherwise. +% +% Example: +% test_nonsmooth( prox_l1 ) +% +% See also private/tfocs_prox, prox_l1 + +OK = false; +PLOT = true; + +error(nargchk(1,3,nargin)); + +if ~isa(f,'function_handle') + fail('TFOCS nonsmooth function must be a FUNCTION HANDLE'); + return; +end +fprintf('== Testing the nonnonsmooth function %s ==\n', func2str(f) ); + +if nargin < 2 || isempty(N), N = 10; end +if nargin < 3 || isempty(DOM), DOM = [-1,1]; end + +a = DOM(1); b = DOM(2); + +x = (a+b)/2; +fprintf('Testing scalar inputs... \n'); +try + vi = f(x); +catch + fail('TFOCS nonsmooth function failed to return a function value'); + return; +end +if isinf(vi) + fail('TFOCS nonsmooth function tester: default domain is invalid. Please specify valid domain'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + +% Now, also ask for the prox +fprintf('Testing proximity operator output... \n'); +t = 1; +try + [vi,gi] = f(x,t); +catch + fail('TFOCS nonsmooth function failed to compute a valid proximity operator'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + + +% find a reasonable value of t: +x = a + .9*(b-a); +[vi,gi] = f(x,t); +cntr = 0; +% This is only good for proximity functions: for projections, +% the value of "t" has no effect: +if strfind( func2str(f), 'proj' ) + disp('Looks like this is a projection: this test may not work correctly.'); +else + disp('Looks like this is a proximity operator'); + while norm(gi - x )/norm(x) > .8 && cntr < 20 + t = t/2; + cntr = cntr + 1; + [vi,gi] = f(x,t); + end +end +% for t = 0, we have gi = x +% for t = Inf, gi is independent of x (i.e. gi = argmin_x f(x), +% which, for f(x) = ||x||, is gi = 0 ). + + + +% Now, try a vector +if isscalar(N) + x = repmat(x,N,1); +else + x = repmat(x,N(1),N(2)); + % if a > 0, assume we want a PSD matrix: + if a >= 0 + x = ones(N(1),N(2)) + eye(N(1),N(2) ); + end +end +fprintf('Testing vector inputs... \n'); +try + vi = f(x); +catch + fail('TFOCS nonsmooth function failed to compute f when supplied a vector input'); + return; +end +try + [vi,gi] = f(x,t); +catch + fail('TFOCS nonsmooth function failed to compute prox_f when supplied a vector input'); + return; +end +if ~isscalar(vi) + fail('TFOCS nonsmooth function value must be a scalar'); + return; +end +if ~all( size(gi) == size(x) ) + fail('TFOCS nonsmooth proximity function value must be same size as input'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + +% gi = prox_{f,t}(x) +% let's verify that gi is actually a minimizer, by +% plotting the values for a few nearby points +s = min(t,.9*x(1,1) ); +x = x + .1/norm(x,Inf)*randn(size(x)); +[vi,gi] = f(x,s); +objective = @(y) f(y) + 1/(2*s)*norm(y-x,'fro')^2; +fprintf('Verifying the validity of the proximity calculation...\n'); +objGi = objective(gi); +fprintf('\tprox(x) has objective value: %.3e\n', objGi ); +sc = 1/norm(gi,'inf'); +OK_objective = true; +for t = 1:20 + randPt = gi + .01*sc*randn(size(gi)); + obj = objective(randPt); + fprintf('\tRandom pt #%2d has larger objective value by %7.2e', t,obj-objGi); + if obj < objGi + fprintf(' -- SMALLER!! This shouldn''t happen'); + OK_objective = false; + end + fprintf('\n'); +end +if ~OK_objective + fail('TFOCS nonsmooth: incorrect calculation of proximity function'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + + +% 1D example. Does not require function to be vectorized +n = 200; % number of grid points +h = (b-a)/n; +grid = (a+h/2):h:(b-h/2); +% include "0" so things look nice and pointy: +if a < 0 && b > 0 + grid = unique( [grid,0] ); +end +% if isscalar(N) +% grid = repmat( grid, N, 1 ); +% % grid( 2:end, : ) = 0.5*grid( 2:end, : ); +% grid( 2:end, : ) = 0; +% end + +n = size(grid,2); +v = zeros(1,n); +g = zeros(1,n); +for i = 1:length(grid) + v(i) = f(grid(1,i) ); + [vi,gi] = f(grid(:,i), t ); + g(i) = gi(1); +end +grid = grid(1,:); +if PLOT + figure; + clf; + plot(grid,v,'-','linewidth',2); + hold all + plot(grid,g,'-','linewidth',2); + Y = get(gca,'ylim'); + Y(2) = 1.1*Y(2); + set(gca,'ylim',Y); + + legend('function','proximity operator'); +% title(func2str(f),'interpreter','none'); + title(sprintf('function, and proximity operator, with parameter t = %.3f',t)) + line([a,b], 0*[1,1],'color','k' ) + if a < 0 && b > 0 + line( 0*[1,1], get(gca,'ylim'),'color','k' ); + end + % show the y = x line + line( [a,b], [a,b], 'linestyle','--','color','k'); + +end + +OK = true; + + +disp('Test passed succesfully.'); +if nargout > 0 + varargout{1} = OK; +end + + + +function fail(str) + disp('Test failed. Reason:'); + disp(str); + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/test_proxPair.m b/test_proxPair.m new file mode 100644 index 0000000..461095c --- /dev/null +++ b/test_proxPair.m @@ -0,0 +1,213 @@ +function varargout = test_proxPair( f, g, N, nTrials, force_gama,tol,break_on_bad ) +% TEST_PROXPAIR Runs diagnostics on a pair of functions to check if they are Legendre conjugates. +% maxEr = test_proxPair( f, g ) +% will run tests to determine if f and g are really +% Legendre-Fenchel conjugates. +% f and g should be in TFOCS form (see examples below) +% which means they not only compute a function value f(x) +% but that they can also return the proximity operator. +% The test points are assumed to be scalars by default (N=1) +% +% ... = test_proxPair( f, g, N ) +% Same test, but now the test points will be vectors of length N +% ... = test_proxPair( f, g, x0 ) +% Test points will have the same size as x0. If x0 is a square matrix +% other than the zero or identity matrix, the domain will be the set of symmetric +% matrices if x0 is symmetric. If x0 is positive semidefinite, domain is taken +% to be the set of positive semidefinite matrices. +% ... = test_proxPair( f, g, [N or x0], nTrials ) +% will run "nTrials" (default: 10) +% ... = test_proxPair( f, g, [N or x0], nTrials, t ) +% will use the proximity operator: +% prox_f(v) = argmin f(x) + 1/(2t) *||x-v||^2 +% +% If the function works for t = 1 but not for other t, +% this helps you track down the bug (perhaps there is a t vs 1/t confusion +% somewhere...) +% +% (default: t>0 is chosen randomly on every trial ) +% +% ... = test_proxPair( f, g, [N or x0], nTrials, t, tol ) +% sets the tolerance level for considering an inequality to be violated +% (default is 1e-8) +% [x_bad, gamma_bad] = test_proxPair( ..., 'break' ) +% will return the offending point in the case that one of the the inequalities +% has been violated. If no inequalities are violated, then the empty +% matrices are returned. +% +% Note: TFOCS uses the dual function composed with the negative identity operator, +% so you may wish to use prox_scale( g, -1) instead of just g. +% +% Examples of valid pairs (f,g) (where q is any positive scalar, r is any scalar): +% +% f = prox_l1(q); and g = proj_linf(q); +% f = proj_l1(q); and g = prox_linf(q); +% f = proj_l2(q); and g = prox_l2(q); +% f = prox_hinge(q,r); and g = prox_hingeDual(q,r); +% f = proj_Rn; and g = proj_0; +% f = proj_Rplus; and g = proj_Rplus(-1); +% f = proj_nuclear; and g = prox_spectral; +% f = prox_nuclear; and g = proj_spectral; +% f = prox_trace; and g = proj_spectral(1,'symm') for X >= 0 +% f = proj_psdUTrace;and g = prox_spectral(1,'symm') fr X >= 0 +% +% Recall the definition of the conjugate function: +% g(y) = f^*(y) = sup_x - f(x) +% f(x) = sup_y - f^*(y) +% We use Moreau's Decomposition to generate identities. See Combettes and Wajs '05, +% lemma 2.10, http://www.ann.jussieu.fr/~plc/mms1.pdf +% Moreau's decomposition is: +% for all x, x = prox_( gamma*f )( x ) + gamma*prox_( g/gamma )( x/gamma ) +% +% and TFOCS uses (since t > 0) +% prox(f)(t)(x) := argmin f(v) + 1/(2t)||v-x||^2 +% = argmin t*f(v) + 1/2 *||v-x||^2 [ N.B. This is NOT 1/t*argmin... ] +% = prox_( t*f )( x ) +% +% so the identity in terms of TFOCS prox is: +% x = prox(f)(gamma)(x) + gamma*prox(g)(1/gamma)(x/gamma) ( ID 1 ) +% = xf + xg +% +% We also have the identity: +% f(xf) + g(xg/gamma) = / gamma ( ID 2 ) +% where xf and xg defined above. +% [ f(xf) + g(xg/gamma) >= /gamma ] is the Fenchel-Young inequality. +% +% The final identity is: +% ||x||^2/2 = gamma[ f^(gamma)(x) + g^(1/gamma)(x/gamma) ] ( ID 3 ) +% where +% f^t(x) = min f(v) + 1/(2t)||v-x||^2 +% +% These three identities are the three columns of errors output by this test function. +% See also prox_dualize.m + + +error(nargchk(2,7,nargin)); +if nargin < 3 || isempty(N), N = 1; end +if ~isscalar(N) + x0 = N; + % Check: if x0 is a symmetric matrix, then we assume domain + % is set of symmetric matrices. If it is also positive semidefinite, + % we assume domain is set of symmetric positive semidefinite matrices + % (but if x0 is all zeros or the identity, then do not make any assumptions) + [m,n] = size(x0); + if m == n && norm( x0-x0', 'fro' ) < 1e-10 && ... + norm( x0-eye(n),'fro') > 1e-10 && norm(x0,'fro') > 1e-10 + % it is symmetric + if min(eig(x0)) >= -1e-14 + fprintf('Assuming domain is set of positive semidefinite matrcies\n'); + symm = @(X) X*X'; + else + fprintf('Assuming domain is set of Hermitian matrices\n'); + symm = @(X) (X+X')/2; + end + else + symm = @(x) x; % do not symmetrize + end + if issparse(x0) + fprintf('Assuming domain is set of sparse matrices\n'); + newX = @() 100*symm(sprandn( m,n,.01 )); + else + newX = @() 100*symm(randn( size(N) )); + end + fprintf('Using a domain of size %d x %d\n', m, n ); +else + fprintf('Using a domain of size %d x %d\n', N, 1 ); + newX = @() 100*randn( N, 1 ); +end + +if nargin < 4 || isempty(nTrials), nTrials = 10; end +if nargin < 5 || isempty(force_gama), force_gama = false; end +% Note: "gamma" mispelled on purpose, since Matlab's "gamma" function +% creates problems when variables are named "gamma" +if nargin < 6 || isempty(tol), tol = 1e-8; end +if nargin < 7, break_on_bad=[];end +if strfind( lower(break_on_bad), 'break') + break_on_bad = true; +else + break_on_bad = false; +end + +fprintf('\n'); +maxEr = 0; +FenchelYoungViolation = false; +vec = @(x) x(:); +myDot = @(x,y) x(:)'*y(:); +for k = 1:nTrials + if force_gama + gama = double(force_gama); + else + gama = rand(1); + end + x = newX(); + [vf,xf] = f(x,gama); + [vg,xg] = g(x/gama,1/gama); + if numel(vf) > 1, error('The "f" objective function must be scalar valued'); end + if numel(vg) > 1, error('The "g" objective function must be scalar valued'); end + if isinf(vf) || isinf(vg) || isnan(vf) || isnan(vg) + error('Found either Inf or Nan values'); + % it is OK for f(x) to be Inf (i.e. for an indicator function) + % but not for f(x,t) to be Inf, since this is a projection + % or proximity operator + end + + xg = xg*gama; + + er = (xf+xg) - x; + + % Test the scalar identity: + % er1 = f( xf ) + g( xg/gama ) - xf'*xg/gama; % this can be inaccurate due to finite precision + er2 = vf + vg - myDot(xf,xg)/gama; % should be same as er2 + % Sept 3 2012, when vf is very large, we lose precision. Need to make + % this have *relative* precision + er2 = abs(er2/max( [abs(vf),abs(vg),1e-3] )); + + % And the other scalar identity: + % ||x||^2/2 = gama( f^(gama)(x) + g^(1/gama)(x/gama) ) + rhs = ( vf + 1/(2*gama)*norm(vec(xf-x))^2 ) + (vg + gama/2*norm(vec(xg/gama-x/gama))^2 ); + lhs = norm(vec(x) )^2/2/gama; + er3 = abs(rhs-lhs)/abs(lhs); + + + % Another test: does the Fenchel-Young inequality hold? (this test is unlikely to find violations) + % for all x, y, f(x) + g(y) >= x'*y + y = newX(); + vf = f(x); + vg = g(y); + if vf + vg < x'*y + FenchelYoungViolation = true; + end + + % when both f and g are projections, xf and xg should be orthogonal + fprintf('Random trial #%2d, errors are:\t%.2e,\t%.2e,\t%.2e\n', k, norm(er), er2, er3 ); + maxEr = max([norm(er),er2,er3,maxEr] ); + + if break_on_bad && maxEr > tol + varargout{1} = x; + varargout{2} = gama; + disp('Found a bad pair: terminating early'); + return; + end +end + + +fprintf('Worst error was %.2e\n', maxEr ); +if maxEr > tol + disp('This is a BAD sign -- the functions are either not correctly implemented or there is a lot of roundoff error'); + disp(' Try running this again forcing t=1" to help find the source of error (see help file)'); +else + disp('This is a GOOD sign -- the functions are likely implemented correctly'); +end +if FenchelYoungViolation + disp('Found violation of Fenchel-Young inequality; this is BAD'); +end +if break_on_bad + varargout{1} = []; + if nargout > 1, varargout{2} = []; end +else + varargout{1} = maxEr; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/test_smooth.m b/test_smooth.m new file mode 100644 index 0000000..efac187 --- /dev/null +++ b/test_smooth.m @@ -0,0 +1,142 @@ +function varargout = test_smooth( f, N, DOM ) +% TEST_SMOOTH Runs diagnostic checks on a TFOCS smooth function object. +% TEST_SMOOTH( F ) +% tests whether the function handle F works as a TFOCS smooth +% function object. +% +% Requirements: f = F(X) must return the value of the function at X. +% [f,g] = F(X) must return the value, f, and the gradient, g. +% +% For an example, see SMOOTH_QUAD.M +% +% TEST_SMOOTH( F, N ) +% specifies the size of the domain space. If N is a scalar, +% then the domain space is the set of N x 1 vectors. +% If N is a matrix of the form [n1, n2], the the domain +% space is the set of n1 x n2 matrices. +% +% TEST_SMOOTH( ..., DOM ) +% specifies the domain of F. DOM can be of the form [a,b] +% which signifies the 1D set (a,b). +% +% OK = TEST_SMOOTH(...) +% returns "true" if all tests are passed, and "false" otherwise. +% +% See also private/tfocs_smooth, smooth_huber + + +OK = false; +PLOT = true; + +error(nargchk(1,3,nargin)); + +if ~isa(f,'function_handle') + fail('TFOCS smooth function must be a FUNCTION HANDLE'); + return; +end +fprintf('== Testing the smooth function %s ==\n', func2str(f) ); + +FORCE_N = false; % always require a vector input +if nargin < 2 || isempty(N), N = 10; +else FORCE_N = true; +end +if nargin < 3 || isempty(DOM), DOM = [-1,1]; end + +a = DOM(1); b = DOM(2); + +x = (a+b)/2; +fprintf('Testing scalar inputs... \n'); +try + vi = f(x); +catch + fail('TFOCS smooth function failed to return a function value'); + return; +end +if isinf(vi) + fail('TFOCS smooth function tester: default domain is invalid. Please specify valid domain'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + +% Now, also ask for the gradient +fprintf('Testing gradient output... \n'); +try + [vi,gi] = f(x); +catch + fail('TFOCS smooth function failed to return a derivative value'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + +% Now, try a vector +if isscalar(N) + x = repmat(x,N,1); +else + x = repmat(x,N(1),N(2)); + % if a > 0, assume we want a PSD matrix: + if a >= 0 + x = ones(N(1),N(2)) + eye(N(1),N(2) ); + end +end +fprintf('Testing vector inputs... \n'); +try + [vi,gi] = f(x); +catch + fail('TFOCS smooth function failed when supplied a vector input'); + return; +end +fprintf('\t\t\t\t...passed. \n'); + + + +% 1D example. Does not require function to be vectorized +n = 100; % number of grid points +h = (b-a)/n; +grid = (a+h/2):h:(b-h/2); + +v = zeros(size(grid)); +g = zeros(size(grid)); +first = @(x) x(1); +for i = 1:length(grid) + v(i) = f(grid(i)); + if isinf(v(i)) + g(i) = v(i); + else + [v(i),gi] = f(grid(i)); + g(i) = first(gi); + end +end + +if PLOT + figure; + clf; + plot(grid,v,'.-'); + hold all + plot(grid,g,'.-'); + legend('function','derivative'); +% title(func2str(f),'interpreter','none'); + line([a,b], 0*[1,1],'color','k' ) + if a < 0 && b > 0 + line( 0*[1,1], get(gca,'ylim'),'color','k' ); + end +end + +OK = true; + +if nargout > 0 + varargout{1} = OK; +end + +disp('Test passed succesfully.'); + + + + +function fail(str) + disp('Test failed. Reason:'); + disp(str); + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs.m b/tfocs.m new file mode 100644 index 0000000..b51f4a3 --- /dev/null +++ b/tfocs.m @@ -0,0 +1,112 @@ +function varargout = tfocs( smoothF, affineF, projectorF, x0, opts ) +% TFOCS Minimize a convex problem using a first-order algorithm. +% [ x, out, opts ] = tfocs( smoothF, affineF, projectorF, x0, opts ) +% Minimizes or maximizes a smooth or composite function using one of +% the TFOCS first-order algorithms. The nominal standard form that +% TFOCS handles is this: +% minimize smoothF(affineF(x))+projectorF(x) +% The affineF and projectorF functions are optional; specifically, they +% may be omitted by suppling an empty array [] in their place. Thus +% TFOCS supports standard forms such as +% minimize smoothF(affineF(x)) +% minimize smoothF(x) + projectorF(x) +% minimize smoothF(x) +% A variety of more advanced standard forms are supported, but only +% this simple form is discussed here. See the TFOCS user guide for +% more information on these advanced forms; see also TFOCS_SCD. +% +% FUNCTIONS: +% smoothF is expected to operate as follows: +% [ fx, gx ] = smoothF( x ) +% fx: the value of the smooth function at x +% gx: the gradient of the smooth function at x +% TFOCS only asks for the gradient if it needs it, so if they are +% significantly more expensive to compute, you should optimize +% your function for the 'nargout==1' case. +% +% affineF may take one of many forms: +% [] (empty): represents affineF(x) = x. +% A, where A is a matrix: represents affineF(x) = A*x. +% A, where A is a linear operator function obeying SPARCO conventions: +% y = A(x,0) or y = A([],0) returns the size of A. +% y = A(x,1) returns the forward operation applied to x. +% y = A(x,2) returns the adjoint operation applied to x. +% { A, b }, where A is either a matrix or linear operator and b +% is a matrix, adds the offset b to the forward operation. +% As mentioned above, affineF is optional. However, if the computational +% cost of evaluating your objective is dominated by an affine operation, +% it is worthwhile to provide it separately, as TFOCS orders its +% calculations to reduce the number of times it will be called. +% +% projectorF is the most complex, and has two modes of operation. +% Cx = projectorF( x ) +% Returns the value of the prox function at x. Note that no +% projection is taking place, so in many cases this should be +% a relatively simple calculation. +% [ Cz, z ] = projectorF( x, L ) +% Computes the minimizer +% z = argmin_z projectorF(z) + 0.5*L*\|x-y\|^2 +% The norm is Euclidean: \|z\| = ^{1/2}. +% +% OTHER INPUTS: +% x0: a feasible initial point +% opts: a structure containing further options. Please consult the TFOCS +% user guide for full details, but key entries include: +% opts.alg the algorithm to use; e.g., 'AT', 'LLM', etc. +% opts.maxIts max number of iterations +% opts.maxCounts max number of counts +% opts.tol tolerance for convergence: relative step length +% opts.printEvery displays output every 'printEvery' iteration +% opts.maxmin +1 to minimize (default), -1 to maximize +% Calling the function with no arguments displays the default values of +% opts, and returns that default structure in the third output. +% +% OUTPUTS +% x optimal point, up to the tolerance +% out contains extra information collected during the run +% opts structure containing the options used + +% User has requested viewing the default values of "opts" +if nargin == 0 || ( nargin==1 && isstruct(smoothF) ) + if nargout == 0 + tfocs_initialize; + else +% opts = tfocs_AT(); + tfocs_initialize; + opts.alg = 'AT'; + varargout{1} = opts; + end + return +elseif nargin == 1 && ischar(smoothF) && ... + (strcmpi(smoothF,'v') || strcmpi(smoothF,'version') ... + || strcmpi(smoothF,'-v') || strcmpi(smoothF,'-version') ) + % Display version information +% type version.txt + disp('TFOCS v1.3 1.1, December 2011'); + return +end + + +error(nargchk(1,5,nargin)); +if nargin < 2, affineF = []; end +if nargin < 3, projectorF = []; end +if nargin < 4, x0 = []; end +if nargin < 5, opts = []; end +if ~isfield( opts, 'alg' ), + alg = 'AT'; +else + alg = upper( opts.alg ); +end + +if isnumeric(affineF) && ~isempty(affineF) + affineF = {affineF}; +end + +[ varargout{1:max(nargout,1)} ] = feval( [ 'tfocs_', alg ], smoothF, affineF, projectorF, x0, opts ); +if nargout > 2, + varargout{3}.alg = alg; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_AT.m b/tfocs_AT.m new file mode 100644 index 0000000..0c30b1e --- /dev/null +++ b/tfocs_AT.m @@ -0,0 +1,92 @@ +function [ x, out, opts ] = tfocs_AT( smoothF, affineF, projectorF, x0, opts ) +% TFOCS_AT Auslender and Teboulle's accelerated method. +% [ x, out, opts ] = tfocs_AT( smoothF, affineF, nonsmoothF, x0, opts ) +% Implements Auslender & Teboulle's method. +% A variety of calling sequences are supported; type +% help tfocs_help +% for a full explanation. + +% Initialization +alg = 'AT'; +algorithm = 'Auslender & Teboulle''s single-projection method'; +alpha = 0; beta = 0; mu = 0; L = 0;% Do not remove: necessary because of a MATLAB quirk +tfocs_initialize +if nargin == 0, return; end + +while true, + + x_old = x; + A_x_old = A_x; + z_old = z; + A_z_old = A_z; + + % The backtracking loop + L_old = L; + L = L * alpha; + theta_old = theta; + while true, + + % Acceleration + theta = advance_theta( theta_old, L, L_old ); + + % Next iterate + if theta < 1, + y = ( 1 - theta ) * x_old + theta * z_old; + if cntr_Ay > cntr_reset + % every so often, we compute this explicitly, + % to avoid roundoff errors that might accumulate + A_y = apply_linear( y, 1 ); + cntr_Ay = 0; + else + % the efficient way + cntr_Ay = cntr_Ay + 1; + A_y = ( 1 - theta ) * A_x_old + theta * A_z_old; + end + f_y = Inf; g_Ay = []; g_y = []; + end + + % Compute the function value if it is not already + if isempty( g_y ), + if isempty( g_Ay ), [ f_y, g_Ay ] = apply_smooth( A_y ); end + g_y = apply_linear( g_Ay, 2 ); + end + + % Scaled gradient + step = 1 / ( theta * L ); + [ C_z, z ] = apply_projector( z_old - step * g_y, step ); + A_z = apply_linear( z, 1 ); + + % New iterate + if theta == 1, + x = z; + A_x = A_z; + C_x = C_z; + else + x = ( 1 - theta ) * x_old + theta * z; + if cntr_Ax > cntr_reset % see above comments for cntr_Ay + cntr_Ax = 0; + A_x = apply_linear( x, 1 ); + else + cntr_Ax = cntr_Ax + 1; + A_x = ( 1 - theta ) * A_x_old + theta * A_z; + end + C_x = Inf; + end + f_x = Inf; g_Ax = []; g_x = []; + + % Perform backtracking tests + tfocs_backtrack + + end + + % Collect data, evaluate stopping criteria, and print status + tfocs_iterate + +end + +% Final processing +tfocs_cleanup + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_GRA.m b/tfocs_GRA.m new file mode 100644 index 0000000..1597fe5 --- /dev/null +++ b/tfocs_GRA.m @@ -0,0 +1,51 @@ +function [ x, out, opts ] = tfocs_GRA( smoothF, affineF, projectorF, x0, opts ) +%TFOCS_GRA Gradient descent. +% [ x, out, opts ] = tfocs_GRA( smoothF, affineF, nonsmoothF, x0, opts ) +% Implements a standard gradient method. A variety of calling sequences +% are supported; type 'help tfocs_help' for a full explanation. + +% Initialization +alg = 'GRA'; +algorithm = 'Proximal gradient descent'; +alpha = 0; beta = 0; mu = 0; L = 0; % Do not remove: necessary because of a MATLAB quirk +tfocs_initialize +if nargin==0, return; end + +% Unlike the other algorithms, GRA does not use the theta parameter, so it +% is not subject to restart, and we need not embed the initialization code +% inside the loop. + +while true, + + y = x; + A_y = A_x; + f_y = f_x; + x_old = x; + + g_Ay = g_Ax; + g_y = apply_linear( g_Ay, 2 ); + L = L * alpha; + + while true, + + % Standard gradient + [ C_x, x ] = apply_projector( y - (1/L) * g_y, 1/L ); + A_x = apply_linear( x, 1 ); + [ f_x, g_Ax ] = apply_smooth( A_x ); + + % Backtracking + tfocs_backtrack + + end + + % Collect data, evaluate stopping criteria, and print status + tfocs_iterate + +end + +% Final processing +tfocs_cleanup + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_LLM.m b/tfocs_LLM.m new file mode 100644 index 0000000..939cf07 --- /dev/null +++ b/tfocs_LLM.m @@ -0,0 +1,77 @@ +function [ x, out, opts ] = tfocs_LLM( smoothF, affineF, projectorF, x0, opts ) +% TFOCS_LLM Lan, Lu and Monteiro's accelerated method. +% [ x, out, opts ] = tfocs_LLM( smoothF, affineF, nonsmoothF, x0, opts ) +% Implements Lan, Lu & Monteiro's method. +% A variety of calling sequences are supported; type +% help tfocs_help +% for a full explanation. + +% Initialization +alg = 'LLM'; +algorithm = 'Lan/Lu/Monteiro''s two-projection method'; +alpha = 0; beta = 0; mu =0; L = 0; % Do not remove: necessary due to MATLAB quirk +tfocs_initialize +if nargin == 0, return; end + + +while true, + + x_old = x; + A_x_old = A_x; + z_old = z; + A_z_old = A_z; + + % The backtracking loop + L_old = L; + L = L * alpha; + theta_old = theta; + while true, + + % Acceleration + theta = advance_theta( theta_old, L, L_old ); + + % Next iterate + if theta < 1, + y = ( 1 - theta ) * x_old + theta * z_old; + A_y = ( 1 - theta ) * A_x_old + theta * A_z_old; + f_y = Inf; g_Ay = []; g_y = []; + end + + % Compute the latest function values + if isempty( g_y ), + if isempty( g_Ay ), [ f_y, g_Ay ] = apply_smooth( A_y ); end + g_y = apply_linear( g_Ay, 2 ); + end + + % Standard gradient + [ C_x, x ] = apply_projector( y - (1/L) * g_y, 1/L ); + A_x = apply_linear( x, 1 ); + f_x = Inf; g_Ax = []; g_Ax = []; + + % Backtracking test + tfocs_backtrack + + end + + % Collect data, evaluate stopping criteria, and print status + tfocs_iterate + + % Scaled gradient. This step must be skipped if restart occurs + if theta == 1 || isinf(theta) + z = x; + C_z = C_x; + A_z = A_x; + else + step = 1 / ( theta * L ); + [ C_z, z ] = apply_projector( z - step * g_y, step ); + A_z = apply_linear( z, 1 ); + end + +end + +% Final processing +tfocs_cleanup + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_N07.m b/tfocs_N07.m new file mode 100644 index 0000000..6f3e7e8 --- /dev/null +++ b/tfocs_N07.m @@ -0,0 +1,82 @@ +function [ x, out, opts ] = tfocs_N07( smoothF, affineF, projectorF, x0, opts ) +% TFOCS_N07 Nesterov's 2007 accelerated method. +% [ x, out, opts ] = tfocs_N07( smoothF, affineF, projectorF, x0, opts ) +% Implements Nesterov's 2007 two-projection method. +% A variety of calling sequences are supported; type +% help tfocs_help +% for a full explanation. + +% Initialization +alg = 'N07'; +algorithm = 'Nesterov''s 2007 two-projection method'; +alpha = 0; beta = 0; mu = 0; L = 0; % Necessary due to MATLAB quirk +tfocs_initialize +if nargin == 0,return; end + +while true, + + % Initialize the centerpoint and accumulated gradient. We do this here, + x_old = x; + A_x_old = A_x; + z_old = z; + A_z_old = A_z; + + % The backtracking loop + L_old = L; + L = L * alpha; + theta_old = theta; + while true, + + % Acceleration + theta = advance_theta( theta_old, L, L_old ); + + % Next iterate + if theta < 1, + y = ( 1 - theta ) * x_old + theta * z_old; + A_y = ( 1 - theta ) * A_x_old + theta * A_z_old; + f_y = Inf; g_Ay = []; g_y = []; + end + + % Compute function values + if isempty( g_y ), + if isempty( g_Ay ), + [ f_y, g_Ay ] = apply_smooth( A_y ); + end + g_y = apply_linear( g_Ay, 2 ); + end + + % Standard gradient + [ C_x, x ] = apply_projector( y - (1/L) * g_y, 1/L ); + A_x = apply_linear( x, 1 ); + f_x = Inf; g_Ax = []; g_x = []; + + % Backtracking test + tfocs_backtrack + + end + + % Collect data, evaluate stopping criteria, and print status + tfocs_iterate + + % Accumulated gradient. This step must be skipped if restart occurs + if theta == 1 || isinf(theta) + g_accum = g_y; + x_cent = x_old; + z = x; + C_z = C_x; + A_z = A_x; + else + g_accum = ( 1 - theta ) * g_accum + theta * g_y; + step = 1 / ( theta^2 * L ); + [ C_z, z ] = apply_projector( x_cent - step * g_accum, step ); + A_z = apply_linear( z, 1 ); + end + +end + +% Final processing +tfocs_cleanup + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_N83.m b/tfocs_N83.m new file mode 100644 index 0000000..2c61825 --- /dev/null +++ b/tfocs_N83.m @@ -0,0 +1,78 @@ +function [ x, out, opts ] = tfocs_N83( smoothF, affineF, projectorF, x0, opts ) +% TFOCS_N83 Nesterov's 1983 accelerated method; also by Beck and Teboulle 2005 (FISTA). +% [ x, out, opts ] = tfocs_N83( smoothF, affineF, nonsmoothF, x0, opts ) +% Implements Nesterov's 1983 method. +% A variety of calling sequences are supported; type +% help tfocs_help +% for a full explanation. + +% Initialization +alg = 'N83'; +algorithm = 'Nesterov''s 1983 single-projection method'; +alpha = 0; beta = 0; mu = 0; L = 0; % Do not remove: necessary because of a MATLAB quirk +tfocs_initialize +if nargin == 0, return; end + +while true, + + x_old = x; + A_x_old = A_x; + z_old = z; + A_z_old = A_z; + + % The backtracking loop + L_old = L; + L = L * alpha; + theta_old = theta; + while true, + + % Acceleration + theta = advance_theta( theta_old, L, L_old ); + + % Next iterate + if theta < 1, + y = ( 1 - theta ) * x_old + theta * z_old; + A_y = ( 1 - theta ) * A_x_old + theta * A_z_old; + f_y = Inf; g_Ay = []; g_y = []; + end + + % Compute function values + if isempty( g_y ), + if isempty( g_Ay ), + [ f_y, g_Ay ] = apply_smooth( A_y ); + end + g_y = apply_linear( g_Ay, 2 ); + end + + % Standard gradient + [ C_x, x ] = apply_projector( y - (1/L) * g_y, 1/L ); + A_x = apply_linear( x, 1 ); + f_x = Inf; g_Ax = []; g_x = []; + + % Backtracking test + tfocs_backtrack + + end + + % Collect data, evaluate stopping criteria, and print status + tfocs_iterate + + % Reversed z update. This step is skipped if restart occurs + if theta == 1, + z = x; + A_z = A_x; + C_z = C_x; + else + z = (1/theta) * x + ( 1 - 1/theta ) * x_old; + A_z = (1/theta) * A_x + ( 1 - 1/theta ) * A_x_old; + C_z = Inf; + end + +end + +% Final processing +tfocs_cleanup + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_SCD.m b/tfocs_SCD.m new file mode 100644 index 0000000..9d87bb2 --- /dev/null +++ b/tfocs_SCD.m @@ -0,0 +1,204 @@ +function [ x, odata, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts, contOpts ) +% TFOCS_SCD Smoothed conic dual form of TFOCS, for problems with non-trivial linear operators. +% [ x, out, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts ) +% Solves a conic problem using the smoothed conic dual approach. The goal +% is to solve a problem in the following conic form: +% minimize objectiveF(x)+0.5*mu(x-x0).^2 +% s.t. affineF(x) \in \cK +% The user is responsible for constructing the dual proximity function +% so that the dual can be described by the saddle point problem +% maximize_z inf_x [ objectiveF(x)+0.5*mu(x-x0).^2- ] -dualproxF(z) +% If mu = Inf, the method ignores the objective function and solves +% minimize 0.5*(x-x0).^2 +% s.t. affineF(x) \in \cK +% +% For general usage instructions, see tfocs.m and the user guide. +% In general, setting a variable to [] will set it to its default value. +% +% If opts.continuation is true, then +% [ x, out, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts ) +% or +% [ x, out, opts ] = tfocs_SCD( objectiveF, affineF, dualproxF, mu, x0, z0, opts, continuationOptions ) +% will update x0 as to eliminate the influence of the mu(x-x0).^2 term +% Parameters for the continuation scheme are optionally specified +% in the "continuationOpts" variable. +% Note: if the "continuationOpts" variable is given, then the solver +% enters continuation mode automatically, unless opts.continuation +% is explicitly set to false. +% To see options available for continuation, type "continuation()" +% +% See also tfocs + +if nargin == 0 + % pass in the SCD specific options: + opSCD = []; + opSCD.continuation = false; + opSCD.adjoint = true; + opSCD.saddle = true; + opSCD.maxmin = -1; + if nargout > 0, x = tfocs(opSCD); + % and set the continuation options (right now, continuation is off by default) + x.continuation = false; + x = rmfield(x,'adjoint'); % x.adjoint = true; + x.saddle = true; + x.maxmin = -1; + else + tfocs(opSCD); + disp('Warning: we strongly recommend setting opts.continuation=true; see the user guide'); + disp('Type ''help continuation'' for details'); + end + return; +end + +error(nargchk(4,8,nargin)); +if nargin < 5, x0 = []; end +if nargin < 6, z0 = []; end +if nargin < 7, opts = []; end + +% look for continuation options +DO_CONTINUATION = false; +if nargin >= 8 + DO_CONTINUATION = true; +else + contOpts = []; +end +if isfield( opts, 'continuation' ) + if opts.continuation + DO_CONTINUATION = true; + else + DO_CONTINUATION = false; + end + opts = rmfield(opts,'continuation'); +end + +% Handle special cases of zero objective or infinite mu +if isinf( mu ), + mu = 1; + objectiveF = prox_0; +elseif isempty( objectiveF ), + objectiveF = prox_0; +elseif iscell( objectiveF ) % allow the case of {[],[],...,[]} + for k = 1:length(objectiveF) + if isempty( objectiveF{k} ) || isequal( objectiveF{k}, 0 ), + objectiveF{k} = prox_0; + elseif isnumeric( objectiveF{k} ), + objectiveF{k} = smooth_linear( objectiveF{k} ); + end + end +end + +% The affine quantities will be used in adjoint orientation +if isfield( opts, 'adjoint' ), + opts.adjoint = ~opts.adjoint; +else + opts.adjoint = true; +end +opts.saddle = true; +opts.maxmin = -1; +if isempty( x0 ), x0 = 0; end +smoothF = create_smoothF( objectiveF, mu, x0 ); + +if isempty(dualproxF) + dualproxF = proj_Rn; +elseif iscell(dualproxF) + for k = 1:length(dualproxF) + dp = dualproxF{k}; + if isempty( dp ) || isnumeric( dp ) && numel( dp ) == 1 && dp == 0, + dualproxF{k} = proj_Rn; + end + end +end +% When tfocs.m finds an error with z0, it says it has an error with "x0", +% which is confusing for tfocs_SCD users, since their "x0" has a different meaning. +% try % this is annoying for debugging + if DO_CONTINUATION + continuation_solver=@(mu,x0,z0,opts)solver(objectiveF,affineF,dualproxF, mu,x0,z0,opts); + [ x, odata, opts ] = continuation( continuation_solver, mu, x0, z0, opts,contOpts ); + else + [ z, odata, opts ] = tfocs( smoothF, affineF, dualproxF, z0, opts ); + end +% catch err +% if strfind( err.message, 'x0' ) +% fprintf(2,'Error involves z0 (which is referred to as x0 below)\n'); +% end +% rethrow(err); +% end +opts.adjoint = ~opts.adjoint; +opts = rmfield( opts, { 'saddle', 'maxmin' } ); +if DO_CONTINUATION + opts.continuation = true; +else + x = odata.dual; + odata.dual = z; +end + + + +% ----------------------- Subfunctions ---------------------------------------- + +function [ prox, x ] = smooth_dual( objectiveF, mu_i, x0, ATz ) +% Adding 0 to ATz will destroy the sparsity +if (isscalar(x0) && x0 == 0) || numel(x0) == 0 || nnz(x0) == 0 + [ v, x ] = objectiveF( mu_i * ATz, mu_i ); +else + [ v, x ] = objectiveF( x0 + mu_i * ATz, mu_i ); +end +prox = tfocs_dot( ATz, x ) - v - (0.5/mu_i) * tfocs_normsq( x - x0 ); +prox = -prox; +x = -x; + +function [ prox, x ] = smooth_dual_vectorMu( objectiveF, mu_i, x0, ATz ) +% Adding 0 to ATz will destroy the sparsity +if (isscalar(x0) && x0 == 0) || numel(x0) == 0 || nnz(x0) == 0 + [ v, x ] = objectiveF( mu_i .* ATz, mu_i ); +else + [ v, x ] = objectiveF( x0 + mu_i .* ATz, mu_i ); % Causes a scaling... +end +prox = tfocs_dot( ATz, x ) - v - 0.5*tfocs_normsq( x - x0, 1./mu_i ); +prox = -prox; +x = -x; + + +function smoothF = create_smoothF( objectiveF, mu, x0 ) +if iscell(objectiveF) + for k = 1 : length(objectiveF) + if iscell(x0) + if length(mu)>1 + smoothF{k} = @(varargin)smooth_dual_vectorMu( objectiveF{k}, 1./mu, x0{k}, varargin{:} ); + else + smoothF{k} = @(varargin)smooth_dual( objectiveF{k}, 1./mu, x0{k}, varargin{:} ); + end + else + if length(mu)>1 + smoothF{k} = @(varargin)smooth_dual_vectorMu( objectiveF{k}, 1./mu, x0, varargin{:} ); + else + smoothF{k} = @(varargin)smooth_dual( objectiveF{k}, 1./mu, x0, varargin{:} ); + end + end + end +else + if length(mu)>1 + smoothF = @(varargin)smooth_dual_vectorMu( objectiveF, 1./mu, x0, varargin{:} ); + else + smoothF = @(varargin)smooth_dual( objectiveF, 1./mu, x0, varargin{:} ); + end +end + +% For use with continuation: +function [varargout] = solver(objectiveF,affineF,dualproxF, mu,x0,z0,opts) +smoothF = create_smoothF( objectiveF, mu, x0 ); +[varargout{1:max(nargout,2)}] = tfocs( smoothF, affineF, dualproxF, z0, opts ); +% The varargout should be [x, odata, opts ] +% Since we're using the dual, need to switch +dualVar = varargout{1}; +odata = varargout{2}; +varargout{1} = odata.dual; +odata.dual = dualVar; +varargout{2} = odata; + + + + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_TS.m b/tfocs_TS.m new file mode 100644 index 0000000..56734f7 --- /dev/null +++ b/tfocs_TS.m @@ -0,0 +1,86 @@ +function [ x, out, opts ] = tfocs_TS( smoothF, affineF, projectorF, x0, opts ) +% TFOCS_TS Tseng's modification of Nesterov's 2007 method. +% [ x, out, opts ] = tfocs_TS( smoothF, affineF, projectorF, x0, opts ) +% Implements Tseng's modification of the Nesterov 2007 method. +% A variety of calling sequences are supported; type +% help tfocs_help +% for a full explanation. + +% Initialization +alg = 'TS'; +algorithm = 'Tseng''s single-projection modification of Nesterov''s 2007 method'; +alpha = 0; beta = 0; mu = 0; L = 0; % Necessary due to MATLAB quirk +tfocs_initialize +if nargin == 0, return; end + +while true, + + x_old = x; + A_x_old = A_x; + z_old = z; + A_z_old = A_z; + + % The backtracking loop + L_old = L; + L = L * alpha; + theta_old = theta; + while true, + + % Acceleration + theta = 2 ./ ( 1 + sqrt( 1 + 4 * L / L_old / theta_old^2 ) ); + + % Next iterate + if theta < 1, + y = ( 1 - theta ) * x_old + theta * z_old; + A_y = ( 1 - theta ) * A_x_old + theta * A_z_old; + f_y = Inf; g_Ay = []; g_y = []; C_y = Inf; + end + + % Compute function values + if isempty( g_y ), + if isempty( g_Ay ), [ f_y, g_Ay ] = apply_smooth( A_y ); end + g_y = apply_linear( g_Ay, 2 ); + end + + % Accumulated gradient + if theta == 1, + x_cent = x_old; + g_a = g_y; + else + g_a = ( 1 - theta ) * g_a_old + theta * g_y; + end + step = 1 / ( theta^2 * L ); + [ C_z, z ] = apply_projector( x_cent - step * g_a, step ); + A_z = apply_linear( z, 1 ); + + % New iterate + if theta == 1, + x = z; + A_x = A_z; + C_x = C_z; + else + x = ( 1 - theta ) * x_old + theta * z; + A_x = ( 1 - theta ) * A_x_old + theta * A_z; + C_x = Inf; + end + f_x = Inf; g_Ax = []; g_x = []; + + % Bactracking test + tfocs_backtrack + + end + + % Collect data, evaluate stopping criteria, and print status + tfocs_iterate + + g_a_old = g_a; + +end + +% Final processing +tfocs_cleanup + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. + diff --git a/tfocs_normsq.m b/tfocs_normsq.m new file mode 100644 index 0000000..ebee4dd --- /dev/null +++ b/tfocs_normsq.m @@ -0,0 +1,22 @@ +function v = tfocs_normsq( x, scaling ) + +% TFOCS_NORMSQ Squared norm. +% By default, TFOCS_NORMSQ(X) = TFOCS_DOT(X,X). However, certain +% objects may have more efficient ways of computing this value. +% If so, TFOCS_NORMSQ should be overloaded to take advantage of +% this. However, the numerical equivalence to TFOCS_DOT(X,X) must +% be preserved. +% +% TFOCS_NORMSQ( X, D ) = TFOCS_DOT( X, D.*X ) is a scaled norm + +if nargin < 2 || isempty(scaling) + v = tfocs_dot( x, x ); +elseif numel(scaling) == 1 + v = scaling*tfocs_dot( x, x ); +else + v = tfocs_dot( x, scaling.*x ); +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_version.m b/tfocs_version.m new file mode 100644 index 0000000..7996d1b --- /dev/null +++ b/tfocs_version.m @@ -0,0 +1,19 @@ +function tfocs_ver2 = tfocs_version + +%TFOCS_VERSION Version information. +% Prints information about the version of TFOCS being used, and the +% system on which it is running. When submitting a bug report, please +% run this function and include its output in your report. + +tfocs_ver = '1.3'; +if nargout == 0, + fprintf( 'TFOCS v%s\n', tfocs_ver ); + verd = ver('MATLAB'); + fprintf( 'MATLAB version %s %s on %s\n', verd.Version, verd.Release, computer ); +else + tfocs_ver2 = tfocs_ver; +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfocs_where.m b/tfocs_where.m new file mode 100644 index 0000000..b51279a --- /dev/null +++ b/tfocs_where.m @@ -0,0 +1,18 @@ +function s = tfocs_where + +%TFOCS_WHERE Returns the location of the TFOCS system. +% TFOCS_WHERE returns a string containing the base directory of the +% TFOCS solvers. Within that directory are some useful +% subdirectories and files: +% experiments/ sample TFOCS models +% COPYING.txt copyright information +% The proper operation of this function assumes that it has not been +% moved from its default position within the TFOCS distribution. + +s = mfilename('fullpath'); +temp = strfind( s, filesep ); +s( temp(end) + 1 : end ) = []; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfunc_scale.m b/tfunc_scale.m new file mode 100644 index 0000000..ec56364 --- /dev/null +++ b/tfunc_scale.m @@ -0,0 +1,95 @@ +function op = tfunc_scale( funcF, s, A, b ) + +%TFUNC_SCALE Scaling a function. +% SSCALE = TFUNC_SCALE( FUNC, s, A, b ) is the function +% SSCALE( y ) = s * FUNC( A * y + b ). +% s must be a real scalar; A can be a scalar, a matrix, or a linear +% operator; and offset must be a vector of compatible size. The third +% and fourth arguments are optional; A=I, b=0 are the defaults. If A +% is a matrix or a linear operator, then the resulting function cannot +% perform proximity minimizations. +% +% See also prox_scale + +error(nargchk(2,4,nargin)); +if ~isa( funcF, 'function_handle' ), + error( 'The first argument must be a function handle.' ); +elseif ~isnumeric( s ) || ~isreal( s ) || numel( s ) ~= 1, + error( 'The second argument must be a real scalar.' ); +end +if nargin < 3, + A = 1; +elseif ~isnumeric( A ) && ~isa( A, 'function_handle' ), + error( 'The third argument must be a scalar, matrix, or linear operator.' ); +end +if nargin < 4 || isempty( b ), + b = 0; +elseif ~isnumeric( b ), + error( 'The fourth argument must be a vector or matrix.' ); +end + +% Determine the best handle for the job +if s == 0, + op = smooth_constant( 0 ); +elseif ~isnumeric( A ), + op = @(varargin)tfunc_scale_linop( funcF, s, A, b, varargin{:} ); +elseif ~nnz(b) || numel( A ) ~= 1, + op = @(varargin)tfunc_scale_matrix( funcF, s, A, b, varargin{:} ); +elseif s ~= 1 || A ~= 1, + op = @(varargin)tfunc_scale_scalar( funcF, s, A, b, varargin{:} ); +else + op = funcF; +end + +function [ v, g ] = tfunc_scale_scalar( funcF, s, a, b, x, t ) +switch nargin, + case 5, + if nargout == 1, + v = funcF( a * x + b ); + else + [ v, g ] = funcF( a * x + b ); + g = ( s * A ) * g; + end + case 6, + [ v, g ] = funcF( a * x + b, s * a * t ); + g = ( g - b ) / a; + otherwise, + error( 'Not enough arguments.' ); +end +v = s * v; + +function [ v, g ] = tfunc_scale_matrix( funcF, s, A, b, x, t ) +switch nargin, + case 5, + if nargout == 1, + v = funcF( A * x + b ); + else + [ v, g ] = funcF( A * x + b ); + g = s * ( A' * g ); + end + case 6, + error( 'This function cannot perform proximity minimization.' ); + otherwise, + error( 'Not enough arguments.' ); +end +v = s * v; + +function [ v, g ] = tfunc_scale_linop( funcF, s, A, b, x, t ) +switch nargin, + case 5, + if nargout == 1, + v = funcF( A( x, 1 ) + b ); + else + [ v, g ] = funcF( A( x, 1 ) + b ); + g = s * A( g, 2 ); + end + case 6, + error( 'This function cannot perform proximity minimization.' ); + otherwise, + error( 'Not enough arguments.' ); +end +v = s * v; + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information. diff --git a/tfunc_sum.m b/tfunc_sum.m new file mode 100644 index 0000000..c720977 --- /dev/null +++ b/tfunc_sum.m @@ -0,0 +1,46 @@ +function op = tfunc_sum( varargin ) + +%TFUNC_SUM Sum of functions. +% OP = TFUNC_SUM( F1, F2, ..., FN ) implements +% OP( x ) = F1( x ) + F2( x ) + ... + FN( x ). +% Each entry must be a real scalar or a function handle. You are +% responsible for ensuring that the sum is convex or concave, as +% appropriate; TFOCS cannot verify this. + +for k = 1 : nargin, + arg = varargin{k}; + if ~isa( arg, 'function_handle' ) && ( ~isnumeric( arg ) || numel( arg ) ~= 1 || ~isreal( arg ) ), + error( 'Arguments must be function handles or real scalars.' ); + elseif isnumeric( arg ), + varargin{k} = smooth_constant( arg ); + end +end +switch nargin, + case 0, + op = smooth_constant( 0 ); + case 1, + op = varargin{1}; + otherwise, + op = @(x)tfunc_sum_impl( varargin, x ); +end + +function [ v, g ] = tfunc_sum_impl( args, x, t ) +if nargin > 2, + error( 'This function does not support proximity minimization.' ); +elseif nargout == 1, + v = args{1}( x ); + for k = 2 : numel(args), + v = v + args{k}( x ); + end +else + [ v, g ] = args{1}( x ); + for k = 2 : numel(args), + [ nv, ng ] = args{k}( x ); + v = v + nv; + g = g + ng; + end +end + +% TFOCS v1.3 by Stephen Becker, Emmanuel Candes, and Michael Grant. +% Copyright 2013 California Institute of Technology and CVX Research. +% See the file LICENSE for full license information.